LCOV - code coverage report
Current view: top level - src/node - txorphanage.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 93.6 % 329 308
Test Date: 2025-08-13 04:26:42 Functions: 97.6 % 41 40
Branches: 63.1 % 366 231

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2021-2022 The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <node/txorphanage.h>
       6                 :             : 
       7                 :             : #include <consensus/validation.h>
       8                 :             : #include <logging.h>
       9                 :             : #include <policy/policy.h>
      10                 :             : #include <primitives/transaction.h>
      11                 :             : #include <util/feefrac.h>
      12                 :             : #include <util/time.h>
      13                 :             : #include <util/hasher.h>
      14                 :             : 
      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>
      19                 :             : 
      20                 :             : #include <cassert>
      21                 :             : #include <cmath>
      22                 :             : #include <unordered_map>
      23                 :             : 
      24                 :             : namespace node {
      25                 :             : /** Minimum NodeId for lower_bound lookups (in practice, NodeIds start at 0). */
      26                 :             : static constexpr NodeId MIN_PEER{std::numeric_limits<NodeId>::min()};
      27                 :             : /** Maximum NodeId for upper_bound lookups. */
      28                 :             : static constexpr NodeId MAX_PEER{std::numeric_limits<NodeId>::max()};
      29                 :             : class TxOrphanageImpl final : public TxOrphanage {
      30                 :             :     // Type alias for sequence numbers
      31                 :             :     using SequenceNumber = uint64_t;
      32                 :             :     /** Global sequence number, increment each time an announcement is added. */
      33                 :             :     SequenceNumber m_current_sequence{0};
      34                 :             : 
      35                 :             :     /** One orphan announcement. Each announcement (i.e. combination of wtxid, nodeid) is unique. There may be multiple
      36                 :             :      * announcements for the same tx, and multiple transactions with the same txid but different wtxid are possible. */
      37         [ +  - ]:         694 :     struct Announcement
      38                 :             :     {
      39                 :             :         const CTransactionRef m_tx;
      40                 :             :         /** Which peer announced this tx */
      41                 :             :         const NodeId m_announcer;
      42                 :             :         /** What order this transaction entered the orphanage. */
      43                 :             :         const SequenceNumber m_entry_sequence;
      44                 :             :         /** Whether this tx should be reconsidered. Always starts out false. A peer's workset is the collection of all
      45                 :             :          * announcements with m_reconsider=true. */
      46                 :             :         bool m_reconsider{false};
      47                 :             : 
      48                 :         347 :         Announcement(const CTransactionRef& tx, NodeId peer, SequenceNumber seq) :
      49         [ +  - ]:         347 :             m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq}
      50                 :             :         { }
      51                 :             : 
      52                 :             :         /** Get an approximation for "memory usage". The total memory is a function of the memory used to store the
      53                 :             :          * transaction itself, each entry in m_orphans, and each entry in m_outpoint_to_orphan_wtxids. We use weight because
      54                 :             :          * it is often higher than the actual memory usage of the transaction. This metric conveniently encompasses
      55                 :             :          * m_outpoint_to_orphan_wtxids usage since input data does not get the witness discount, and makes it easier to
      56                 :             :          * reason about each peer's limits using well-understood transaction attributes. */
      57                 :        1205 :         TxOrphanage::Usage GetMemUsage()  const {
      58                 :         623 :             return GetTransactionWeight(*m_tx);
      59                 :             :         }
      60                 :             : 
      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 the
      63                 :             :          * number of entries in m_outpoint_to_orphan_wtxids (thus an additional 1 for every 10 inputs). Transactions with a
      64                 :             :          * small number of inputs (9 or fewer) are counted as 1 to make it easier to reason about each peer's limits in
      65                 :             :          * terms of "normal" transactions. */
      66                 :        1205 :         TxOrphanage::Count GetLatencyScore() const {
      67   [ -  +  -  +  :         623 :             return 1 + (m_tx->vin.size() / 10);
                   -  + ]
      68                 :             :         }
      69                 :             :     };
      70                 :             : 
      71                 :             :     // Index by wtxid, then peer
      72                 :             :     struct ByWtxid {};
      73                 :             :     using ByWtxidView = std::tuple<Wtxid, NodeId>;
      74                 :             :     struct WtxidExtractor
      75                 :             :     {
      76                 :             :         using result_type = ByWtxidView;
      77                 :        3877 :         result_type operator()(const Announcement& ann) const
      78                 :             :         {
      79                 :        3877 :             return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer};
      80                 :             :         }
      81                 :             :     };
      82                 :             : 
      83                 :             :     // Sort by peer, then by whether it is ready to reconsider, then by recency.
      84                 :             :     struct ByPeer {};
      85                 :             :     using ByPeerView = std::tuple<NodeId, bool, SequenceNumber>;
      86                 :             :     struct ByPeerViewExtractor {
      87                 :             :         using result_type = ByPeerView;
      88                 :        2761 :         result_type operator()(const Announcement& ann) const
      89                 :             :         {
      90                 :        2761 :             return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence};
      91                 :             :         }
      92                 :             :     };
      93                 :             : 
      94                 :             :     struct OrphanIndices final : boost::multi_index::indexed_by<
      95                 :             :         boost::multi_index::ordered_unique<boost::multi_index::tag<ByWtxid>, WtxidExtractor>,
      96                 :             :         boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor>
      97                 :             :     >{};
      98                 :             : 
      99                 :             :     using AnnouncementMap = boost::multi_index::multi_index_container<Announcement, OrphanIndices>;
     100                 :             :     template<typename Tag>
     101                 :             :     using Iter = typename AnnouncementMap::index<Tag>::type::iterator;
     102                 :             :     AnnouncementMap m_orphans;
     103                 :             : 
     104                 :             :     const TxOrphanage::Count m_max_global_latency_score{DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE};
     105                 :             :     const TxOrphanage::Usage m_reserved_usage_per_peer{DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};
     106                 :             : 
     107                 :             :     /** Number of unique orphans by wtxid. Less than or equal to the number of entries in m_orphans. */
     108                 :             :     TxOrphanage::Count m_unique_orphans{0};
     109                 :             : 
     110                 :             :     /** Memory used by orphans (see Announcement::GetMemUsage()), deduplicated by wtxid. */
     111                 :             :     TxOrphanage::Usage m_unique_orphan_usage{0};
     112                 :             : 
     113                 :             :     /** The sum of each unique transaction's latency scores including the inputs only (see Announcement::GetLatencyScore
     114                 :             :      * but subtract 1 for the announcements themselves). The total orphanage's latency score is given by this value +
     115                 :             :      * the number of entries in m_orphans. */
     116                 :             :     TxOrphanage::Count m_unique_rounded_input_scores{0};
     117                 :             : 
     118                 :             :     /** Index from the parents' outputs to wtxids that exist in m_orphans. Used to find children of
     119                 :             :      * a transaction that can be reconsidered and to remove entries that conflict with a block.*/
     120                 :             :     std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids;
     121                 :             : 
     122                 :             :     /** Set of Wtxids for which (exactly) one announcement with m_reconsider=true exists. */
     123                 :             :     std::set<Wtxid> m_reconsiderable_wtxids;
     124                 :             : 
     125                 :         139 :     struct PeerDoSInfo {
     126                 :             :         TxOrphanage::Usage m_total_usage{0};
     127                 :             :         TxOrphanage::Count m_count_announcements{0};
     128                 :             :         TxOrphanage::Count m_total_latency_score{0};
     129                 :          17 :         bool operator==(const PeerDoSInfo& other) const
     130                 :             :         {
     131                 :          34 :             return m_total_usage == other.m_total_usage &&
     132   [ +  -  +  - ]:          17 :                    m_count_announcements == other.m_count_announcements &&
     133         [ -  + ]:          17 :                    m_total_latency_score == other.m_total_latency_score;
     134                 :             :         }
     135                 :         344 :         void Add(const Announcement& ann)
     136                 :             :         {
     137                 :         344 :             m_total_usage += ann.GetMemUsage();
     138         [ -  + ]:         344 :             m_total_latency_score += ann.GetLatencyScore();
     139                 :         344 :             m_count_announcements += 1;
     140                 :         344 :         }
     141                 :          91 :         bool Subtract(const Announcement& ann)
     142                 :             :         {
     143                 :          91 :             Assume(m_total_usage >= ann.GetMemUsage());
     144         [ -  + ]:          91 :             Assume(m_total_latency_score >= ann.GetLatencyScore());
     145                 :          91 :             Assume(m_count_announcements >= 1);
     146                 :             : 
     147                 :          91 :             m_total_usage -= ann.GetMemUsage();
     148         [ -  + ]:          91 :             m_total_latency_score -= ann.GetLatencyScore();
     149                 :          91 :             m_count_announcements -= 1;
     150                 :          91 :             return m_count_announcements == 0;
     151                 :             :         }
     152                 :             :         /** There are 2 DoS scores:
     153                 :             :         * - Latency score (ratio of total latency score / max allowed latency score)
     154                 :             :         * - Memory score (ratio of total memory usage / max allowed memory usage).
     155                 :             :         *
     156                 :             :         * If the peer is using more than the allowed for either resource, its DoS score is > 1.
     157                 :             :         * A peer having a DoS score > 1 does not necessarily mean that something is wrong, since we
     158                 :             :         * do not trim unless the orphanage exceeds global limits, but it means that this peer will
     159                 :             :         * be selected for trimming sooner. If the global latency score or global memory usage
     160                 :             :         * limits are exceeded, it must be that there is a peer whose DoS score > 1. */
     161                 :         276 :         FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const
     162                 :             :         {
     163         [ -  + ]:         276 :             assert(max_peer_latency_score > 0);
     164         [ -  + ]:         276 :             assert(max_peer_memory > 0);
     165                 :         276 :             const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score);
     166                 :         276 :             const FeeFrac mem_score(m_total_usage, max_peer_memory);
     167                 :         276 :             return std::max<FeeFrac>(latency_score, mem_score);
     168                 :             :         }
     169                 :             :     };
     170                 :             :     /** Store per-peer statistics. Used to determine each peer's DoS score. The size of this map is used to determine the
     171                 :             :      * number of peers and thus global {latency score, memory} limits. */
     172                 :             :     std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info;
     173                 :             : 
     174                 :             :     /** Erase from m_orphans and update m_peer_orphanage_info. */
     175                 :             :     template<typename Tag>
     176                 :             :     void Erase(Iter<Tag> it);
     177                 :             : 
     178                 :             :     /** Erase by wtxid. */
     179                 :             :     bool EraseTxInternal(const Wtxid& wtxid);
     180                 :             : 
     181                 :             :     /** Check if there is exactly one announcement with the same wtxid as it. */
     182                 :             :     bool IsUnique(Iter<ByWtxid> it) const;
     183                 :             : 
     184                 :             :     /** Check if the orphanage needs trimming. */
     185                 :             :     bool NeedsTrim() const;
     186                 :             : 
     187                 :             :     /** Limit the orphanage to MaxGlobalLatencyScore and MaxGlobalUsage. */
     188                 :             :     void LimitOrphans();
     189                 :             : 
     190                 :             : public:
     191   [ +  -  +  - ]:         257 :     TxOrphanageImpl() = default;
     192                 :           6 :     TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :
     193                 :           6 :         m_max_global_latency_score{max_global_latency_score},
     194   [ +  -  +  - ]:           6 :         m_reserved_usage_per_peer{reserved_peer_usage}
     195                 :           6 :     {}
     196                 :         526 :     ~TxOrphanageImpl() noexcept override = default;
     197                 :             : 
     198                 :             :     TxOrphanage::Count CountAnnouncements() const override;
     199                 :             :     TxOrphanage::Count CountUniqueOrphans() const override;
     200                 :             :     TxOrphanage::Count AnnouncementsFromPeer(NodeId peer) const override;
     201                 :             :     TxOrphanage::Count LatencyScoreFromPeer(NodeId peer) const override;
     202                 :             :     TxOrphanage::Usage UsageByPeer(NodeId peer) const override;
     203                 :             : 
     204                 :             :     TxOrphanage::Count MaxGlobalLatencyScore() const override;
     205                 :             :     TxOrphanage::Count TotalLatencyScore() const override;
     206                 :             :     TxOrphanage::Usage ReservedPeerUsage() const override;
     207                 :             : 
     208                 :             :     /** Maximum allowed (deduplicated) latency score for all transactions (see Announcement::GetLatencyScore()). Dynamic
     209                 :             :      * based on number of peers. Each peer has an equal amount, but the global maximum latency score stays constant. The
     210                 :             :      * number of peers times MaxPeerLatencyScore() (rounded) adds up to MaxGlobalLatencyScore().  As long as every peer's
     211                 :             :      * m_total_latency_score / MaxPeerLatencyScore() < 1, MaxGlobalLatencyScore() is not exceeded. */
     212                 :             :     TxOrphanage::Count MaxPeerLatencyScore() const override;
     213                 :             : 
     214                 :             :     /** Maximum allowed (deduplicated) memory usage for all transactions (see Announcement::GetMemUsage()). Dynamic based
     215                 :             :      * on number of peers. More peers means more allowed memory usage. The number of peers times ReservedPeerUsage()
     216                 :             :      * adds up to MaxGlobalUsage(). As long as every peer's m_total_usage / ReservedPeerUsage() < 1, MaxGlobalUsage() is
     217                 :             :      * not exceeded. */
     218                 :             :     TxOrphanage::Usage MaxGlobalUsage() const override;
     219                 :             : 
     220                 :             :     bool AddTx(const CTransactionRef& tx, NodeId peer) override;
     221                 :             :     bool AddAnnouncer(const Wtxid& wtxid, NodeId peer) override;
     222                 :             :     CTransactionRef GetTx(const Wtxid& wtxid) const override;
     223                 :             :     bool HaveTx(const Wtxid& wtxid) const override;
     224                 :             :     bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const override;
     225                 :             :     CTransactionRef GetTxToReconsider(NodeId peer) override;
     226                 :             :     bool EraseTx(const Wtxid& wtxid) override;
     227                 :             :     void EraseForPeer(NodeId peer) override;
     228                 :             :     void EraseForBlock(const CBlock& block) override;
     229                 :             :     std::vector<std::pair<Wtxid, NodeId>> AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) override;
     230                 :             :     bool HaveTxToReconsider(NodeId peer) override;
     231                 :             :     std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override;
     232                 :             :     std::vector<OrphanInfo> GetOrphanTransactions() const override;
     233                 :             :     TxOrphanage::Usage TotalOrphanUsage() const override;
     234                 :             :     void SanityCheck() const override;
     235                 :             : };
     236                 :             : 
     237                 :             : template<typename Tag>
     238                 :          91 : void TxOrphanageImpl::Erase(Iter<Tag> it)
     239                 :             : {
     240                 :             :     // Update m_peer_orphanage_info and clean up entries if they point to an empty struct.
     241                 :             :     // This means peers that are not storing any orphans do not have an entry in
     242                 :             :     // m_peer_orphanage_info (they can be added back later if they announce another orphan) and
     243                 :             :     // ensures disconnected peers are not tracked forever.
     244                 :          91 :     auto peer_it = m_peer_orphanage_info.find(it->m_announcer);
     245                 :          91 :     Assume(peer_it != m_peer_orphanage_info.end());
     246         [ +  + ]:          91 :     if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it);
     247                 :             : 
     248         [ +  + ]:          91 :     if (IsUnique(m_orphans.project<ByWtxid>(it))) {
     249         [ -  + ]:          56 :         m_unique_orphans -= 1;
     250         [ -  + ]:          56 :         m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;
     251                 :          56 :         m_unique_orphan_usage -= it->GetMemUsage();
     252                 :             : 
     253                 :             :         // Remove references in m_outpoint_to_orphan_wtxids
     254                 :          56 :         const auto& wtxid{it->m_tx->GetWitnessHash()};
     255         [ +  + ]:         113 :         for (const auto& input : it->m_tx->vin) {
     256                 :          57 :             auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
     257         [ +  - ]:         114 :             if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
     258         [ +  + ]:          57 :                 it_prev->second.erase(wtxid);
     259                 :             :                 // Clean up keys if they point to an empty set.
     260         [ +  + ]:          57 :                 if (it_prev->second.empty()) {
     261                 :          55 :                     m_outpoint_to_orphan_wtxids.erase(it_prev);
     262                 :             :                 }
     263                 :             :             }
     264                 :             :         }
     265                 :             :     }
     266                 :             : 
     267                 :             :     // If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't
     268                 :             :     // have any reconsiderable announcements left after erasing.
     269         [ +  + ]:          91 :     if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
     270                 :             : 
     271                 :          91 :     m_orphans.get<Tag>().erase(it);
     272                 :          91 : }
     273                 :             : 
     274                 :         435 : bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const
     275                 :             : {
     276                 :             :     // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid.
     277                 :         435 :     auto& index = m_orphans.get<ByWtxid>();
     278         [ +  - ]:         435 :     if (it == index.end()) return false;
     279   [ +  +  +  + ]:        1200 :     if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
     280   [ +  +  +  + ]:         397 :     if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
     281                 :             :     return true;
     282                 :             : }
     283                 :             : 
     284                 :          26 : TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const
     285                 :             : {
     286                 :          26 :     auto it = m_peer_orphanage_info.find(peer);
     287         [ -  + ]:          26 :     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage;
     288                 :             : }
     289                 :             : 
     290                 :          34 : TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); }
     291                 :             : 
     292                 :         480 : TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; }
     293                 :             : 
     294                 :         138 : TxOrphanage::Count TxOrphanageImpl::CountUniqueOrphans() const { return m_unique_orphans; }
     295                 :             : 
     296                 :          73 : TxOrphanage::Count TxOrphanageImpl::AnnouncementsFromPeer(NodeId peer) const {
     297                 :          73 :     auto it = m_peer_orphanage_info.find(peer);
     298         [ +  + ]:          73 :     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements;
     299                 :             : }
     300                 :             : 
     301                 :           2 : TxOrphanage::Count TxOrphanageImpl::LatencyScoreFromPeer(NodeId peer) const {
     302                 :           2 :     auto it = m_peer_orphanage_info.find(peer);
     303         [ +  - ]:           2 :     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score;
     304                 :             : }
     305                 :             : 
     306                 :         355 : bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer)
     307                 :             : {
     308                 :         355 :     const auto& wtxid{tx->GetWitnessHash()};
     309                 :         355 :     const auto& txid{tx->GetHash()};
     310                 :             : 
     311                 :             :     // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack.
     312                 :         355 :     TxOrphanage::Usage sz = GetTransactionWeight(*tx);
     313         [ +  + ]:         355 :     if (sz > MAX_STANDARD_TX_WEIGHT) {
     314   [ +  -  +  -  :          22 :         LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
                   +  - ]
     315                 :          11 :         return false;
     316                 :             :     }
     317                 :             : 
     318                 :             :     // We will return false if the tx already exists under a different peer.
     319                 :         344 :     const bool brand_new{!HaveTx(wtxid)};
     320                 :             : 
     321         [ +  + ]:         344 :     auto [iter, inserted] = m_orphans.get<ByWtxid>().emplace(tx, peer, m_current_sequence);
     322                 :             :     // If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false.
     323         [ +  + ]:         344 :     if (!inserted) return false;
     324                 :             : 
     325                 :         342 :     ++m_current_sequence;
     326                 :         342 :     auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
     327                 :         342 :     peer_info.Add(*iter);
     328                 :             : 
     329                 :             :     // Add links in m_outpoint_to_orphan_wtxids
     330         [ +  + ]:         342 :     if (brand_new) {
     331         [ +  + ]:        1248 :         for (const auto& input : tx->vin) {
     332                 :         957 :             auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;
     333                 :         957 :             wtxids_for_prevout.emplace(wtxid);
     334                 :             :         }
     335                 :             : 
     336                 :         291 :         m_unique_orphans += 1;
     337                 :         291 :         m_unique_orphan_usage += iter->GetMemUsage();
     338         [ -  + ]:         291 :         m_unique_rounded_input_scores += iter->GetLatencyScore() - 1;
     339                 :             : 
     340   [ +  -  +  -  :         582 :         LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n",
                   +  - ]
     341                 :             :                     txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size());
     342                 :         291 :         Assume(IsUnique(iter));
     343                 :             :     } else {
     344   [ +  -  +  -  :         102 :         LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
                   +  - ]
     345                 :             :                     peer, txid.ToString(), wtxid.ToString());
     346                 :          51 :         Assume(!IsUnique(iter));
     347                 :             :     }
     348                 :             : 
     349                 :             :     // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
     350                 :         342 :     LimitOrphans();
     351                 :         342 :     return brand_new;
     352                 :             : }
     353                 :             : 
     354                 :           3 : bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
     355                 :             : {
     356                 :           3 :     auto& index_by_wtxid = m_orphans.get<ByWtxid>();
     357                 :           3 :     auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
     358                 :             : 
     359                 :             :     // Do nothing if this transaction isn't already present. We can't create an entry if we don't
     360                 :             :     // have the tx data.
     361         [ +  - ]:           3 :     if (it == index_by_wtxid.end()) return false;
     362         [ +  - ]:           3 :     if (it->m_tx->GetWitnessHash() != wtxid) return false;
     363                 :             : 
     364                 :             :     // Add another announcement, copying the CTransactionRef from one that already exists.
     365                 :           3 :     const auto& ptx = it->m_tx;
     366         [ +  + ]:           3 :     auto [iter, inserted] = index_by_wtxid.emplace(ptx, peer, m_current_sequence);
     367                 :             :     // If the announcement (same wtxid, same peer) already exists, emplacement fails. Return false.
     368         [ +  + ]:           3 :     if (!inserted) return false;
     369                 :             : 
     370                 :           2 :     ++m_current_sequence;
     371                 :           2 :     auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
     372                 :           2 :     peer_info.Add(*iter);
     373                 :             : 
     374                 :           2 :     const auto& txid = ptx->GetHash();
     375   [ +  -  +  -  :           4 :     LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
                   +  - ]
     376                 :             :                 peer, txid.ToString(), wtxid.ToString());
     377                 :             : 
     378                 :           2 :     Assume(!IsUnique(iter));
     379                 :             : 
     380                 :             :     // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
     381                 :           2 :     LimitOrphans();
     382                 :           2 :     return true;
     383                 :             : }
     384                 :             : 
     385                 :          54 : bool TxOrphanageImpl::EraseTxInternal(const Wtxid& wtxid)
     386                 :             : {
     387                 :          54 :     auto& index_by_wtxid = m_orphans.get<ByWtxid>();
     388                 :             : 
     389                 :          54 :     auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
     390   [ +  +  +  + ]:          54 :     if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false;
     391                 :             : 
     392                 :           9 :     auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
     393                 :           9 :     unsigned int num_ann{0};
     394                 :           9 :     const auto txid = it->m_tx->GetHash();
     395         [ +  + ]:          20 :     while (it != it_end) {
     396                 :          11 :         Assume(it->m_tx->GetWitnessHash() == wtxid);
     397                 :          11 :         Erase<ByWtxid>(it++);
     398                 :          11 :         num_ann += 1;
     399                 :             :     }
     400   [ +  -  +  -  :          18 :     LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann);
                   +  - ]
     401                 :             : 
     402                 :             :     return true;
     403                 :             : }
     404                 :             : 
     405                 :          49 : bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid)
     406                 :             : {
     407                 :          49 :     const auto ret = EraseTxInternal(wtxid);
     408                 :             : 
     409                 :             :     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
     410                 :          49 :     LimitOrphans();
     411                 :             : 
     412                 :          49 :     return ret;
     413                 :             : }
     414                 :             : 
     415                 :             : /** Erase all entries by this peer. */
     416                 :          34 : void TxOrphanageImpl::EraseForPeer(NodeId peer)
     417                 :             : {
     418                 :          34 :     auto& index_by_peer = m_orphans.get<ByPeer>();
     419                 :          34 :     auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
     420   [ +  +  +  + ]:          34 :     if (it == index_by_peer.end() || it->m_announcer != peer) return;
     421                 :             : 
     422                 :           8 :     unsigned int num_ann{0};
     423   [ +  +  +  + ]:          21 :     while (it != index_by_peer.end() && it->m_announcer == peer) {
     424                 :             :         // Delete item, cleaning up m_outpoint_to_orphan_wtxids iff this entry is unique by wtxid.
     425                 :          13 :         Erase<ByPeer>(it++);
     426                 :          13 :         num_ann += 1;
     427                 :             :     }
     428                 :           8 :     Assume(!m_peer_orphanage_info.contains(peer));
     429                 :             : 
     430   [ +  -  +  - ]:           8 :     if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer);
     431                 :             : 
     432                 :             :     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
     433                 :           8 :     LimitOrphans();
     434                 :             : }
     435                 :             : 
     436                 :             : /** If the data structure needs trimming, evicts announcements by selecting the DoSiest peer and evicting its oldest
     437                 :             :  * announcement (sorting non-reconsiderable orphans first, to give reconsiderable orphans a greater chance of being
     438                 :             :  * processed). Does nothing if no global limits are exceeded.  This eviction strategy effectively "reserves" an
     439                 :             :  * amount of announcements and space for each peer. The reserved amount is protected from eviction even if there
     440                 :             :  * are peers spamming the orphanage.
     441                 :             :  */
     442                 :         403 : void TxOrphanageImpl::LimitOrphans()
     443                 :             : {
     444         [ +  + ]:         403 :     if (!NeedsTrim()) return;
     445                 :             : 
     446                 :          58 :     const auto original_unique_txns{CountUniqueOrphans()};
     447                 :             : 
     448                 :             :     // Even though it's possible for MaxPeerLatencyScore to increase within this call to LimitOrphans
     449                 :             :     // (e.g. if a peer's orphans are removed entirely, changing the number of peers), use consistent limits throughout.
     450                 :          58 :     const auto max_lat{MaxPeerLatencyScore()};
     451                 :          58 :     const auto max_mem{ReservedPeerUsage()};
     452                 :             : 
     453                 :             :     // We have exceeded the global limit(s). Now, identify who is using too much and evict their orphans.
     454                 :             :     // Create a heap of pairs (NodeId, DoS score), sorted by descending DoS score.
     455                 :          58 :     std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
     456         [ +  - ]:          58 :     heap_peer_dos.reserve(m_peer_orphanage_info.size());
     457         [ +  + ]:         267 :     for (const auto& [nodeid, entry] : m_peer_orphanage_info) {
     458                 :             :         // Performance optimization: only consider peers with a DoS score > 1.
     459                 :         209 :         const auto dos_score = entry.GetDosScore(max_lat, max_mem);
     460         [ +  + ]:         209 :         if (dos_score >> FeeFrac{1, 1}) {
     461         [ +  - ]:          58 :             heap_peer_dos.emplace_back(nodeid, dos_score);
     462                 :             :         }
     463                 :             :     }
     464                 :          58 :     static constexpr auto compare_score = [](const auto& left, const auto& right) {
     465         [ #  # ]:           0 :         if (left.second != right.second) return left.second < right.second;
     466                 :             :         // Tiebreak by considering the more recent peer (higher NodeId) to be worse.
     467                 :           0 :         return left.first < right.first;
     468                 :             :     };
     469                 :          58 :     std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
     470                 :             : 
     471                 :          58 :     unsigned int num_erased{0};
     472                 :             :     // This outer loop finds the peer with the highest DoS score, which is a fraction of memory and latency scores
     473                 :             :     // over the respective allowances. We continue until the orphanage is within global limits. That means some peers
     474                 :             :     // might still have a DoS score > 1 at the end.
     475                 :             :     // Note: if ratios are the same, FeeFrac tiebreaks by denominator. In practice, since the latency denominator (number of
     476                 :             :     // announcements and inputs) is always lower, this means that a peer with only high latency scores will be targeted
     477                 :             :     // before a peer using a lot of memory, even if they have the same ratios.
     478                 :          58 :     do {
     479                 :          58 :         Assume(!heap_peer_dos.empty());
     480                 :             :         // This is a max-heap, so the worst peer is at the front. pop_heap()
     481                 :             :         // moves it to the back, and the next worst peer is moved to the front.
     482                 :          58 :         std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
     483                 :          58 :         const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());
     484                 :          58 :         heap_peer_dos.pop_back();
     485                 :             : 
     486                 :             :         // If needs trim, then at least one peer has a DoS score higher than 1.
     487                 :          58 :         Assume(dos_score >> (FeeFrac{1, 1}));
     488                 :             : 
     489                 :          58 :         auto it_worst_peer = m_peer_orphanage_info.find(worst_peer);
     490                 :             : 
     491                 :             :         // This inner loop trims until this peer is no longer the DoSiest one or has a score within 1. The score 1 is
     492                 :             :         // just a conservative fallback: once the last peer goes below ratio 1, NeedsTrim() will return false anyway.
     493                 :             :         // We evict the oldest announcement(s) from this peer, sorting non-reconsiderable before reconsiderable.
     494                 :             :         // The number of inner loop iterations is bounded by the total number of announcements.
     495         [ +  - ]:          58 :         const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second;
     496                 :          58 :         auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0});
     497                 :          58 :         unsigned int num_erased_this_round{0};
     498                 :          58 :         unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
     499   [ +  -  +  + ]:         116 :         while (NeedsTrim()) {
     500         [ +  - ]:          67 :             if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break;
     501         [ +  - ]:          67 :             if (!Assume(it_ann->m_announcer == worst_peer)) break;
     502                 :             : 
     503         [ +  - ]:          67 :             Erase<ByPeer>(it_ann++);
     504                 :          67 :             num_erased += 1;
     505                 :          67 :             num_erased_this_round += 1;
     506                 :             : 
     507                 :             :             // If we erased the last orphan from this peer, it_worst_peer will be invalidated.
     508                 :          67 :             it_worst_peer = m_peer_orphanage_info.find(worst_peer);
     509   [ +  -  +  +  :          76 :             if (it_worst_peer == m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold) break;
                   +  + ]
     510                 :             :         }
     511   [ +  -  +  -  :          58 :         LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
                   +  - ]
     512                 :             : 
     513   [ +  -  -  + ]:          58 :         if (!NeedsTrim()) break;
     514                 :             : 
     515                 :             :         // Unless this peer is empty, put it back in the heap so we continue to consider evicting its orphans.
     516                 :             :         // We may select this peer for evictions again if there are multiple DoSy peers.
     517   [ -  -  -  - ]:          58 :         if (it_worst_peer != m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) {
     518         [ #  # ]:           0 :             heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem));
     519                 :           0 :             std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
     520                 :             :         }
     521                 :             :     } while (true);
     522                 :             : 
     523         [ +  - ]:          58 :     const auto remaining_unique_orphans{CountUniqueOrphans()};
     524   [ +  -  +  -  :          58 :     LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
                   +  - ]
     525                 :          58 : }
     526                 :             : 
     527                 :           4 : std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
     528                 :             : {
     529                 :           4 :     std::vector<std::pair<Wtxid, NodeId>> ret;
     530                 :           4 :     auto& index_by_wtxid = m_orphans.get<ByWtxid>();
     531   [ -  +  +  + ]:          12 :     for (unsigned int i = 0; i < tx.vout.size(); i++) {
     532                 :           8 :         const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i));
     533         [ +  + ]:          12 :         if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) {
     534         [ +  + ]:           8 :             for (const auto& wtxid : it_by_prev->second) {
     535                 :             :                 // If a reconsiderable announcement for this wtxid already exists, skip it.
     536         [ -  + ]:           4 :                 if (m_reconsiderable_wtxids.contains(wtxid)) continue;
     537                 :             : 
     538                 :             :                 // Belt and suspenders, each entry in m_outpoint_to_orphan_wtxids should always have at least 1 announcement.
     539                 :           4 :                 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
     540   [ +  -  -  + ]:           4 :                 if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue;
     541                 :             : 
     542                 :             :                 // Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing
     543                 :             :                 // inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us
     544                 :             :                 // from processing the orphan by disconnecting.
     545                 :           4 :                 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
     546                 :           4 :                 const auto num_announcers{std::distance(it, it_end)};
     547         [ -  + ]:           4 :                 if (!Assume(num_announcers > 0)) continue;
     548                 :           4 :                 std::advance(it, rng.randrange(num_announcers));
     549                 :             : 
     550         [ +  - ]:           4 :                 if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break;
     551                 :             : 
     552                 :             :                 // Mark this orphan as ready to be reconsidered.
     553                 :           4 :                 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; };
     554                 :           4 :                 Assume(!it->m_reconsider);
     555                 :           4 :                 index_by_wtxid.modify(it, mark_reconsidered_modifier);
     556         [ +  - ]:           4 :                 ret.emplace_back(wtxid, it->m_announcer);
     557         [ +  - ]:           4 :                 m_reconsiderable_wtxids.insert(wtxid);
     558                 :             : 
     559   [ +  -  +  -  :           8 :                 LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
          +  -  +  -  +  
                      - ]
     560                 :             :                             it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
     561                 :             :             }
     562                 :             :         }
     563                 :             :     }
     564                 :           4 :     return ret;
     565                 :           0 : }
     566                 :             : 
     567                 :         702 : bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const
     568                 :             : {
     569                 :         702 :     auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
     570   [ +  +  +  + ]:         702 :     return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
     571                 :             : }
     572                 :             : 
     573                 :          50 : CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const
     574                 :             : {
     575                 :          50 :     auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
     576   [ +  +  +  +  :          50 :     if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx;
                   +  - ]
     577                 :          45 :     return nullptr;
     578                 :             : }
     579                 :             : 
     580                 :          80 : bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
     581                 :             : {
     582                 :          80 :     return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0;
     583                 :             : }
     584                 :             : 
     585                 :             : /** If there is a tx that can be reconsidered, return it and set it back to
     586                 :             :  * non-reconsiderable. Otherwise, return a nullptr. */
     587                 :           9 : CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer)
     588                 :             : {
     589                 :           9 :     auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
     590   [ +  +  +  +  :           9 :     if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
                   +  - ]
     591                 :             :         // Flip m_reconsider. Even if this transaction stays in orphanage, it shouldn't be
     592                 :             :         // reconsidered again until there is a new reason to do so.
     593                 :           3 :         static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; };
     594                 :           3 :         m_orphans.get<ByPeer>().modify(it, mark_reconsidered_modifier);
     595                 :             :         // As there is exactly one m_reconsider announcement per reconsiderable wtxids, flipping
     596                 :             :         // the m_reconsider flag means the wtxid is no longer reconsiderable.
     597                 :           3 :         m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
     598         [ +  - ]:           3 :         return it->m_tx;
     599                 :             :     }
     600                 :           6 :     return nullptr;
     601                 :             : }
     602                 :             : 
     603                 :             : /** Return whether there is a tx that can be reconsidered. */
     604                 :           6 : bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer)
     605                 :             : {
     606                 :           6 :     auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
     607   [ +  +  +  +  :           6 :     return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
                   -  + ]
     608                 :             : }
     609                 :             : 
     610                 :           4 : void TxOrphanageImpl::EraseForBlock(const CBlock& block)
     611                 :             : {
     612         [ +  + ]:           4 :     if (m_orphans.empty()) return;
     613                 :             : 
     614                 :           2 :     std::set<Wtxid> wtxids_to_erase;
     615         [ +  + ]:           7 :     for (const CTransactionRef& ptx : block.vtx) {
     616                 :           5 :         const CTransaction& block_tx = *ptx;
     617                 :             : 
     618                 :             :         // Which orphan pool entries must we evict?
     619         [ +  + ]:          11 :         for (const auto& input : block_tx.vin) {
     620                 :           6 :             auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
     621         [ +  + ]:          11 :             if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
     622                 :             :                 // Copy all wtxids to wtxids_to_erase.
     623         [ +  - ]:           5 :                 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
     624                 :             :             }
     625                 :             :         }
     626                 :             :     }
     627                 :             : 
     628                 :           2 :     unsigned int num_erased{0};
     629         [ +  + ]:           7 :     for (const auto& wtxid : wtxids_to_erase) {
     630                 :             :         // Don't use EraseTx here because it calls LimitOrphans and announcements deleted in that call are not reflected
     631                 :             :         // in its return result. Waiting until the end to do LimitOrphans helps save repeated computation and allows us
     632                 :             :         // to check that num_erased is what we expect.
     633   [ +  -  -  + ]:           5 :         num_erased += EraseTxInternal(wtxid) ? 1 : 0;
     634                 :             :     }
     635                 :             : 
     636         [ +  - ]:           2 :     if (num_erased != 0) {
     637   [ +  -  +  -  :           2 :         LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased);
                   +  - ]
     638                 :             :     }
     639         [ +  - ]:           2 :     Assume(wtxids_to_erase.size() == num_erased);
     640                 :             : 
     641                 :             :     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
     642         [ +  - ]:           2 :     LimitOrphans();
     643                 :           2 : }
     644                 :             : 
     645                 :          17 : std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const
     646                 :             : {
     647                 :          17 :     std::vector<CTransactionRef> children_found;
     648                 :          17 :     const auto& parent_txid{parent->GetHash()};
     649                 :             : 
     650                 :             :     // Iterate through all orphans from this peer, in reverse order, so that more recent
     651                 :             :     // transactions are added first. Doing so helps avoid work when one of the orphans replaced
     652                 :             :     // an earlier one. Since we require the NodeId to match, one peer's announcement order does
     653                 :             :     // not bias how we process other peer's orphans.
     654                 :          17 :     auto& index_by_peer = m_orphans.get<ByPeer>();
     655                 :          17 :     auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()});
     656                 :          17 :     auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
     657                 :             : 
     658         [ +  + ]:          43 :     while (it_upper != it_lower) {
     659                 :          26 :         --it_upper;
     660         [ +  - ]:          26 :         if (!Assume(it_upper->m_announcer == peer)) break;
     661                 :             :         // Check if this tx spends from parent.
     662         [ +  + ]:          46 :         for (const auto& input : it_upper->m_tx->vin) {
     663         [ +  + ]:          34 :             if (input.prevout.hash == parent_txid) {
     664         [ +  - ]:          14 :                 children_found.emplace_back(it_upper->m_tx);
     665                 :             :                 break;
     666                 :             :             }
     667                 :             :         }
     668                 :             :     }
     669                 :          17 :     return children_found;
     670                 :           0 : }
     671                 :             : 
     672                 :           0 : std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const
     673                 :             : {
     674                 :           0 :     std::vector<TxOrphanage::OrphanInfo> result;
     675         [ #  # ]:           0 :     result.reserve(m_unique_orphans);
     676                 :             : 
     677                 :           0 :     auto& index_by_wtxid = m_orphans.get<ByWtxid>();
     678                 :           0 :     auto it = index_by_wtxid.begin();
     679                 :           0 :     std::set<NodeId> this_orphan_announcers;
     680         [ #  # ]:           0 :     while (it != index_by_wtxid.end()) {
     681         [ #  # ]:           0 :         this_orphan_announcers.insert(it->m_announcer);
     682                 :             :         // If this is the last entry, or the next entry has a different wtxid, build a OrphanInfo.
     683   [ #  #  #  # ]:           0 :         if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) {
     684         [ #  # ]:           0 :             result.emplace_back(it->m_tx, std::move(this_orphan_announcers));
     685                 :           0 :             this_orphan_announcers.clear();
     686                 :             :         }
     687                 :             : 
     688                 :           0 :         ++it;
     689                 :             :     }
     690         [ #  # ]:           0 :     Assume(m_unique_orphans == result.size());
     691                 :             : 
     692                 :           0 :     return result;
     693                 :           0 : }
     694                 :             : 
     695                 :          10 : void TxOrphanageImpl::SanityCheck() const
     696                 :             : {
     697                 :          10 :     std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
     698                 :          10 :     std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
     699                 :          10 :     std::set<COutPoint> all_outpoints;
     700                 :          10 :     std::set<Wtxid> reconstructed_reconsiderable_wtxids;
     701                 :             : 
     702         [ +  + ]:         342 :     for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) {
     703         [ +  + ]:        1191 :         for (const auto& input : it->m_tx->vin) {
     704         [ +  - ]:        1025 :             all_outpoints.insert(input.prevout);
     705                 :             :         }
     706   [ -  +  +  - ]:         166 :         unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
     707                 :             : 
     708         [ +  - ]:         166 :         auto& peer_info = reconstructed_peer_info[it->m_announcer];
     709                 :         166 :         peer_info.m_total_usage += it->GetMemUsage();
     710                 :         166 :         peer_info.m_count_announcements += 1;
     711         [ -  + ]:         166 :         peer_info.m_total_latency_score += it->GetLatencyScore();
     712                 :             : 
     713         [ +  + ]:         166 :         if (it->m_reconsider) {
     714   [ +  -  -  + ]:           1 :             auto [_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
     715                 :             :             // Check that there is only ever 1 announcement per wtxid with m_reconsider set.
     716         [ -  + ]:           1 :             assert(added);
     717                 :             :         }
     718                 :             :     }
     719         [ -  + ]:          10 :     assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size());
     720                 :             : 
     721                 :             :     // Recalculated per-peer stats are identical to m_peer_orphanage_info
     722         [ -  + ]:          10 :     assert(reconstructed_peer_info == m_peer_orphanage_info);
     723                 :             : 
     724                 :             :     // Recalculated set of reconsiderable wtxids must match.
     725         [ -  + ]:          10 :     assert(m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids);
     726                 :             : 
     727                 :             :     // All outpoints exist in m_outpoint_to_orphan_wtxids, all keys in m_outpoint_to_orphan_wtxids correspond to some
     728                 :             :     // orphan, and all wtxids referenced in m_outpoint_to_orphan_wtxids are also in m_orphans.
     729                 :             :     // This ensures m_outpoint_to_orphan_wtxids is cleaned up.
     730         [ -  + ]:          10 :     assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size());
     731         [ +  + ]:         244 :     for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) {
     732         [ -  + ]:         234 :         assert(all_outpoints.contains(outpoint));
     733         [ +  + ]:         999 :         for (const auto& wtxid : wtxid_set) {
     734         [ -  + ]:         765 :             assert(unique_wtxids_to_scores.contains(wtxid));
     735                 :             :         }
     736                 :             :     }
     737                 :             : 
     738                 :             :     // Cached m_unique_orphans value is correct.
     739         [ -  + ]:          10 :     assert(m_orphans.size() >= m_unique_orphans);
     740         [ -  + ]:          10 :     assert(m_orphans.size() <= m_peer_orphanage_info.size() * m_unique_orphans);
     741         [ -  + ]:          10 :     assert(unique_wtxids_to_scores.size() == m_unique_orphans);
     742                 :             : 
     743                 :          10 :     const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
     744                 :         151 :         TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; });
     745         [ -  + ]:          10 :     assert(calculated_dedup_usage == m_unique_orphan_usage);
     746                 :             : 
     747                 :             :     // Global usage is deduplicated, should be less than or equal to the sum of all per-peer usages.
     748                 :          10 :     const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
     749                 :          17 :         TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; });
     750         [ -  + ]:          10 :     assert(summed_peer_usage >= m_unique_orphan_usage);
     751                 :             : 
     752                 :             :     // Cached m_unique_rounded_input_scores value is correct.
     753                 :          10 :     const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
     754                 :         151 :         TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; });
     755         [ -  + ]:          10 :     assert(calculated_total_latency_score == m_unique_rounded_input_scores);
     756                 :             : 
     757                 :             :     // Global latency score is deduplicated, should be less than or equal to the sum of all per-peer latency scores.
     758                 :          10 :     const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
     759                 :          17 :         TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; });
     760         [ -  + ]:          10 :     assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size());
     761                 :             : 
     762   [ +  -  -  + ]:          10 :     assert(!NeedsTrim());
     763                 :          10 : }
     764                 :             : 
     765                 :         595 : TxOrphanage::Count TxOrphanageImpl::MaxGlobalLatencyScore() const { return m_max_global_latency_score; }
     766                 :         590 : TxOrphanage::Count TxOrphanageImpl::TotalLatencyScore() const { return m_unique_rounded_input_scores + m_orphans.size(); }
     767                 :          65 : TxOrphanage::Usage TxOrphanageImpl::ReservedPeerUsage() const { return m_reserved_usage_per_peer; }
     768         [ +  + ]:          75 : TxOrphanage::Count TxOrphanageImpl::MaxPeerLatencyScore() const { return m_max_global_latency_score / std::max<unsigned int>(m_peer_orphanage_info.size(), 1); }
     769         [ +  + ]:         481 : TxOrphanage::Usage TxOrphanageImpl::MaxGlobalUsage() const { return m_reserved_usage_per_peer * std::max<int64_t>(m_peer_orphanage_info.size(), 1); }
     770                 :             : 
     771                 :         587 : bool TxOrphanageImpl::NeedsTrim() const
     772                 :             : {
     773   [ +  +  +  + ]:         587 :     return TotalLatencyScore() > MaxGlobalLatencyScore() || TotalOrphanUsage() > MaxGlobalUsage();
     774                 :             : }
     775                 :         257 : std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept
     776                 :             : {
     777         [ -  + ]:         257 :     return std::make_unique<TxOrphanageImpl>();
     778                 :             : }
     779                 :           6 : std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept
     780                 :             : {
     781         [ -  + ]:           6 :     return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
     782                 :             : }
     783                 :             : } // namespace node
        

Generated by: LCOV version 2.0-1