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) 2023-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 <chain.h>6#include <consensus/amount.h>7#include <consensus/consensus.h>8#include <consensus/validation.h>9#include <node/mining_types.h>10#include <policy/feerate.h>11#include <policy/packages.h>12#include <policy/policy.h>13#include <policy/settings.h>14#include <policy/truc_policy.h>15#include <primitives/transaction.h>16#include <script/script.h>17#include <sync.h>18#include <test/fuzz/FuzzedDataProvider.h>19#include <test/fuzz/fuzz.h>20#include <test/fuzz/util.h>21#include <test/fuzz/util/mempool.h>22#include <test/util/mining.h>23#include <test/util/random.h>24#include <test/util/script.h>25#include <test/util/setup_common.h>26#include <test/util/txmempool.h>27#include <txmempool.h>28#include <util/check.h>29#include <util/hasher.h>30#include <util/time.h>31#include <util/translation.h>32#include <validation.h>33#include <validationinterface.h>35#include <cstddef>36#include <cstdint>37#include <functional>38#include <iterator>39#include <limits>40#include <map>41#include <memory>42#include <optional>43#include <set>44#include <span>45#include <string>46#include <unordered_map>47#include <utility>48#include <vector>49using node::NodeContext;51namespace {53const TestingSetup* g_setup;54std::vector<COutPoint> g_outpoints_coinbase_init_mature;56struct MockedTxPool : public CTxMemPool {57void RollingFeeUpdate() EXCLUSIVE_LOCKS_REQUIRED(!cs)58{59LOCK(cs);60lastRollingFeeUpdate = GetTime();61blockSinceLastRollingFeeBump = true;62}63};65void initialize_tx_pool()66{67static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();68g_setup = testing_setup.get();69SetMockTime(WITH_LOCK(g_setup->m_node.chainman->GetMutex(), return g_setup->m_node.chainman->ActiveTip()->Time()));71for (int i = 0; i < 2 * COINBASE_MATURITY; ++i) {72COutPoint prevout{MineBlock(g_setup->m_node, {73.coinbase_output_script = P2WSH_EMPTY,74})};75if (i < COINBASE_MATURITY) {76// Remember the txids to avoid expensive disk access later on77g_outpoints_coinbase_init_mature.push_back(prevout);78}79}80g_setup->m_node.validation_signals->SyncWithValidationInterfaceQueue();81}83struct OutpointsUpdater final : public CValidationInterface {84std::set<COutPoint>& m_mempool_outpoints;86explicit OutpointsUpdater(std::set<COutPoint>& r)87: m_mempool_outpoints{r} {}89void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override90{91// for coins spent we always want to be able to rbf so they're not removed93// outputs from this tx can now be spent94for (uint32_t index{0}; index < tx.info.m_tx->vout.size(); ++index) {95m_mempool_outpoints.insert(COutPoint{tx.info.m_tx->GetHash(), index});96}97}99void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override100{101// outpoints spent by this tx are now available102for (const auto& input : tx->vin) {103// Could already exist if this was a replacement104m_mempool_outpoints.insert(input.prevout);105}106// outpoints created by this tx no longer exist107for (uint32_t index{0}; index < tx->vout.size(); ++index) {108m_mempool_outpoints.erase(COutPoint{tx->GetHash(), index});109}110}111};113struct TransactionsDelta final : public CValidationInterface {114std::set<CTransactionRef>& m_added;116explicit TransactionsDelta(std::set<CTransactionRef>& a)117: m_added{a} {}119void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /* mempool_sequence */) override120{121// Transactions may be entered and booted any number of times122m_added.insert(tx.info.m_tx);123}125void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t /* mempool_sequence */) override126{127// Transactions may be entered and booted any number of times128m_added.erase(tx);129}130};132void MockTime(FuzzedDataProvider& fuzzed_data_provider, const Chainstate& chainstate)133{134const auto time = ConsumeTime(fuzzed_data_provider,135chainstate.m_chain.Tip()->GetMedianTimePast() + 1,136std::numeric_limits<decltype(chainstate.m_chain.Tip()->nTime)>::max());137SetMockTime(time);138}140std::unique_ptr<CTxMemPool> MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeContext& node)141{142// Take the default options for tests...143CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};146// ...override specific options for this specific fuzz suite147mempool_opts.limits.ancestor_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);148mempool_opts.limits.descendant_count = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 50);149mempool_opts.max_size_bytes = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 200) * 1'000'000;150mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};151// Only interested in 2 cases: sigop cost 0 or when single legacy sigop cost is >> 1KvB152nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 1) * 10'000;154mempool_opts.check_ratio = 1;155mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();157bilingual_str error;158// ...and construct a CTxMemPool from it159auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};160// ... ignore the error since it might be beneficial to fuzz even when the161// mempool size is unreasonably small162Assert(error.empty() || error.original.starts_with("-maxmempool must be at least "));163return mempool;164}166std::unique_ptr<CTxMemPool> MakeEphemeralMempool(const NodeContext& node)167{168// Take the default options for tests...169CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};171mempool_opts.check_ratio = 1;173// Require standardness rules otherwise ephemeral dust is no-op174mempool_opts.require_standard = true;176// And set minrelay to 0 to allow ephemeral parent tx even with non-TRUC177mempool_opts.min_relay_feerate = CFeeRate(0);179bilingual_str error;180// ...and construct a CTxMemPool from it181auto mempool{std::make_unique<CTxMemPool>(std::move(mempool_opts), error)};182Assert(error.empty());183return mempool;184}186// Scan mempool for a tx that has spent dust and return a187// prevout of the child that isn't the dusty parent itself.188// This is used to double-spend the child out of the mempool,189// leaving the parent childless.190// This assumes CheckMempoolEphemeralInvariants has passed for tx_pool.191std::optional<COutPoint> GetChildEvictingPrevout(const CTxMemPool& tx_pool)192{193LOCK(tx_pool.cs);194for (const auto& tx_info : tx_pool.infoAll()) {195const auto& entry = *Assert(tx_pool.GetEntry(tx_info.tx->GetHash()));196std::vector<uint32_t> dust_indexes{GetDust(*tx_info.tx, tx_pool.m_opts.dust_relay_feerate)};197if (!dust_indexes.empty()) {198const auto& children = tx_pool.GetChildren(entry);199if (!children.empty()) {200Assert(children.size() == 1);201// Find an input that doesn't spend from parent's txid202const auto& only_child = children.begin()->get().GetTx();203for (const auto& tx_input : only_child.vin) {204if (tx_input.prevout.hash != tx_info.tx->GetHash()) {205return tx_input.prevout;206}207}208}209}210}212return std::nullopt;213}215FUZZ_TARGET(ephemeral_package_eval, .init = initialize_tx_pool)216{217SeedRandomStateForTest(SeedRand::ZEROS);218FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());219const auto& node = g_setup->m_node;220auto& chainstate{static_cast<DummyChainState&>(node.chainman->ActiveChainstate())};222MockTime(fuzzed_data_provider, chainstate);224// All RBF-spendable outpoints outside of the unsubmitted package225std::set<COutPoint> mempool_outpoints;226std::unordered_map<COutPoint, CAmount, SaltedOutpointHasher> outpoints_value;227for (const auto& outpoint : g_outpoints_coinbase_init_mature) {228Assert(mempool_outpoints.insert(outpoint).second);229outpoints_value[outpoint] = 50 * COIN;230}232auto outpoints_updater = std::make_shared<OutpointsUpdater>(mempool_outpoints);233node.validation_signals->RegisterSharedValidationInterface(outpoints_updater);235auto tx_pool_{MakeEphemeralMempool(node)};236MockedTxPool& tx_pool = *static_cast<MockedTxPool*>(tx_pool_.get());238chainstate.SetMempool(&tx_pool);240LIMITED_WHILE (fuzzed_data_provider.remaining_bytes() > 0, 300) {241Assert(!mempool_outpoints.empty());243std::vector<CTransactionRef> txs;245// Find something we may want to double-spend with two input single tx246std::optional<COutPoint> outpoint_to_rbf{fuzzed_data_provider.ConsumeBool() ? GetChildEvictingPrevout(tx_pool) : std::nullopt};248// Make small packages249const auto num_txs = outpoint_to_rbf ? 1 : fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4);
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.