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.
251m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;252m_unique_orphan_usage -= it->GetMemUsage();254// Remove references in m_outpoint_to_orphan_wtxids255const auto& wtxid{it->m_tx->GetWitnessHash()};256for (const auto& input : it->m_tx->vin) {257auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);258if (it_prev != m_outpoint_to_orphan_wtxids.end()) {259it_prev->second.erase(wtxid);260// Clean up keys if they point to an empty set.261if (it_prev->second.empty()) {262m_outpoint_to_orphan_wtxids.erase(it_prev);263}264}265}266}268// If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't269// have any reconsiderable announcements left after erasing.270if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());272m_orphans.get<Tag>().erase(it);273}275bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const276{277// Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid.278auto& index = m_orphans.get<ByWtxid>();279if (it == index.end()) return false;280if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;281if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;282return true;283}285TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const286{287auto it = m_peer_orphanage_info.find(peer);288return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage;289}291TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); }293TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; }295TxOrphanage::Count TxOrphanageImpl::CountUniqueOrphans() const { return m_unique_orphans; }297TxOrphanage::Count TxOrphanageImpl::AnnouncementsFromPeer(NodeId peer) const {298auto it = m_peer_orphanage_info.find(peer);299return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements;300}302TxOrphanage::Count TxOrphanageImpl::LatencyScoreFromPeer(NodeId peer) const {303auto it = m_peer_orphanage_info.find(peer);304return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score;305}307bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer)308{309const auto& wtxid{tx->GetWitnessHash()};310const auto& txid{tx->GetHash()};312// Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack.313TxOrphanage::Usage sz = GetTransactionWeight(*tx);314if (sz > MAX_STANDARD_TX_WEIGHT) {315LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());316return false;317}319// We will return false if the tx already exists under a different peer.320const bool brand_new{!HaveTx(wtxid)};322auto [iter, inserted] = m_orphans.get<ByWtxid>().emplace(tx, peer, m_current_sequence);323// If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false.324if (!inserted) return false;326++m_current_sequence;327auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;328peer_info.Add(*iter);330// Add links in m_outpoint_to_orphan_wtxids331if (brand_new) {332for (const auto& input : tx->vin) {333auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;334wtxids_for_prevout.emplace(wtxid);335}337m_unique_orphans += 1;338m_unique_orphan_usage += iter->GetMemUsage();339m_unique_rounded_input_scores += iter->GetLatencyScore() - 1;341LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n",342txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size());343Assume(IsUnique(iter));344} else {345LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",346peer, txid.ToString(), wtxid.ToString());347Assume(!IsUnique(iter));348}350// DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)351LimitOrphans();352return brand_new;353}355bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer)356{357auto& index_by_wtxid = m_orphans.get<ByWtxid>();358auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});360// Do nothing if this transaction isn't already present. We can't create an entry if we don't361// have the tx data.362if (it == index_by_wtxid.end()) return false;363if (it->m_tx->GetWitnessHash() != wtxid) return false;365// Add another announcement, copying the CTransactionRef from one that already exists.366const auto& ptx = it->m_tx;367auto [iter, inserted] = index_by_wtxid.emplace(ptx, peer, m_current_sequence);368// If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false.369if (!inserted) return false;371++m_current_sequence;372auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;373peer_info.Add(*iter);375const auto& txid = ptx->GetHash();376LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",377peer, txid.ToString(), wtxid.ToString());379Assume(!IsUnique(iter));381// DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)382LimitOrphans();383return true;384}386bool TxOrphanageImpl::EraseTxInternal(const Wtxid& wtxid)387{388auto& index_by_wtxid = m_orphans.get<ByWtxid>();390auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});391if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false;393auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});394unsigned int num_ann{0};395const auto txid = it->m_tx->GetHash();396while (it != it_end) {397Assume(it->m_tx->GetWitnessHash() == wtxid);398Erase<ByWtxid>(it++);399num_ann += 1;400}401LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann);403return true;404}406bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid)407{408const auto ret = EraseTxInternal(wtxid);410// Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.411LimitOrphans();413return ret;414}416/** Erase all entries by this peer. */417void TxOrphanageImpl::EraseForPeer(NodeId peer)418{419auto& index_by_peer = m_orphans.get<ByPeer>();420auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0});421if (it == index_by_peer.end() || it->m_announcer != peer) return;423unsigned int num_ann{0};424while (it != index_by_peer.end() && it->m_announcer == peer) {425// Delete item, cleaning up m_outpoint_to_orphan_wtxids iff this entry is unique by wtxid.426Erase<ByPeer>(it++);427num_ann += 1;428}429Assume(!m_peer_orphanage_info.contains(peer));431if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer);433// Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.434LimitOrphans();435}437/** If the data structure needs trimming, evicts announcements by selecting the DoSiest peer and evicting its oldest438* announcement (sorting non-reconsiderable orphans first, to give reconsiderable orphans a greater chance of being439* processed). Does nothing if no global limits are exceeded. This eviction strategy effectively "reserves" an440* amount of announcements and space for each peer. The reserved amount is protected from eviction even if there441* are peers spamming the orphanage.442*/443void TxOrphanageImpl::LimitOrphans()444{445if (!NeedsTrim()) return;447const auto original_unique_txns{CountUniqueOrphans()};449// Even though it's possible for MaxPeerLatencyScore to increase within this call to LimitOrphans450// (e.g. if a peer's orphans are removed entirely, changing the number of peers), use consistent limits throughout.451const auto max_lat{MaxPeerLatencyScore()};452const auto max_mem{ReservedPeerUsage()};454// We have exceeded the global limit(s). Now, identify who is using too much and evict their orphans.455// Create a heap of pairs (NodeId, DoS score), sorted by descending DoS score.456std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;457heap_peer_dos.reserve(m_peer_orphanage_info.size());458for (const auto& [nodeid, entry] : m_peer_orphanage_info) {459// Performance optimization: only consider peers with a DoS score > 1.460const auto dos_score = entry.GetDosScore(max_lat, max_mem);461if (ByRatio{dos_score} > ByRatio{FeeFrac{1, 1}}) {462heap_peer_dos.emplace_back(nodeid, dos_score);463}464}465static constexpr auto compare_score = [](const auto& left, const auto& right) {466if (left.second != right.second) {467// Note: if ratios are the same, this tiebreaks by denominator. In practice, since the468// latency denominator (number of announcements and inputs) is always lower, this means469// that a peer with only high latency scores will be targeted before a peer using a lot470// of memory, even if they have the same ratios.471return ByRatioNegSize{left.second} < ByRatioNegSize{right.second};472}473// Tiebreak by considering the more recent peer (higher NodeId) to be worse.474return left.first < right.first;475};476std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);478unsigned int num_erased{0};479// This outer loop finds the peer with the highest DoS score, which is a fraction of memory and latency scores480// over the respective allowances. We continue until the orphanage is within global limits. That means some peers481// might still have a DoS score > 1 at the end.482do {483Assume(!heap_peer_dos.empty());484// This is a max-heap, so the worst peer is at the front. pop_heap()485// moves it to the back, and the next worst peer is moved to the front.486std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);487const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());488heap_peer_dos.pop_back();490// If needs trim, then at least one peer has a DoS score higher than 1.491Assume(ByRatio{dos_score} > ByRatio{FeeFrac(1, 1)});493auto it_worst_peer = m_peer_orphanage_info.find(worst_peer);495// This inner loop trims until this peer is no longer the DoSiest one or has a score within 1. The score 1 is496// just a conservative fallback: once the last peer goes below ratio 1, NeedsTrim() will return false anyway.497// We evict the oldest announcement(s) from this peer, sorting non-reconsiderable before reconsiderable.498// The number of inner loop iterations is bounded by the total number of announcements.499const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second;500auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0});
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.