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