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<FeeFrac>(latency_score, mem_score);
176 template<
typename Tag>
238template<typename Tag>
250 m_unique_orphans -= 1;
251 m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;
252 m_unique_orphan_usage -= it->GetMemUsage();
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);
261 if (it_prev->second.empty()) {
262 m_outpoint_to_orphan_wtxids.erase(it_prev);
270 if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
272 m_orphans.get<Tag>().erase(it);
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);
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);
461 if (dos_score >>
FeeFrac{1, 1}) {
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)
return left.second < right.second;
468 return left.first < right.first;
470 std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
472 unsigned int num_erased{0};
480 Assume(!heap_peer_dos.empty());
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();
496 const auto& dos_threshold = heap_peer_dos.empty() ?
FeeFrac{1, 1} : heap_peer_dos.front().second;
498 unsigned int num_erased_this_round{0};
499 unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
502 if (!
Assume(it_ann->m_announcer == worst_peer))
break;
506 num_erased_this_round += 1;
510 if (it_worst_peer ==
m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold)
break;
512 LogDebug(
BCLog::TXPACKAGES,
"peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
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);
525 LogDebug(
BCLog::TXPACKAGES,
"orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
530 std::vector<std::pair<Wtxid, NodeId>>
ret;
532 for (
unsigned int i = 0; i < tx.
vout.size(); i++) {
535 for (
const auto& wtxid : it_by_prev->second) {
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));
551 if (!
Assume(it->m_tx->GetWitnessHash() == wtxid))
break;
554 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
true; };
555 Assume(!it->m_reconsider);
557 ret.emplace_back(wtxid, it->m_announcer);
561 it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
571 return it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
577 if (it_lower !=
m_orphans.get<
ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid)
return it_lower->m_tx;
591 if (it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
594 static constexpr auto mark_reconsidered_modifier = [](
auto& ann) { ann.m_reconsider =
false; };
608 return it !=
m_orphans.get<
ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
615 std::set<Wtxid> wtxids_to_erase;
620 for (
const auto& input : block_tx.
vin) {
624 std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
629 unsigned int num_erased{0};
630 for (
const auto& wtxid : wtxids_to_erase) {
637 if (num_erased != 0) {
640 Assume(wtxids_to_erase.size() == num_erased);
648 std::vector<CTransactionRef> children_found;
649 const auto& parent_txid{parent->GetHash()};
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});
659 while (it_upper != it_lower) {
661 if (!
Assume(it_upper->m_announcer == peer))
break;
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);
670 return children_found;
675 std::vector<TxOrphanage::OrphanInfo> result;
680 std::set<NodeId> this_orphan_announcers;
682 this_orphan_announcers.insert(it->m_announcer);
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();
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;
704 for (
const auto& input : it->m_tx->vin) {
705 all_outpoints.insert(input.prevout);
707 unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
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();
714 if (it->m_reconsider) {
715 auto [
_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
733 assert(all_outpoints.contains(outpoint));
734 for (
const auto& wtxid : wtxid_set) {
735 assert(unique_wtxids_to_scores.contains(wtxid));
744 const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
754 const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
778 return std::make_unique<TxOrphanageImpl>();
782 return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
for(unsigned int i=0;i< 256;++i)
#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.
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.
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.
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)
transaction_identifier< true > Wtxid
Wtxid commits to all transaction fields including the witness.
consteval auto _(util::TranslatedLiteral str)