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) 2020-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 <node/mempool_args.h>6#include <policy/rbf.h>7#include <primitives/transaction.h>8#include <sync.h>9#include <test/fuzz/FuzzedDataProvider.h>10#include <test/fuzz/fuzz.h>11#include <test/fuzz/util.h>12#include <test/fuzz/util/mempool.h>13#include <test/util/setup_common.h>14#include <test/util/time.h>15#include <test/util/txmempool.h>16#include <txmempool.h>17#include <util/check.h>18#include <util/translation.h>20#include <cstdint>21#include <optional>22#include <string>23#include <vector>25namespace {26const BasicTestingSetup* g_setup;27} // namespace29const int NUM_ITERS = 10000;31std::vector<COutPoint> g_outpoints;33void initialize_rbf()34{35static const auto testing_setup = MakeNoLogFileContext<>();36g_setup = testing_setup.get();37}39void initialize_package_rbf()40{41static const auto testing_setup = MakeNoLogFileContext<>();42g_setup = testing_setup.get();44// Create a fixed set of unique "UTXOs" to source parents from45// to avoid fuzzer giving circular references46for (int i = 0; i < NUM_ITERS; ++i) {47g_outpoints.emplace_back();48g_outpoints.back().n = i;49}51}53FUZZ_TARGET(rbf, .init = initialize_rbf)54{55SeedRandomStateForTest(SeedRand::ZEROS);56FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());57FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};58std::optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);59if (!mtx) {60return;61}63bilingual_str error;64CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};65Assert(error.empty());67LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), NUM_ITERS) {68const std::optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);69if (!another_mtx) {70break;71}72const CTransaction another_tx{*another_mtx};73if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) {74mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0};75}76LOCK2(cs_main, pool.cs);77if (!pool.GetIter(another_tx.GetHash())) {78TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx));79}80}81const CTransaction tx{*mtx};82if (fuzzed_data_provider.ConsumeBool()) {83LOCK2(cs_main, pool.cs);84if (!pool.GetIter(tx.GetHash())) {85TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, tx));86}87}88{89LOCK(pool.cs);90(void)IsRBFOptIn(tx, pool);91}92}94FUZZ_TARGET(package_rbf, .init = initialize_package_rbf)95{96SeedRandomStateForTest(SeedRand::ZEROS);97FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());98FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};100// "Real" virtual size is not important for this test since ConsumeTxMemPoolEntry generates its own virtual size values101// so we construct small transactions for performance reasons. Child simply needs an input for later to perhaps connect to parent.102CMutableTransaction child;103child.vin.resize(1);105bilingual_str error;106CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error};107Assert(error.empty());109// Add a bunch of parent-child pairs to the mempool, and remember them.110std::vector<CTransaction> mempool_txs;111uint32_t iter{0};113// Keep track of the total vsize of CTxMemPoolEntry's being added to the mempool to avoid overflow114// Add replacement_vsize since this is added to new diagram during RBF check115std::optional<CMutableTransaction> replacement_tx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);116if (!replacement_tx) {117return;118}119replacement_tx->vin.resize(1);120replacement_tx->vin[0].prevout = g_outpoints.at(iter++);121CTransaction replacement_tx_final{*replacement_tx};122auto replacement_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, replacement_tx_final);123int32_t replacement_weight = replacement_entry.GetAdjustedWeight();124// Ensure that we don't hit FeeFrac limits, as we store TxGraph entries in terms of FeePerWeight125int64_t running_vsize_total{replacement_entry.GetTxSize()};127LOCK2(cs_main, pool.cs);129while (fuzzed_data_provider.ConsumeBool()) {130if (iter >= NUM_ITERS) break;132// Make sure txns only have one input, and that a unique input is given to avoid circular references133CMutableTransaction parent;134parent.vin.resize(1);135parent.vin[0].prevout = g_outpoints.at(iter++);136parent.vout.emplace_back(0, CScript());138mempool_txs.emplace_back(parent);139const auto parent_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());140running_vsize_total += parent_entry.GetTxSize();141if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {142// We aren't adding this final tx to mempool, so we don't want to conflict with it143mempool_txs.pop_back();144break;145}146assert(!pool.GetIter(parent_entry.GetTx().GetHash()));147TryAddToMempool(pool, parent_entry);149// It's possible that adding this to the mempool failed due to cluster150// size limits; if so bail out.151if(!pool.GetIter(parent_entry.GetTx().GetHash())) {152mempool_txs.pop_back();153continue;154}156child.vin[0].prevout = COutPoint{mempool_txs.back().GetHash(), 0};157mempool_txs.emplace_back(child);158const auto child_entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, mempool_txs.back());159running_vsize_total += child_entry.GetTxSize();160if (running_vsize_total * WITNESS_SCALE_FACTOR > std::numeric_limits<int32_t>::max()) {161// We aren't adding this final tx to mempool, so we don't want to conflict with it162mempool_txs.pop_back();163break;164}165if (!pool.GetIter(child_entry.GetTx().GetHash())) {166TryAddToMempool(pool, child_entry);167// Adding this transaction to the mempool may fail due to cluster168// size limits; if so bail out.169if(!pool.GetIter(child_entry.GetTx().GetHash())) {170mempool_txs.pop_back();171continue;172}173}175if (fuzzed_data_provider.ConsumeBool()) {176pool.PrioritiseTransaction(mempool_txs.back().GetHash(), fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(-100000, 100000));177}178}180// Pick some transactions at random to be the direct conflicts181CTxMemPool::setEntries direct_conflicts;182for (auto& tx : mempool_txs) {183if (fuzzed_data_provider.ConsumeBool() && pool.GetIter(tx.GetHash())) {184direct_conflicts.insert(*pool.GetIter(tx.GetHash()));185}186}188// Calculate all conflicts:189CTxMemPool::setEntries all_conflicts;190for (auto& txiter : direct_conflicts) {191pool.CalculateDescendants(txiter, all_conflicts);192}194CAmount replacement_fees = ConsumeMoney(fuzzed_data_provider);195auto changeset = pool.GetChangeSet();196for (auto& txiter : all_conflicts) {197changeset->StageRemoval(txiter);198}199changeset->StageAddition(replacement_entry.GetSharedTx(), replacement_fees,200replacement_entry.GetTime().count(), replacement_entry.GetHeight(),201replacement_entry.GetSequence(), replacement_entry.GetSpendsCoinbase(),202replacement_entry.GetSigOpCost(), replacement_entry.GetLockPoints());203// Calculate the chunks for a replacement.204auto calc_results{changeset->CalculateChunksForRBF()};206if (calc_results.has_value()) {207// Sanity checks on the chunks.209// Feerates are monotonically decreasing.210FeeFrac first_sum;211for (size_t i = 0; i < calc_results->first.size(); ++i) {212first_sum += calc_results->first[i];213if (i) assert(ByRatio{calc_results->first[i - 1]} >= ByRatio{calc_results->first[i]});214}215FeeFrac second_sum;216for (size_t i = 0; i < calc_results->second.size(); ++i) {217second_sum += calc_results->second[i];218if (i) assert(ByRatio{calc_results->second[i - 1]} >= ByRatio{calc_results->second[i]});219}221FeeFrac replaced;222for (auto txiter : all_conflicts) {223replaced.fee += txiter->GetModifiedFee();224replaced.size += txiter->GetAdjustedWeight();225}226// The total fee & size of the new diagram minus replaced fee & size should be the total227// fee & size of the old diagram minus replacement fee & size.228assert((first_sum - replaced) == (second_sum - FeeFrac{replacement_fees, replacement_weight}));229}231// If internals report error, wrapper should too232auto err_tuple{ImprovesFeerateDiagram(*changeset)};233if (!calc_results.has_value()) {234assert(err_tuple.value().first == DiagramCheckError::UNCALCULABLE);235} else {236// Diagram check succeeded237auto old_sum = std::accumulate(calc_results->first.begin(), calc_results->first.end(), FeeFrac{});238auto new_sum = std::accumulate(calc_results->second.begin(), calc_results->second.end(), FeeFrac{});239if (!err_tuple.has_value()) {240// New diagram's final fee should always match or exceed old diagram's241assert(old_sum.fee <= new_sum.fee);242} else if (old_sum.fee > new_sum.fee) {243// Or it failed, and if old diagram had higher fees, it should be a failure244assert(err_tuple.value().first == DiagramCheckError::FAILURE);245}246}247}
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.