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) 2024-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.5from decimal import Decimal7from test_framework.test_framework import BitcoinTestFramework8from test_framework.util import (9assert_not_equal,10assert_equal,11assert_greater_than,12assert_greater_than_or_equal,13assert_raises_rpc_error,14get_fee,15)16from test_framework.wallet import (17COIN,18DEFAULT_FEE,19MiniWallet,20)21from test_framework.blocktools import (22create_empty_fork,23)25MAX_REPLACEMENT_CANDIDATES = 10026TRUC_MAX_VSIZE = 1000027TRUC_CHILD_MAX_VSIZE = 100029def cleanup(extra_args=None):30def decorator(func):31def wrapper(self):32if extra_args is not None:33self.restart_node(0, extra_args=extra_args)34func(self)36# Clear mempool again after test37self.generate(self.nodes[0], 1)38if extra_args is not None:39self.restart_node(0)40return wrapper41return decorator43class MempoolTRUC(BitcoinTestFramework):44def set_test_params(self):45self.num_nodes = 146self.extra_args = [[]]47self.setup_clean_chain = True49def check_mempool(self, txids):50"""Assert exact contents of the node's mempool (by txid)."""51mempool_contents = self.nodes[0].getrawmempool()52assert_equal(len(txids), len(mempool_contents))53assert all([txid in txids for txid in mempool_contents])55def trigger_reorg(self, fork_blocks):56"""Trigger reorg of the fork blocks."""57for block in fork_blocks:58self.nodes[0].submitblock(block.serialize().hex())59assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)61@cleanup()62def test_truc_max_vsize(self):63node = self.nodes[0]64self.log.info("Test TRUC-specific maximum transaction vsize")65tx_v3_heavy = self.wallet.create_self_transfer(target_vsize=TRUC_MAX_VSIZE + 1, version=3)66assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), TRUC_MAX_VSIZE)67expected_error_heavy = f"TRUC-violation, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"68assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])69self.check_mempool([])71# Ensure we are hitting the TRUC-specific limit and not something else72tx_v2_heavy = self.wallet.send_self_transfer(from_node=node, target_vsize=TRUC_MAX_VSIZE + 1, version=2)73self.check_mempool([tx_v2_heavy["txid"]])75@cleanup()76def test_truc_acceptance(self):77node = self.nodes[0]78self.log.info("Test a child of a TRUC transaction cannot be more than 1000vB")79tx_v3_parent_normal = self.wallet.send_self_transfer(from_node=node, version=3)80self.check_mempool([tx_v3_parent_normal["txid"]])81tx_v3_child_heavy = self.wallet.create_self_transfer(82utxo_to_spend=tx_v3_parent_normal["new_utxo"],83target_vsize=TRUC_CHILD_MAX_VSIZE + 1,84version=385)86assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), TRUC_CHILD_MAX_VSIZE)87expected_error_child_heavy = f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"88assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])89self.check_mempool([tx_v3_parent_normal["txid"]])90# tx has no descendants91assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 1)93self.log.info("Test that, during replacements, only the new transaction counts for TRUC descendant limit")94tx_v3_child_almost_heavy = self.wallet.send_self_transfer(95from_node=node,96fee_rate=DEFAULT_FEE,97utxo_to_spend=tx_v3_parent_normal["new_utxo"],98target_vsize=TRUC_CHILD_MAX_VSIZE - 3,99version=3100)101assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_almost_heavy["tx"].get_vsize())102self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy["txid"]])103assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)104tx_v3_child_almost_heavy_rbf = self.wallet.send_self_transfer(105from_node=node,106fee_rate=DEFAULT_FEE * 2,107utxo_to_spend=tx_v3_parent_normal["new_utxo"],108target_vsize=875,109version=3110)111assert_greater_than_or_equal(tx_v3_child_almost_heavy["tx"].get_vsize() + tx_v3_child_almost_heavy_rbf["tx"].get_vsize(),112TRUC_CHILD_MAX_VSIZE)113self.check_mempool([tx_v3_parent_normal["txid"], tx_v3_child_almost_heavy_rbf["txid"]])114assert_equal(node.getmempoolentry(tx_v3_parent_normal["txid"])["descendantcount"], 2)116@cleanup(extra_args=None)117def test_truc_replacement(self):118node = self.nodes[0]119self.log.info("Test TRUC transactions may be replaced by TRUC transactions")120utxo_v3_bip125 = self.wallet.get_utxo()121tx_v3_bip125 = self.wallet.send_self_transfer(122from_node=node,123fee_rate=DEFAULT_FEE,124utxo_to_spend=utxo_v3_bip125,125version=3126)127self.check_mempool([tx_v3_bip125["txid"]])129tx_v3_bip125_rbf = self.wallet.send_self_transfer(130from_node=node,131fee_rate=DEFAULT_FEE * 2,132utxo_to_spend=utxo_v3_bip125,133version=3134)135self.check_mempool([tx_v3_bip125_rbf["txid"]])137self.log.info("Test TRUC transactions may be replaced by non-TRUC (BIP125) transactions")138tx_v3_bip125_rbf_v2 = self.wallet.send_self_transfer(139from_node=node,140fee_rate=DEFAULT_FEE * 3,141utxo_to_spend=utxo_v3_bip125,142version=2143)144self.check_mempool([tx_v3_bip125_rbf_v2["txid"]])146self.log.info("Test that replacements cannot cause violation of inherited TRUC")147utxo_v3_parent = self.wallet.get_utxo()148tx_v3_parent = self.wallet.send_self_transfer(149from_node=node,150fee_rate=DEFAULT_FEE,151utxo_to_spend=utxo_v3_parent,152version=3153)154tx_v3_child = self.wallet.send_self_transfer(155from_node=node,156fee_rate=DEFAULT_FEE,157utxo_to_spend=tx_v3_parent["new_utxo"],158version=3159)160self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])162tx_v3_child_rbf_v2 = self.wallet.create_self_transfer(163fee_rate=DEFAULT_FEE * 2,164utxo_to_spend=tx_v3_parent["new_utxo"],165version=2166)167expected_error_v2_v3 = f"TRUC-violation, non-version=3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"168assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])169self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])172@cleanup()173def test_truc_reorg(self):174node = self.nodes[0]176# Prep for fork177fork_blocks = create_empty_fork(node)178self.log.info("Test that, during a reorg, TRUC rules are not enforced")179self.check_mempool([])181# Testing 2<-3 versions allowed182tx_v2_block = self.wallet.create_self_transfer(version=2)184# Testing 3<-2 versions allowed185tx_v3_block = self.wallet.create_self_transfer(version=3)187# Testing overly-large child size188tx_v3_block2 = self.wallet.create_self_transfer(version=3)190# Also create a linear chain of 3 TRUC transactions that will be directly mined, followed by one v2 in-mempool after block is made191tx_chain_1 = self.wallet.create_self_transfer(version=3)192tx_chain_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_1["new_utxo"], version=3)193tx_chain_3 = self.wallet.create_self_transfer(utxo_to_spend=tx_chain_2["new_utxo"], version=3)195tx_to_mine = [tx_v3_block["hex"], tx_v2_block["hex"], tx_v3_block2["hex"], tx_chain_1["hex"], tx_chain_2["hex"], tx_chain_3["hex"]]196self.generateblock(node, output="raw(42)", transactions=tx_to_mine)198self.check_mempool([])200tx_v2_from_v3 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block["new_utxo"], version=2)201tx_v3_from_v2 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v2_block["new_utxo"], version=3)202tx_v3_child_large = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_v3_block2["new_utxo"], target_vsize=1250, version=3)203assert_greater_than(node.getmempoolentry(tx_v3_child_large["txid"])["vsize"], TRUC_CHILD_MAX_VSIZE)204tx_chain_4 = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=tx_chain_3["new_utxo"], version=2)205self.check_mempool([tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_4["txid"]])207# Reorg should have all block transactions re-accepted, ignoring TRUC enforcement208self.trigger_reorg(fork_blocks)209self.check_mempool([tx_v3_block["txid"], tx_v2_block["txid"], tx_v3_block2["txid"], tx_v2_from_v3["txid"], tx_v3_from_v2["txid"], tx_v3_child_large["txid"], tx_chain_1["txid"], tx_chain_2["txid"], tx_chain_3["txid"], tx_chain_4["txid"]])211@cleanup(extra_args=["-limitclustercount=1"])212def test_nondefault_package_limits(self):213"""214Max standard tx size + TRUC rules imply the cluster rules (at their default215values), but those checks must not be skipped. Ensure both sets of checks are done by216changing the cluster limit configurations.217"""218node = self.nodes[0]219self.log.info("Test that a decreased cluster count limit also applies to TRUC child")220parent_target_vsize = 9990221child_target_vsize = 500222tx_v3_parent_large1 = self.wallet.send_self_transfer(223from_node=node,224target_vsize=parent_target_vsize,225version=3226)227tx_v3_child_large1 = self.wallet.create_self_transfer(228utxo_to_spend=tx_v3_parent_large1["new_utxo"],229target_vsize=child_target_vsize,230version=3231)233# Parent and child are within v3 limits, but cluster count limit is exceeded.234assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large1["tx"].get_vsize())235assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large1["tx"].get_vsize())237assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large1["hex"])238self.check_mempool([tx_v3_parent_large1["txid"]])239assert_equal(node.getmempoolentry(tx_v3_parent_large1["txid"])["descendantcount"], 1)240self.generate(node, 1)242self.log.info("Test that a decreased limitclustersize also applies to TRUC child")243self.restart_node(0, extra_args=["-limitclustersize=10", "-acceptnonstdtxn=1"])244tx_v3_parent_large2 = self.wallet.send_self_transfer(from_node=node, target_vsize=parent_target_vsize, version=3)245tx_v3_child_large2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent_large2["new_utxo"], target_vsize=child_target_vsize, version=3)246# Parent and child are within TRUC limits247assert_greater_than_or_equal(TRUC_MAX_VSIZE, tx_v3_parent_large2["tx"].get_vsize())248assert_greater_than_or_equal(TRUC_CHILD_MAX_VSIZE, tx_v3_child_large2["tx"].get_vsize())249assert_raises_rpc_error(-26, "too-large-cluster", node.sendrawtransaction, tx_v3_child_large2["hex"])250self.log.info("Test that a decreased limitclustercount also applies to TRUC transactions")
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.