Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2020-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test mempool descendants/ancestors information update.7Test mempool update of transaction descendants/ancestors information (count, size)8when transactions have been re-added from a disconnected block to the mempool.9"""10from decimal import Decimal11from math import ceil12import time14from test_framework.blocktools import create_empty_fork15from test_framework.test_framework import BitcoinTestFramework16from test_framework.util import assert_equal, assert_greater_than_or_equal, assert_raises_rpc_error17from test_framework.wallet import MiniWallet18from test_framework.mempool_util import DEFAULT_CLUSTER_LIMIT20MAX_DISCONNECTED_TX_POOL_BYTES = 20_000_00022class MempoolUpdateFromBlockTest(BitcoinTestFramework):23def set_test_params(self):24self.num_nodes = 125self.extra_args = [['-limitclustersize=1000']]27def trigger_reorg(self, fork_blocks):28"""Trigger reorg of the fork blocks."""29for block in fork_blocks:30self.nodes[0].submitblock(block.serialize().hex())31assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)33def transaction_graph_test(self, size, *, n_tx_to_mine, fee=100_000):34"""Create an acyclic tournament (a type of directed graph) of transactions and use it for testing.36Keyword arguments:37size -- the order N of the tournament which is equal to the number of the created transactions38n_tx_to_mine -- the number of transactions that should be mined into a block40If all of the N created transactions tx[0]..tx[N-1] reside in the mempool,41the following holds:42the tx[K] transaction:43- has N-K descendants (including this one), and44- has K+1 ancestors (including this one)46More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory)47"""48wallet = MiniWallet(self.nodes[0])50# Prep for fork with empty blocks to not use invalidateblock directly51# for reorg case. The rpc has different codepath52fork_blocks = create_empty_fork(self.nodes[0], fork_length=7)54tx_id = []55tx_size = []56self.log.info('Creating {} transactions...'.format(size))57for i in range(0, size):58self.log.debug('Preparing transaction #{}...'.format(i))59# Prepare inputs.60if i == 0:61inputs = [wallet.get_utxo()] # let MiniWallet provide a start UTXO62else:63inputs = []64for j, tx in enumerate(tx_id[0:i]):65# Transaction tx[K] is a child of each of previous transactions tx[0]..tx[K-1] at their output K-1.66vout = i - j - 167inputs.append(wallet.get_utxo(txid=tx_id[j], vout=vout))69# Prepare outputs.70tx_count = i + 171if tx_count < size:72# Transaction tx[K] is an ancestor of each of subsequent transactions tx[K+1]..tx[N-1].73n_outputs = size - tx_count74else:75n_outputs = 177# Create a new transaction.78new_tx = wallet.send_self_transfer_multi(79from_node=self.nodes[0],80utxos_to_spend=inputs,81num_outputs=n_outputs,82fee_per_output=ceil(fee / n_outputs)83)84tx_id.append(new_tx['txid'])85tx_size.append(new_tx['tx'].get_vsize())87if tx_count in n_tx_to_mine:88# The created transactions are mined into blocks by batches.89self.log.info('The batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))90self.generate(self.nodes[0], 1)[0]91assert_equal(len(self.nodes[0].getrawmempool()), 0)92self.log.info('All of the transactions from the current batch have been mined into a block.')93elif tx_count == size:94# At the end the old fork is submitted to cause reorg, and all of the created95# transactions should be re-added from disconnected blocks to the mempool.96self.log.info('The last batch of {} transactions has been accepted into the mempool.'.format(len(self.nodes[0].getrawmempool())))97start = time.time()98# Trigger reorg99for block in fork_blocks:100self.nodes[0].submitblock(block.serialize().hex())101end = time.time()102assert_equal(len(self.nodes[0].getrawmempool()), size)103self.log.info('All of the recently mined transactions have been re-added into the mempool in {} seconds.'.format(end - start))105self.log.info('Checking descendants/ancestors properties of all of the in-mempool transactions...')106for k, tx in enumerate(tx_id):107self.log.debug('Check transaction #{}.'.format(k))108entry = self.nodes[0].getmempoolentry(tx)109assert_equal(entry['descendantcount'], size - k)110assert_equal(entry['descendantsize'], sum(tx_size[k:size]))111assert_equal(entry['ancestorcount'], k + 1)112assert_equal(entry['ancestorsize'], sum(tx_size[0:(k + 1)]))114self.generate(self.nodes[0], 1)115assert_equal(self.nodes[0].getrawmempool(), [])116wallet.rescan_utxos()118def test_max_disconnect_pool_bytes(self):119self.log.info('Creating independent transactions to test MAX_DISCONNECTED_TX_POOL_BYTES limit during reorg')121# Generate coins for the hundreds of transactions we will make122parent_target_vsize = 100_000123wallet = MiniWallet(self.nodes[0])124self.generate(wallet, (MAX_DISCONNECTED_TX_POOL_BYTES // parent_target_vsize) + 100)126assert_equal(self.nodes[0].getrawmempool(), [])128# Set up empty fork blocks ahead of time, needs to be longer than full fork made later129fork_blocks = create_empty_fork(self.nodes[0], fork_length=60)131large_std_txs = []132# Add children to ensure they're recursively removed if disconnectpool trimming of parent occurs133small_child_txs = []134aggregate_serialized_size = 0135while aggregate_serialized_size < MAX_DISCONNECTED_TX_POOL_BYTES:136# Mine parents in FIFO order via fee ordering137large_std_txs.append(wallet.create_self_transfer(target_vsize=parent_target_vsize, fee=Decimal("0.00400000") - (Decimal("0.00001000") * len(large_std_txs))))138small_child_txs.append(wallet.create_self_transfer(utxo_to_spend=large_std_txs[-1]['new_utxo']))139# Slight underestimate of dynamic cost, so we'll be over during reorg140aggregate_serialized_size += len(large_std_txs[-1]["tx"].serialize())142for large_std_tx in large_std_txs:143self.nodes[0].sendrawtransaction(large_std_tx["hex"])145assert_equal(self.nodes[0].getmempoolinfo()["size"], len(large_std_txs))147# Mine non-empty chain that will be reorged shortly148self.generate(self.nodes[0], len(fork_blocks) - 1)149assert_equal(self.nodes[0].getrawmempool(), [])151# Stick children in mempool, evicted with parent potentially152for small_child_tx in small_child_txs:153self.nodes[0].sendrawtransaction(small_child_tx["hex"])155assert_equal(self.nodes[0].getmempoolinfo()["size"], len(small_child_txs))157# Reorg back before the first block in the series, should drop something158# but not all, and any time parent is dropped, child is also removed159self.trigger_reorg(fork_blocks=fork_blocks)160mempool = self.nodes[0].getrawmempool()161# At least one parent must be dropped, but more may be dropped,162# depending on the dynamic cost overhead.163expected_parent_count = len(large_std_txs) - 1164assert_greater_than_or_equal(expected_parent_count * 2, len(mempool))165expected_parent_count = len(mempool) // 2167parent_presence = [tx["txid"] in mempool for tx in large_std_txs]169# The txns at the end of the list, or most recently confirmed, should have been trimmed170assert_equal(parent_presence, [tx["txid"] in mempool for tx in small_child_txs])171assert_equal(parent_presence, [True] * expected_parent_count + [False] * (len(large_std_txs) - expected_parent_count))173def test_chainlimits_exceeded(self):174self.log.info('Check that too long chains on reorg are handled')176wallet = MiniWallet(self.nodes[0])177self.generate(wallet, 101)179assert_equal(self.nodes[0].getrawmempool(), [])181# Prep fork182fork_blocks = create_empty_fork(self.nodes[0])184# Two higher than descendant count185chain = wallet.create_self_transfer_chain(chain_length=DEFAULT_CLUSTER_LIMIT + 2)186for tx in chain[:-2]:187self.nodes[0].sendrawtransaction(tx["hex"])189assert_raises_rpc_error(-26, "too-large-cluster", self.nodes[0].sendrawtransaction, chain[-2]["hex"])191# Mine a block with all but last transaction, non-standardly long chain192self.generateblock(self.nodes[0], output="raw(42)", transactions=[tx["hex"] for tx in chain[:-1]])193assert_equal(self.nodes[0].getrawmempool(), [])195# Last tx fits now196self.nodes[0].sendrawtransaction(chain[-1]["hex"])198# Finally, reorg to empty chain to kick everything back into mempool199# at normal chain limits200for block in fork_blocks:201self.nodes[0].submitblock(block.serialize().hex())202mempool = self.nodes[0].getrawmempool()203assert_equal(set(mempool), set([tx["txid"] for tx in chain[:-2]]))205def run_test(self):206# Mine in batches of 25 to test multi-block reorg under chain limits207self.transaction_graph_test(size=DEFAULT_CLUSTER_LIMIT, n_tx_to_mine=[25, 50, 75])209self.test_max_disconnect_pool_bytes()211self.test_chainlimits_exceeded()213if __name__ == '__main__':214MempoolUpdateFromBlockTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.