From d1e2481274edf2ac14747be633d86ecd46814ef4 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Thu, 16 Sep 2021 14:22:39 +0200 Subject: [PATCH 1/3] test: scale amounts in test_doublespend_chain down by factor 10 This is done in order to prepare the make_utxo helper to use MiniWallet, which only supports creating transactions with single inputs, i.e. we need to create amounts small enough to be funded by coinbase transactions (50 BTC). --- test/functional/feature_rbf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index cb7556feb47..d4e483ccd5f 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -161,14 +161,14 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_doublespend_chain(self): """Doublespend of a long chain""" - initial_nValue = 50 * COIN + initial_nValue = 5 * COIN tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) prevout = tx0_outpoint remaining_value = initial_nValue chain_txids = [] - while remaining_value > 10 * COIN: - remaining_value -= 1 * COIN + while remaining_value > 1 * COIN: + remaining_value -= int(0.1 * COIN) tx = CTransaction() tx.vin = [CTxIn(prevout, nSequence=0)] tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))] @@ -178,10 +178,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): prevout = COutPoint(int(txid, 16), 0) # Whether the double-spend is allowed is evaluated by including all - # child fees - 40 BTC - so this attempt is rejected. + # child fees - 4 BTC - so this attempt is rejected. dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - 30 * COIN, DUMMY_P2WPKH_SCRIPT)] + dbl_tx.vout = [CTxOut(initial_nValue - 3 * COIN, DUMMY_P2WPKH_SCRIPT)] dbl_tx_hex = dbl_tx.serialize().hex() # This will raise an exception due to insufficient fee @@ -190,7 +190,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Accepted with sufficient fee dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] + dbl_tx.vout = [CTxOut(int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)] dbl_tx_hex = dbl_tx.serialize().hex() self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) From 0f275246027266fa256d0a09251bb2c88d9bd72f Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Thu, 16 Sep 2021 14:32:43 +0200 Subject: [PATCH 2/3] test: scale amounts in test_doublespend_tree down by factor 10 This is done in order to prepare the make_utxo helper to use MiniWallet, which only supports creating transactions with single inputs, i.e. we need to create amounts small enough to be funded by coinbase transactions (50 BTC). --- test/functional/feature_rbf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index d4e483ccd5f..c9eb5a8919d 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -201,10 +201,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_doublespend_tree(self): """Doublespend of a big tree of transactions""" - initial_nValue = 50 * COIN + initial_nValue = 5 * COIN tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) - def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001 * COIN, _total_txs=None): + def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.00001 * COIN, _total_txs=None): if _total_txs is None: _total_txs = [0] if _total_txs[0] >= max_txs: @@ -235,7 +235,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): _total_txs=_total_txs): yield x - fee = int(0.0001 * COIN) + fee = int(0.00001 * COIN) n = MAX_REPLACEMENT_LIMIT tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) assert_equal(len(tree_txs), n) @@ -248,10 +248,10 @@ class ReplaceByFeeTest(BitcoinTestFramework): # This will raise an exception due to insufficient fee assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0) - # 1 BTC fee is enough + # 0.1 BTC fee is enough dbl_tx = CTransaction() dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)] - dbl_tx.vout = [CTxOut(initial_nValue - fee * n - 1 * COIN, DUMMY_P2WPKH_SCRIPT)] + dbl_tx.vout = [CTxOut(initial_nValue - fee * n - int(0.1 * COIN), DUMMY_P2WPKH_SCRIPT)] dbl_tx_hex = dbl_tx.serialize().hex() self.nodes[0].sendrawtransaction(dbl_tx_hex, 0) @@ -264,7 +264,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Try again, but with more total transactions than the "max txs # double-spent at once" anti-DoS limit. for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2): - fee = int(0.0001 * COIN) + fee = int(0.00001 * COIN) tx0_outpoint = self.make_utxo(self.nodes[0], initial_nValue) tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee)) assert_equal(len(tree_txs), n) From f680d27155374de658d40db0ba40460919aa1ba2 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Thu, 16 Sep 2021 14:42:50 +0200 Subject: [PATCH 3/3] test: use MiniWallet for make_utxo helper in feature_rbf.py --- test/functional/feature_rbf.py | 47 ++++++++++++------------ test/functional/test_framework/wallet.py | 4 +- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index c9eb5a8919d..087529af685 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -7,7 +7,6 @@ from copy import deepcopy from decimal import Decimal -from test_framework.blocktools import COINBASE_MATURITY from test_framework.messages import ( BIP125_SEQUENCE_NUMBER, COIN, @@ -18,10 +17,18 @@ from test_framework.messages import ( ) from test_framework.script import CScript, OP_DROP from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round -from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT +from test_framework.util import ( + assert_equal, + assert_greater_than, + assert_raises_rpc_error, +) +from test_framework.script_util import ( + DUMMY_P2WPKH_SCRIPT, + DUMMY_2_P2WPKH_SCRIPT, +) from test_framework.wallet import MiniWallet + MAX_REPLACEMENT_LIMIT = 100 class ReplaceByFeeTest(BitcoinTestFramework): def set_test_params(self): @@ -89,29 +96,23 @@ class ReplaceByFeeTest(BitcoinTestFramework): def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT): """Create a txout with a given amount and scriptPubKey - Mines coins as needed. + Assumes that MiniWallet has enough funds to cover the amount and the fixed fee + (from it's internal utxos, the one with the largest value is taken). confirmed - txouts created will be confirmed in the blockchain; unconfirmed otherwise. """ - fee = 1 * COIN - while node.getbalance() < satoshi_round((amount + fee) / COIN): - self.generate(node, COINBASE_MATURITY) - - new_addr = node.getnewaddress() - txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN)) - tx1 = node.getrawtransaction(txid, 1) - txid = int(txid, 16) - i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout']))) - - tx2 = CTransaction() - tx2.vin = [CTxIn(COutPoint(txid, i))] - tx2.vout = [CTxOut(amount, scriptPubKey)] - tx2.rehash() - - signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex()) - - txid = node.sendrawtransaction(signed_tx['hex'], 0) + # MiniWallet only supports sweeping utxos to its own internal scriptPubKey, so in + # order to create an output with arbitrary amount/scriptPubKey, we have to add it + # manually after calling the create_self_transfer method. The MiniWallet output's + # nValue has to be adapted accordingly (amount and fee deduction). To keep things + # simple, we use a fixed fee of 1000 Satoshis here. + fee = 1000 + tx = self.wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx'] + assert_greater_than(tx.vout[0].nValue, amount + fee) + tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet + tx.vout.append(CTxOut(amount, scriptPubKey)) # desired output -> to be returned + txid = self.wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex()) # If requested, ensure txouts are confirmed. if confirmed: @@ -124,7 +125,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert new_size < mempool_size mempool_size = new_size - return COutPoint(int(txid, 16), 0) + return COutPoint(int(txid, 16), 1) def test_simple_doublespend(self): """Simple doublespend""" diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index ba5b95f930f..ea45661d9ae 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -179,8 +179,10 @@ class MiniWallet: return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx} def sendrawtransaction(self, *, from_node, tx_hex): - from_node.sendrawtransaction(tx_hex) + txid = from_node.sendrawtransaction(tx_hex) self.scan_tx(from_node.decoderawtransaction(tx_hex)) + return txid + def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): """Build a transaction that spends parent_txid.vout[n] and produces one output with