#include <node/txorphanage.h>
#include <consensus/validation.h>
#include <logging.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <util/feefrac.h>
#include <util/time.h>
#include <util/hasher.h>
#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <boost/multi_index_container.hpp>
#include <cassert>
#include <cmath>
#include <unordered_map>
namespace node {
static constexpr NodeId MIN_PEER{std::numeric_limits<NodeId>::min()};
static constexpr NodeId MAX_PEER{std::numeric_limits<NodeId>::max()};
class TxOrphanageImpl final : public TxOrphanage {
using SequenceNumber = uint64_t;
SequenceNumber m_current_sequence{0};
struct Announcement
{
const CTransactionRef m_tx;
const NodeId m_announcer;
const SequenceNumber m_entry_sequence;
bool m_reconsider{false};
Announcement(const CTransactionRef& tx, NodeId peer, SequenceNumber seq) :
m_tx{tx}, m_announcer{peer}, m_entry_sequence{seq}
{ }
TxOrphanage::Usage GetMemUsage() const {
return GetTransactionWeight(*m_tx);
}
TxOrphanage::Count GetLatencyScore() const {
return 1 + (m_tx->vin.size() / 10);
}
};
struct ByWtxid {};
using ByWtxidView = std::tuple<Wtxid, NodeId>;
struct WtxidExtractor
{
using result_type = ByWtxidView;
result_type operator()(const Announcement& ann) const
{
return ByWtxidView{ann.m_tx->GetWitnessHash(), ann.m_announcer};
}
};
struct ByPeer {};
using ByPeerView = std::tuple<NodeId, bool, SequenceNumber>;
struct ByPeerViewExtractor {
using result_type = ByPeerView;
result_type operator()(const Announcement& ann) const
{
return ByPeerView{ann.m_announcer, ann.m_reconsider, ann.m_entry_sequence};
}
};
using AnnouncementMap = 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>
>
>;
template<typename Tag>
using Iter = typename AnnouncementMap::index<Tag>::type::iterator;
AnnouncementMap m_orphans;
const TxOrphanage::Count m_max_global_latency_score{DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE};
const TxOrphanage::Usage m_reserved_usage_per_peer{DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};
TxOrphanage::Count m_unique_orphans{0};
TxOrphanage::Usage m_unique_orphan_usage{0};
TxOrphanage::Count m_unique_rounded_input_scores{0};
std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_wtxids;
std::set<Wtxid> m_reconsiderable_wtxids;
struct PeerDoSInfo {
TxOrphanage::Usage m_total_usage{0};
TxOrphanage::Count m_count_announcements{0};
TxOrphanage::Count m_total_latency_score{0};
bool operator==(const PeerDoSInfo& other) const
{
return m_total_usage == other.m_total_usage &&
m_count_announcements == other.m_count_announcements &&
m_total_latency_score == other.m_total_latency_score;
}
void Add(const Announcement& ann)
{
m_total_usage += ann.GetMemUsage();
m_total_latency_score += ann.GetLatencyScore();
m_count_announcements += 1;
}
bool Subtract(const Announcement& ann)
{
Assume(m_total_usage >= ann.GetMemUsage());
Assume(m_total_latency_score >= ann.GetLatencyScore());
Assume(m_count_announcements >= 1);
m_total_usage -= ann.GetMemUsage();
m_total_latency_score -= ann.GetLatencyScore();
m_count_announcements -= 1;
return m_count_announcements == 0;
}
FeeFrac GetDosScore(TxOrphanage::Count max_peer_latency_score, TxOrphanage::Usage max_peer_memory) const
{
assert(max_peer_latency_score > 0);
assert(max_peer_memory > 0);
const FeeFrac latency_score(m_total_latency_score, max_peer_latency_score);
const FeeFrac mem_score(m_total_usage, max_peer_memory);
return std::max<FeeFrac>(latency_score, mem_score);
}
};
std::unordered_map<NodeId, PeerDoSInfo> m_peer_orphanage_info;
template<typename Tag>
void Erase(Iter<Tag> it);
bool EraseTxInternal(const Wtxid& wtxid);
bool IsUnique(Iter<ByWtxid> it) const;
bool NeedsTrim() const;
void LimitOrphans();
public:
TxOrphanageImpl() = default;
TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :
m_max_global_latency_score{max_global_latency_score},
m_reserved_usage_per_peer{reserved_peer_usage}
{}
~TxOrphanageImpl() noexcept override = default;
TxOrphanage::Count CountAnnouncements() const override;
TxOrphanage::Count CountUniqueOrphans() const override;
TxOrphanage::Count AnnouncementsFromPeer(NodeId peer) const override;
TxOrphanage::Count LatencyScoreFromPeer(NodeId peer) const override;
TxOrphanage::Usage UsageByPeer(NodeId peer) const override;
TxOrphanage::Count MaxGlobalLatencyScore() const override;
TxOrphanage::Count TotalLatencyScore() const override;
TxOrphanage::Usage ReservedPeerUsage() const override;
TxOrphanage::Count MaxPeerLatencyScore() const override;
TxOrphanage::Usage MaxGlobalUsage() const override;
bool AddTx(const CTransactionRef& tx, NodeId peer) override;
bool AddAnnouncer(const Wtxid& wtxid, NodeId peer) override;
CTransactionRef GetTx(const Wtxid& wtxid) const override;
bool HaveTx(const Wtxid& wtxid) const override;
bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const override;
CTransactionRef GetTxToReconsider(NodeId peer) override;
bool EraseTx(const Wtxid& wtxid) override;
void EraseForPeer(NodeId peer) override;
void EraseForBlock(const CBlock& block) override;
std::vector<std::pair<Wtxid, NodeId>> AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) override;
bool HaveTxToReconsider(NodeId peer) override;
std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override;
std::vector<OrphanInfo> GetOrphanTransactions() const override;
TxOrphanage::Usage TotalOrphanUsage() const override;
void SanityCheck() const override;
};
template<typename Tag>
void TxOrphanageImpl::Erase(Iter<Tag> it)
{
auto peer_it = m_peer_orphanage_info.find(it->m_announcer);
Assume(peer_it != m_peer_orphanage_info.end());
if (peer_it->second.Subtract(*it)) m_peer_orphanage_info.erase(peer_it);
if (IsUnique(m_orphans.project<ByWtxid>(it))) {
m_unique_orphans -= 1;
m_unique_rounded_input_scores -= it->GetLatencyScore() - 1;
m_unique_orphan_usage -= it->GetMemUsage();
const auto& wtxid{it->m_tx->GetWitnessHash()};
for (const auto& input : it->m_tx->vin) {
auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
it_prev->second.erase(wtxid);
if (it_prev->second.empty()) {
m_outpoint_to_orphan_wtxids.erase(it_prev);
}
}
}
}
if (it->m_reconsider) m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
m_orphans.get<Tag>().erase(it);
}
bool TxOrphanageImpl::IsUnique(Iter<ByWtxid> it) const
{
auto& index = m_orphans.get<ByWtxid>();
if (it == index.end()) return false;
if (std::next(it) != index.end() && std::next(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
if (it != index.begin() && std::prev(it)->m_tx->GetWitnessHash() == it->m_tx->GetWitnessHash()) return false;
return true;
}
TxOrphanage::Usage TxOrphanageImpl::UsageByPeer(NodeId peer) const
{
auto it = m_peer_orphanage_info.find(peer);
return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_usage;
}
TxOrphanage::Count TxOrphanageImpl::CountAnnouncements() const { return m_orphans.size(); }
TxOrphanage::Usage TxOrphanageImpl::TotalOrphanUsage() const { return m_unique_orphan_usage; }
TxOrphanage::Count TxOrphanageImpl::CountUniqueOrphans() const { return m_unique_orphans; }
TxOrphanage::Count TxOrphanageImpl::AnnouncementsFromPeer(NodeId peer) const {
auto it = m_peer_orphanage_info.find(peer);
return it == m_peer_orphanage_info.end() ? 0 : it->second.m_count_announcements;
}
TxOrphanage::Count TxOrphanageImpl::LatencyScoreFromPeer(NodeId peer) const {
auto it = m_peer_orphanage_info.find(peer);
return it == m_peer_orphanage_info.end() ? 0 : it->second.m_total_latency_score;
}
bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer)
{
const auto& wtxid{tx->GetWitnessHash()};
const auto& txid{tx->GetHash()};
TxOrphanage::Usage sz = GetTransactionWeight(*tx);
if (sz > MAX_STANDARD_TX_WEIGHT) {
LogDebug(BCLog::TXPACKAGES, "ignoring large orphan tx (size: %u, txid: %s, wtxid: %s)\n", sz, txid.ToString(), wtxid.ToString());
return false;
}
const bool brand_new{!HaveTx(wtxid)};
auto [iter, inserted] = m_orphans.get<ByWtxid>().emplace(tx, peer, m_current_sequence);
if (!inserted) return false;
++m_current_sequence;
auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
peer_info.Add(*iter);
if (brand_new) {
for (const auto& input : tx->vin) {
auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;
wtxids_for_prevout.emplace(wtxid);
}
m_unique_orphans += 1;
m_unique_orphan_usage += iter->GetMemUsage();
m_unique_rounded_input_scores += iter->GetLatencyScore() - 1;
LogDebug(BCLog::TXPACKAGES, "stored orphan tx %s (wtxid=%s), weight: %u (mapsz %u outsz %u)\n",
txid.ToString(), wtxid.ToString(), sz, m_orphans.size(), m_outpoint_to_orphan_wtxids.size());
Assume(IsUnique(iter));
} else {
LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
peer, txid.ToString(), wtxid.ToString());
Assume(!IsUnique(iter));
}
LimitOrphans();
return brand_new;
}
bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
{
auto& index_by_wtxid = m_orphans.get<ByWtxid>();
auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
if (it == index_by_wtxid.end()) return false;
if (it->m_tx->GetWitnessHash() != wtxid) return false;
const auto& ptx = it->m_tx;
auto [iter, inserted] = index_by_wtxid.emplace(ptx, peer, m_current_sequence);
if (!inserted) return false;
++m_current_sequence;
auto& peer_info = m_peer_orphanage_info.try_emplace(peer).first->second;
peer_info.Add(*iter);
const auto& txid = ptx->GetHash();
LogDebug(BCLog::TXPACKAGES, "added peer=%d as announcer of orphan tx %s (wtxid=%s)\n",
peer, txid.ToString(), wtxid.ToString());
Assume(!IsUnique(iter));
LimitOrphans();
return true;
}
bool TxOrphanageImpl::EraseTxInternal(const Wtxid& wtxid)
{
auto& index_by_wtxid = m_orphans.get<ByWtxid>();
auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
if (it == index_by_wtxid.end() || it->m_tx->GetWitnessHash() != wtxid) return false;
auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
unsigned int num_ann{0};
const auto txid = it->m_tx->GetHash();
while (it != it_end) {
Assume(it->m_tx->GetWitnessHash() == wtxid);
Erase<ByWtxid>(it++);
num_ann += 1;
}
LogDebug(BCLog::TXPACKAGES, "removed orphan tx %s (wtxid=%s) (%u announcements)\n", txid.ToString(), wtxid.ToString(), num_ann);
return true;
}
bool TxOrphanageImpl::EraseTx(const Wtxid& wtxid)
{
const auto ret = EraseTxInternal(wtxid);
LimitOrphans();
return ret;
}
void TxOrphanageImpl::EraseForPeer(NodeId peer)
{
auto& index_by_peer = m_orphans.get<ByPeer>();
auto it = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
if (it == index_by_peer.end() || it->m_announcer != peer) return;
unsigned int num_ann{0};
while (it != index_by_peer.end() && it->m_announcer == peer) {
Erase<ByPeer>(it++);
num_ann += 1;
}
Assume(!m_peer_orphanage_info.contains(peer));
if (num_ann > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", num_ann, peer);
LimitOrphans();
}
void TxOrphanageImpl::LimitOrphans()
{
if (!NeedsTrim()) return;
const auto original_unique_txns{CountUniqueOrphans()};
const auto max_lat{MaxPeerLatencyScore()};
const auto max_mem{ReservedPeerUsage()};
std::vector<std::pair<NodeId, FeeFrac>> heap_peer_dos;
heap_peer_dos.reserve(m_peer_orphanage_info.size());
for (const auto& [nodeid, entry] : m_peer_orphanage_info) {
const auto dos_score = entry.GetDosScore(max_lat, max_mem);
if (dos_score >> FeeFrac{1, 1}) {
heap_peer_dos.emplace_back(nodeid, dos_score);
}
}
static constexpr auto compare_score = [](const auto& left, const auto& right) {
if (left.second != right.second) return left.second < right.second;
return left.first < right.first;
};
std::make_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
unsigned int num_erased{0};
do {
Assume(!heap_peer_dos.empty());
std::pop_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
const auto [worst_peer, dos_score] = std::move(heap_peer_dos.back());
heap_peer_dos.pop_back();
Assume(dos_score >> (FeeFrac{1, 1}));
auto it_worst_peer = m_peer_orphanage_info.find(worst_peer);
const auto& dos_threshold = heap_peer_dos.empty() ? FeeFrac{1, 1} : heap_peer_dos.front().second;
auto it_ann = m_orphans.get<ByPeer>().lower_bound(ByPeerView{worst_peer, false, 0});
unsigned int num_erased_this_round{0};
unsigned int starting_num_ann{it_worst_peer->second.m_count_announcements};
while (NeedsTrim()) {
if (!Assume(it_ann != m_orphans.get<ByPeer>().end())) break;
if (!Assume(it_ann->m_announcer == worst_peer)) break;
Erase<ByPeer>(it_ann++);
num_erased += 1;
num_erased_this_round += 1;
it_worst_peer = m_peer_orphanage_info.find(worst_peer);
if (it_worst_peer == m_peer_orphanage_info.end() || it_worst_peer->second.GetDosScore(max_lat, max_mem) <= dos_threshold) break;
}
LogDebug(BCLog::TXPACKAGES, "peer=%d orphanage overflow, removed %u of %u announcements\n", worst_peer, num_erased_this_round, starting_num_ann);
if (!NeedsTrim()) break;
if (it_worst_peer != m_peer_orphanage_info.end() && it_worst_peer->second.m_count_announcements > 0) {
heap_peer_dos.emplace_back(worst_peer, it_worst_peer->second.GetDosScore(max_lat, max_mem));
std::push_heap(heap_peer_dos.begin(), heap_peer_dos.end(), compare_score);
}
} while (true);
const auto remaining_unique_orphans{CountUniqueOrphans()};
LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx (%u announcements)\n", original_unique_txns - remaining_unique_orphans, num_erased);
}
std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
{
std::vector<std::pair<Wtxid, NodeId>> ret;
auto& index_by_wtxid = m_orphans.get<ByWtxid>();
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const auto it_by_prev = m_outpoint_to_orphan_wtxids.find(COutPoint(tx.GetHash(), i));
if (it_by_prev != m_outpoint_to_orphan_wtxids.end()) {
for (const auto& wtxid : it_by_prev->second) {
if (m_reconsiderable_wtxids.contains(wtxid)) continue;
auto it = index_by_wtxid.lower_bound(ByWtxidView{wtxid, MIN_PEER});
if (!Assume(it != index_by_wtxid.end() && it->m_tx->GetWitnessHash() == wtxid)) continue;
auto it_end = index_by_wtxid.upper_bound(ByWtxidView{wtxid, MAX_PEER});
const auto num_announcers{std::distance(it, it_end)};
if (!Assume(num_announcers > 0)) continue;
std::advance(it, rng.randrange(num_announcers));
if (!Assume(it->m_tx->GetWitnessHash() == wtxid)) break;
static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = true; };
Assume(!it->m_reconsider);
index_by_wtxid.modify(it, mark_reconsidered_modifier);
ret.emplace_back(wtxid, it->m_announcer);
m_reconsiderable_wtxids.insert(wtxid);
LogDebug(BCLog::TXPACKAGES, "added %s (wtxid=%s) to peer %d workset\n",
it->m_tx->GetHash().ToString(), it->m_tx->GetWitnessHash().ToString(), it->m_announcer);
}
}
}
return ret;
}
bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const
{
auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
return it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid;
}
CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const
{
auto it_lower = m_orphans.get<ByWtxid>().lower_bound(ByWtxidView{wtxid, MIN_PEER});
if (it_lower != m_orphans.get<ByWtxid>().end() && it_lower->m_tx->GetWitnessHash() == wtxid) return it_lower->m_tx;
return nullptr;
}
bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
{
return m_orphans.get<ByWtxid>().count(ByWtxidView{wtxid, peer}) > 0;
}
CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer)
{
auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
if (it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider) {
static constexpr auto mark_reconsidered_modifier = [](auto& ann) { ann.m_reconsider = false; };
m_orphans.get<ByPeer>().modify(it, mark_reconsidered_modifier);
m_reconsiderable_wtxids.erase(it->m_tx->GetWitnessHash());
return it->m_tx;
}
return nullptr;
}
bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer)
{
auto it = m_orphans.get<ByPeer>().lower_bound(ByPeerView{peer, true, 0});
return it != m_orphans.get<ByPeer>().end() && it->m_announcer == peer && it->m_reconsider;
}
void TxOrphanageImpl::EraseForBlock(const CBlock& block)
{
if (m_orphans.empty()) return;
std::set<Wtxid> wtxids_to_erase;
for (const CTransactionRef& ptx : block.vtx) {
const CTransaction& block_tx = *ptx;
for (const auto& input : block_tx.vin) {
auto it_prev = m_outpoint_to_orphan_wtxids.find(input.prevout);
if (it_prev != m_outpoint_to_orphan_wtxids.end()) {
std::copy(it_prev->second.cbegin(), it_prev->second.cend(), std::inserter(wtxids_to_erase, wtxids_to_erase.end()));
}
}
}
unsigned int num_erased{0};
for (const auto& wtxid : wtxids_to_erase) {
num_erased += EraseTxInternal(wtxid) ? 1 : 0;
}
if (num_erased != 0) {
LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) included or conflicted by block\n", num_erased);
}
Assume(wtxids_to_erase.size() == num_erased);
LimitOrphans();
}
std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId peer) const
{
std::vector<CTransactionRef> children_found;
const auto& parent_txid{parent->GetHash()};
auto& index_by_peer = m_orphans.get<ByPeer>();
auto it_upper = index_by_peer.upper_bound(ByPeerView{peer, true, std::numeric_limits<uint64_t>::max()});
auto it_lower = index_by_peer.lower_bound(ByPeerView{peer, false, 0});
while (it_upper != it_lower) {
--it_upper;
if (!Assume(it_upper->m_announcer == peer)) break;
for (const auto& input : it_upper->m_tx->vin) {
if (input.prevout.hash == parent_txid) {
children_found.emplace_back(it_upper->m_tx);
break;
}
}
}
return children_found;
}
std::vector<TxOrphanage::OrphanInfo> TxOrphanageImpl::GetOrphanTransactions() const
{
std::vector<TxOrphanage::OrphanInfo> result;
result.reserve(m_unique_orphans);
auto& index_by_wtxid = m_orphans.get<ByWtxid>();
auto it = index_by_wtxid.begin();
std::set<NodeId> this_orphan_announcers;
while (it != index_by_wtxid.end()) {
this_orphan_announcers.insert(it->m_announcer);
if (std::next(it) == index_by_wtxid.end() || std::next(it)->m_tx->GetWitnessHash() != it->m_tx->GetWitnessHash()) {
result.emplace_back(it->m_tx, std::move(this_orphan_announcers));
this_orphan_announcers.clear();
}
++it;
}
Assume(m_unique_orphans == result.size());
return result;
}
void TxOrphanageImpl::SanityCheck() const
{
std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
std::set<COutPoint> all_outpoints;
std::set<Wtxid> reconstructed_reconsiderable_wtxids;
for (auto it = m_orphans.begin(); it != m_orphans.end(); ++it) {
for (const auto& input : it->m_tx->vin) {
all_outpoints.insert(input.prevout);
}
unique_wtxids_to_scores.emplace(it->m_tx->GetWitnessHash(), std::make_pair(it->GetMemUsage(), it->GetLatencyScore() - 1));
auto& peer_info = reconstructed_peer_info[it->m_announcer];
peer_info.m_total_usage += it->GetMemUsage();
peer_info.m_count_announcements += 1;
peer_info.m_total_latency_score += it->GetLatencyScore();
if (it->m_reconsider) {
auto [_, added] = reconstructed_reconsiderable_wtxids.insert(it->m_tx->GetWitnessHash());
assert(added);
}
}
assert(reconstructed_peer_info.size() == m_peer_orphanage_info.size());
assert(reconstructed_peer_info == m_peer_orphanage_info);
assert(m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids);
assert(all_outpoints.size() == m_outpoint_to_orphan_wtxids.size());
for (const auto& [outpoint, wtxid_set] : m_outpoint_to_orphan_wtxids) {
assert(all_outpoints.contains(outpoint));
for (const auto& wtxid : wtxid_set) {
assert(unique_wtxids_to_scores.contains(wtxid));
}
}
assert(m_orphans.size() >= m_unique_orphans);
assert(m_orphans.size() <= m_peer_orphanage_info.size() * m_unique_orphans);
assert(unique_wtxids_to_scores.size() == m_unique_orphans);
const auto calculated_dedup_usage = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.first; });
assert(calculated_dedup_usage == m_unique_orphan_usage);
const auto summed_peer_usage = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
TxOrphanage::Usage{0}, [](TxOrphanage::Usage sum, const auto pair) { return sum + pair.second.m_total_usage; });
assert(summed_peer_usage >= m_unique_orphan_usage);
const auto calculated_total_latency_score = std::accumulate(unique_wtxids_to_scores.begin(), unique_wtxids_to_scores.end(),
TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.second; });
assert(calculated_total_latency_score == m_unique_rounded_input_scores);
const auto summed_peer_latency_score = std::accumulate(m_peer_orphanage_info.begin(), m_peer_orphanage_info.end(),
TxOrphanage::Count{0}, [](TxOrphanage::Count sum, const auto pair) { return sum + pair.second.m_total_latency_score; });
assert(summed_peer_latency_score >= m_unique_rounded_input_scores + m_orphans.size());
assert(!NeedsTrim());
}
TxOrphanage::Count TxOrphanageImpl::MaxGlobalLatencyScore() const { return m_max_global_latency_score; }
TxOrphanage::Count TxOrphanageImpl::TotalLatencyScore() const { return m_unique_rounded_input_scores + m_orphans.size(); }
TxOrphanage::Usage TxOrphanageImpl::ReservedPeerUsage() const { return m_reserved_usage_per_peer; }
TxOrphanage::Count TxOrphanageImpl::MaxPeerLatencyScore() const { return m_max_global_latency_score / std::max<unsigned int>(m_peer_orphanage_info.size(), 1); }
TxOrphanage::Usage TxOrphanageImpl::MaxGlobalUsage() const { return m_reserved_usage_per_peer * std::max<int64_t>(m_peer_orphanage_info.size(), 1); }
bool TxOrphanageImpl::NeedsTrim() const
{
return TotalLatencyScore() > MaxGlobalLatencyScore() || TotalOrphanUsage() > MaxGlobalUsage();
}
std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept
{
return std::make_unique<TxOrphanageImpl>();
}
std::unique_ptr<TxOrphanage> MakeTxOrphanage(TxOrphanage::Count max_global_latency_score, TxOrphanage::Usage reserved_peer_usage) noexcept
{
return std::make_unique<TxOrphanageImpl>(max_global_latency_score, reserved_peer_usage);
}
}