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 mempool limiting together/eviction with the wallet."""7from decimal import Decimal8import time10from test_framework.mempool_util import (11fill_mempool,12)13from test_framework.p2p import P2PTxInvStore14from test_framework.test_framework import BitcoinTestFramework15from test_framework.util import (16assert_equal,17assert_fee_amount,18assert_greater_than,19assert_raises_rpc_error,20)21from test_framework.wallet import (22COIN,23DEFAULT_FEE,24MiniWallet,25)28class MempoolLimitTest(BitcoinTestFramework):29def set_test_params(self):30self.setup_clean_chain = True31self.num_nodes = 132self.extra_args = [[33"-maxmempool=5",34]]36def test_mid_package_eviction_success(self):37node = self.nodes[0]38self.log.info("Check a package where each parent passes the current mempoolminfee but a parent could be evicted before getting child's descendant feerate")40# Clear mempool so it can be filled with minrelay txns41self.restart_node(0, extra_args=self.extra_args[0] + ["-persistmempool=0"])42assert_equal(node.getrawmempool(), [])44# Restarting the node resets mempool minimum feerate45assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])47fill_mempool(self, node)48current_info = node.getmempoolinfo()49mempoolmin_feerate = current_info["mempoolminfee"]51mempool_txids = node.getrawmempool()52mempool_entries = [node.getmempoolentry(entry) for entry in mempool_txids]53fees_btc_per_kvb = [entry["fees"]["base"] / (Decimal(entry["vsize"]) / 1000) for entry in mempool_entries]54mempool_entry_minrate = min(fees_btc_per_kvb)55mempool_entry_minrate = mempool_entry_minrate.quantize(Decimal("0.00000000"))57# There is a gap, our parents will be minrate, with child bringing up descendant fee sufficiently to avoid58# eviction even though parents cause eviction on their own59assert_greater_than(mempool_entry_minrate, mempoolmin_feerate)61package_hex = []62# UTXOs to be spent by the ultimate child transaction63parent_utxos = []65# Series of parents that don't need CPFP and are submitted individually. Each one is large66# which means in aggregate they could trigger eviction, but child submission should result67# in them not being evicted68parent_vsize = 2500069num_big_parents = 370# Need to be large enough to trigger eviction71# (note that the mempool usage of a tx is about three times its vsize)72assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"])74big_parent_txids = []75big_parent_wtxids = []76for i in range(num_big_parents):77# Last parent is higher feerate causing other parents to possibly78# be evicted if trimming was allowed, which would cause the package to end up failing79parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate80parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)81parent_utxos.append(parent["new_utxo"])82package_hex.append(parent["hex"])83big_parent_txids.append(parent["txid"])84big_parent_wtxids.append(parent["wtxid"])85# There is room for each of these transactions independently86assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]88# Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate89child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000)90package_hex.append(child["hex"])92# Package should be submitted, temporarily exceeding maxmempool, but not evicted.93package_res = None94with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):95package_res = node.submitpackage(package=package_hex, maxfeerate=0)97assert_equal(package_res["package_msg"], "success")99# Ensure that intra-package trimming is not happening.100# Each transaction separately satisfies the current101# minfee and shouldn't need package evaluation to102# be included. If trimming of a parent were to happen,103# package evaluation would happen to reintrodce the evicted104# parent.105assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1)106for wtxid in big_parent_wtxids + [child["wtxid"]]:107assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1)109# Maximum size must never be exceeded.110assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])112# Package found in mempool still113resulting_mempool_txids = node.getrawmempool()114assert child["txid"] in resulting_mempool_txids115for txid in big_parent_txids:116assert txid in resulting_mempool_txids118# Check every evicted tx was higher feerate than parents which evicted it119eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids)120parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids]121max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries])122for eviction in eviction_set:123assert eviction in mempool_txids124for txid, entry in zip(mempool_txids, mempool_entries):125if txid == eviction:126evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000)127assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate)129def test_mid_package_replacement(self):130node = self.nodes[0]131self.log.info("Check a package where an early tx depends on a later-replaced mempool tx")133self.restart_node(0, extra_args=self.extra_args[0])135# Restarting the node resets mempool minimum feerate136assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])138fill_mempool(self, node)139current_info = node.getmempoolinfo()140mempoolmin_feerate = current_info["mempoolminfee"]142# Mempool transaction is replaced by a package transaction.143double_spent_utxo = self.wallet.get_utxo(confirmed_only=True)144replaced_tx = self.wallet.send_self_transfer(145from_node=node,146utxo_to_spend=double_spent_utxo,147fee_rate=mempoolmin_feerate,148confirmed_only=True149)150# Already in mempool when package is submitted.151assert replaced_tx["txid"] in node.getrawmempool()153# This parent spends the above mempool transaction that exists when its inputs are first154# looked up, but will disappear if the replacement occurs. It is rejected for being too low fee (but eligible for155# reconsideration), and its inputs are cached. When the mempool transaction is replaced, its156# coin is no longer available, but the cache could still contain the tx.157cpfp_parent = self.wallet.create_self_transfer(158utxo_to_spend=replaced_tx["new_utxo"],159fee_rate=mempoolmin_feerate - Decimal('0.000001'),160confirmed_only=True)162self.wallet.rescan_utxos()164# Parent that replaces the parent of cpfp_parent.165replacement_tx = self.wallet.create_self_transfer(166utxo_to_spend=double_spent_utxo,167fee_rate=10*mempoolmin_feerate,168confirmed_only=True169)170parent_utxos = [cpfp_parent["new_utxo"], replacement_tx["new_utxo"]]172# Create a child spending everything, CPFPing the low-feerate parent.173approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize()174cpfp_fee = (2 * mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"]175child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(cpfp_fee * COIN))176# It's very important that the cpfp_parent is before replacement_tx so that its input (from177# replaced_tx) is first looked up *before* replacement_tx is submitted.178package_hex = [cpfp_parent["hex"], replacement_tx["hex"], child["hex"]]180# Package should be submitted, temporarily exceeding maxmempool, and then evicted.181res = node.submitpackage(package_hex)182assert_equal(res["package_msg"], "transaction failed")183assert "bad-txns-inputs-missingorspent" in [tx_res["error"] for _, tx_res in res["tx-results"].items() if "error" in tx_res]185# Maximum size must never be exceeded.186assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])188resulting_mempool_txids = node.getrawmempool()189# The replacement should be successful.190assert replacement_tx["txid"] in resulting_mempool_txids191# The replaced tx and all of its descendants must not be in mempool.192assert replaced_tx["txid"] not in resulting_mempool_txids193assert cpfp_parent["txid"] not in resulting_mempool_txids194assert child["txid"] not in resulting_mempool_txids197def run_test(self):198node = self.nodes[0]199self.wallet = MiniWallet(node)200miniwallet = self.wallet202# Generate coins needed to create transactions in the subtests (excluding coins used in fill_mempool).203self.generate(miniwallet, 20)205relayfee = node.getnetworkinfo()['relayfee']206self.log.info('Check that mempoolminfee is minrelaytxfee')207assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])209node.setmocktime(int(time.time())-3600)210fill_mempool(self, node)211node.setmocktime(0) # bump time forward so the rate limit buckets refresh and don't block broadcast213# Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool214self.log.info('Create a mempool tx that will not pass mempoolminfee')215assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee)217self.log.info("Check that submitpackage allows cpfp of a parent below mempool min feerate")218node = self.nodes[0]219peer = node.add_p2p_connection(P2PTxInvStore())221# Package with 2 parents and 1 child. One parent has a high feerate due to modified fees,222# another is below the mempool minimum feerate but bumped by the child.223tx_poor = miniwallet.create_self_transfer(fee_rate=relayfee)224tx_rich = miniwallet.create_self_transfer(fee=0, fee_rate=0)225node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN))226package_txns = [tx_rich, tx_poor]227coins = [tx["new_utxo"] for tx in package_txns]228tx_child = miniwallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE229package_txns.append(tx_child)231submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])232assert_equal(submitpackage_result["package_msg"], "success")234rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]235poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]236child_result = submitpackage_result["tx-results"][tx_child["tx"].wtxid_hex]237assert_fee_amount(poor_parent_result["fees"]["base"], tx_poor["tx"].get_vsize(), relayfee)238assert_equal(rich_parent_result["fees"]["base"], 0)239assert_equal(child_result["fees"]["base"], DEFAULT_FEE)240# The "rich" parent does not require CPFP so its effective feerate is just its individual feerate.241assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"])242assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]])243# The "poor" parent and child's effective feerates are the same, composed of their total244# fees divided by their combined vsize.245package_fees = poor_parent_result["fees"]["base"] + child_result["fees"]["base"]246package_vsize = tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize()247assert_fee_amount(package_fees, package_vsize, poor_parent_result["fees"]["effective-feerate"])248assert_fee_amount(package_fees, package_vsize, child_result["fees"]["effective-feerate"])249assert_equal([tx_poor["wtxid"], tx_child["tx"].wtxid_hex], poor_parent_result["fees"]["effective-includes"])250assert_equal([tx_poor["wtxid"], tx_child["tx"].wtxid_hex], child_result["fees"]["effective-includes"])
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.