Bitcoin Core 29.99.0
P2P Digital Currency
txorphanage.cpp
Go to the documentation of this file.
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
8#include <logging.h>
9#include <policy/policy.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
24namespace node {
26static constexpr NodeId MIN_PEER{std::numeric_limits<NodeId>::min()};
28static constexpr NodeId MAX_PEER{std::numeric_limits<NodeId>::max()};
29class TxOrphanageImpl final : public TxOrphanage {
30 // Type alias for sequence numbers
31 using SequenceNumber = uint64_t;
34
38 {
46 bool m_reconsider{false};
47
49 m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq}
50 { }
51
59 }
60
67 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>;
75 {
78 {
79 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>;
89 {
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;
103
106
109
112
117
120 std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids;
121
123 std::set<Wtxid> m_reconsiderable_wtxids;
124
125 struct PeerDoSInfo {
129 bool operator==(const PeerDoSInfo& other) const
130 {
131 return m_total_usage == other.m_total_usage &&
134 }
135 void Add(const Announcement& ann)
136 {
137 m_total_usage += ann.GetMemUsage();
140 }
141 bool Subtract(const Announcement& ann)
142 {
146
147 m_total_usage -= ann.GetMemUsage();
150 return m_count_announcements == 0;
151 }
161 FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const
162 {
163 assert(max_peer_latency_score > 0);
164 assert(max_peer_memory > 0);
165 const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score);
166 const FeeFrac mem_score(m_total_usage, max_peer_memory);
167 return std::max<FeeFrac>(latency_score, mem_score);
168 }
169 };
172 std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info;
173
175 template<typename Tag>
176 void Erase(Iter<Tag> it);
177
179 bool EraseTxInternal(const Wtxid& wtxid);
180
182 bool IsUnique(Iter<ByWtxid> it) const;
183
185 bool NeedsTrim() const;
186
188 void LimitOrphans();
189
190public:
191 TxOrphanageImpl() = default;
192 TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :
193 m_max_global_latency_score{max_global_latency_score},
194 m_reserved_usage_per_peer{reserved_peer_usage}
195 {}
196 ~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
212 TxOrphanage::Count MaxPeerLatencyScore() const override;
213
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;
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
237template<typename Tag>
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 auto peer_it = m_peer_orphanage_info.find(it->m_announcer);
245 Assume(peer_it != m_peer_orphanage_info.end());
246 if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it);
247
248 if (IsUnique(m_orphans.project<ByWtxid>(it))) {
249 m_unique_orphans -= 1;
250 m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;
251 m_unique_orphan_usage -= it->GetMemUsage();
252
253 // Remove references in m_outpoint_to_orphan_wtxids
254 const auto& wtxid{it->m_tx->GetWitnessHash()};
255 for (const auto& input : it->m_tx->vin) {
256 auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
257 if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
258 it_prev->second.erase(wtxid);
259 // Clean up keys if they point to an empty set.
260 if (it_prev->second.empty()) {
261 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 if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
270
271 m_orphans.get<Tag>().erase(it);
272}
273
275{
276 // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid.
277 auto& index = m_orphans.get<ByWtxid>();
278 if (it == index.end()) return false;
279 if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
280 if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
281 return true;
282}
283
285{
286 auto it = m_peer_orphanage_info.find(peer);
287 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage;
288}
289
291
293
295
297 auto it = m_peer_orphanage_info.find(peer);
298 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements;
299}
300
302 auto it = m_peer_orphanage_info.find(peer);
303 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score;
304}
305
307{
308 const auto& wtxid{tx->GetWitnessHash()};
309 const auto& txid{tx->GetHash()};
310
311 // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack.
313 if (sz > MAX_STANDARD_TX_WEIGHT) {
314 LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
315 return false;
316 }
317
318 // We will return false if the tx already exists under a different peer.
319 const bool brand_new{!HaveTx(wtxid)};
320
321 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 if (!inserted) return false;
324
326 auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
327 peer_info.Add(*iter);
328
329 // Add links in m_outpoint_to_orphan_wtxids
330 if (brand_new) {
331 for (const auto& input : tx->vin) {
332 auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;
333 wtxids_for_prevout.emplace(wtxid);
334 }
335
336 m_unique_orphans += 1;
337 m_unique_orphan_usage += iter->GetMemUsage();
338 m_unique_rounded_input_scores += iter->GetLatencyScore() - 1;
339
340 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 Assume(IsUnique(iter));
343 } else {
344 LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
345 peer, txid.ToString(), wtxid.ToString());
346 Assume(!IsUnique(iter));
347 }
348
349 // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
350 LimitOrphans();
351 return brand_new;
352}
353
355{
356 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
357 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 if (it == index_by_wtxid.end()) return false;
362 if (it->m_tx->GetWitnessHash() != wtxid) return false;
363
364 // Add another announcement, copying the CTransactionRef from one that already exists.
365 const auto& ptx = it->m_tx;
366 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 if (!inserted) return false;
369
371 auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
372 peer_info.Add(*iter);
373
374 const auto& txid = ptx->GetHash();
375 LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
376 peer, txid.ToString(), wtxid.ToString());
377
378 Assume(!IsUnique(iter));
379
380 // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
381 LimitOrphans();
382 return true;
383}
384
386{
387 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
388
389 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
390 if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false;
391
392 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
393 unsigned int num_ann{0};
394 const auto txid = it->m_tx->GetHash();
395 while (it != it_end) {
396 Assume(it->m_tx->GetWitnessHash() == wtxid);
397 Erase<ByWtxid>(it++);
398 num_ann += 1;
399 }
400 LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann);
401
402 return true;
403}
404
406{
407 const auto ret = EraseTxInternal(wtxid);
408
409 // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
410 LimitOrphans();
411
412 return ret;
413}
414
417{
418 auto& index_by_peer = m_orphans.get<ByPeer>();
419 auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
420 if (it == index_by_peer.end() || it->m_announcer != peer) return;
421
422 unsigned int num_ann{0};
423 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 Erase<ByPeer>(it++);
426 num_ann += 1;
427 }
428 Assume(!m_peer_orphanage_info.contains(peer));
429
430 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 LimitOrphans();
434}
435
443{
444 if (!NeedsTrim()) return;
445
446 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 const auto max_lat{MaxPeerLatencyScore()};
451 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 std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
456 heap_peer_dos.reserve(m_peer_orphanage_info.size());
457 for (const auto& [nodeid, entry] : m_peer_orphanage_info) {
458 // Performance optimization: only consider peers with a DoS score > 1.
459 const auto dos_score = entry.GetDosScore(max_lat, max_mem);
460 if (dos_score >> FeeFrac{1, 1}) {
461 heap_peer_dos.emplace_back(nodeid, dos_score);
462 }
463 }
464 static constexpr auto compare_score = [](const auto& left, const auto& right) {
465 if (left.second != right.second) return left.second < right.second;
466 // Tiebreak by considering the more recent peer (higher NodeId) to be worse.
467 return left.first < right.first;
468 };
469 std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
470
471 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 do {
479 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 std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
483 const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());
484 heap_peer_dos.pop_back();
485
486 // If needs trim, then at least one peer has a DoS score higher than 1.
487 Assume(dos_score >> (FeeFrac{1, 1}));
488
489 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 const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second;
496 auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0});
497 unsigned int num_erased_this_round{0};
498 unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
499 while (NeedsTrim()) {
500 if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break;
501 if (!Assume(it_ann->m_announcer == worst_peer)) break;
502
503 Erase<ByPeer>(it_ann++);
504 num_erased += 1;
505 num_erased_this_round += 1;
506
507 // If we erased the last orphan from this peer, it_worst_peer will be invalidated.
508 it_worst_peer = m_peer_orphanage_info.find(worst_peer);
509 if (it_worst_peer == m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold) break;
510 }
511 LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
512
513 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 if (it_worst_peer != m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) {
518 heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem));
519 std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
520 }
521 } while (true);
522
523 const auto remaining_unique_orphans{CountUniqueOrphans()};
524 LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
525}
526
527std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
528{
529 std::vector<std::pair<Wtxid, NodeId>> ret;
530 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
531 for (unsigned int i = 0; i < tx.vout.size(); i++) {
532 const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i));
533 if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) {
534 for (const auto& wtxid : it_by_prev->second) {
535 // If a reconsiderable announcement for this wtxid already exists, skip it.
536 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 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
540 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 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
546 const auto num_announcers{std::distance(it, it_end)};
547 if (!Assume(num_announcers > 0)) continue;
548 std::advance(it, rng.randrange(num_announcers));
549
550 if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break;
551
552 // Mark this orphan as ready to be reconsidered.
553 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; };
554 Assume(!it->m_reconsider);
555 index_by_wtxid.modify(it, mark_reconsidered_modifier);
556 ret.emplace_back(wtxid, it->m_announcer);
557 m_reconsiderable_wtxids.insert(wtxid);
558
559 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 return ret;
565}
566
567bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const
568{
569 auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
570 return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
571}
572
574{
575 auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
576 if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx;
577 return nullptr;
578}
579
580bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
581{
582 return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0;
583}
584
588{
589 auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
590 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 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; };
594 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 m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
598 return it->m_tx;
599 }
600 return nullptr;
601}
602
605{
606 auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
607 return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
608}
609
611{
612 if (m_orphans.empty()) return;
613
614 std::set<Wtxid> wtxids_to_erase;
615 for (const CTransactionRef& ptx : block.vtx) {
616 const CTransaction& block_tx = *ptx;
617
618 // Which orphan pool entries must we evict?
619 for (const auto& input : block_tx.vin) {
620 auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
621 if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
622 // Copy all wtxids to wtxids_to_erase.
623 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
624 }
625 }
626 }
627
628 unsigned int num_erased{0};
629 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 num_erased += EraseTxInternal(wtxid) ? 1 : 0;
634 }
635
636 if (num_erased != 0) {
637 LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased);
638 }
639 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 LimitOrphans();
643}
644
645std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const
646{
647 std::vector<CTransactionRef> children_found;
648 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 auto& index_by_peer = m_orphans.get<ByPeer>();
655 auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()});
656 auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
657
658 while (it_upper != it_lower) {
659 --it_upper;
660 if (!Assume(it_upper->m_announcer == peer)) break;
661 // Check if this tx spends from parent.
662 for (const auto& input : it_upper->m_tx->vin) {
663 if (input.prevout.hash == parent_txid) {
664 children_found.emplace_back(it_upper->m_tx);
665 break;
666 }
667 }
668 }
669 return children_found;
670}
671
672std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const
673{
674 std::vector<TxOrphanage::OrphanInfo> result;
675 result.reserve(m_unique_orphans);
676
677 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
678 auto it = index_by_wtxid.begin();
679 std::set<NodeId> this_orphan_announcers;
680 while (it != index_by_wtxid.end()) {
681 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 if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) {
684 result.emplace_back(it->m_tx, std::move(this_orphan_announcers));
685 this_orphan_announcers.clear();
686 }
687
688 ++it;
689 }
690 Assume(m_unique_orphans == result.size());
691
692 return result;
693}
694
696{
697 std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
698 std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
699 std::set<COutPoint> all_outpoints;
700 std::set<Wtxid> reconstructed_reconsiderable_wtxids;
701
702 for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) {
703 for (const auto& input : it->m_tx->vin) {
704 all_outpoints.insert(input.prevout);
705 }
706 unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
707
708 auto& peer_info = reconstructed_peer_info[it->m_announcer];
709 peer_info.m_total_usage += it->GetMemUsage();
710 peer_info.m_count_announcements += 1;
711 peer_info.m_total_latency_score += it->GetLatencyScore();
712
713 if (it->m_reconsider) {
714 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 assert(added);
717 }
718 }
719 assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size());
720
721 // Recalculated per-peer stats are identical to m_peer_orphanage_info
722 assert(reconstructed_peer_info == m_peer_orphanage_info);
723
724 // Recalculated set of reconsiderable wtxids must match.
725 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 assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size());
731 for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) {
732 assert(all_outpoints.contains(outpoint));
733 for (const auto& wtxid : wtxid_set) {
734 assert(unique_wtxids_to_scores.contains(wtxid));
735 }
736 }
737
738 // Cached m_unique_orphans value is correct.
741 assert(unique_wtxids_to_scores.size() == m_unique_orphans);
742
743 const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
744 TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; });
745 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 const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
749 TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; });
750 assert(summed_peer_usage >= m_unique_orphan_usage);
751
752 // Cached m_unique_rounded_input_scores value is correct.
753 const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
754 TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; });
755 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 const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
759 TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; });
760 assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size());
761
762 assert(!NeedsTrim());
763}
764
770
772{
774}
775std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept
776{
777 return std::make_unique<TxOrphanageImpl>();
778}
779std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept
780{
781 return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
782}
783} // namespace node
int ret
#define Assume(val)
Assume is the identity function.
Definition: check.h:118
Definition: block.h:69
std::vector< CTransactionRef > vtx
Definition: block.h:72
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:29
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:296
const std::vector< CTxOut > vout
Definition: transaction.h:307
const Txid & GetHash() const LIFETIMEBOUND
Definition: transaction.h:343
const std::vector< CTxIn > vin
Definition: transaction.h:306
Fast randomness source.
Definition: random.h:386
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
A class to track orphan transactions (failed on TX_MISSING_INPUTS) Since we cannot distinguish orphan...
Definition: txorphanage.h:38
unsigned int Count
Definition: txorphanage.h:41
typename AnnouncementMap::index< Tag >::type::iterator Iter
void EraseForBlock(const CBlock &block) override
Erase all orphans included in or invalidated by a new block.
boost::multi_index::multi_index_container< Announcement, OrphanIndices > AnnouncementMap
Definition: txorphanage.cpp:99
TxOrphanage::Usage MaxGlobalUsage() const override
Maximum allowed (deduplicated) memory usage for all transactions (see Announcement::GetMemUsage()).
void Erase(Iter< Tag > it)
Erase from m_orphans and update m_peer_orphanage_info.
TxOrphanage::Count m_unique_orphans
Number of unique orphans by wtxid.
bool AddAnnouncer(const Wtxid &wtxid, NodeId peer) override
Add an additional announcer to an orphan if it exists.
bool IsUnique(Iter< ByWtxid > it) const
Check if there is exactly one announcement with the same wtxid as it.
bool HaveTxToReconsider(NodeId peer) override
Return whether there is a tx that can be reconsidered.
bool AddTx(const CTransactionRef &tx, NodeId peer) override
Add a new orphan transaction.
CTransactionRef GetTx(const Wtxid &wtxid) const override
Get a transaction by its witness txid.
std::vector< std::pair< Wtxid, NodeId > > AddChildrenToWorkSet(const CTransaction &tx, FastRandomContext &rng) override
Add any orphans that list a particular tx as a parent into the from peer's work set.
TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage)
bool HaveTxFromPeer(const Wtxid &wtxid, NodeId peer) const override
Check if a {tx, peer} exists in the orphanage.
TxOrphanage::Usage m_unique_orphan_usage
Memory used by orphans (see Announcement::GetMemUsage()), deduplicated by wtxid.
const TxOrphanage::Usage m_reserved_usage_per_peer
std::tuple< NodeId, bool, SequenceNumber > ByPeerView
Definition: txorphanage.cpp:85
SequenceNumber m_current_sequence
Global sequence number, increment each time an announcement is added.
Definition: txorphanage.cpp:33
TxOrphanage::Count m_unique_rounded_input_scores
The sum of each unique transaction's latency scores including the inputs only (see Announcement::GetL...
std::set< Wtxid > m_reconsiderable_wtxids
Set of Wtxids for which (exactly) one announcement with m_reconsider=true exists.
bool EraseTxInternal(const Wtxid &wtxid)
Erase by wtxid.
CTransactionRef GetTxToReconsider(NodeId peer) override
If there is a tx that can be reconsidered, return it and set it back to non-reconsiderable.
bool EraseTx(const Wtxid &wtxid) override
Erase an orphan by wtxid, including all announcements if there are multiple.
~TxOrphanageImpl() noexcept override=default
TxOrphanage::Count AnnouncementsFromPeer(NodeId peer) const override
Number of orphans stored from this peer.
TxOrphanage::Count MaxPeerLatencyScore() const override
Maximum allowed (deduplicated) latency score for all transactions (see Announcement::GetLatencyScore(...
void EraseForPeer(NodeId peer) override
Erase all entries by this peer.
AnnouncementMap m_orphans
std::unordered_map< NodeId, PeerDoSInfo > m_peer_orphanage_info
Store per-peer statistics.
bool HaveTx(const Wtxid &wtxid) const override
Check if we already have an orphan transaction (by wtxid only)
void LimitOrphans()
Limit the orphanage to MaxGlobalLatencyScore and MaxGlobalUsage.
TxOrphanage::Count TotalLatencyScore() const override
Get the total latency score of all orphans.
bool NeedsTrim() const
Check if the orphanage needs trimming.
std::unordered_map< COutPoint, std::set< Wtxid >, SaltedOutpointHasher > m_outpoint_to_orphan_wtxids
Index from the parents' outputs to wtxids that exist in m_orphans.
TxOrphanage::Count CountUniqueOrphans() const override
Number of unique orphans (by wtxid).
const TxOrphanage::Count m_max_global_latency_score
TxOrphanage::Count LatencyScoreFromPeer(NodeId peer) const override
Latency score of transactions announced by this peer.
TxOrphanage::Usage ReservedPeerUsage() const override
Get the reserved usage per peer.
TxOrphanage::Count CountAnnouncements() const override
Number of announcements, i.e.
TxOrphanage::Count MaxGlobalLatencyScore() const override
Get the maximum global latency score allowed.
std::vector< CTransactionRef > GetChildrenFromSamePeer(const CTransactionRef &parent, NodeId nodeid) const override
Get all children that spend from this tx and were received from nodeid.
TxOrphanage::Usage UsageByPeer(NodeId peer) const override
Total usage (weight) of orphans for which this peer is an announcer.
TxOrphanage::Usage TotalOrphanUsage() const override
Get the total usage (weight) of all orphans.
std::vector< OrphanInfo > GetOrphanTransactions() const override
Get all orphan transactions.
std::tuple< Wtxid, NodeId > ByWtxidView
Definition: txorphanage.cpp:73
void SanityCheck() const override
Check consistency between PeerOrphanInfo and m_orphans.
transaction_identifier represents the two canonical transaction identifier types (txid,...
std::string ToString() const
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition: validation.h:132
volatile double sum
Definition: examples.cpp:10
#define LogDebug(category,...)
Definition: logging.h:381
@ TXPACKAGES
Definition: logging.h:96
Definition: messages.h:20
static constexpr NodeId MIN_PEER
Minimum NodeId for lower_bound lookups (in practice, NodeIds start at 0).
Definition: txorphanage.cpp:26
std::unique_ptr< TxOrphanage > MakeTxOrphanage() noexcept
Create a new TxOrphanage instance.
static constexpr unsigned int DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE
Default value for TxOrphanage::m_max_global_latency_score.
Definition: txorphanage.h:23
static constexpr int64_t DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER
Default value for TxOrphanage::m_reserved_usage_per_peer.
Definition: txorphanage.h:20
static constexpr NodeId MAX_PEER
Maximum NodeId for upper_bound lookups.
Definition: txorphanage.cpp:28
int64_t NodeId
Definition: net.h:98
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition: policy.h:34
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:423
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:40
Allows providing orphan information externally.
Definition: txorphanage.h:44
One orphan announcement.
Definition: txorphanage.cpp:38
const NodeId m_announcer
Which peer announced this tx.
Definition: txorphanage.cpp:41
TxOrphanage::Count GetLatencyScore() const
Get an approximation of how much this transaction contributes to latency in EraseForBlock and EraseFo...
Definition: txorphanage.cpp:66
TxOrphanage::Usage GetMemUsage() const
Get an approximation for "memory usage".
Definition: txorphanage.cpp:57
bool m_reconsider
Whether this tx should be reconsidered.
Definition: txorphanage.cpp:46
const SequenceNumber m_entry_sequence
What order this transaction entered the orphanage.
Definition: txorphanage.cpp:43
Announcement(const CTransactionRef &tx, NodeId peer, SequenceNumber seq)
Definition: txorphanage.cpp:48
result_type operator()(const Announcement &ann) const
Definition: txorphanage.cpp:88
FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const
There are 2 DoS scores:
void Add(const Announcement &ann)
TxOrphanage::Count m_total_latency_score
TxOrphanage::Count m_count_announcements
bool operator==(const PeerDoSInfo &other) const
bool Subtract(const Announcement &ann)
result_type operator()(const Announcement &ann) const
Definition: txorphanage.cpp:77
static int count
consteval auto _(util::TranslatedLiteral str)
Definition: translation.h:79
assert(!tx.IsCoinBase())