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// Copyright (c) 2022-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <test/util/txmempool.h>7#include <chainparams.h>8#include <node/context.h>9#include <node/mempool_args.h>10#include <policy/rbf.h>11#include <policy/truc_policy.h>12#include <txmempool.h>13#include <test/util/transaction_utils.h>14#include <util/check.h>15#include <util/time.h>16#include <util/translation.h>17#include <validation.h>19using node::NodeContext;21CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node)22{23CTxMemPool::Options mempool_opts{24// Default to always checking mempool regardless of25// chainparams.DefaultConsistencyChecks for tests26.check_ratio = 1,27.signals = node.validation_signals.get(),28};29const auto result{ApplyArgsManOptions(*node.args, ::Params(), mempool_opts)};30Assert(result);31return mempool_opts;32}34CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction& tx) const35{36return FromTx(MakeTransactionRef(tx));37}39CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const40{41return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch<std::chrono::seconds>(time), nHeight, m_sequence, spendsCoinbase, sigOpCost, lp};42}44std::optional<std::string> CheckPackageMempoolAcceptResult(const Package& txns,45const PackageMempoolAcceptResult& result,46bool expect_valid,47const CTxMemPool* mempool)48{49if (expect_valid) {50if (result.m_state.IsInvalid()) {51return strprintf("Package validation unexpectedly failed: %s", result.m_state.ToString());52}53} else {54if (result.m_state.IsValid()) {55return strprintf("Package validation unexpectedly succeeded. %s", result.m_state.ToString());56}57}58if (result.m_state.GetResult() != PackageValidationResult::PCKG_POLICY && txns.size() != result.m_tx_results.size()) {59return strprintf("txns size %u does not match tx results size %u", txns.size(), result.m_tx_results.size());60}61for (const auto& tx : txns) {62const auto& wtxid = tx->GetWitnessHash();63if (!result.m_tx_results.contains(wtxid)) {64return strprintf("result not found for tx %s", wtxid.ToString());65}67const auto& atmp_result = result.m_tx_results.at(wtxid);68const bool valid{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID};69if (expect_valid && atmp_result.m_state.IsInvalid()) {70return strprintf("tx %s unexpectedly failed: %s", wtxid.ToString(), atmp_result.m_state.ToString());71}73// Each subpackage is allowed MAX_REPLACEMENT_CANDIDATES replacements (only checking individually here)74if (atmp_result.m_replaced_transactions.size() > MAX_REPLACEMENT_CANDIDATES) {75return strprintf("tx %s result replaced too many transactions",76wtxid.ToString());77}79// Replacements can't happen for subpackages larger than 280if (!atmp_result.m_replaced_transactions.empty() &&81atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() > 2) {82return strprintf("tx %s was part of a too-large package RBF subpackage",83wtxid.ToString());84}86if (!atmp_result.m_replaced_transactions.empty() && mempool) {87LOCK(mempool->cs);88// If replacements occurred and it used 2 transactions, this is a package RBF and should result in a cluster of size 289if (atmp_result.m_wtxids_fee_calculations.has_value() && atmp_result.m_wtxids_fee_calculations.value().size() == 2) {90const auto cluster = mempool->GatherClusters({tx->GetHash()});91if (cluster.size() != 2) return strprintf("tx %s has too many ancestors or descendants for a package rbf", wtxid.ToString());92}93}95// m_vsize and m_base_fees should exist iff the result was VALID or MEMPOOL_ENTRY96const bool mempool_entry{atmp_result.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY};97if (atmp_result.m_base_fees.has_value() != (valid || mempool_entry)) {98return strprintf("tx %s result should %shave m_base_fees", wtxid.ToString(), valid || mempool_entry ? "" : "not ");99}100if (atmp_result.m_vsize.has_value() != (valid || mempool_entry)) {101return strprintf("tx %s result should %shave m_vsize", wtxid.ToString(), valid || mempool_entry ? "" : "not ");102}104// m_other_wtxid should exist iff the result was DIFFERENT_WITNESS105const bool diff_witness{atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS};106if (atmp_result.m_other_wtxid.has_value() != diff_witness) {107return strprintf("tx %s result should %shave m_other_wtxid", wtxid.ToString(), diff_witness ? "" : "not ");108}110// m_effective_feerate and m_wtxids_fee_calculations should exist iff the result was valid111// or if the failure was TX_RECONSIDERABLE112const bool valid_or_reconsiderable{atmp_result.m_result_type == MempoolAcceptResult::ResultType::VALID ||113atmp_result.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE};114if (atmp_result.m_effective_feerate.has_value() != valid_or_reconsiderable) {115return strprintf("tx %s result should %shave m_effective_feerate",116wtxid.ToString(), valid ? "" : "not ");117}118if (atmp_result.m_wtxids_fee_calculations.has_value() != valid_or_reconsiderable) {119return strprintf("tx %s result should %shave m_effective_feerate",120wtxid.ToString(), valid ? "" : "not ");121}123if (mempool) {124// The tx by txid should be in the mempool iff the result was not INVALID.125const bool txid_in_mempool{atmp_result.m_result_type != MempoolAcceptResult::ResultType::INVALID};126if (mempool->exists(tx->GetHash()) != txid_in_mempool) {127return strprintf("tx %s should %sbe in mempool", wtxid.ToString(), txid_in_mempool ? "" : "not ");128}129// Additionally, if the result was DIFFERENT_WITNESS, we shouldn't be able to find the tx in mempool by wtxid.130if (tx->HasWitness() && atmp_result.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) {131if (mempool->exists(wtxid)) {132return strprintf("wtxid %s should not be in mempool", wtxid.ToString());133}134}135for (const auto& tx_ref : atmp_result.m_replaced_transactions) {136if (mempool->exists(tx_ref->GetHash())) {137return strprintf("tx %s should not be in mempool as it was replaced", tx_ref->GetWitnessHash().ToString());138}139}140}141}142return std::nullopt;143}145void CheckMempoolEphemeralInvariants(const CTxMemPool& tx_pool)146{147LOCK(tx_pool.cs);148for (const auto& tx_info : tx_pool.infoAll()) {149const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));151std::vector<uint32_t> dust_indexes = GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate);153Assert(dust_indexes.size() < 2);155if (dust_indexes.empty()) continue;157// Transaction must have no base fee158Assert(entry.GetFee() == 0 && entry.GetModifiedFee() == 0);160// Transaction has single dust; make sure it's swept or will not be mined161const auto& children = tx_pool.GetChildren(entry);163// Multiple children should never happen as non-dust-spending child164// can get mined as package165Assert(children.size() < 2);167if (children.empty()) {168// No children and no fees; modified fees aside won't get mined so it's fine169// Happens naturally if child spend is RBF cycled away.170continue;171}173// Only-child should be spending the dust174const auto& only_child = children.begin()->get().GetTx();175COutPoint dust_outpoint{tx_info.tx->GetHash(), dust_indexes[0]};176Assert(std::any_of(only_child.vin.begin(), only_child.vin.end(), [&dust_outpoint](const CTxIn& txin) {177return txin.prevout == dust_outpoint;178}));179}180}182void CheckMempoolTRUCInvariants(const CTxMemPool& tx_pool)183{184LOCK(tx_pool.cs);185for (const auto& tx_info : tx_pool.infoAll()) {186const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));187auto [desc_count, desc_size, desc_fees] = tx_pool.CalculateDescendantData(entry);188auto [anc_count, anc_size, anc_fees] = tx_pool.CalculateAncestorData(entry);190if (tx_info.tx->version == TRUC_VERSION) {191// Check that special maximum virtual size is respected192Assert(entry.GetTxSize() <= TRUC_MAX_VSIZE);194// Check that special TRUC ancestor/descendant limits and rules are always respected195Assert(desc_count <= TRUC_DESCENDANT_LIMIT);196Assert(anc_count <= TRUC_ANCESTOR_LIMIT);197Assert(desc_size <= TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE);198Assert(anc_size <= TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE);199// If this transaction has at least 1 ancestor, it's a "child" and has restricted weight.200if (anc_count > 1) {201Assert(entry.GetTxSize() <= TRUC_CHILD_MAX_VSIZE);202// All TRUC transactions must only have TRUC unconfirmed parents.203const auto& parents = tx_pool.GetParents(entry);204Assert(parents.begin()->get().GetSharedTx()->version == TRUC_VERSION);205}206} else if (anc_count > 1) {207// All non-TRUC transactions must only have non-TRUC unconfirmed parents.208for (const auto& parent : tx_pool.GetParents(entry)) {209Assert(parent.get().GetSharedTx()->version != TRUC_VERSION);210}211}212}213}215void TryAddToMempool(CTxMemPool& tx_pool, const CTxMemPoolEntry& entry)216{217LOCK2(cs_main, tx_pool.cs);218auto changeset = tx_pool.GetChangeSet();219changeset->StageAddition(entry.GetSharedTx(), entry.GetFee(),220entry.GetTime().count(), entry.GetHeight(), entry.GetSequence(),221entry.GetSpendsCoinbase(), entry.GetSigOpCost(), entry.GetLockPoints());222if (changeset->CheckMemPoolPolicyLimits()) changeset->Apply();223}225void MockMempoolMinFee(const CFeeRate& target_feerate, CTxMemPool& mempool)226{227LOCK2(cs_main, mempool.cs);228// Transactions in the mempool will affect the new minimum feerate.229assert(mempool.size() == 0);230// The target feerate cannot be too low...231// ...otherwise the transaction's feerate will need to be negative.232assert(target_feerate > mempool.m_opts.incremental_relay_feerate);233// ...otherwise this is not meaningful. The feerate policy uses the maximum of both feerates.234assert(target_feerate > mempool.m_opts.min_relay_feerate);236// Manually create an invalid transaction. Manually set the fee in the CTxMemPoolEntry to237// achieve the exact target feerate.238CMutableTransaction mtx{};239mtx.vin.emplace_back(COutPoint{Txid::FromUint256(uint256{123}), 0});240mtx.vout.emplace_back(1 * COIN, GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)));241// Set a large size so that the fee evaluated at target_feerate (which is usually in sats/kvB) is an integer.242// Otherwise, GetMinFee() may end up slightly different from target_feerate.243BulkTransaction(mtx, 4000);244const auto tx{MakeTransactionRef(mtx)};245LockPoints lp;246// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.247const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) -248mempool.m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx));249{250auto changeset = mempool.GetChangeSet();
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.