Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mempool_updatefromblock.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/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(0.00100000)): | |||||
"""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.00000001')) | |||||
outputs = {} | |||||
for n in range(0, n_outputs): | |||||
outputs[self.nodes[0].getnewaddress()] = output_value | |||||
else: | |||||
output_value = ( | |||||
inputs_value - | |||||
fee).quantize( | |||||
Decimal('0.00000001')) | |||||
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']) | |||||
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)])) | |||||
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() |