/Users/eugenesiegel/btc/bitcoin/src/node/txorphanage.cpp
| Line | Count | Source (jump to first uncovered line) | 
| 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 |  |     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 |  |         Announcement(const CTransactionRef& tx, NodeId peer, SequenceNumber seq) : | 
| 49 | 0 |             m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq} | 
| 50 | 0 |         { } | 
| 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 | 0 |         TxOrphanage::Usage GetMemUsage()  const { | 
| 58 | 0 |             return GetTransactionWeight(*m_tx); | 
| 59 | 0 |         } | 
| 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 | 0 |         TxOrphanage::Count GetLatencyScore() const { | 
| 67 | 0 |             return 1 + (m_tx->vin.size() / 10); | 
| 68 | 0 |         } | 
| 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 |  |         result_type operator()(const Announcement& ann) const | 
| 78 | 0 |         { | 
| 79 | 0 |             return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer}; | 
| 80 | 0 |         } | 
| 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 |  |         result_type operator()(const Announcement& ann) const | 
| 89 | 0 |         { | 
| 90 | 0 |             return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence}; | 
| 91 | 0 |         } | 
| 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 |  |     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 |  |         bool operator==(const PeerDoSInfo& other) const | 
| 130 | 0 |         { | 
| 131 | 0 |             return m_total_usage == other.m_total_usage && | 
| 132 | 0 |                    m_count_announcements == other.m_count_announcements && | 
| 133 | 0 |                    m_total_latency_score == other.m_total_latency_score; | 
| 134 | 0 |         } | 
| 135 |  |         void Add(const Announcement& ann) | 
| 136 | 0 |         { | 
| 137 | 0 |             m_total_usage += ann.GetMemUsage(); | 
| 138 | 0 |             m_total_latency_score += ann.GetLatencyScore(); | 
| 139 | 0 |             m_count_announcements += 1; | 
| 140 | 0 |         } | 
| 141 |  |         bool Subtract(const Announcement& ann) | 
| 142 | 0 |         { | 
| 143 | 0 |             Assume(m_total_usage >= ann.GetMemUsage()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 144 | 0 |             Assume(m_total_latency_score >= ann.GetLatencyScore()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 145 | 0 |             Assume(m_count_announcements >= 1); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 146 |  | 
 | 
| 147 | 0 |             m_total_usage -= ann.GetMemUsage(); | 
| 148 | 0 |             m_total_latency_score -= ann.GetLatencyScore(); | 
| 149 | 0 |             m_count_announcements -= 1; | 
| 150 | 0 |             return m_count_announcements == 0; | 
| 151 | 0 |         } | 
| 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 |  |         FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const | 
| 162 | 0 |         { | 
| 163 | 0 |             assert(max_peer_latency_score > 0); | 
| 164 | 0 |             assert(max_peer_memory > 0); | 
| 165 | 0 |             const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score); | 
| 166 | 0 |             const FeeFrac mem_score(m_total_usage, max_peer_memory); | 
| 167 | 0 |             return std::max<FeeFrac>(latency_score, mem_score); | 
| 168 | 0 |         } | 
| 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 | 51.2k |     TxOrphanageImpl() = default; | 
| 192 |  |     TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) : | 
| 193 | 0 |         m_max_global_latency_score{max_global_latency_score}, | 
| 194 | 0 |         m_reserved_usage_per_peer{reserved_peer_usage} | 
| 195 | 0 |     {} | 
| 196 | 51.2k |     ~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 |  | void TxOrphanageImpl::Erase(Iter<Tag> it) | 
| 239 | 0 | { | 
| 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 | 0 |     auto peer_it = m_peer_orphanage_info.find(it->m_announcer); | 
| 245 | 0 |     Assume(peer_it != m_peer_orphanage_info.end()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 |     Assume(peer_it != m_peer_orphanage_info.end()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 246 | 0 |     if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it); | 
| 247 |  | 
 | 
| 248 | 0 |     if (IsUnique(m_orphans.project<ByWtxid>(it))) { | 
| 249 | 0 |         m_unique_orphans -= 1; | 
| 250 | 0 |         m_unique_rounded_input_scores -= it->GetLatencyScore() - 1; | 
| 251 | 0 |         m_unique_orphan_usage -= it->GetMemUsage(); | 
| 252 |  |  | 
| 253 |  |         // Remove references in m_outpoint_to_orphan_wtxids | 
| 254 | 0 |         const auto& wtxid{it->m_tx->GetWitnessHash()}; | 
| 255 | 0 |         for (const auto& input : it->m_tx->vin) { | 
| 256 | 0 |             auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 
| 257 | 0 |             if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 
| 258 | 0 |                 it_prev->second.erase(wtxid); | 
| 259 |  |                 // Clean up keys if they point to an empty set. | 
| 260 | 0 |                 if (it_prev->second.empty()) { | 
| 261 | 0 |                     m_outpoint_to_orphan_wtxids.erase(it_prev); | 
| 262 | 0 |                 } | 
| 263 | 0 |             } | 
| 264 | 0 |         } | 
| 265 | 0 |     } | 
| 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 | 0 |     if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 
| 270 |  | 
 | 
| 271 | 0 |     m_orphans.get<Tag>().erase(it); | 
| 272 | 0 | } Unexecuted instantiation: _ZN4node15TxOrphanageImpl5EraseINS0_7ByWtxidEEEvN5boost11multi_index21multi_index_containerINS0_12AnnouncementENS0_13OrphanIndicesENSt3__19allocatorIS6_EEE5indexIT_E4type8iteratorEUnexecuted instantiation: _ZN4node15TxOrphanageImpl5EraseINS0_6ByPeerEEEvN5boost11multi_index21multi_index_containerINS0_12AnnouncementENS0_13OrphanIndicesENSt3__19allocatorIS6_EEE5indexIT_E4type8iteratorE | 
| 273 |  |  | 
| 274 |  | bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const | 
| 275 | 0 | { | 
| 276 |  |     // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid. | 
| 277 | 0 |     auto& index = m_orphans.get<ByWtxid>(); | 
| 278 | 0 |     if (it == index.end()) return false; | 
| 279 | 0 |     if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; | 
| 280 | 0 |     if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false; | 
| 281 | 0 |     return true; | 
| 282 | 0 | } | 
| 283 |  |  | 
| 284 |  | TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const | 
| 285 | 192k | { | 
| 286 | 192k |     auto it = m_peer_orphanage_info.find(peer); | 
| 287 | 192k |     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage0; | 
| 288 | 192k | } | 
| 289 |  |  | 
| 290 | 0 | TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); } | 
| 291 |  |  | 
| 292 | 770k | TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; } | 
| 293 |  |  | 
| 294 | 51.2k | TxOrphanage::Count TxOrphanageImpl::CountUniqueOrphans() const { return m_unique_orphans; } | 
| 295 |  |  | 
| 296 | 0 | TxOrphanage::Count TxOrphanageImpl::AnnouncementsFromPeer(NodeId peer) const { | 
| 297 | 0 |     auto it = m_peer_orphanage_info.find(peer); | 
| 298 | 0 |     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements; | 
| 299 | 0 | } | 
| 300 |  |  | 
| 301 | 0 | TxOrphanage::Count TxOrphanageImpl::LatencyScoreFromPeer(NodeId peer) const { | 
| 302 | 0 |     auto it = m_peer_orphanage_info.find(peer); | 
| 303 | 0 |     return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score; | 
| 304 | 0 | } | 
| 305 |  |  | 
| 306 |  | bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer) | 
| 307 | 0 | { | 
| 308 | 0 |     const auto& wtxid{tx->GetWitnessHash()}; | 
| 309 | 0 |     const auto& txid{tx->GetHash()}; | 
| 310 |  |  | 
| 311 |  |     // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack. | 
| 312 | 0 |     TxOrphanage::Usage sz = GetTransactionWeight(*tx); | 
| 313 | 0 |     if (sz > MAX_STANDARD_TX_WEIGHT) { | 
| 314 | 0 |         LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString()); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 315 | 0 |         return false; | 
| 316 | 0 |     } | 
| 317 |  |  | 
| 318 |  |     // We will return false if the tx already exists under a different peer. | 
| 319 | 0 |     const bool brand_new{!HaveTx(wtxid)}; | 
| 320 |  | 
 | 
| 321 | 0 |     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 | 0 |     if (!inserted) return false; | 
| 324 |  |  | 
| 325 | 0 |     ++m_current_sequence; | 
| 326 | 0 |     auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second; | 
| 327 | 0 |     peer_info.Add(*iter); | 
| 328 |  |  | 
| 329 |  |     // Add links in m_outpoint_to_orphan_wtxids | 
| 330 | 0 |     if (brand_new) { | 
| 331 | 0 |         for (const auto& input : tx->vin) { | 
| 332 | 0 |             auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second; | 
| 333 | 0 |             wtxids_for_prevout.emplace(wtxid); | 
| 334 | 0 |         } | 
| 335 |  | 
 | 
| 336 | 0 |         m_unique_orphans += 1; | 
| 337 | 0 |         m_unique_orphan_usage += iter->GetMemUsage(); | 
| 338 | 0 |         m_unique_rounded_input_scores += iter->GetLatencyScore() - 1; | 
| 339 |  | 
 | 
| 340 | 0 |         LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n", | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 341 | 0 |                     txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size()); | 
| 342 | 0 |         Assume(IsUnique(iter)); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 343 | 0 |     } else { | 
| 344 | 0 |         LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n", | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 345 | 0 |                     peer, txid.ToString(), wtxid.ToString()); | 
| 346 | 0 |         Assume(!IsUnique(iter)); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 347 | 0 |     } | 
| 348 |  |  | 
| 349 |  |     // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) | 
| 350 | 0 |     LimitOrphans(); | 
| 351 | 0 |     return brand_new; | 
| 352 | 0 | } | 
| 353 |  |  | 
| 354 |  | bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer) | 
| 355 | 0 | { | 
| 356 | 0 |     auto& index_by_wtxid = m_orphans.get<ByWtxid>(); | 
| 357 | 0 |     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 | 0 |     if (it == index_by_wtxid.end()) return false; | 
| 362 | 0 |     if (it->m_tx->GetWitnessHash() != wtxid) return false; | 
| 363 |  |  | 
| 364 |  |     // Add another announcement, copying the CTransactionRef from one that already exists. | 
| 365 | 0 |     const auto& ptx = it->m_tx; | 
| 366 | 0 |     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 | 0 |     if (!inserted) return false; | 
| 369 |  |  | 
| 370 | 0 |     ++m_current_sequence; | 
| 371 | 0 |     auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second; | 
| 372 | 0 |     peer_info.Add(*iter); | 
| 373 |  | 
 | 
| 374 | 0 |     const auto& txid = ptx->GetHash(); | 
| 375 | 0 |     LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n", | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 376 | 0 |                 peer, txid.ToString(), wtxid.ToString()); | 
| 377 |  | 
 | 
| 378 | 0 |     Assume(!IsUnique(iter)); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 379 |  |  | 
| 380 |  |     // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789) | 
| 381 | 0 |     LimitOrphans(); | 
| 382 | 0 |     return true; | 
| 383 | 0 | } | 
| 384 |  |  | 
| 385 |  | bool TxOrphanageImpl::EraseTxInternal(const Wtxid& wtxid) | 
| 386 | 719k | { | 
| 387 | 719k |     auto& index_by_wtxid = m_orphans.get<ByWtxid>(); | 
| 388 |  |  | 
| 389 | 719k |     auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); | 
| 390 | 719k |     if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid0) return false; | 
| 391 |  |  | 
| 392 | 0 |     auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); | 
| 393 | 0 |     unsigned int num_ann{0}; | 
| 394 | 0 |     const auto txid = it->m_tx->GetHash(); | 
| 395 | 0 |     while (it != it_end) { | 
| 396 | 0 |         Assume(it->m_tx->GetWitnessHash() == wtxid); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 397 | 0 |         Erase<ByWtxid>(it++); | 
| 398 | 0 |         num_ann += 1; | 
| 399 | 0 |     } | 
| 400 | 0 |     LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 401 |  | 
 | 
| 402 | 0 |     return true; | 
| 403 | 719k | } | 
| 404 |  |  | 
| 405 |  | bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid) | 
| 406 | 719k | { | 
| 407 | 719k |     const auto ret = EraseTxInternal(wtxid); | 
| 408 |  |  | 
| 409 |  |     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. | 
| 410 | 719k |     LimitOrphans(); | 
| 411 |  |  | 
| 412 | 719k |     return ret; | 
| 413 | 719k | } | 
| 414 |  |  | 
| 415 |  | /** Erase all entries by this peer. */ | 
| 416 |  | void TxOrphanageImpl::EraseForPeer(NodeId peer) | 
| 417 | 192k | { | 
| 418 | 192k |     auto& index_by_peer = m_orphans.get<ByPeer>(); | 
| 419 | 192k |     auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); | 
| 420 | 192k |     if (it == index_by_peer.end() || it->m_announcer != peer0) return; | 
| 421 |  |  | 
| 422 | 0 |     unsigned int num_ann{0}; | 
| 423 | 0 |     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 | 0 |         Erase<ByPeer>(it++); | 
| 426 | 0 |         num_ann += 1; | 
| 427 | 0 |     } | 
| 428 | 0 |     Assume(!m_peer_orphanage_info.contains(peer)); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 429 |  | 
 | 
| 430 | 0 |     if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 431 |  |  | 
| 432 |  |     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. | 
| 433 | 0 |     LimitOrphans(); | 
| 434 | 0 | } | 
| 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 |  | void TxOrphanageImpl::LimitOrphans() | 
| 443 | 719k | { | 
| 444 | 719k |     if (!NeedsTrim()) return; | 
| 445 |  |  | 
| 446 | 0 |     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 | 0 |     const auto max_lat{MaxPeerLatencyScore()}; | 
| 451 | 0 |     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 | 0 |     std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos; | 
| 456 | 0 |     heap_peer_dos.reserve(m_peer_orphanage_info.size()); | 
| 457 | 0 |     for (const auto& [nodeid, entry] : m_peer_orphanage_info) { | 
| 458 |  |         // Performance optimization: only consider peers with a DoS score > 1. | 
| 459 | 0 |         const auto dos_score = entry.GetDosScore(max_lat, max_mem); | 
| 460 | 0 |         if (dos_score >> FeeFrac{1, 1}) { | 
| 461 | 0 |             heap_peer_dos.emplace_back(nodeid, dos_score); | 
| 462 | 0 |         } | 
| 463 | 0 |     } | 
| 464 | 0 |     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 | 0 |     }; | 
| 469 | 0 |     std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score); | 
| 470 |  | 
 | 
| 471 | 0 |     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 | 0 |     do { | 
| 479 | 0 |         Assume(!heap_peer_dos.empty()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 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 | 0 |         std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score); | 
| 483 | 0 |         const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back()); | 
| 484 | 0 |         heap_peer_dos.pop_back(); | 
| 485 |  |  | 
| 486 |  |         // If needs trim, then at least one peer has a DoS score higher than 1. | 
| 487 | 0 |         Assume(dos_score >> (FeeFrac{1, 1}));| Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 488 |  | 
 | 
| 489 | 0 |         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 | 0 |         const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second; | 
| 496 | 0 |         auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0}); | 
| 497 | 0 |         unsigned int num_erased_this_round{0}; | 
| 498 | 0 |         unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements}; | 
| 499 | 0 |         while (NeedsTrim()) { | 
| 500 | 0 |             if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 501 | 0 |             if (!Assume(it_ann->m_announcer == worst_peer)) break; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 502 |  |  | 
| 503 | 0 |             Erase<ByPeer>(it_ann++); | 
| 504 | 0 |             num_erased += 1; | 
| 505 | 0 |             num_erased_this_round += 1; | 
| 506 |  |  | 
| 507 |  |             // If we erased the last orphan from this peer, it_worst_peer will be invalidated. | 
| 508 | 0 |             it_worst_peer = m_peer_orphanage_info.find(worst_peer); | 
| 509 | 0 |             if (it_worst_peer == m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold) break; | 
| 510 | 0 |         } | 
| 511 | 0 |         LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 512 |  | 
 | 
| 513 | 0 |         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 | 0 |         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 | 0 |         } | 
| 521 | 0 |     } while (true); | 
| 522 |  |  | 
| 523 | 0 |     const auto remaining_unique_orphans{CountUniqueOrphans()}; | 
| 524 | 0 |     LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 525 | 0 | } | 
| 526 |  |  | 
| 527 |  | std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) | 
| 528 | 491k | { | 
| 529 | 491k |     std::vector<std::pair<Wtxid, NodeId>> ret; | 
| 530 | 491k |     auto& index_by_wtxid = m_orphans.get<ByWtxid>(); | 
| 531 | 982k |     for (unsigned int i = 0; i < tx.vout.size(); i++491k) { | 
| 532 | 491k |         const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i)); | 
| 533 | 491k |         if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) { | 
| 534 | 0 |             for (const auto& wtxid : it_by_prev->second) { | 
| 535 |  |                 // If a reconsiderable announcement for this wtxid already exists, skip it. | 
| 536 | 0 |                 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 | 0 |                 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER}); | 
| 540 | 0 |                 if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 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 | 0 |                 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER}); | 
| 546 | 0 |                 const auto num_announcers{std::distance(it, it_end)}; | 
| 547 | 0 |                 if (!Assume(num_announcers > 0)) continue; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 548 | 0 |                 std::advance(it, rng.randrange(num_announcers)); | 
| 549 |  | 
 | 
| 550 | 0 |                 if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 551 |  |  | 
| 552 |  |                 // Mark this orphan as ready to be reconsidered. | 
| 553 | 0 |                 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; }; | 
| 554 | 0 |                 Assume(!it->m_reconsider); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 555 | 0 |                 index_by_wtxid.modify(it, mark_reconsidered_modifier); | 
| 556 | 0 |                 ret.emplace_back(wtxid, it->m_announcer); | 
| 557 | 0 |                 m_reconsiderable_wtxids.insert(wtxid); | 
| 558 |  | 
 | 
| 559 | 0 |                 LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n", | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 560 | 0 |                             it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer); | 
| 561 | 0 |             } | 
| 562 | 0 |         } | 
| 563 | 491k |     } | 
| 564 | 491k |     return ret; | 
| 565 | 491k | } | 
| 566 |  |  | 
| 567 |  | bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const | 
| 568 | 3.53M | { | 
| 569 | 3.53M |     auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); | 
| 570 | 3.53M |     return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid0; | 
| 571 | 3.53M | } | 
| 572 |  |  | 
| 573 |  | CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const | 
| 574 | 0 | { | 
| 575 | 0 |     auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER}); | 
| 576 | 0 |     if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx; | 
| 577 | 0 |     return nullptr; | 
| 578 | 0 | } | 
| 579 |  |  | 
| 580 |  | bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const | 
| 581 | 0 | { | 
| 582 | 0 |     return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0; | 
| 583 | 0 | } | 
| 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 |  | CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer) | 
| 588 | 6.84M | { | 
| 589 | 6.84M |     auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); | 
| 590 | 6.84M |     if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer0&& it->m_reconsider0) { | 
| 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 | 0 |         static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; }; | 
| 594 | 0 |         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 | 0 |         m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash()); | 
| 598 | 0 |         return it->m_tx; | 
| 599 | 0 |     } | 
| 600 | 6.84M |     return nullptr; | 
| 601 | 6.84M | } | 
| 602 |  |  | 
| 603 |  | /** Return whether there is a tx that can be reconsidered. */ | 
| 604 |  | bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer) | 
| 605 | 6.33M | { | 
| 606 | 6.33M |     auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0}); | 
| 607 | 6.33M |     return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer0&& it->m_reconsider0; | 
| 608 | 6.33M | } | 
| 609 |  |  | 
| 610 |  | void TxOrphanageImpl::EraseForBlock(const CBlock& block) | 
| 611 | 21.7k | { | 
| 612 | 21.7k |     if (m_orphans.empty()) return; | 
| 613 |  |  | 
| 614 | 0 |     std::set<Wtxid> wtxids_to_erase; | 
| 615 | 0 |     for (const CTransactionRef& ptx : block.vtx) { | 
| 616 | 0 |         const CTransaction& block_tx = *ptx; | 
| 617 |  |  | 
| 618 |  |         // Which orphan pool entries must we evict? | 
| 619 | 0 |         for (const auto& input : block_tx.vin) { | 
| 620 | 0 |             auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout); | 
| 621 | 0 |             if (it_prev != m_outpoint_to_orphan_wtxids.end()) { | 
| 622 |  |                 // Copy all wtxids to wtxids_to_erase. | 
| 623 | 0 |                 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end())); | 
| 624 | 0 |             } | 
| 625 | 0 |         } | 
| 626 | 0 |     } | 
| 627 |  | 
 | 
| 628 | 0 |     unsigned int num_erased{0}; | 
| 629 | 0 |     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 | 0 |         num_erased += EraseTxInternal(wtxid) ? 1 : 0; | 
| 634 | 0 |     } | 
| 635 |  | 
 | 
| 636 | 0 |     if (num_erased != 0) { | 
| 637 | 0 |         LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased); | Line | Count | Source |  | 381 | 0 | #define LogDebug(category, ...) LogPrintLevel(category, BCLog::Level::Debug, __VA_ARGS__) | Line | Count | Source |  | 373 | 0 |     do {                                                              \ |  | 374 | 0 |         if (LogAcceptCategory((category), (level))) {                 \ |  | 375 | 0 |             bool rate_limit{level >= BCLog::Level::Info};             \ |  | 376 | 0 |             LogPrintLevel_(category, level, rate_limit, __VA_ARGS__); \ | Line | Count | Source |  | 350 | 0 | #define LogPrintLevel_(category, level, should_ratelimit, ...) LogPrintFormatInternal(std::source_location::current(), category, level, should_ratelimit, __VA_ARGS__) | 
 |  | 377 | 0 |         }                                                             \ |  | 378 | 0 |     } while (0) | 
 | 
 | 
| 638 | 0 |     } | 
| 639 | 0 |     Assume(wtxids_to_erase.size() == num_erased); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 640 |  |  | 
| 641 |  |     // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here. | 
| 642 | 0 |     LimitOrphans(); | 
| 643 | 0 | } | 
| 644 |  |  | 
| 645 |  | std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const | 
| 646 | 747k | { | 
| 647 | 747k |     std::vector<CTransactionRef> children_found; | 
| 648 | 747k |     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 | 747k |     auto& index_by_peer = m_orphans.get<ByPeer>(); | 
| 655 | 747k |     auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()}); | 
| 656 | 747k |     auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0}); | 
| 657 |  |  | 
| 658 | 747k |     while (it_upper != it_lower) { | 
| 659 | 0 |         --it_upper; | 
| 660 | 0 |         if (!Assume(it_upper->m_announcer == peer)) break; | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 661 |  |         // Check if this tx spends from parent. | 
| 662 | 0 |         for (const auto& input : it_upper->m_tx->vin) { | 
| 663 | 0 |             if (input.prevout.hash == parent_txid) { | 
| 664 | 0 |                 children_found.emplace_back(it_upper->m_tx); | 
| 665 | 0 |                 break; | 
| 666 | 0 |             } | 
| 667 | 0 |         } | 
| 668 | 0 |     } | 
| 669 | 747k |     return children_found; | 
| 670 | 747k | } | 
| 671 |  |  | 
| 672 |  | std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const | 
| 673 | 0 | { | 
| 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 | 0 |         } | 
| 687 |  | 
 | 
| 688 | 0 |         ++it; | 
| 689 | 0 |     } | 
| 690 | 0 |     Assume(m_unique_orphans == result.size()); | Line | Count | Source |  | 118 | 0 | #define Assume(val) inline_assertion_check<false>(val, __FILE__, __LINE__, __func__, #val) | 
 | 
| 691 |  | 
 | 
| 692 | 0 |     return result; | 
| 693 | 0 | } | 
| 694 |  |  | 
| 695 |  | void TxOrphanageImpl::SanityCheck() const | 
| 696 | 0 | { | 
| 697 | 0 |     std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info; | 
| 698 | 0 |     std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores; | 
| 699 | 0 |     std::set<COutPoint> all_outpoints; | 
| 700 | 0 |     std::set<Wtxid> reconstructed_reconsiderable_wtxids; | 
| 701 |  | 
 | 
| 702 | 0 |     for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) { | 
| 703 | 0 |         for (const auto& input : it->m_tx->vin) { | 
| 704 | 0 |             all_outpoints.insert(input.prevout); | 
| 705 | 0 |         } | 
| 706 | 0 |         unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1)); | 
| 707 |  | 
 | 
| 708 | 0 |         auto& peer_info = reconstructed_peer_info[it->m_announcer]; | 
| 709 | 0 |         peer_info.m_total_usage += it->GetMemUsage(); | 
| 710 | 0 |         peer_info.m_count_announcements += 1; | 
| 711 | 0 |         peer_info.m_total_latency_score += it->GetLatencyScore(); | 
| 712 |  | 
 | 
| 713 | 0 |         if (it->m_reconsider) { | 
| 714 | 0 |             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 | 0 |             assert(added); | 
| 717 | 0 |         } | 
| 718 | 0 |     } | 
| 719 | 0 |     assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size()); | 
| 720 |  |  | 
| 721 |  |     // Recalculated per-peer stats are identical to m_peer_orphanage_info | 
| 722 | 0 |     assert(reconstructed_peer_info == m_peer_orphanage_info); | 
| 723 |  |  | 
| 724 |  |     // Recalculated set of reconsiderable wtxids must match. | 
| 725 | 0 |     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 | 0 |     assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size()); | 
| 731 | 0 |     for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) { | 
| 732 | 0 |         assert(all_outpoints.contains(outpoint)); | 
| 733 | 0 |         for (const auto& wtxid : wtxid_set) { | 
| 734 | 0 |             assert(unique_wtxids_to_scores.contains(wtxid)); | 
| 735 | 0 |         } | 
| 736 | 0 |     } | 
| 737 |  |  | 
| 738 |  |     // Cached m_unique_orphans value is correct. | 
| 739 | 0 |     assert(m_orphans.size() >= m_unique_orphans); | 
| 740 | 0 |     assert(m_orphans.size() <= m_peer_orphanage_info.size() * m_unique_orphans); | 
| 741 | 0 |     assert(unique_wtxids_to_scores.size() == m_unique_orphans); | 
| 742 |  |  | 
| 743 | 0 |     const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), | 
| 744 | 0 |         TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; }); | 
| 745 | 0 |     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 | 0 |     const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), | 
| 749 | 0 |         TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; }); | 
| 750 | 0 |     assert(summed_peer_usage >= m_unique_orphan_usage); | 
| 751 |  |  | 
| 752 |  |     // Cached m_unique_rounded_input_scores value is correct. | 
| 753 | 0 |     const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(), | 
| 754 | 0 |         TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; }); | 
| 755 | 0 |     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 | 0 |     const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(), | 
| 759 | 0 |         TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; }); | 
| 760 | 0 |     assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size()); | 
| 761 |  |  | 
| 762 | 0 |     assert(!NeedsTrim()); | 
| 763 | 0 | } | 
| 764 |  |  | 
| 765 | 719k | TxOrphanage::Count TxOrphanageImpl::MaxGlobalLatencyScore() const { return m_max_global_latency_score; } | 
| 766 | 719k | TxOrphanage::Count TxOrphanageImpl::TotalLatencyScore() const { return m_unique_rounded_input_scores + m_orphans.size(); } | 
| 767 | 0 | TxOrphanage::Usage TxOrphanageImpl::ReservedPeerUsage() const { return m_reserved_usage_per_peer; } | 
| 768 | 0 | TxOrphanage::Count TxOrphanageImpl::MaxPeerLatencyScore() const { return m_max_global_latency_score / std::max<unsigned int>(m_peer_orphanage_info.size(), 1); } | 
| 769 | 719k | TxOrphanage::Usage TxOrphanageImpl::MaxGlobalUsage() const { return m_reserved_usage_per_peer * std::max<int64_t>(m_peer_orphanage_info.size(), 1); } | 
| 770 |  |  | 
| 771 |  | bool TxOrphanageImpl::NeedsTrim() const | 
| 772 | 719k | { | 
| 773 | 719k |     return TotalLatencyScore() > MaxGlobalLatencyScore() || TotalOrphanUsage() > MaxGlobalUsage(); | 
| 774 | 719k | } | 
| 775 |  | std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept | 
| 776 | 51.2k | { | 
| 777 | 51.2k |     return std::make_unique<TxOrphanageImpl>(); | 
| 778 | 51.2k | } | 
| 779 |  | std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept | 
| 780 | 0 | { | 
| 781 | 0 |     return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage); | 
| 782 | 0 | } | 
| 783 |  | } // namespace node |