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