Skip to content

Commit 4789436

Browse files
committed
functional test: add MAX_DISCONNECTED_TX_POOL_BYTES coverage
1 parent c779ee3 commit 4789436

File tree

1 file changed

+83
-1
lines changed

1 file changed

+83
-1
lines changed

test/functional/mempool_updatefromblock.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,44 @@
77
Test mempool update of transaction descendants/ancestors information (count, size)
88
when transactions have been re-added from a disconnected block to the mempool.
99
"""
10+
from decimal import Decimal
1011
from math import ceil
1112
import time
1213

14+
from test_framework.blocktools import (
15+
create_block,
16+
create_coinbase,
17+
)
1318
from test_framework.test_framework import BitcoinTestFramework
1419
from test_framework.util import assert_equal
1520
from test_framework.wallet import MiniWallet
1621

22+
MAX_DISCONNECTED_TX_POOL_BYTES = 20_000_000
1723

1824
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
1925
def set_test_params(self):
2026
self.num_nodes = 1
21-
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']]
27+
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100', '-datacarriersize=100000']]
28+
29+
def create_empty_fork(self, fork_length):
30+
'''
31+
Creates a fork using first node's chaintip as the starting point.
32+
Returns a list of blocks to submit in order.
33+
'''
34+
tip = int(self.nodes[0].getbestblockhash(), 16)
35+
height = self.nodes[0].getblockcount()
36+
block_time = self.nodes[0].getblock(self.nodes[0].getbestblockhash())['time'] + 1
37+
38+
blocks = []
39+
for _ in range(fork_length):
40+
block = create_block(tip, create_coinbase(height + 1), block_time)
41+
block.solve()
42+
blocks.append(block)
43+
tip = block.sha256
44+
block_time += 1
45+
height += 1
46+
47+
return blocks
2248

2349
def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000):
2450
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
@@ -97,10 +123,66 @@ def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000):
97123
assert_equal(entry['ancestorcount'], k + 1)
98124
assert_equal(entry['ancestorsize'], sum(tx_size[0:(k + 1)]))
99125

126+
self.generate(self.nodes[0], 1)
127+
assert_equal(self.nodes[0].getrawmempool(), [])
128+
wallet.rescan_utxos()
129+
130+
def test_max_disconnect_pool_bytes(self):
131+
self.log.info('Creating independent transactions to test MAX_DISCONNECTED_TX_POOL_BYTES limit during reorg')
132+
133+
# Generate coins for the hundreds of transactions we will make
134+
parent_target_vsize = 100_000
135+
wallet = MiniWallet(self.nodes[0])
136+
self.generate(wallet, (MAX_DISCONNECTED_TX_POOL_BYTES // parent_target_vsize) + 100)
137+
138+
assert_equal(self.nodes[0].getrawmempool(), [])
139+
140+
# Set up empty fork blocks ahead of time, needs to be longer than full fork made later
141+
fork_blocks = self.create_empty_fork(fork_length=60)
142+
143+
large_std_txs = []
144+
# Add children to ensure they're recursively removed if disconnectpool trimming of parent occurs
145+
small_child_txs = []
146+
aggregate_serialized_size = 0
147+
while aggregate_serialized_size < MAX_DISCONNECTED_TX_POOL_BYTES:
148+
# Mine parents in FIFO order via fee ordering
149+
large_std_txs.append(wallet.create_self_transfer(target_vsize=parent_target_vsize, fee=Decimal("0.00400000") - (Decimal("0.00001000") * len(large_std_txs))))
150+
small_child_txs.append(wallet.create_self_transfer(utxo_to_spend=large_std_txs[-1]['new_utxo']))
151+
# Slight underestimate of dynamic cost, so we'll be over during reorg
152+
aggregate_serialized_size += len(large_std_txs[-1]["tx"].serialize())
153+
154+
for large_std_tx in large_std_txs:
155+
self.nodes[0].sendrawtransaction(large_std_tx["hex"])
156+
157+
assert_equal(self.nodes[0].getmempoolinfo()["size"], len(large_std_txs))
158+
159+
# Mine non-empty chain that will be reorged shortly
160+
self.generate(self.nodes[0], len(fork_blocks) - 1)
161+
assert_equal(self.nodes[0].getrawmempool(), [])
162+
163+
# Stick children in mempool, evicted with parent potentially
164+
for small_child_tx in small_child_txs:
165+
self.nodes[0].sendrawtransaction(small_child_tx["hex"])
166+
167+
assert_equal(self.nodes[0].getmempoolinfo()["size"], len(small_child_txs))
168+
169+
# Reorg back before the first block in the series, should drop something
170+
# but not all, and any time parent is dropped, child is also removed
171+
for block in fork_blocks:
172+
self.nodes[0].submitblock(block.serialize().hex())
173+
mempool = self.nodes[0].getrawmempool()
174+
expected_parent_count = len(large_std_txs) - 2
175+
assert_equal(len(mempool), expected_parent_count * 2)
176+
177+
# The txns at the end of the list, or most recently confirmed, should have been trimmed
178+
assert_equal([tx["txid"] in mempool for tx in large_std_txs], [tx["txid"] in mempool for tx in small_child_txs])
179+
assert_equal([tx["txid"] in mempool for tx in large_std_txs], [True] * expected_parent_count + [False] * 2)
180+
100181
def run_test(self):
101182
# Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 25 to not fire "too many unconfirmed parents" error.
102183
self.transaction_graph_test(size=100, n_tx_to_mine=[25, 50, 75])
103184

185+
self.test_max_disconnect_pool_bytes()
104186

105187
if __name__ == '__main__':
106188
MempoolUpdateFromBlockTest(__file__).main()

0 commit comments

Comments
 (0)