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 <consensus/amount.h>6#include <consensus/validation.h>7#include <net_processing.h>8#include <node/eviction.h>9#include <node/txorphanage.h>10#include <policy/policy.h>11#include <primitives/transaction.h>12#include <script/script.h>13#include <sync.h>14#include <test/fuzz/FuzzedDataProvider.h>15#include <test/fuzz/fuzz.h>16#include <test/fuzz/util.h>17#include <test/util/setup_common.h>18#include <test/util/time.h>19#include <uint256.h>20#include <util/check.h>21#include <util/feefrac.h>22#include <util/time.h>24#include <algorithm>25#include <bitset>26#include <cmath>27#include <cstdint>28#include <iostream>29#include <memory>30#include <set>31#include <utility>32#include <vector>34void initialize_orphanage()35{36static const auto testing_setup = MakeNoLogFileContext();37}39FUZZ_TARGET(txorphan, .init = initialize_orphanage)40{41SeedRandomStateForTest(SeedRand::ZEROS);42FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());43FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};44FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};46auto orphanage = node::MakeTxOrphanage();47std::vector<COutPoint> outpoints; // Duplicates are tolerated48outpoints.reserve(200'000);50// initial outpoints used to construct transactions later51for (uint8_t i = 0; i < 4; i++) {52outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);53}55CTransactionRef ptx_potential_parent = nullptr;57std::vector<CTransactionRef> tx_history;59LIMITED_WHILE (outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 1000) {60// construct transaction61const CTransactionRef tx = [&] {62CMutableTransaction tx_mut;63const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());64const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);65// pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not66// running any transaction validation logic before adding transactions to the orphanage67tx_mut.vin.reserve(num_in);68for (uint32_t i = 0; i < num_in; i++) {69auto& prevout = PickValue(fuzzed_data_provider, outpoints);70// try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen71tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));72}73// output amount will not affect txorphanage74tx_mut.vout.reserve(num_out);75for (uint32_t i = 0; i < num_out; i++) {76tx_mut.vout.emplace_back(CAmount{0}, CScript{});77}78auto new_tx = MakeTransactionRef(tx_mut);79// add newly constructed outpoints to the coin pool80for (uint32_t i = 0; i < num_out; i++) {81outpoints.emplace_back(new_tx->GetHash(), i);82}83return new_tx;84}();86tx_history.push_back(tx);88const auto wtxid{tx->GetWitnessHash()};90// Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a91// previous loop and potentially the parent of this tx.92if (ptx_potential_parent) {93// Set up future GetTxToReconsider call.94orphanage->AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);96// Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.97NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();98for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {99assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {100return input.prevout.hash == ptx_potential_parent->GetHash();101}));102}103}105// trigger orphanage functions106LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 1000) {107NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();108const auto total_bytes_start{orphanage->TotalOrphanUsage()};109const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)};110const auto tx_weight{GetTransactionWeight(*tx)};112CallOneOf(113fuzzed_data_provider,114[&] {115{116CTransactionRef ref = orphanage->GetTxToReconsider(peer_id);117if (ref) {118Assert(orphanage->HaveTx(ref->GetWitnessHash()));119}120}121},122[&] {123bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());124bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);125// AddTx should return false if tx is too big or already have it126// tx weight is unknown, we only check when tx is already in orphanage127{128bool add_tx = orphanage->AddTx(tx, peer_id);129// have_tx == true -> add_tx == false130Assert(!have_tx || !add_tx);131// have_tx_and_peer == true -> add_tx == false132Assert(!have_tx_and_peer || !add_tx);133// After AddTx, the orphanage may trim itself, so the peer's usage may have gone up or down.135if (add_tx) {136Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT);137} else {138// Peer may have been added as an announcer.139if (orphanage->UsageByPeer(peer_id) > total_peer_bytes_start) {140Assert(orphanage->HaveTxFromPeer(wtxid, peer_id));141}143// If announcement was added, total bytes does not increase.144// However, if eviction was triggered, the value may decrease.145Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);146}147}148// We are not guaranteed to have_tx after AddTx. There are a few possible reasons:149// - tx itself exceeds the per-peer memory usage limit, so LimitOrphans had to remove it immediately150// - tx itself exceeds the per-peer latency score limit, so LimitOrphans had to remove it immediately151// - the orphanage needed trim and all other announcements from this peer are reconsiderable152},153[&] {154bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());155bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);156// AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.157{158bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);159// have_tx == false -> added_announcer == false160Assert(have_tx || !added_announcer);161// have_tx_and_peer == true -> added_announcer == false162Assert(!have_tx_and_peer || !added_announcer);164// If announcement was added, total bytes does not increase.165// However, if eviction was triggered, the value may decrease.166Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);167}168},169[&] {170bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());171bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)};172// EraseTx should return 0 if m_orphans doesn't have the tx173{174auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)};175Assert(have_tx == orphanage->EraseTx(tx->GetWitnessHash()));176// After EraseTx, the orphanage may trim itself, so any peer's usage may decrease.177if (!have_tx) {178Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before);179} else if (have_tx_and_peer) {180Assert(orphanage->UsageByPeer(peer_id) <= bytes_from_peer_before - tx_weight);181} else {182Assert(orphanage->UsageByPeer(peer_id) <= bytes_from_peer_before);183}184}185have_tx = orphanage->HaveTx(tx->GetWitnessHash());186have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);187// have_tx should be false and EraseTx should fail188{189Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid));190}191},192[&] {193orphanage->EraseForPeer(peer_id);194Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id));195Assert(orphanage->UsageByPeer(peer_id) == 0);196},197[&] {198// Make a block out of txs and then EraseForBlock199CBlock block;200int64_t block_weight{0};201int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000);202for (int i{0}; i < num_txs; ++i) {203auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history);204const auto tx_weight = GetTransactionWeight(*tx_to_remove);205if (block_weight + tx_weight > MAX_BLOCK_WEIGHT) break;206block_weight += tx_weight;207block.vtx.push_back(tx_to_remove);208}209orphanage->EraseForBlock(block);210for (const auto& tx_removed : block.vtx) {211Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash()));212Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));213}214}215);216}218// Set tx as potential parent to be used for future GetChildren() calls.219if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {220ptx_potential_parent = tx;221}223const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())};224const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr};225Assert(have_tx == get_tx_nonnull);226}227orphanage->SanityCheck();228}230FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage)231{232SeedRandomStateForTest(SeedRand::ZEROS);233FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());234FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};235FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};237// We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and238// should therefore never have any orphans evicted.239const unsigned int MAX_PEERS = 125;240const unsigned int num_peers = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS);241// Generate a vector of bools for whether each peer is protected from eviction242std::bitset<MAX_PEERS> protected_peers;243for (unsigned int i = 0; i < num_peers; i++) {244protected_peers.set(i, fuzzed_data_provider.ConsumeBool());245}247// Params for orphanage.248const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000);249const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000);250auto orphanage = node::MakeTxOrphanage(global_latency_score_limit, per_peer_weight_reservation);
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.