Skip to content

Commit

Permalink
Merge pull request #113 from eric-volz/txbuilder/replaceable
Browse files Browse the repository at this point in the history
Make transactions replaceable within TxBuilder
  • Loading branch information
eric-volz authored Sep 20, 2023
2 parents c615987 + 74be68e commit 1a62913
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 10 deletions.
40 changes: 33 additions & 7 deletions defichain/transactions/builder/rawtransactionbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from defichain.exceptions.transactions import TxBuilderError, NotYetSupportedError

from defichain.transactions.address import Address
from defichain.transactions.constants import AddressTypes, DefiTxType
from defichain.transactions.constants import AddressTypes, DefiTxType, SEQUENCE
from defichain.networks import Network
from defichain.transactions.remotedata.remotedata import RemoteData
from defichain.transactions.rawtransactions import Transaction, TxInput, TxP2PKHInput, TxP2WPKHInput, TxP2SHInput, \
TxAddressOutput, TxDefiOutput, estimate_fee
from defichain.transactions.defitx.modules.basedefitx import BaseDefiTx
from defichain.transactions.utils import Converter


class RawTransactionBuilder:
Expand All @@ -16,12 +17,13 @@ class RawTransactionBuilder:
def new_transaction() -> Transaction:
return Transaction([], [])

def __init__(self, address: str, account: Account, dataSource: RemoteData, feePerByte: float):
self._address, self._account, self._dataSource, self._feePerByte = None, None, None, None
def __init__(self, address: str, account: Account, dataSource: RemoteData, feePerByte: float, replaceable: bool):
self._address, self._account, self._dataSource, self._feePerByte, self._replaceable = None, None, None, None, None
self.set_address(address)
self.set_account(account)
self.set_dataSource(dataSource)
self.set_feePerByte(feePerByte)
self.set_replaceable(replaceable)

# Build Transaction
def build_transactionInputs(self, inputs=[]) -> Transaction:
Expand All @@ -30,18 +32,35 @@ def build_transactionInputs(self, inputs=[]) -> Transaction:
if inputs or self.get_dataSource() is None:
tx.set_inputs(inputs)
else:
# Transaction should be replaceable
if self.get_replaceable():
sequence: str = Converter.int_to_hex(Converter.hex_to_int(SEQUENCE) - 2, 4)
else:
sequence: str = SEQUENCE

for input in self.get_dataSource().get_unspent(self.get_address()):
address = Address.from_scriptPublicKey(self.get_account().get_network(), input["scriptPubKey"])
# Build P2PKH Input
if address.get_addressType() == AddressTypes.P2PKH:
tx.add_input(TxP2PKHInput(input["txid"], input["vout"], self.get_address(), input["value"]))
tx.add_input(TxP2PKHInput(input["txid"],
input["vout"],
self.get_address(),
input["value"],
sequence))
# Build P2SH Input
elif address.get_addressType() == AddressTypes.P2SH:
tx.add_input(TxP2SHInput(input["txid"], input["vout"], self.get_account().get_p2wpkh(),
input["value"]))
tx.add_input(TxP2SHInput(input["txid"],
input["vout"],
self.get_account().get_p2wpkh(),
input["value"],
sequence))
# Build P2WPKH Input
elif address.get_addressType() == AddressTypes.P2WPKH:
tx.add_input(TxP2WPKHInput(input["txid"], input["vout"], self.get_address(), input["value"]))
tx.add_input(TxP2WPKHInput(input["txid"],
input["vout"],
self.get_address(),
input["value"],
sequence))
# Check Inputs for masternode collateral
tx.set_inputs(self.checkMasternodeInputs(tx.get_inputs()))
if not tx.get_inputs():
Expand Down Expand Up @@ -127,6 +146,9 @@ def get_dataSource(self) -> "RemoteData":
def get_feePerByte(self) -> float:
return self._feePerByte

def get_replaceable(self) -> bool:
return self._replaceable

def get_network(self) -> Network:
return self.get_account().get_network()

Expand All @@ -142,3 +164,7 @@ def set_dataSource(self, dataSource: RemoteData) -> None:

def set_feePerByte(self, feePerByte: float) -> None:
self._feePerByte = feePerByte

def set_replaceable(self, replaceable: bool) -> None:
self._replaceable = replaceable

19 changes: 16 additions & 3 deletions defichain/transactions/builder/txbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,17 @@ class TxBuilder:
:param feePerByte: (optional) approximate fee paid per byte
:type feePerByte: float
"""
def __init__(self, address: str, account: Account, dataSource: "Ocean | Node | None", feePerByte=1.0):
self._address, self._account, self._dataSource, self._feePerByte = None, None, None, None
def __init__(self, address: str, account: Account, dataSource: "Ocean | Node | None", feePerByte=1.0,
replaceable: bool = False):
self._address, self._account, self._dataSource, self._feePerByte, self._replaceable = None, None, None, None, None
self._set_address(address)
self._set_account(account)
self._set_dataSource(dataSource)
self._set_feePerByte(feePerByte)
self._set_replaceable(replaceable)

self._builder = RawTransactionBuilder(self.get_address(), self.get_account(), self.get_dataSource(),
self.get_feePerByte())
self.get_feePerByte(), self.get_replaceable())

self.utxo = UTXO(self._builder)
self.data = Data(self._builder)
Expand Down Expand Up @@ -170,6 +172,14 @@ def get_feePerByte(self) -> float:
"""
return self._feePerByte

def get_replaceable(self) -> bool:
"""
Returns if the builder object creates transactions that are replaceable
:return: bool
"""
return self._replaceable

# Set Information
def _set_address(self, address: str) -> None:
self._address = address
Expand All @@ -190,4 +200,7 @@ def _set_dataSource(self, dataSource: "Ocean | Node | None") -> None:
def _set_feePerByte(self, feePerByte: float) -> None:
self._feePerByte = feePerByte

def _set_replaceable(self, replaceable: bool) -> None:
self._replaceable = replaceable


10 changes: 10 additions & 0 deletions tests/transactions/builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ class Addresses:
builder_p2sh = TxBuilder(Keys.account.get_p2sh(), Keys.account, ocean)
builder_p2wpkh = TxBuilder(Keys.account.get_p2wpkh(), Keys.account, ocean)

builder_replaceable = TxBuilder(Keys.account.get_p2wpkh(), Keys.account, ocean, replaceable=True)


class TestAccounts:
addressAmountTo = BuildAddressAmounts()
Expand Down Expand Up @@ -303,3 +305,11 @@ class TestVault:
"23a8495022054527bcc3ea6492a094331899f28a0d5ab48a1dcb3b2f1616d984795042d1f1c0121" \
"03f110404297e471ad86d1aabc8a885bd4d1ec71bc3f31bef8ed2ff9ad3032460000000000"


class TestReplaceableTransactions:
replaceable_transaction_serialized = "0400000000010149b8ea9b2b0224e44126b86bd1e2889a7dac0ec06fcfb0dc4dd13782e1c84" \
"fce0100000000fdffffff01fc3b000000000000160014ad56321e69b7e2d30aeca9f49979ff" \
"c53084296f0002483045022100f8ed99fa62c18ecde71dee21d62d8e1c62037d0fd984013c5" \
"65ef37a5ea52090022057cd12501d5e8d7c4a82219102679dfeb751f009f3e238bfe59840f1" \
"5debf141012103f110404297e471ad86d1aabc8a885bd4d1ec71bc3f31bef8ed2ff9ad30324" \
"60000000000"
14 changes: 14 additions & 0 deletions tests/transactions/builder/test_replaceable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest
from . import builder_replaceable, TestReplaceableTransactions, Addresses

from defichain.transactions.rawtransactions import Transaction
from defichain.networks import DefichainMainnet


@pytest.mark.transactions
def test_replaceable(): # 01
tx = builder_replaceable.utxo.sendall(Addresses.P2WPKH)

assert tx.serialize() == TestReplaceableTransactions.replaceable_transaction_serialized

assert Transaction.deserialize(DefichainMainnet, tx.serialize()).serialize() == tx.serialize()

0 comments on commit 1a62913

Please sign in to comment.