Bitcoin Core 31.1.0
P2P Digital Currency
Loading...
Searching...
No Matches
txorphanage.cpp
Go to the documentation of this file.
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
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
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 using AnnouncementMap = boost::multi_index::multi_index_container<
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;
104
107
110
113
118
121 std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids;
122
124 std::set<Wtxid> m_reconsiderable_wtxids;
125
126 struct PeerDoSInfo {
130 bool operator==(const PeerDoSInfo& other) const
131 {
132 return m_total_usage == other.m_total_usage &&
135 }
136 void Add(const Announcement& ann)
137 {
138 m_total_usage += ann.GetMemUsage();
141 }
153
162 FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const
163 {
164 assert(max_peer_latency_score > 0);
165 assert(max_peer_memory > 0);
166 const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score);
167 const FeeFrac mem_score(m_total_usage, max_peer_memory);
168 return std::max<FeeFrac>(latency_score, mem_score);
169 }
170 };
171
173 std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info;
174
176 template<typename Tag>
177 void Erase(Iter<Tag> it);
178
180 bool EraseTxInternal(const Wtxid& wtxid);
181
183 bool IsUnique(Iter<ByWtxid> it) const;
184
186 bool NeedsTrim() const;
187
189 void LimitOrphans();
190
191public:
192 TxOrphanageImpl() = default;
193 TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :
194 m_max_global_latency_score{max_global_latency_score},
195 m_reserved_usage_per_peer{reserved_peer_usage}
196 {}
197 ~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
213 TxOrphanage::Count MaxPeerLatencyScore() const override;
214
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;
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
238template<typename Tag>
240{
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 auto peer_it = m_peer_orphanage_info.find(it->m_announcer);
246 Assume(peer_it != m_peer_orphanage_info.end());
247 if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it);
248
249 if (IsUnique(m_orphans.project<ByWtxid>(it))) {
250 m_unique_orphans -= 1;
251 m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;
252 m_unique_orphan_usage -= it->GetMemUsage();
253
254 // Remove references in m_outpoint_to_orphan_wtxids
255 const auto& wtxid{it->m_tx->GetWitnessHash()};
256 for (const auto& input : it->m_tx->vin) {
257 auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
258 if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
259 it_prev->second.erase(wtxid);
260 // Clean up keys if they point to an empty set.
261 if (it_prev->second.empty()) {
262 m_outpoint_to_orphan_wtxids.erase(it_prev);
263 }
264 }
265 }
266 }
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 if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
271
272 m_orphans.get<Tag>().erase(it);
273}
274
276{
277 // Iterators ByWtxid are sorted by wtxid, so check if neighboring elements have the same wtxid.
278 auto& index = m_orphans.get<ByWtxid>();
279 if (it == index.end()) return false;
280 if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
281 if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
282 return true;
283}
284
286{
287 auto it = m_peer_orphanage_info.find(peer);
288 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage;
289}
290
292
294
296
298 auto it = m_peer_orphanage_info.find(peer);
299 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements;
300}
301
303 auto it = m_peer_orphanage_info.find(peer);
304 return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score;
305}
306
308{
309 const auto& wtxid{tx->GetWitnessHash()};
310 const auto& txid{tx->GetHash()};
311
312 // Ignore transactions above max standard size to avoid a send-big-orphans memory exhaustion attack.
314 if (sz > MAX_STANDARD_TX_WEIGHT) {
315 LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
316 return false;
317 }
318
319 // We will return false if the tx already exists under a different peer.
320 const bool brand_new{!HaveTx(wtxid)};
321
322 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 if (!inserted) return false;
325
327 auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
328 peer_info.Add(*iter);
329
330 // Add links in m_outpoint_to_orphan_wtxids
331 if (brand_new) {
332 for (const auto& input : tx->vin) {
333 auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;
334 wtxids_for_prevout.emplace(wtxid);
335 }
336
337 m_unique_orphans += 1;
338 m_unique_orphan_usage += iter->GetMemUsage();
339 m_unique_rounded_input_scores += iter->GetLatencyScore() - 1;
340
341 LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n",
342 txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size());
343 Assume(IsUnique(iter));
344 } else {
345 LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
346 peer, txid.ToString(), wtxid.ToString());
347 Assume(!IsUnique(iter));
348 }
349
350 // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
351 LimitOrphans();
352 return brand_new;
353}
354
356{
357 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
358 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 if (it == index_by_wtxid.end()) return false;
363 if (it->m_tx->GetWitnessHash() != wtxid) return false;
364
365 // Add another announcement, copying the CTransactionRef from one that already exists.
366 const auto& ptx = it->m_tx;
367 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 if (!inserted) return false;
370
372 auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
373 peer_info.Add(*iter);
374
375 const auto& txid = ptx->GetHash();
376 LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
377 peer, txid.ToString(), wtxid.ToString());
378
379 Assume(!IsUnique(iter));
380
381 // DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
382 LimitOrphans();
383 return true;
384}
385
387{
388 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
389
390 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
391 if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false;
392
393 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
394 unsigned int num_ann{0};
395 const auto txid = it->m_tx->GetHash();
396 while (it != it_end) {
397 Assume(it->m_tx->GetWitnessHash() == wtxid);
398 Erase<ByWtxid>(it++);
399 num_ann += 1;
400 }
401 LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann);
402
403 return true;
404}
405
407{
408 const auto ret = EraseTxInternal(wtxid);
409
410 // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
411 LimitOrphans();
412
413 return ret;
414}
415
418{
419 auto& index_by_peer = m_orphans.get<ByPeer>();
420 auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
421 if (it == index_by_peer.end() || it->m_announcer != peer) return;
422
423 unsigned int num_ann{0};
424 while (it != index_by_peer.end() && it->m_announcer == peer) {
425 // Delete item, cleaning up m_outpoint_to_orphan_wtxids iff this entry is unique by wtxid.
426 Erase<ByPeer>(it++);
427 num_ann += 1;
428 }
429 Assume(!m_peer_orphanage_info.contains(peer));
430
431 if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer);
432
433 // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
434 LimitOrphans();
435}
436
444{
445 if (!NeedsTrim()) return;
446
447 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 const auto max_lat{MaxPeerLatencyScore()};
452 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 std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
457 heap_peer_dos.reserve(m_peer_orphanage_info.size());
458 for (const auto& [nodeid, entry] : m_peer_orphanage_info) {
459 // Performance optimization: only consider peers with a DoS score > 1.
460 const auto dos_score = entry.GetDosScore(max_lat, max_mem);
461 if (dos_score >> FeeFrac{1, 1}) {
462 heap_peer_dos.emplace_back(nodeid, dos_score);
463 }
464 }
465 static constexpr auto compare_score = [](const auto& left, const auto& right) {
466 if (left.second != right.second) return left.second < right.second;
467 // Tiebreak by considering the more recent peer (higher NodeId) to be worse.
468 return left.first < right.first;
469 };
470 std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
471
472 unsigned int num_erased{0};
473 // This outer loop finds the peer with the highest DoS score, which is a fraction of memory and latency scores
474 // over the respective allowances. We continue until the orphanage is within global limits. That means some peers
475 // might still have a DoS score > 1 at the end.
476 // Note: if ratios are the same, FeeFrac tiebreaks by denominator. In practice, since the latency denominator (number of
477 // announcements and inputs) is always lower, this means that a peer with only high latency scores will be targeted
478 // before a peer using a lot of memory, even if they have the same ratios.
479 do {
480 Assume(!heap_peer_dos.empty());
481 // This is a max-heap, so the worst peer is at the front. pop_heap()
482 // moves it to the back, and the next worst peer is moved to the front.
483 std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
484 const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());
485 heap_peer_dos.pop_back();
486
487 // If needs trim, then at least one peer has a DoS score higher than 1.
488 Assume(dos_score >> (FeeFrac{1, 1}));
489
490 auto it_worst_peer = m_peer_orphanage_info.find(worst_peer);
491
492 // This inner loop trims until this peer is no longer the DoSiest one or has a score within 1. The score 1 is
493 // just a conservative fallback: once the last peer goes below ratio 1, NeedsTrim() will return false anyway.
494 // We evict the oldest announcement(s) from this peer, sorting non-reconsiderable before reconsiderable.
495 // The number of inner loop iterations is bounded by the total number of announcements.
496 const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second;
497 auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0});
498 unsigned int num_erased_this_round{0};
499 unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
500 while (NeedsTrim()) {
501 if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break;
502 if (!Assume(it_ann->m_announcer == worst_peer)) break;
503
504 Erase<ByPeer>(it_ann++);
505 num_erased += 1;
506 num_erased_this_round += 1;
507
508 // If we erased the last orphan from this peer, it_worst_peer will be invalidated.
509 it_worst_peer = m_peer_orphanage_info.find(worst_peer);
510 if (it_worst_peer == m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold) break;
511 }
512 LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
513
514 if (!NeedsTrim()) break;
515
516 // Unless this peer is empty, put it back in the heap so we continue to consider evicting its orphans.
517 // We may select this peer for evictions again if there are multiple DoSy peers.
518 if (it_worst_peer != m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) {
519 heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem));
520 std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
521 }
522 } while (true);
523
524 const auto remaining_unique_orphans{CountUniqueOrphans()};
525 LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
526}
527
528std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
529{
530 std::vector<std::pair<Wtxid, NodeId>> ret;
531 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
532 for (unsigned int i = 0; i < tx.vout.size(); i++) {
533 const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i));
534 if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) {
535 for (const auto& wtxid : it_by_prev->second) {
536 // If a reconsiderable announcement for this wtxid already exists, skip it.
537 if (m_reconsiderable_wtxids.contains(wtxid)) continue;
538
539 // Belt and suspenders, each entry in m_outpoint_to_orphan_wtxids should always have at least 1 announcement.
540 auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
541 if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue;
542
543 // Select a random peer to assign orphan processing, reducing wasted work if the orphan is still missing
544 // inputs. However, we don't want to create an issue in which the assigned peer can purposefully stop us
545 // from processing the orphan by disconnecting.
546 auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
547 const auto num_announcers{std::distance(it, it_end)};
548 if (!Assume(num_announcers > 0)) continue;
549 std::advance(it, rng.randrange(num_announcers));
550
551 if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break;
552
553 // Mark this orphan as ready to be reconsidered.
554 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; };
555 Assume(!it->m_reconsider);
556 index_by_wtxid.modify(it, mark_reconsidered_modifier);
557 ret.emplace_back(wtxid, it->m_announcer);
558 m_reconsiderable_wtxids.insert(wtxid);
559
560 LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
561 it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
562 }
563 }
564 }
565 return ret;
566}
567
568bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const
569{
570 auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
571 return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
572}
573
575{
576 auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
577 if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx;
578 return nullptr;
579}
580
581bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
582{
583 return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0;
584}
585
589{
590 auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
591 if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
592 // Flip m_reconsider. Even if this transaction stays in orphanage, it shouldn't be
593 // reconsidered again until there is a new reason to do so.
594 static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; };
595 m_orphans.get<ByPeer>().modify(it, mark_reconsidered_modifier);
596 // As there is exactly one m_reconsider announcement per reconsiderable wtxids, flipping
597 // the m_reconsider flag means the wtxid is no longer reconsiderable.
598 m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
599 return it->m_tx;
600 }
601 return nullptr;
602}
603
606{
607 auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
608 return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
609}
610
612{
613 if (m_orphans.empty()) return;
614
615 std::set<Wtxid> wtxids_to_erase;
616 for (const CTransactionRef& ptx : block.vtx) {
617 const CTransaction& block_tx = *ptx;
618
619 // Which orphan pool entries must we evict?
620 for (const auto& input : block_tx.vin) {
621 auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
622 if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
623 // Copy all wtxids to wtxids_to_erase.
624 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
625 }
626 }
627 }
628
629 unsigned int num_erased{0};
630 for (const auto& wtxid : wtxids_to_erase) {
631 // Don't use EraseTx here because it calls LimitOrphans and announcements deleted in that call are not reflected
632 // in its return result. Waiting until the end to do LimitOrphans helps save repeated computation and allows us
633 // to check that num_erased is what we expect.
634 num_erased += EraseTxInternal(wtxid) ? 1 : 0;
635 }
636
637 if (num_erased != 0) {
638 LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased);
639 }
640 Assume(wtxids_to_erase.size() == num_erased);
641
642 // Deletions can cause the orphanage's MaxGlobalUsage to decrease, so we may need to trim here.
643 LimitOrphans();
644}
645
646std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const
647{
648 std::vector<CTransactionRef> children_found;
649 const auto& parent_txid{parent->GetHash()};
650
651 // Iterate through all orphans from this peer, in reverse order, so that more recent
652 // transactions are added first. Doing so helps avoid work when one of the orphans replaced
653 // an earlier one. Since we require the NodeId to match, one peer's announcement order does
654 // not bias how we process other peer's orphans.
655 auto& index_by_peer = m_orphans.get<ByPeer>();
656 auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()});
657 auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
658
659 while (it_upper != it_lower) {
660 --it_upper;
661 if (!Assume(it_upper->m_announcer == peer)) break;
662 // Check if this tx spends from parent.
663 for (const auto& input : it_upper->m_tx->vin) {
664 if (input.prevout.hash == parent_txid) {
665 children_found.emplace_back(it_upper->m_tx);
666 break;
667 }
668 }
669 }
670 return children_found;
671}
672
673std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const
674{
675 std::vector<TxOrphanage::OrphanInfo> result;
676 result.reserve(m_unique_orphans);
677
678 auto& index_by_wtxid = m_orphans.get<ByWtxid>();
679 auto it = index_by_wtxid.begin();
680 std::set<NodeId> this_orphan_announcers;
681 while (it != index_by_wtxid.end()) {
682 this_orphan_announcers.insert(it->m_announcer);
683 // If this is the last entry, or the next entry has a different wtxid, build a OrphanInfo.
684 if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) {
685 result.emplace_back(it->m_tx, std::move(this_orphan_announcers));
686 this_orphan_announcers.clear();
687 }
688
689 ++it;
690 }
691 Assume(m_unique_orphans == result.size());
692
693 return result;
694}
695
697{
698 std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
699 std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
700 std::set<COutPoint> all_outpoints;
701 std::set<Wtxid> reconstructed_reconsiderable_wtxids;
702
703 for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) {
704 for (const auto& input : it->m_tx->vin) {
705 all_outpoints.insert(input.prevout);
706 }
707 unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
708
709 auto& peer_info = reconstructed_peer_info[it->m_announcer];
710 peer_info.m_total_usage += it->GetMemUsage();
711 peer_info.m_count_announcements += 1;
712 peer_info.m_total_latency_score += it->GetLatencyScore();
713
714 if (it->m_reconsider) {
715 auto [_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
716 // Check that there is only ever 1 announcement per wtxid with m_reconsider set.
717 assert(added);
718 }
719 }
720 assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size());
721
722 // Recalculated per-peer stats are identical to m_peer_orphanage_info
723 assert(reconstructed_peer_info == m_peer_orphanage_info);
724
725 // Recalculated set of reconsiderable wtxids must match.
726 assert(m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids);
727
728 // All outpoints exist in m_outpoint_to_orphan_wtxids, all keys in m_outpoint_to_orphan_wtxids correspond to some
729 // orphan, and all wtxids referenced in m_outpoint_to_orphan_wtxids are also in m_orphans.
730 // This ensures m_outpoint_to_orphan_wtxids is cleaned up.
731 assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size());
732 for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) {
733 assert(all_outpoints.contains(outpoint));
734 for (const auto& wtxid : wtxid_set) {
735 assert(unique_wtxids_to_scores.contains(wtxid));
736 }
737 }
738
739 // Cached m_unique_orphans value is correct.
742 assert(unique_wtxids_to_scores.size() == m_unique_orphans);
743
744 const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
745 TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; });
746 assert(calculated_dedup_usage == m_unique_orphan_usage);
747
748 // Global usage is deduplicated, should be less than or equal to the sum of all per-peer usages.
749 const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
750 TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; });
751 assert(summed_peer_usage >= m_unique_orphan_usage);
752
753 // Cached m_unique_rounded_input_scores value is correct.
754 const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
755 TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; });
756 assert(calculated_total_latency_score == m_unique_rounded_input_scores);
757
758 // Global latency score is deduplicated, should be less than or equal to the sum of all per-peer latency scores.
759 const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
760 TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; });
761 assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size());
762
763 assert(!NeedsTrim());
764}
765
771
776std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept
777{
778 return std::make_unique<TxOrphanageImpl>();
779}
780std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept
781{
782 return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
783}
784} // namespace node
for(unsigned int i=0;i< 256;++i)
int ret
if(!SetupNetworking())
#define Assume(val)
Assume is the identity function.
Definition check.h:125
Definition block.h:74
std::vector< CTransactionRef > vtx
Definition block.h:77
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.
const std::vector< CTxOut > vout
const Txid & GetHash() const LIFETIMEBOUND
const std::vector< CTxIn > vin
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.
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
SequenceNumber m_current_sequence
Global sequence number, increment each time an announcement is added.
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.
boost::multi_index::multi_index_container< Announcement, boost::multi_index::indexed_by< boost::multi_index::ordered_unique< boost::multi_index::tag< ByWtxid >, WtxidExtractor >, boost::multi_index::ordered_unique< boost::multi_index::tag< ByPeer >, ByPeerViewExtractor > > > AnnouncementMap
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
void SanityCheck() const override
Check consistency between PeerOrphanInfo and m_orphans.
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition validation.h:132
volatile double sum
Definition examples.cpp:10
#define LogDebug(category,...)
Definition log.h:115
@ TXPACKAGES
Definition categories.h:46
static constexpr NodeId MIN_PEER
Minimum NodeId for lower_bound lookups (in practice, NodeIds start at 0).
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 common.h:29
int64_t NodeId
Definition net.h:103
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition policy.h:37
std::shared_ptr< const CTransaction > CTransactionRef
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
const NodeId m_announcer
Which peer announced this tx.
TxOrphanage::Count GetLatencyScore() const
Get an approximation of how much this transaction contributes to latency in EraseForBlock and EraseFo...
TxOrphanage::Usage GetMemUsage() const
Get an approximation for "memory usage".
bool m_reconsider
Whether this tx should be reconsidered.
const SequenceNumber m_entry_sequence
What order this transaction entered the orphanage.
Announcement(const CTransactionRef &tx, NodeId peer, SequenceNumber seq)
result_type operator()(const Announcement &ann) const
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
static int count
transaction_identifier< true > Wtxid
Wtxid commits to all transaction fields including the witness.
consteval auto _(util::TranslatedLiteral str)
Definition translation.h:79
assert(!tx.IsCoinBase())