diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py index 5d3304b09..1b2e9ce7d 100755 --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -1,158 +1,154 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool descendants/ancestors information update. Test mempool update of transaction descendants/ancestors information (count, size) when transactions have been re-added from a disconnected block to the mempool. """ import time from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class MempoolUpdateFromBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [ ['-limitdescendantsize=5000', '-limitancestorsize=5000']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(1000)): """Create an acyclic tournament (a type of directed graph) of transactions and use it for testing. Keyword arguments: size -- the order N of the tournament which is equal to the number of the created transactions n_tx_to_mine -- the number of transaction that should be mined into a block If all of the N created transactions tx[0]..tx[N-1] reside in the mempool, the following holds: the tx[K] transaction: - has N-K descendants (including this one), and - has K+1 ancestors (including this one) More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory) """ if not start_input_txid: start_input_txid = self.nodes[0].getblock( self.nodes[0].getblockhash(1))['tx'][0] if not end_address: end_address = self.nodes[0].getnewaddress() first_block_hash = '' tx_id = [] tx_size = [] self.log.info('Creating {} transactions...'.format(size)) for i in range(0, size): self.log.debug('Preparing transaction #{}...'.format(i)) # Prepare inputs. if i == 0: inputs = [{'txid': start_input_txid, 'vout': 0}] inputs_value = self.nodes[0].gettxout( start_input_txid, 0)['value'] else: inputs = [] inputs_value = 0 for j, tx in enumerate(tx_id[0:i]): # Transaction tx[K] is a child of each of previous # transactions tx[0]..tx[K-1] at their output K-1. vout = i - j - 1 inputs.append({'txid': tx_id[j], 'vout': vout}) inputs_value += self.nodes[0].gettxout(tx, vout)['value'] self.log.debug('inputs={}'.format(inputs)) self.log.debug('inputs_value={}'.format(inputs_value)) # Prepare outputs. tx_count = i + 1 if tx_count < size: # Transaction tx[K] is an ancestor of each of subsequent # transactions tx[K+1]..tx[N-1]. n_outputs = size - tx_count output_value = ( (inputs_value - fee) / Decimal(n_outputs)).quantize( Decimal('0.01')) outputs = {} for n in range(0, n_outputs): outputs[self.nodes[0].getnewaddress()] = output_value else: output_value = ( inputs_value - fee).quantize( Decimal('0.01')) outputs = {end_address: output_value} self.log.debug('output_value={}'.format(output_value)) self.log.debug('outputs={}'.format(outputs)) # Create a new transaction. unsigned_raw_tx = self.nodes[0].createrawtransaction( inputs, outputs) signed_raw_tx = self.nodes[0].signrawtransactionwithwallet( unsigned_raw_tx) tx_id.append( self.nodes[0].sendrawtransaction( signed_raw_tx['hex'])) - tx_size.append(self.nodes[0].getrawmempool( - True)[tx_id[-1]]['size']) + tx_size.append(self.nodes[0].getmempoolentry(tx_id[-1])['size']) if tx_count in n_tx_to_mine: # The created transactions are mined into blocks by batches. self.log.info('The batch of {} transactions has been accepted' ' into the mempool.'.format(len(self.nodes[0].getrawmempool()))) block_hash = self.nodes[0].generate(1)[0] if not first_block_hash: first_block_hash = block_hash assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.info( 'All of the transactions from the current batch have been' ' mined into a block.') elif tx_count == size: # At the end all of the mined blocks are invalidated, and all of the created # transactions should be re-added from disconnected blocks to # the mempool. self.log.info('The last batch of {} transactions has been' ' accepted into the mempool.'.format(len(self.nodes[0].getrawmempool()))) start = time.time() self.nodes[0].invalidateblock(first_block_hash) end = time.time() assert_equal(len(self.nodes[0].getrawmempool()), size) self.log.info( 'All of the recently mined transactions have been re-added' ' into the mempool in {} seconds.'.format(end - start)) self.log.info( 'Checking descendants/ancestors properties of all of the' ' in-mempool transactions...') for k, tx in enumerate(tx_id): self.log.debug('Check transaction #{}.'.format(k)) - assert_equal(self.nodes[0].getrawmempool(True)[ - tx]['descendantcount'], size - k) - assert_equal(self.nodes[0].getrawmempool(True)[ - tx]['descendantsize'], sum(tx_size[k:size])) - assert_equal(self.nodes[0].getrawmempool( - True)[tx]['ancestorcount'], k + 1) - assert_equal(self.nodes[0].getrawmempool(True)[ - tx]['ancestorsize'], sum(tx_size[0:(k + 1)])) + entry = self.nodes[0].getmempoolentry(tx) + assert_equal(entry['descendantcount'], size - k) + assert_equal(entry['descendantsize'], sum(tx_size[k:size])) + assert_equal(entry['ancestorcount'], k + 1) + assert_equal(entry['ancestorsize'], sum(tx_size[0:(k + 1)])) def run_test(self): # Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 50 to not fire # "too many unconfirmed parents" error. self.transaction_graph_test(size=200, n_tx_to_mine=[50, 100, 150]) if __name__ == '__main__': MempoolUpdateFromBlockTest().main()