Skip to content

Commit 84aa484

Browse files
committed
test: fix transaction_graph_test reorg test
The current test directly uses invalidatblock to trigger mempool re-entry of transactions. Unfortunately, the behavior doesn't match what a real reorg would look like. As a result you get surprising behavior such as the mempool descendant chain limits being exceeded, or if a fork is greater than 10 blocks deep, evicted block transactions stop being submitted back into in the mempool. Fix this by preparing an empty fork chain, and then continuing with the logic, finally submitting the fork chain once the rest of the test is prepared. This triggers a more typical codepath. Also, extend the descendant limit to 100, like ancestor limit.
1 parent eaf44f3 commit 84aa484

File tree

1 file changed

+22
-14
lines changed

1 file changed

+22
-14
lines changed

test/functional/mempool_updatefromblock.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121

2222
MAX_DISCONNECTED_TX_POOL_BYTES = 20_000_000
2323

24+
CUSTOM_ANCESTOR_COUNT = 100
25+
CUSTOM_DESCENDANT_COUNT = CUSTOM_ANCESTOR_COUNT
26+
2427
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
2528
def set_test_params(self):
2629
self.num_nodes = 1
27-
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100', '-datacarriersize=100000']]
30+
# Ancestor and descendant limits depend on transaction_graph_test requirements
31+
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', f'-limitancestorcount={CUSTOM_ANCESTOR_COUNT}', f'-limitdescendantcount={CUSTOM_DESCENDANT_COUNT}', '-datacarriersize=100000']]
2832

2933
def create_empty_fork(self, fork_length):
3034
'''
@@ -46,12 +50,12 @@ def create_empty_fork(self, fork_length):
4650

4751
return blocks
4852

49-
def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000):
53+
def transaction_graph_test(self, size, *, n_tx_to_mine, fee=100_000):
5054
"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.
5155
5256
Keyword arguments:
5357
size -- the order N of the tournament which is equal to the number of the created transactions
54-
n_tx_to_mine -- the number of transaction that should be mined into a block
58+
n_tx_to_mine -- the number of transactions that should be mined into a block
5559
5660
If all of the N created transactions tx[0]..tx[N-1] reside in the mempool,
5761
the following holds:
@@ -62,7 +66,11 @@ def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000):
6266
More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)
6367
"""
6468
wallet = MiniWallet(self.nodes[0])
65-
first_block_hash = ''
69+
70+
# Prep for fork with empty blocks to not use invalidateblock directly
71+
# for reorg case. The rpc has different codepath
72+
fork_blocks = self.create_empty_fork(fork_length=7)
73+
6674
tx_id = []
6775
tx_size = []
6876
self.log.info('Creating {} transactions...'.format(size))
@@ -99,17 +107,17 @@ def transaction_graph_test(self, size, n_tx_to_mine=None, fee=100_000):
99107
if tx_count in n_tx_to_mine:
100108
# The created transactions are mined into blocks by batches.
101109
self.log.info('The batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
102-
block_hash = self.generate(self.nodes[0], 1)[0]
103-
if not first_block_hash:
104-
first_block_hash = block_hash
110+
self.generate(self.nodes[0], 1)[0]
105111
assert_equal(len(self.nodes[0].getrawmempool()), 0)
106112
self.log.info('All of the transactions from the current batch have been mined into a block.')
107113
elif tx_count == size:
108-
# At the end all of the mined blocks are invalidated, and all of the created
114+
# At the end the old fork is submitted to cause reorg, and all of the created
109115
# transactions should be re-added from disconnected blocks to the mempool.
110116
self.log.info('The last batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))
111117
start = time.time()
112-
self.nodes[0].invalidateblock(first_block_hash)
118+
# Trigger reorg
119+
for block in fork_blocks:
120+
self.nodes[0].submitblock(block.serialize().hex())
113121
end = time.time()
114122
assert_equal(len(self.nodes[0].getrawmempool()), size)
115123
self.log.info('All of the recently mined transactions have been re-added into the mempool in {} seconds.'.format(end - start))
@@ -189,12 +197,12 @@ def test_chainlimits_exceeded(self):
189197
# Prep fork
190198
fork_blocks = self.create_empty_fork(fork_length=10)
191199

192-
# Two higher than default descendant count
193-
chain = wallet.create_self_transfer_chain(chain_length=27)
200+
# Two higher than descendant count
201+
chain = wallet.create_self_transfer_chain(chain_length=CUSTOM_DESCENDANT_COUNT + 2)
194202
for tx in chain[:-2]:
195203
self.nodes[0].sendrawtransaction(tx["hex"])
196204

197-
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants for tx", self.nodes[0].sendrawtransaction, chain[-2]["hex"])
205+
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 100]", self.nodes[0].sendrawtransaction, chain[-2]["hex"])
198206

199207
# Mine a block with all but last transaction, non-standardly long chain
200208
self.generateblock(self.nodes[0], output="raw(42)", transactions=[tx["hex"] for tx in chain[:-1]])
@@ -211,8 +219,8 @@ def test_chainlimits_exceeded(self):
211219
assert_equal(set(mempool), set([tx["txid"] for tx in chain[:-2]]))
212220

213221
def run_test(self):
214-
# Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 25 to not fire "too many unconfirmed parents" error.
215-
self.transaction_graph_test(size=100, n_tx_to_mine=[25, 50, 75])
222+
# Mine in batches of 25 to test multi-block reorg under chain limits
223+
self.transaction_graph_test(size=CUSTOM_ANCESTOR_COUNT, n_tx_to_mine=[25, 50, 75])
216224

217225
self.test_max_disconnect_pool_bytes()
218226

0 commit comments

Comments
 (0)