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) 2014-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 descendant package tracking code."""7from decimal import Decimal9from test_framework.messages import (10DEFAULT_CLUSTER_LIMIT,11)12from test_framework.p2p import P2PTxInvStore13from test_framework.test_framework import BitcoinTestFramework14from test_framework.util import (15assert_equal,16)17from test_framework.wallet import MiniWallet18from test_framework.blocktools import create_empty_fork20# custom limits for node121CUSTOM_CLUSTER_LIMIT = 1022assert CUSTOM_CLUSTER_LIMIT < DEFAULT_CLUSTER_LIMIT24class MempoolPackagesTest(BitcoinTestFramework):25def set_test_params(self):26self.num_nodes = 227# whitelist peers to speed up tx relay / mempool sync28self.noban_tx_relay = True29self.extra_args = [30[31],32[33"-limitclustercount={}".format(CUSTOM_CLUSTER_LIMIT),34],35]37def trigger_reorg(self, fork_blocks, node):38"""Trigger reorg of the fork blocks."""39for block in fork_blocks:40node.submitblock(block.serialize().hex())41assert_equal(node.getbestblockhash(), fork_blocks[-1].hash_hex)43def run_test(self):44self.wallet = MiniWallet(self.nodes[0])45self.wallet.rescan_utxos()47peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs49# DEFAULT_CLUSTER_LIMIT transactions off a confirmed tx should be fine for default node50chain = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_CLUSTER_LIMIT)51witness_chain = [t["wtxid"] for t in chain]52ancestor_vsize = 053ancestor_fees = Decimal(0)55for i, t in enumerate(chain):56ancestor_vsize += t["tx"].get_vsize()57ancestor_fees += t["fee"]58self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=t["hex"])60# Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)61# Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between62peer_inv_store.wait_for_broadcast(witness_chain)64# Check mempool has DEFAULT_CLUSTER_LIMIT transactions in it, and descendant and ancestor65# count and fees should look correct66mempool = self.nodes[0].getrawmempool(True)67assert_equal(len(mempool), DEFAULT_CLUSTER_LIMIT)68descendant_count = 169descendant_fees = 070descendant_vsize = 072assert_equal(ancestor_vsize, sum([mempool[tx]['vsize'] for tx in mempool]))73ancestor_count = DEFAULT_CLUSTER_LIMIT74assert_equal(ancestor_fees, sum([mempool[tx]['fees']['base'] for tx in mempool]))76descendants = []77ancestors = [t["txid"] for t in chain]78chain = [t["txid"] for t in chain]79for x in reversed(chain):80# Check that getmempoolentry is consistent with getrawmempool81entry = self.nodes[0].getmempoolentry(x)82assert_equal(entry, mempool[x])84# Check that gettxspendingprevout is consistent with getrawmempool85witnesstx = self.nodes[0].getrawtransaction(txid=x, verbose=True)86for tx_in in witnesstx["vin"]:87spending_result = self.nodes[0].gettxspendingprevout([ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"]} ])88assert_equal(spending_result, [ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"], 'spendingtxid' : x} ])90# Check that the descendant calculations are correct91assert_equal(entry['descendantcount'], descendant_count)92descendant_fees += entry['fees']['base']93assert_equal(entry['fees']['modified'], entry['fees']['base'])94assert_equal(entry['fees']['descendant'], descendant_fees)95descendant_vsize += entry['vsize']96assert_equal(entry['descendantsize'], descendant_vsize)97descendant_count += 199# Check that ancestor calculations are correct100assert_equal(entry['ancestorcount'], ancestor_count)101assert_equal(entry['fees']['ancestor'], ancestor_fees)102assert_equal(entry['ancestorsize'], ancestor_vsize)103ancestor_vsize -= entry['vsize']104ancestor_fees -= entry['fees']['base']105ancestor_count -= 1107# Check that parent/child list is correct108assert_equal(entry['spentby'], descendants[-1:])109assert_equal(entry['depends'], ancestors[-2:-1])111# Check that getmempooldescendants is correct112assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))114# Check getmempooldescendants verbose output is correct115for descendant, dinfo in self.nodes[0].getmempooldescendants(x, True).items():116assert_equal(dinfo['depends'], [chain[chain.index(descendant)-1]])117if dinfo['descendantcount'] > 1:118assert_equal(dinfo['spentby'], [chain[chain.index(descendant)+1]])119else:120assert_equal(dinfo['spentby'], [])121descendants.append(x)123# Check that getmempoolancestors is correct124ancestors.remove(x)125assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))127# Check that getmempoolancestors verbose output is correct128for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items():129assert_equal(ainfo['spentby'], [chain[chain.index(ancestor)+1]])130if ainfo['ancestorcount'] > 1:131assert_equal(ainfo['depends'], [chain[chain.index(ancestor)-1]])132else:133assert_equal(ainfo['depends'], [])136# Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true137v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)138assert_equal(len(v_ancestors), len(chain)-1)139for x in v_ancestors.keys():140assert_equal(mempool[x], v_ancestors[x])141assert chain[-1] not in v_ancestors.keys()143v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)144assert_equal(len(v_descendants), len(chain)-1)145for x in v_descendants.keys():146assert_equal(mempool[x], v_descendants[x])147assert chain[0] not in v_descendants.keys()149# Check that ancestor modified fees includes fee deltas from150# prioritisetransaction151self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000)152ancestor_fees = 0153for x in chain:154entry = self.nodes[0].getmempoolentry(x)155ancestor_fees += entry['fees']['base']156assert_equal(entry['fees']['ancestor'], ancestor_fees + Decimal('0.00001'))158# Undo the prioritisetransaction for later tests159self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000)161# Check that descendant modified fees includes fee deltas from162# prioritisetransaction163self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000)165descendant_fees = 0166for x in reversed(chain):167entry = self.nodes[0].getmempoolentry(x)168descendant_fees += entry['fees']['base']169assert_equal(entry['fees']['descendant'], descendant_fees + Decimal('0.00001'))171# Check that prioritising a tx before it's added to the mempool works172# First clear the mempool by mining a block.173self.generate(self.nodes[0], 1)174assert_equal(len(self.nodes[0].getrawmempool()), 0)175# Prioritise a transaction that has been mined, then add it back to the176# mempool by using invalidateblock.177self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000)178self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())179# Keep node1's tip synced with node0180self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())182# Now check that the transaction is in the mempool, with the right modified fee183descendant_fees = 0184for x in reversed(chain):185entry = self.nodes[0].getmempoolentry(x)186descendant_fees += entry['fees']['base']187if (x == chain[-1]):188assert_equal(entry['fees']['modified'], entry['fees']['base'] + Decimal("0.00002"))189assert_equal(entry['fees']['descendant'], descendant_fees + Decimal("0.00002"))191# Now test descendant chain limits192tx_children = []193# First create one parent tx with 10 children194tx_with_children = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10)195parent_transaction = tx_with_children["txid"]196transaction_package = tx_with_children["new_utxos"]198# Sign and send up to MAX_DESCENDANT transactions chained off the parent tx199chain = [] # save sent txs for the purpose of checking node1's mempool later (see below)200for _ in range(DEFAULT_CLUSTER_LIMIT - 1):201utxo = transaction_package.pop(0)202new_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10, utxos_to_spend=[utxo])203txid = new_tx["txid"]204chain.append(txid)205if utxo['txid'] is parent_transaction:206tx_children.append(txid)207transaction_package.extend(new_tx["new_utxos"])209mempool = self.nodes[0].getrawmempool(True)210assert_equal(mempool[parent_transaction]['descendantcount'], DEFAULT_CLUSTER_LIMIT)211assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children))213for child in tx_children:214assert_equal(mempool[child]['depends'], [parent_transaction])216# Check that node1's mempool is as expected, containing:217# - parent tx for descendant test218# - txs chained off parent tx (-> custom descendant limit)219self.wait_until(lambda: len(self.nodes[1].getrawmempool()) == 2*CUSTOM_CLUSTER_LIMIT, timeout=10)220mempool0 = self.nodes[0].getrawmempool(False)221mempool1 = self.nodes[1].getrawmempool(False)222assert set(mempool1).issubset(set(mempool0))223assert parent_transaction in mempool1224for tx in chain:225if tx in mempool1:226entry0 = self.nodes[0].getmempoolentry(tx)227entry1 = self.nodes[1].getmempoolentry(tx)228assert not entry0['unbroadcast']229assert not entry1['unbroadcast']230assert entry1["descendantcount"] <= CUSTOM_CLUSTER_LIMIT231assert_equal(entry1['fees']['base'], entry0['fees']['base'])232assert_equal(entry1['vsize'], entry0['vsize'])233assert_equal(entry1['depends'], entry0['depends'])235# Test reorg handling236# First, the basics:237fork_blocks = create_empty_fork(self.nodes[0])238mempool0 = self.nodes[0].getrawmempool(False)239self.generate(self.nodes[0], 1)240self.trigger_reorg(fork_blocks, self.nodes[0])242# Check that the txs are returned to the mempool, and that transaction ordering is243# unchanged, as it is deterministic.244assert_equal(self.nodes[0].getrawmempool(), mempool0)246# Clean-up the mempool247self.generate(self.nodes[0], 1)249# Now test the case where node1 has a transaction T in its mempool that250# depends on transactions A and B which are in a mined block, and the
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.