From aa8a65e4a88bfbd83ac756a87bfb8faf52fb675d Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Tue, 11 Jan 2022 03:30:23 +0100 Subject: [PATCH] test: use MiniWallet for mempool_accept.py This test can now be run even with the Bitcoin Core wallet disabled. --- test/functional/mempool_accept.py | 80 +++++++++++------------- test/functional/test_framework/wallet.py | 4 +- 2 files changed, 38 insertions(+), 46 deletions(-) diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index eef82d34b6..d3961e753d 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool acceptance of raw transactions.""" +from copy import deepcopy from decimal import Decimal import math @@ -34,6 +35,7 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet class MempoolAcceptanceTest(BitcoinTestFramework): @@ -44,9 +46,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ]] * self.num_nodes self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def check_mempool_result(self, result_expected, *args, **kwargs): """Wrapper to check result of testmempoolaccept on node_0's mempool""" result_test = self.nodes[0].testmempoolaccept(*args, **kwargs) @@ -57,12 +56,13 @@ class MempoolAcceptanceTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] + self.wallet = MiniWallet(node) + self.wallet.rescan_utxos() self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) - coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) @@ -71,12 +71,12 @@ class MempoolAcceptanceTest(BitcoinTestFramework): assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') - coin = coins.pop() # Pick a random coin(base) to spend - raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], - outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], - ))['hex'] - txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] # Pick a random coin(base) to spend + tx.vout.append(deepcopy(tx.vout[0])) + tx.vout[0].nValue = int(0.3 * COIN) + tx.vout[1].nValue = int(49 * COIN) + raw_tx_in_block = tx.serialize().hex() + txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 self.check_mempool_result( @@ -86,11 +86,10 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction not in the mempool') fee = Decimal('0.000007') - raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later - outputs=[{node.getnewaddress(): Decimal('0.3') - fee}], - ))['hex'] - tx = tx_from_hex(raw_tx_0) + utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=BIP125_SEQUENCE_NUMBER)['tx'] + tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN) + raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], @@ -98,15 +97,15 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A final transaction not in the mempool') - coin = coins.pop() # Pick a random coin(base) to spend output_amount = Decimal('0.025') - raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": SEQUENCE_FINAL}], - outputs=[{node.getnewaddress(): output_amount}], + tx = self.wallet.create_self_transfer( + sequence=SEQUENCE_FINAL, locktime=node.getblockcount() + 2000, # Can be anything - ))['hex'] + )['tx'] + tx.vout[0].nValue = int(output_amount * COIN) + raw_tx_final = tx.serialize().hex() tx = tx_from_hex(raw_tx_final) - fee_expected = coin['amount'] - output_amount + fee_expected = Decimal('50.0') - output_amount self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], rawtxs=[tx.serialize().hex()], @@ -127,8 +126,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF - raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] - tx = tx_from_hex(raw_tx_0) + raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}], @@ -141,7 +139,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): # take original raw_tx_0 tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}], rawtxs=[tx.serialize().hex()], @@ -151,7 +148,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with missing inputs, that never existed') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[tx.serialize().hex()], @@ -160,17 +156,17 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with missing inputs, that existed once in the past') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend - raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] + raw_tx_1 = tx.serialize().hex() txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them - raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[ - {'txid': txid_0, 'vout': 0}, - {'txid': txid_1, 'vout': 0}, - ], - outputs=[{node.getnewaddress(): 0.1}] - ))['hex'] - txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] + tx.vin.append(deepcopy(tx.vin[0])) + tx.wit.vtxinwit.append(deepcopy(tx.wit.vtxinwit[0])) + tx.vin[0].prevout = COutPoint(hash=int(txid_0, 16), n=0) + tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0) + tx.vout[0].nValue = int(0.1 * COIN) + raw_tx_spend_both = tx.serialize().hex() + txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again @@ -183,12 +179,11 @@ class MempoolAcceptanceTest(BitcoinTestFramework): rawtxs=[raw_tx_1], ) - self.log.info('Create a signed "reference" tx for later use') - raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': txid_spend_both, 'vout': 0}], - outputs=[{node.getnewaddress(): 0.05}], - ))['hex'] - tx = tx_from_hex(raw_tx_reference) + self.log.info('Create a "reference" tx for later use') + utxo_to_spend = self.wallet.get_utxo(txid=txid_spend_both) + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=SEQUENCE_FINAL)['tx'] + tx.vout[0].nValue = int(0.05 * COIN) + raw_tx_reference = tx.serialize().hex() # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], @@ -199,8 +194,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with no outputs') tx = tx_from_hex(raw_tx_reference) tx.vout = [] - # Skip re-signing the transaction for context independent checks from now on - # tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], @@ -257,7 +250,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A coinbase transaction') - # Pick the input of the first tx we signed, so it has to be a coinbase tx + # Pick the input of the first tx we created, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx = tx_from_hex(raw_tx_coinbase_spent) self.check_mempool_result( @@ -334,7 +327,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction that is locked by BIP68 sequence logic') tx = tx_from_hex(raw_tx_reference) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one - # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}], rawtxs=[tx.serialize().hex()], diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index fc0a7eb2e4..dd41a740ae 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -214,8 +214,8 @@ class MiniWallet: assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx} - def sendrawtransaction(self, *, from_node, tx_hex): - txid = from_node.sendrawtransaction(tx_hex) + def sendrawtransaction(self, *, from_node, tx_hex, **kwargs): + txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs) self.scan_tx(from_node.decoderawtransaction(tx_hex)) return txid