Inside Bitcoin
Opening the selected file…
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) 2021-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/txorphanage.h>7#include <consensus/validation.h>8#include <policy/policy.h>9#include <primitives/transaction.h>10#include <util/feefrac.h>11#include <util/hasher.h>12#include <util/log.h>13#include <util/time.h>15#include <boost/multi_index/indexed_by.hpp>16#include <boost/multi_index/ordered_index.hpp>17#include <boost/multi_index/tag.hpp>18#include <boost/multi_index_container.hpp>20#include <cassert>21#include <cmath>22#include <unordered_map>24namespace node {25/** Minimum NodeId for lower_bound lookups (in practice, NodeIds start at 0). */26static constexpr NodeId MIN_PEER{std::numeric_limits<NodeId>::min()};27/** Maximum NodeId for upper_bound lookups. */28static constexpr NodeId MAX_PEER{std::numeric_limits<NodeId>::max()};29class TxOrphanageImpl final : public TxOrphanage {30// Type alias for sequence numbers31using SequenceNumber = uint64_t;32/** Global sequence number, increment each time an announcement is added. */33SequenceNumber m_current_sequence{0};35/** One orphan announcement. Each announcement (i.e. combination of wtxid, nodeid) is unique. There may be multiple36* announcements for the same tx, and multiple transactions with the same txid but different wtxid are possible. */37struct Announcement38{39const CTransactionRef m_tx;40/** Which peer announced this tx */41const NodeId m_announcer;42/** What order this transaction entered the orphanage. */43const SequenceNumber m_entry_sequence;44/** Whether this tx should be reconsidered. Always starts out false. A peer's workset is the collection of all45* announcements with m_reconsider=true. */46bool m_reconsider{false};48Announcement(const CTransactionRef& tx, NodeId peer, SequenceNumber seq) :49m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq}50{ }52/** Get an approximation for "memory usage". The total memory is a function of the memory used to store the53* transaction itself, each entry in m_orphans, and each entry in m_outpoint_to_orphan_wtxids. We use weight because54* it is often higher than the actual memory usage of the transaction. This metric conveniently encompasses55* m_outpoint_to_orphan_wtxids usage since input data does not get the witness discount, and makes it easier to56* reason about each peer's limits using well-understood transaction attributes. */57TxOrphanage::Usage GetMemUsage() const {58return GetTransactionWeight(*m_tx);59}61/** Get an approximation of how much this transaction contributes to latency in EraseForBlock and EraseForPeer.62* The computation time is a function of the number of entries in m_orphans (thus 1 per announcement) and the63* number of entries in m_outpoint_to_orphan_wtxids (thus an additional 1 for every 10 inputs). Transactions with a64* small number of inputs (9 or fewer) are counted as 1 to make it easier to reason about each peer's limits in65* terms of "normal" transactions. */66TxOrphanage::Count GetLatencyScore() const {67return 1 + (m_tx->vin.size() / 10);68}69};71// Index by wtxid, then peer72struct ByWtxid {};73using ByWtxidView = std::tuple<Wtxid, NodeId>;74struct WtxidExtractor75{76using result_type = ByWtxidView;77result_type operator()(const Announcement& ann) const78{79return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer};80}81};83// Sort by peer, then by whether it is ready to reconsider, then by recency.84struct ByPeer {};85using ByPeerView = std::tuple<NodeId, bool, SequenceNumber>;86struct ByPeerViewExtractor {87using result_type = ByPeerView;88result_type operator()(const Announcement& ann) const89{90return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence};91}92};94using AnnouncementMap = boost::multi_index::multi_index_container<95Announcement,96boost::multi_index::indexed_by<97boost::multi_index::ordered_unique<boost::multi_index::tag<ByWtxid>, WtxidExtractor>,98boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor>99>100>;101template<typename Tag>102using Iter = typename AnnouncementMap::index<Tag>::type::iterator;103AnnouncementMap m_orphans;105const TxOrphanage::Count m_max_global_latency_score{DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE};106const TxOrphanage::Usage m_reserved_usage_per_peer{DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};108/** Number of unique orphans by wtxid. Less than or equal to the number of entries in m_orphans. */109TxOrphanage::Count m_unique_orphans{0};111/** Memory used by orphans (see Announcement::GetMemUsage()), deduplicated by wtxid. */112TxOrphanage::Usage m_unique_orphan_usage{0};114/** The sum of each unique transaction's latency scores including the inputs only (see Announcement::GetLatencyScore115* but subtract 1 for the announcements themselves). The total orphanage's latency score is given by this value +116* the number of entries in m_orphans. */117TxOrphanage::Count m_unique_rounded_input_scores{0};119/** Index from the parents' outputs to wtxids that exist in m_orphans. Used to find children of120* a transaction that can be reconsidered and to remove entries that conflict with a block.*/121std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids;123/** Set of Wtxids for which (exactly) one announcement with m_reconsider=true exists. */124std::set<Wtxid> m_reconsiderable_wtxids;126struct PeerDoSInfo {127TxOrphanage::Usage m_total_usage{0};128TxOrphanage::Count m_count_announcements{0};129TxOrphanage::Count m_total_latency_score{0};130bool operator==(const PeerDoSInfo& other) const131{132return m_total_usage == other.m_total_usage &&133m_count_announcements == other.m_count_announcements &&134m_total_latency_score == other.m_total_latency_score;135}136void Add(const Announcement& ann)137{138m_total_usage += ann.GetMemUsage();139m_total_latency_score += ann.GetLatencyScore();140m_count_announcements += 1;141}142bool Subtract(const Announcement& ann)143{144Assume(m_total_usage >= ann.GetMemUsage());145Assume(m_total_latency_score >= ann.GetLatencyScore());146Assume(m_count_announcements >= 1);148m_total_usage -= ann.GetMemUsage();149m_total_latency_score -= ann.GetLatencyScore();150m_count_announcements -= 1;151return m_count_announcements == 0;152}153/** There are 2 DoS scores:154* - Latency score (ratio of total latency score / max allowed latency score)155* - Memory score (ratio of total memory usage / max allowed memory usage).156*157* If the peer is using more than the allowed for either resource, its DoS score is > 1.158* A peer having a DoS score > 1 does not necessarily mean that something is wrong, since we159* do not trim unless the orphanage exceeds global limits, but it means that this peer will160* be selected for trimming sooner. If the global latency score or global memory usage161* limits are exceeded, it must be that there is a peer whose DoS score > 1. */162FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const163{164assert(max_peer_latency_score > 0);165assert(max_peer_memory > 0);166const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score);167const FeeFrac mem_score(m_total_usage, max_peer_memory);168return std::max<ByRatioNegSize<FeeFrac>>(latency_score, mem_score);169}170};171/** Store per-peer statistics. Used to determine each peer's DoS score. The size of this map is used to determine the172* number of peers and thus global {latency score, memory} limits. */173std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info;175/** Erase from m_orphans and update m_peer_orphanage_info. */176template<typename Tag>177void Erase(Iter<Tag> it);179/** Erase by wtxid. */180bool EraseTxInternal(const Wtxid& wtxid);182/** Check if there is exactly one announcement with the same wtxid as it. */183bool IsUnique(Iter<ByWtxid> it) const;185/** Check if the orphanage needs trimming. */186bool NeedsTrim() const;188/** Limit the orphanage to MaxGlobalLatencyScore and MaxGlobalUsage. */189void LimitOrphans();191public:192TxOrphanageImpl() = default;193TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :194m_max_global_latency_score{max_global_latency_score},195m_reserved_usage_per_peer{reserved_peer_usage}196{}197~TxOrphanageImpl() noexcept override = default;199TxOrphanage::Count CountAnnouncements() const override;200TxOrphanage::Count CountUniqueOrphans() const override;201TxOrphanage::Count AnnouncementsFromPeer(NodeId peer) const override;202TxOrphanage::Count LatencyScoreFromPeer(NodeId peer) const override;203TxOrphanage::Usage UsageByPeer(NodeId peer) const override;205TxOrphanage::Count MaxGlobalLatencyScore() const override;206TxOrphanage::Count TotalLatencyScore() const override;207TxOrphanage::Usage ReservedPeerUsage() const override;209/** Maximum allowed (deduplicated) latency score for all transactions (see Announcement::GetLatencyScore()). Dynamic210* based on number of peers. Each peer has an equal amount, but the global maximum latency score stays constant. The211* number of peers times MaxPeerLatencyScore() (rounded) adds up to MaxGlobalLatencyScore(). As long as every peer's212* m_total_latency_score / MaxPeerLatencyScore() < 1, MaxGlobalLatencyScore() is not exceeded. */213TxOrphanage::Count MaxPeerLatencyScore() const override;215/** Maximum allowed (deduplicated) memory usage for all transactions (see Announcement::GetMemUsage()). Dynamic based216* on number of peers. More peers means more allowed memory usage. The number of peers times ReservedPeerUsage()217* adds up to MaxGlobalUsage(). As long as every peer's m_total_usage / ReservedPeerUsage() < 1, MaxGlobalUsage() is218* not exceeded. */219TxOrphanage::Usage MaxGlobalUsage() const override;221bool AddTx(const CTransactionRef& tx, NodeId peer) override;222bool AddAnnouncer(const Wtxid& wtxid, NodeId peer) override;223CTransactionRef GetTx(const Wtxid& wtxid) const override;224bool HaveTx(const Wtxid& wtxid) const override;225bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const override;226CTransactionRef GetTxToReconsider(NodeId peer) override;227bool EraseTx(const Wtxid& wtxid) override;228void EraseForPeer(NodeId peer) override;229void EraseForBlock(const CBlock& block) override;230std::vector<std::pair<Wtxid, NodeId>> AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) override;231bool HaveTxToReconsider(NodeId peer) override;232std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override;233std::vector<OrphanInfo> GetOrphanTransactions() const override;234TxOrphanage::Usage TotalOrphanUsage() const override;235void SanityCheck() const override;236};238template<typename Tag>239void TxOrphanageImpl::Erase(Iter<Tag> it)240{241// Update m_peer_orphanage_info and clean up entries if they point to an empty struct.242// This means peers that are not storing any orphans do not have an entry in243// m_peer_orphanage_info (they can be added back later if they announce another orphan) and244// ensures disconnected peers are not tracked forever.245auto peer_it = m_peer_orphanage_info.find(it->m_announcer);246Assume(peer_it != m_peer_orphanage_info.end());247if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it);249if (IsUnique(m_orphans.project<ByWtxid>(it))) {250m_unique_orphans -= 1;
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.