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>;
96 boost::multi_index::indexed_by<
97 boost::multi_index::ordered_unique<boost::multi_index::tag<ByWtxid>,
WtxidExtractor>,
101 template<
typename Tag>
102 using Iter =
typename AnnouncementMap::index<Tag>::type::iterator;
164 assert(max_peer_latency_score > 0);
165 assert(max_peer_memory > 0);
168 return std::max<ByRatioNegSize<FeeFrac>>(latency_score, mem_score);
176 template<
typename Tag>
238template<typename Tag>
255 const auto& wtxid{it->m_tx->GetWitnessHash()};
256 for (
const auto& input : it->m_tx->vin) {
259 it_prev->second.erase(wtxid);
261 if (it_prev->second.empty()) {
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;
309 const auto& wtxid{tx->GetWitnessHash()};
310 const auto& txid{tx->GetHash()};
315 LogDebug(
BCLog::TXPACKAGES,
"ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
320 const bool brand_new{!
HaveTx(wtxid)};
324 if (!inserted)
return false;
328 peer_info.Add(*iter);
332 for (
const auto& input : tx->vin) {
334 wtxids_for_prevout.emplace(wtxid);
346 peer, txid.ToString(), wtxid.ToString());
363 if (it->m_tx->GetWitnessHash() != wtxid)
return false;
366 const auto& ptx = it->m_tx;
369 if (!inserted)
return false;
373 peer_info.Add(*iter);
375 const auto& txid = ptx->GetHash();
377 peer, txid.ToString(), wtxid.
ToString());
391 if (it ==
index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid)
return false;
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++);
420 auto it = index_by_peer.lower_bound(
ByPeerView{peer,
false, 0});
421 if (it == index_by_peer.end() || it->m_announcer != peer)
return;
423 unsigned int num_ann{0};
424 while (it != index_by_peer.end() && it->m_announcer == peer) {
456 std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
460 const auto dos_score = entry.GetDosScore(max_lat, max_mem);
462 heap_peer_dos.emplace_back(nodeid, dos_score);
465 static constexpr auto compare_score = [](
const auto& left,
const auto& right) {
466 if (left.second != right.second) {
474 return left.first < right.first;
476 std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
478 unsigned int num_erased{0};
483 Assume(!heap_peer_dos.empty());
486 std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
487 const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());
488 heap_peer_dos.pop_back();
499 const auto& dos_threshold = heap_peer_dos.empty() ?
FeeFrac{1, 1} : heap_peer_dos.front().second;
501 unsigned int num_erased_this_round{0};
502 unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
505 if (!
Assume(it_ann->m_announcer == worst_peer))
break;
507 Erase<ByPeer>(it_ann++);
509 num_erased_this_round += 1;
516 LogDebug(
BCLog::TXPACKAGES,
"peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
522 if (it_worst_peer !=
m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) {
523 heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem));
524 std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
529 LogDebug(
BCLog::TXPACKAGES,
"orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
534 std::vector<std::pair<Wtxid, NodeId>>
ret;
536 for (
unsigned int i = 0; i < tx.
vout.size(); i++) {
539 for (
const auto& wtxid : it_by_prev->second) {
551 const auto num_announcers{std::distance(it, it_end)};
552 if (!
Assume(num_announcers > 0))
continue;
553 std::advance(it, rng.
randrange(num_announcers));
555 if (!
Assume(it->m_tx->GetWitnessHash() == wtxid))
break;
558 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
true; };
559 Assume(!it->m_reconsider);
561 ret.emplace_back(wtxid, it->m_announcer);
565 it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
575 return it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
581 if (it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid)
return it_lower->m_tx;
595 if (it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
598 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
false; };
612 return it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
619 std::set<Wtxid> wtxids_to_erase;
624 for (
const auto& input : block_tx.
vin) {
628 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
633 unsigned int num_erased{0};
634 for (
const auto& wtxid : wtxids_to_erase) {
641 if (num_erased != 0) {
644 Assume(wtxids_to_erase.size() == num_erased);
652 std::vector<CTransactionRef> children_found;
653 const auto& parent_txid{parent->GetHash()};
660 auto it_upper = index_by_peer.upper_bound(
ByPeerView{peer,
true, std::numeric_limits<uint64_t>::max()});
661 auto it_lower = index_by_peer.lower_bound(
ByPeerView{peer,
false, 0});
663 while (it_upper != it_lower) {
665 if (!
Assume(it_upper->m_announcer == peer))
break;
667 for (
const auto& input : it_upper->m_tx->vin) {
668 if (input.prevout.hash == parent_txid) {
669 children_found.emplace_back(it_upper->m_tx);
674 return children_found;
679 std::vector<TxOrphanage::OrphanInfo> result;
684 std::set<NodeId> this_orphan_announcers;
686 this_orphan_announcers.insert(it->m_announcer);
688 if (std::next(it) ==
index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) {
689 result.emplace_back(it->m_tx, std::move(this_orphan_announcers));
690 this_orphan_announcers.clear();
702 std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
703 std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
704 std::set<COutPoint> all_outpoints;
705 std::set<Wtxid> reconstructed_reconsiderable_wtxids;
708 for (
const auto& input : it->m_tx->vin) {
709 all_outpoints.insert(input.prevout);
711 unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
713 auto& peer_info = reconstructed_peer_info[it->m_announcer];
714 peer_info.m_total_usage += it->GetMemUsage();
715 peer_info.m_count_announcements += 1;
716 peer_info.m_total_latency_score += it->GetLatencyScore();
718 if (it->m_reconsider) {
719 auto [
_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
737 assert(all_outpoints.contains(outpoint));
738 for (
const auto& wtxid : wtxid_set) {
739 assert(unique_wtxids_to_scores.contains(wtxid));
748 const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
758 const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
782 return std::make_unique<TxOrphanageImpl>();
786 return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
#define Assume(val)
Assume is the identity function.
Wrapper around FeeFrac & derived types, which adds a feerate-based ordering which treats equal-feerat...
Wrapper around FeeFrac & derived types, which adds a total ordering which first sorts by feerate and ...
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.
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.
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
~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.
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)