Skip to content

Commit 8098dea

Browse files
committed
test: Add mempool_updatefromblock.py
1 parent 965c0c3 commit 8098dea

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2020 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test mempool descendants/ancestors information update.
6+
7+
Test mempool update of transaction descendants/ancestors information (count, size)
8+
when transactions have been re-added from a disconnected block to the mempool.
9+
"""
10+
import time
11+
12+
from decimal import Decimal
13+
from test_framework.test_framework import BitcoinTestFramework
14+
from test_framework.util import assert_equal
15+
16+
17+
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
18+
def set_test_params(self):
19+
self.num_nodes = 1
20+
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000']]
21+
22+
def skip_test_if_missing_module(self):
23+
self.skip_if_no_wallet()
24+
25+
def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', fee=Decimal(0.00100000)):
26+
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
27+
28+
Keyword arguments:
29+
size -- the order N of the tournament which is equal to the number of the created transactions
30+
n_tx_to_mine -- the number of transaction that should be mined into a block
31+
32+
If all of the N created transactions tx[0]..tx[N-1] reside in the mempool,
33+
the following holds:
34+
the tx[K] transaction:
35+
- has N-K descendants (including this one), and
36+
- has K+1 ancestors (including this one)
37+
38+
More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)
39+
"""
40+
41+
if not start_input_txid:
42+
start_input_txid = self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0]
43+
44+
if not end_address:
45+
end_address = self.nodes[0].getnewaddress()
46+
47+
first_block_hash = ''
48+
tx_id = []
49+
tx_size = []
50+
self.log.info('Creating {} transactions...'.format(size))
51+
for i in range(0, size):
52+
self.log.debug('Preparing transaction #{}...'.format(i))
53+
# Prepare inputs.
54+
if i == 0:
55+
inputs = [{'txid': start_input_txid, 'vout': 0}]
56+
inputs_value = self.nodes[0].gettxout(start_input_txid, 0)['value']
57+
else:
58+
inputs = []
59+
inputs_value = 0
60+
for j, tx in enumerate(tx_id[0:i]):
61+
# Transaction tx[K] is a child of each of previous transactions tx[0]..tx[K-1] at their output K-1.
62+
vout = i - j - 1
63+
inputs.append({'txid': tx_id[j], 'vout': vout})
64+
inputs_value += self.nodes[0].gettxout(tx, vout)['value']
65+
66+
self.log.debug('inputs={}'.format(inputs))
67+
self.log.debug('inputs_value={}'.format(inputs_value))
68+
69+
# Prepare outputs.
70+
tx_count = i + 1
71+
if tx_count < size:
72+
# Transaction tx[K] is an ancestor of each of subsequent transactions tx[K+1]..tx[N-1].
73+
n_outputs = size - tx_count
74+
output_value = ((inputs_value - fee) / Decimal(n_outputs)).quantize(Decimal('0.00000001'))
75+
outputs = {}
76+
for n in range(0, n_outputs):
77+
outputs[self.nodes[0].getnewaddress()] = output_value
78+
else:
79+
output_value = (inputs_value - fee).quantize(Decimal('0.00000001'))
80+
outputs = {end_address: output_value}
81+
82+
self.log.debug('output_value={}'.format(output_value))
83+
self.log.debug('outputs={}'.format(outputs))
84+
85+
# Create a new transaction.
86+
unsigned_raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
87+
signed_raw_tx = self.nodes[0].signrawtransactionwithwallet(unsigned_raw_tx)
88+
tx_id.append(self.nodes[0].sendrawtransaction(signed_raw_tx['hex']))
89+
tx_size.append(self.nodes[0].getrawmempool(True)[tx_id[-1]]['vsize'])
90+
91+
if tx_count in n_tx_to_mine:
92+
# The created transactions are mined into blocks by batches.
93+
self.log.info('The batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
94+
block_hash = self.nodes[0].generate(1)[0]
95+
if not first_block_hash:
96+
first_block_hash = block_hash
97+
assert_equal(len(self.nodes[0].getrawmempool()), 0)
98+
self.log.info('All of the transactions from the current batch have been mined into a block.')
99+
elif tx_count == size:
100+
# At the end all of the mined blocks are invalidated, and all of the created
101+
# transactions should be re-added from disconnected blocks to the mempool.
102+
self.log.info('The last batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
103+
start = time.time()
104+
self.nodes[0].invalidateblock(first_block_hash)
105+
end = time.time()
106+
assert_equal(len(self.nodes[0].getrawmempool()), size)
107+
self.log.info('All of the recently mined transactions have been re-added into the mempool in {} seconds.'.format(end - start))
108+
109+
self.log.info('Checking descendants/ancestors properties of all of the in-mempool transactions...')
110+
for k, tx in enumerate(tx_id):
111+
self.log.debug('Check transaction #{}.'.format(k))
112+
assert_equal(self.nodes[0].getrawmempool(True)[tx]['descendantcount'], size - k)
113+
assert_equal(self.nodes[0].getrawmempool(True)[tx]['descendantsize'], sum(tx_size[k:size]))
114+
assert_equal(self.nodes[0].getrawmempool(True)[tx]['ancestorcount'], k + 1)
115+
assert_equal(self.nodes[0].getrawmempool(True)[tx]['ancestorsize'], sum(tx_size[0:(k + 1)]))
116+
117+
def run_test(self):
118+
# Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 25 to not fire "too many unconfirmed parents" error.
119+
self.transaction_graph_test(size=100, n_tx_to_mine=[25, 50, 75])
120+
121+
122+
if __name__ == '__main__':
123+
MempoolUpdateFromBlockTest().main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
'p2p_segwit.py',
9191
'p2p_timeouts.py',
9292
'p2p_tx_download.py',
93+
'mempool_updatefromblock.py',
9394
'wallet_dump.py',
9495
'wallet_listtransactions.py',
9596
# vv Tests less than 60s vv

0 commit comments

Comments
 (0)