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>
22#include <unordered_map>
67 return 1 + (
m_tx->vin.size() / 10);
85 using ByPeerView = std::tuple<NodeId, bool, SequenceNumber>;
95 boost::multi_index::ordered_unique<boost::multi_index::tag<ByWtxid>, WtxidExtractor>,
96 boost::multi_index::ordered_unique<boost::multi_index::tag<ByPeer>, ByPeerViewExtractor>
99 using AnnouncementMap = boost::multi_index::multi_index_container<Announcement, OrphanIndices>;
100 template<
typename Tag>
101 using Iter =
typename AnnouncementMap::index<Tag>::type::iterator;
163 assert(max_peer_latency_score > 0);
164 assert(max_peer_memory > 0);
167 return std::max<FeeFrac>(latency_score, mem_score);
175 template<
typename Tag>
237template<typename Tag>
254 const auto& wtxid{it->m_tx->GetWitnessHash()};
255 for (
const auto& input : it->m_tx->vin) {
258 it_prev->second.erase(wtxid);
260 if (it_prev->second.empty()) {
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;
308 const auto& wtxid{tx->GetWitnessHash()};
309 const auto& txid{tx->GetHash()};
314 LogDebug(
BCLog::TXPACKAGES,
"ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
319 const bool brand_new{!
HaveTx(wtxid)};
323 if (!inserted)
return false;
327 peer_info.Add(*iter);
331 for (
const auto& input : tx->vin) {
333 wtxids_for_prevout.emplace(wtxid);
345 peer, txid.ToString(), wtxid.ToString());
362 if (it->m_tx->GetWitnessHash() != wtxid)
return false;
365 const auto& ptx = it->m_tx;
368 if (!inserted)
return false;
372 peer_info.Add(*iter);
374 const auto& txid = ptx->GetHash();
376 peer, txid.ToString(), wtxid.
ToString());
390 if (it ==
index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid)
return false;
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++);
419 auto it = index_by_peer.lower_bound(
ByPeerView{peer,
false, 0});
420 if (it == index_by_peer.end() || it->m_announcer != peer)
return;
422 unsigned int num_ann{0};
423 while (it != index_by_peer.end() && it->m_announcer == peer) {
455 std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
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);
464 static constexpr auto compare_score = [](
const auto& left,
const auto& right) {
465 if (left.second != right.second)
return left.second < right.second;
467 return left.first < right.first;
469 std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
471 unsigned int num_erased{0};
479 Assume(!heap_peer_dos.empty());
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();
495 const auto& dos_threshold = heap_peer_dos.empty() ?
FeeFrac{1, 1} : heap_peer_dos.front().second;
497 unsigned int num_erased_this_round{0};
498 unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
501 if (!
Assume(it_ann->m_announcer == worst_peer))
break;
503 Erase<ByPeer>(it_ann++);
505 num_erased_this_round += 1;
509 if (it_worst_peer ==
m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold)
break;
511 LogDebug(
BCLog::TXPACKAGES,
"peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
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);
524 LogDebug(
BCLog::TXPACKAGES,
"orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
529 std::vector<std::pair<Wtxid, NodeId>>
ret;
531 for (
unsigned int i = 0; i < tx.
vout.size(); i++) {
534 for (
const auto& wtxid : it_by_prev->second) {
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));
550 if (!
Assume(it->m_tx->GetWitnessHash() == wtxid))
break;
553 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
true; };
554 Assume(!it->m_reconsider);
556 ret.emplace_back(wtxid, it->m_announcer);
560 it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
570 return it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
576 if (it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid)
return it_lower->m_tx;
590 if (it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
593 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
false; };
607 return it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
614 std::set<Wtxid> wtxids_to_erase;
619 for (
const auto& input : block_tx.
vin) {
623 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
628 unsigned int num_erased{0};
629 for (
const auto& wtxid : wtxids_to_erase) {
636 if (num_erased != 0) {
639 Assume(wtxids_to_erase.size() == num_erased);
647 std::vector<CTransactionRef> children_found;
648 const auto& parent_txid{parent->GetHash()};
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});
658 while (it_upper != it_lower) {
660 if (!
Assume(it_upper->m_announcer == peer))
break;
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);
669 return children_found;
674 std::vector<TxOrphanage::OrphanInfo> result;
679 std::set<NodeId> this_orphan_announcers;
681 this_orphan_announcers.insert(it->m_announcer);
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();
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;
703 for (
const auto& input : it->m_tx->vin) {
704 all_outpoints.insert(input.prevout);
706 unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
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();
713 if (it->m_reconsider) {
714 auto [
_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
732 assert(all_outpoints.contains(outpoint));
733 for (
const auto& wtxid : wtxid_set) {
734 assert(unique_wtxids_to_scores.contains(wtxid));
743 const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
753 const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
777 return std::make_unique<TxOrphanageImpl>();
781 return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
#define Assume(val)
Assume is the identity function.
std::vector< CTransactionRef > vtx
An outpoint - a combination of a transaction hash and an index n into its vout.
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
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
A class to track orphan transactions (failed on TX_MISSING_INPUTS) Since we cannot distinguish orphan...
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
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.
TxOrphanageImpl()=default
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.
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.
transaction_identifier represents the two canonical transaction identifier types (txid,...
std::string ToString() const
static int32_t GetTransactionWeight(const CTransaction &tx)
#define LogDebug(category,...)
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.
static constexpr int64_t DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER
Default value for TxOrphanage::m_reserved_usage_per_peer.
static constexpr NodeId MAX_PEER
Maximum NodeId for upper_bound lookups.
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
std::shared_ptr< const CTransaction > CTransactionRef
Data structure storing a fee and size, ordered by increasing fee/size.
Allows providing orphan information externally.
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...
const CTransactionRef m_tx
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)
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::Usage m_total_usage
TxOrphanage::Count m_total_latency_score
TxOrphanage::Count m_count_announcements
bool operator==(const PeerDoSInfo &other) const
bool Subtract(const Announcement &ann)
consteval auto _(util::TranslatedLiteral str)