[prep/refactor] make TxOrphanage a virtual class implemented by TxOrphanageImpl

This commit is contained in:
glozow
2025-06-03 13:54:06 -04:00
parent 77ebe8f280
commit 3da6d7f8f6
8 changed files with 351 additions and 323 deletions

View File

@@ -97,7 +97,7 @@ void TxDownloadManagerImpl::ActiveTipChange()
void TxDownloadManagerImpl::BlockConnected(const std::shared_ptr<const CBlock>& pblock)
{
m_orphanage.EraseForBlock(*pblock);
m_orphanage->EraseForBlock(*pblock);
for (const auto& ptx : pblock->vtx) {
RecentConfirmedTransactionsFilter().insert(ptx->GetHash().ToUint256());
@@ -137,7 +137,7 @@ bool TxDownloadManagerImpl::AlreadyHaveTx(const GenTxid& gtxid, bool include_rec
// While we won't query by txid, we can try to "guess" what the wtxid is based on the txid.
// A non-segwit transaction's txid == wtxid. Query this txhash "casted" to a wtxid. This will
// help us find non-segwit transactions, saving bandwidth, and should have no false positives.
if (m_orphanage.HaveTx(Wtxid::FromUint256(hash))) return true;
if (m_orphanage->HaveTx(Wtxid::FromUint256(hash))) return true;
if (include_reconsiderable && RecentRejectsReconsiderableFilter().contains(hash)) return true;
@@ -157,7 +157,7 @@ void TxDownloadManagerImpl::ConnectedPeer(NodeId nodeid, const TxDownloadConnect
void TxDownloadManagerImpl::DisconnectedPeer(NodeId nodeid)
{
m_orphanage.EraseForPeer(nodeid);
m_orphanage->EraseForPeer(nodeid);
m_txrequest.DisconnectedPeer(nodeid);
if (auto it = m_peer_info.find(nodeid); it != m_peer_info.end()) {
@@ -174,7 +174,7 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid,
// - exists in orphanage
// - peer can be an orphan resolution candidate
if (const auto* wtxid = std::get_if<Wtxid>(&gtxid)) {
if (auto orphan_tx{m_orphanage.GetTx(*wtxid)}) {
if (auto orphan_tx{m_orphanage->GetTx(*wtxid)}) {
auto unique_parents{GetUniqueParents(*orphan_tx)};
std::erase_if(unique_parents, [&](const auto& txid) {
return AlreadyHaveTx(txid, /*include_reconsiderable=*/false);
@@ -187,7 +187,7 @@ bool TxDownloadManagerImpl::AddTxAnnouncement(NodeId peer, const GenTxid& gtxid,
}
if (MaybeAddOrphanResolutionCandidate(unique_parents, *wtxid, peer, now)) {
m_orphanage.AddAnnouncer(orphan_tx->GetWitnessHash(), peer);
m_orphanage->AddAnnouncer(orphan_tx->GetWitnessHash(), peer);
}
// Return even if the peer isn't an orphan resolution candidate. This would be caught by AlreadyHaveTx.
@@ -227,7 +227,7 @@ bool TxDownloadManagerImpl::MaybeAddOrphanResolutionCandidate(const std::vector<
{
auto it_peer = m_peer_info.find(nodeid);
if (it_peer == m_peer_info.end()) return false;
if (m_orphanage.HaveTxFromPeer(wtxid, nodeid)) return false;
if (m_orphanage->HaveTxFromPeer(wtxid, nodeid)) return false;
const auto& peer_entry = m_peer_info.at(nodeid);
const auto& info = peer_entry.m_connection_info;
@@ -305,7 +305,7 @@ std::optional<PackageToValidate> TxDownloadManagerImpl::Find1P1CPackage(const CT
// children instead of the real one provided by the honest peer. Since we track all announcers
// of an orphan, this does not exclude parent + orphan pairs that we happened to request from
// different peers.
const auto cpfp_candidates_same_peer{m_orphanage.GetChildrenFromSamePeer(ptx, nodeid)};
const auto cpfp_candidates_same_peer{m_orphanage->GetChildrenFromSamePeer(ptx, nodeid)};
// These children should be sorted from newest to oldest. In the (probably uncommon) case
// of children that replace each other, this helps us accept the highest feerate (probably the
@@ -327,9 +327,9 @@ void TxDownloadManagerImpl::MempoolAcceptedTx(const CTransactionRef& tx)
m_txrequest.ForgetTxHash(tx->GetHash());
m_txrequest.ForgetTxHash(tx->GetWitnessHash());
m_orphanage.AddChildrenToWorkSet(*tx, m_opts.m_rng);
m_orphanage->AddChildrenToWorkSet(*tx, m_opts.m_rng);
// If it came from the orphanage, remove it. No-op if the tx is not in txorphanage.
m_orphanage.EraseTx(tx->GetWitnessHash());
m_orphanage->EraseTx(tx->GetWitnessHash());
}
std::vector<Txid> TxDownloadManagerImpl::GetUniqueParents(const CTransaction& tx)
@@ -398,7 +398,7 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
const auto& wtxid = ptx->GetWitnessHash();
// Potentially flip add_extra_compact_tx to false if tx is already in orphanage, which
// means it was already added to vExtraTxnForCompact.
add_extra_compact_tx &= !m_orphanage.HaveTx(wtxid);
add_extra_compact_tx &= !m_orphanage->HaveTx(wtxid);
// If there is no candidate for orphan resolution, AddTx will not be called. This means
// that if a peer is overloading us with invs and orphans, they will eventually not be
@@ -411,7 +411,7 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
for (const auto& nodeid : orphan_resolution_candidates) {
if (MaybeAddOrphanResolutionCandidate(unique_parents, ptx->GetWitnessHash(), nodeid, now)) {
m_orphanage.AddTx(ptx, nodeid);
m_orphanage->AddTx(ptx, nodeid);
}
}
@@ -422,7 +422,7 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
// DoS prevention: do not allow m_orphanage to grow unbounded (see CVE-2012-3789)
// Note that, if the orphanage reaches capacity, it's possible that we immediately evict
// the transaction we just added.
m_orphanage.LimitOrphans(m_opts.m_rng);
m_orphanage->LimitOrphans(m_opts.m_rng);
} else {
unique_parents.clear();
LogDebug(BCLog::MEMPOOL, "not keeping orphan with rejected parents %s (wtxid=%s)\n",
@@ -491,7 +491,7 @@ node::RejectedTxTodo TxDownloadManagerImpl::MempoolRejectedTx(const CTransaction
// If the tx failed in ProcessOrphanTx, it should be removed from the orphanage unless the
// tx was still missing inputs. If the tx was not in the orphanage, EraseTx does nothing and returns 0.
if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS && m_orphanage.EraseTx(ptx->GetWitnessHash()) > 0) {
if (state.GetResult() != TxValidationResult::TX_MISSING_INPUTS && m_orphanage->EraseTx(ptx->GetWitnessHash()) > 0) {
LogDebug(BCLog::TXPACKAGES, " removed orphan tx %s (wtxid=%s)\n", ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
}
@@ -561,28 +561,28 @@ std::pair<bool, std::optional<PackageToValidate>> TxDownloadManagerImpl::Receive
bool TxDownloadManagerImpl::HaveMoreWork(NodeId nodeid)
{
return m_orphanage.HaveTxToReconsider(nodeid);
return m_orphanage->HaveTxToReconsider(nodeid);
}
CTransactionRef TxDownloadManagerImpl::GetTxToReconsider(NodeId nodeid)
{
return m_orphanage.GetTxToReconsider(nodeid);
return m_orphanage->GetTxToReconsider(nodeid);
}
void TxDownloadManagerImpl::CheckIsEmpty(NodeId nodeid)
{
assert(m_txrequest.Count(nodeid) == 0);
assert(m_orphanage.UsageByPeer(nodeid) == 0);
assert(m_orphanage->UsageByPeer(nodeid) == 0);
}
void TxDownloadManagerImpl::CheckIsEmpty()
{
assert(m_orphanage.TotalOrphanUsage() == 0);
assert(m_orphanage.Size() == 0);
assert(m_orphanage->TotalOrphanUsage() == 0);
assert(m_orphanage->Size() == 0);
assert(m_txrequest.Size() == 0);
assert(m_num_wtxid_peers == 0);
}
std::vector<TxOrphanage::OrphanTxBase> TxDownloadManagerImpl::GetOrphanTransactions() const
{
return m_orphanage.GetOrphanTransactions();
return m_orphanage->GetOrphanTransactions();
}
} // namespace node

View File

@@ -22,7 +22,7 @@ public:
TxDownloadOptions m_opts;
/** Manages unvalidated tx data (orphan transactions for which we are downloading ancestors). */
TxOrphanage m_orphanage;
std::unique_ptr<TxOrphanage> m_orphanage;
/** Tracks candidates for requesting and downloading transaction data. */
TxRequestTracker m_txrequest;
@@ -128,7 +128,7 @@ public:
return *m_lazy_recent_confirmed_transactions;
}
TxDownloadManagerImpl(const TxDownloadOptions& options) : m_opts{options}, m_txrequest{options.m_deterministic_txrequest} {}
TxDownloadManagerImpl(const TxDownloadOptions& options) : m_opts{options}, m_orphanage{MakeTxOrphanage()}, m_txrequest{options.m_deterministic_txrequest} {}
struct PeerInfo {
/** Information relevant to scheduling tx requests. */

View File

@@ -12,8 +12,85 @@
#include <cassert>
namespace node{
bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
namespace node {
class TxOrphanageImpl final : public TxOrphanage {
private:
struct OrphanTx : public OrphanTxBase {
NodeSeconds nTimeExpire;
size_t list_pos;
};
/** Total usage (weight) of all entries in m_orphans. */
int64_t m_total_orphan_usage{0};
/** Total number of <peer, tx> pairs. Can be larger than m_orphans.size() because multiple peers
* may have announced the same orphan. */
unsigned int m_total_announcements{0};
/** Map from wtxid to orphan transaction record. Limited by
* DEFAULT_MAX_ORPHAN_TRANSACTIONS */
std::map<Wtxid, OrphanTx> m_orphans;
struct PeerOrphanInfo {
/** List of transactions that should be reconsidered: added to in AddChildrenToWorkSet,
* removed from one-by-one with each call to GetTxToReconsider. The wtxids may refer to
* transactions that are no longer present in orphanage; these are lazily removed in
* GetTxToReconsider. */
std::set<Wtxid> m_work_set;
/** Total weight of orphans for which this peer is an announcer.
* If orphans are provided by different peers, its weight will be accounted for in each
* PeerOrphanInfo, so the total of all peers' m_total_usage may be larger than
* m_total_orphan_size. If a peer is removed as an announcer, even if the orphan still
* remains in the orphanage, this number will be decremented. */
int64_t m_total_usage{0};
};
std::map<NodeId, PeerOrphanInfo> m_peer_orphanage_info;
using OrphanMap = decltype(m_orphans);
struct IteratorComparator
{
template<typename I>
bool operator()(const I& a, const I& b) const
{
return a->first < b->first;
}
};
/** Index from the parents' COutPoint into the m_orphans. Used
* to remove orphan transactions from the m_orphans */
std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it;
/** Orphan transactions in vector for quick random eviction */
std::vector<OrphanMap::iterator> m_orphan_list;
/** Timestamp for the next scheduled sweep of expired orphans */
NodeSeconds m_next_sweep{0s};
public:
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;
int EraseTx(const Wtxid& wtxid) override;
void EraseForPeer(NodeId peer) override;
void EraseForBlock(const CBlock& block) override;
void LimitOrphans(FastRandomContext& rng) override;
void AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) override;
bool HaveTxToReconsider(NodeId peer) override;
std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override;
size_t Size() const override { return m_orphans.size(); }
std::vector<OrphanTxBase> GetOrphanTransactions() const override;
int64_t TotalOrphanUsage() const override { return m_total_orphan_usage; }
int64_t UsageByPeer(NodeId peer) const override;
void SanityCheck() const override;
};
bool TxOrphanageImpl::AddTx(const CTransactionRef& tx, NodeId peer)
{
const Txid& hash = tx->GetHash();
const Wtxid& wtxid = tx->GetWitnessHash();
@@ -53,7 +130,7 @@ bool TxOrphanage::AddTx(const CTransactionRef& tx, NodeId peer)
return true;
}
bool TxOrphanage::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
bool TxOrphanageImpl::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
{
const auto it = m_orphans.find(wtxid);
if (it != m_orphans.end()) {
@@ -70,7 +147,7 @@ bool TxOrphanage::AddAnnouncer(const Wtxid& wtxid, NodeId peer)
return false;
}
int TxOrphanage::EraseTx(const Wtxid& wtxid)
int TxOrphanageImpl::EraseTx(const Wtxid& wtxid)
{
std::map<Wtxid, OrphanTx>::iterator it = m_orphans.find(wtxid);
if (it == m_orphans.end())
@@ -116,7 +193,7 @@ int TxOrphanage::EraseTx(const Wtxid& wtxid)
return 1;
}
void TxOrphanage::EraseForPeer(NodeId peer)
void TxOrphanageImpl::EraseForPeer(NodeId peer)
{
// Zeroes out this peer's m_total_usage.
m_peer_orphanage_info.erase(peer);
@@ -141,7 +218,7 @@ void TxOrphanage::EraseForPeer(NodeId peer)
if (nErased > 0) LogDebug(BCLog::TXPACKAGES, "Erased %d orphan transaction(s) from peer=%d\n", nErased, peer);
}
void TxOrphanage::LimitOrphans(FastRandomContext& rng)
void TxOrphanageImpl::LimitOrphans(FastRandomContext& rng)
{
unsigned int nEvicted = 0;
auto nNow{Now<NodeSeconds>()};
@@ -173,7 +250,7 @@ void TxOrphanage::LimitOrphans(FastRandomContext& rng)
if (nEvicted > 0) LogDebug(BCLog::TXPACKAGES, "orphanage overflow, removed %u tx\n", nEvicted);
}
void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
void TxOrphanageImpl::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng)
{
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const auto it_by_prev = m_outpoint_to_orphan_it.find(COutPoint(tx.GetHash(), i));
@@ -201,24 +278,25 @@ void TxOrphanage::AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext
}
}
bool TxOrphanage::HaveTx(const Wtxid& wtxid) const
bool TxOrphanageImpl::HaveTx(const Wtxid& wtxid) const
{
return m_orphans.count(wtxid);
}
CTransactionRef TxOrphanage::GetTx(const Wtxid& wtxid) const
CTransactionRef TxOrphanageImpl::GetTx(const Wtxid& wtxid) const
{
auto it = m_orphans.find(wtxid);
return it != m_orphans.end() ? it->second.tx : nullptr;
}
bool TxOrphanage::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
bool TxOrphanageImpl::HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const
{
auto it = m_orphans.find(wtxid);
return (it != m_orphans.end() && it->second.announcers.contains(peer));
}
CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer)
{
auto peer_it = m_peer_orphanage_info.find(peer);
if (peer_it == m_peer_orphanage_info.end()) return nullptr;
@@ -236,7 +314,7 @@ CTransactionRef TxOrphanage::GetTxToReconsider(NodeId peer)
return nullptr;
}
bool TxOrphanage::HaveTxToReconsider(NodeId peer)
bool TxOrphanageImpl::HaveTxToReconsider(NodeId peer)
{
auto peer_it = m_peer_orphanage_info.find(peer);
if (peer_it == m_peer_orphanage_info.end()) return false;
@@ -245,7 +323,7 @@ bool TxOrphanage::HaveTxToReconsider(NodeId peer)
return !work_set.empty();
}
void TxOrphanage::EraseForBlock(const CBlock& block)
void TxOrphanageImpl::EraseForBlock(const CBlock& block)
{
std::vector<Wtxid> vOrphanErase;
@@ -273,7 +351,7 @@ void TxOrphanage::EraseForBlock(const CBlock& block)
}
}
std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const
std::vector<CTransactionRef> TxOrphanageImpl::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const
{
// First construct a vector of iterators to ensure we do not return duplicates of the same tx
// and so we can sort by nTimeExpire.
@@ -313,7 +391,7 @@ std::vector<CTransactionRef> TxOrphanage::GetChildrenFromSamePeer(const CTransac
return children_found;
}
std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() const
std::vector<TxOrphanage::OrphanTxBase> TxOrphanageImpl::GetOrphanTransactions() const
{
std::vector<OrphanTxBase> ret;
ret.reserve(m_orphans.size());
@@ -323,7 +401,13 @@ std::vector<TxOrphanage::OrphanTxBase> TxOrphanage::GetOrphanTransactions() cons
return ret;
}
void TxOrphanage::SanityCheck() const
int64_t TxOrphanageImpl::UsageByPeer(NodeId peer) const
{
auto peer_it = m_peer_orphanage_info.find(peer);
return peer_it == m_peer_orphanage_info.end() ? 0 : peer_it->second.m_total_usage;
}
void TxOrphanageImpl::SanityCheck() const
{
// Check that cached m_total_announcements is correct
unsigned int counted_total_announcements{0};
@@ -362,4 +446,10 @@ void TxOrphanage::SanityCheck() const
}
}
}
std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept
{
return std::make_unique<TxOrphanageImpl>();
}
} // namespace node

View File

@@ -15,7 +15,7 @@
#include <map>
#include <set>
namespace node{
namespace node {
/** Expiration time for orphan transactions */
static constexpr auto ORPHAN_TX_EXPIRE_TIME{20min};
/** Minimum time between orphan transactions expire time checks */
@@ -31,56 +31,6 @@ static const uint32_t DEFAULT_MAX_ORPHAN_TRANSACTIONS{100};
*/
class TxOrphanage {
public:
/** Add a new orphan transaction */
bool AddTx(const CTransactionRef& tx, NodeId peer);
/** Add an additional announcer to an orphan if it exists. Otherwise, do nothing. */
bool AddAnnouncer(const Wtxid& wtxid, NodeId peer);
CTransactionRef GetTx(const Wtxid& wtxid) const;
/** Check if we already have an orphan transaction (by wtxid only) */
bool HaveTx(const Wtxid& wtxid) const;
/** Check if a {tx, peer} exists in the orphanage.*/
bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const;
/** Extract a transaction from a peer's work set
* Returns nullptr if there are no transactions to work on.
* Otherwise returns the transaction reference, and removes
* it from the work set.
*/
CTransactionRef GetTxToReconsider(NodeId peer);
/** Erase an orphan by wtxid */
int EraseTx(const Wtxid& wtxid);
/** Maybe erase all orphans announced by a peer (eg, after that peer disconnects). If an orphan
* has been announced by another peer, don't erase, just remove this peer from the list of announcers. */
void EraseForPeer(NodeId peer);
/** Erase all orphans included in or invalidated by a new block */
void EraseForBlock(const CBlock& block);
/** Limit the orphanage to DEFAULT_MAX_ORPHAN_TRANSACTIONS. */
void LimitOrphans(FastRandomContext& rng);
/** Add any orphans that list a particular tx as a parent into the from peer's work set */
void AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng);
/** Does this peer have any work to do? */
bool HaveTxToReconsider(NodeId peer);
/** Get all children that spend from this tx and were received from nodeid. Sorted from most
* recent to least recent. */
std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const;
/** Return how many entries exist in the orphange */
size_t Size() const
{
return m_orphans.size();
}
/** Allows providing orphan information externally */
struct OrphanTxBase {
CTransactionRef tx;
@@ -93,77 +43,75 @@ public:
}
};
std::vector<OrphanTxBase> GetOrphanTransactions() const;
virtual ~TxOrphanage() = default;
/** Add a new orphan transaction */
virtual bool AddTx(const CTransactionRef& tx, NodeId peer) = 0;
/** Add an additional announcer to an orphan if it exists. Otherwise, do nothing. */
virtual bool AddAnnouncer(const Wtxid& wtxid, NodeId peer) = 0;
/** Get a transaction by its witness txid */
virtual CTransactionRef GetTx(const Wtxid& wtxid) const = 0;
/** Check if we already have an orphan transaction (by wtxid only) */
virtual bool HaveTx(const Wtxid& wtxid) const = 0;
/** Check if a {tx, peer} exists in the orphanage.*/
virtual bool HaveTxFromPeer(const Wtxid& wtxid, NodeId peer) const = 0;
/** Extract a transaction from a peer's work set
* Returns nullptr if there are no transactions to work on.
* Otherwise returns the transaction reference, and removes
* it from the work set.
*/
virtual CTransactionRef GetTxToReconsider(NodeId peer) = 0;
/** Erase an orphan by wtxid */
virtual int EraseTx(const Wtxid& wtxid) = 0;
/** Maybe erase all orphans announced by a peer (eg, after that peer disconnects). If an orphan
* has been announced by another peer, don't erase, just remove this peer from the list of announcers. */
virtual void EraseForPeer(NodeId peer) = 0;
/** Erase all orphans included in or invalidated by a new block */
virtual void EraseForBlock(const CBlock& block) = 0;
/** Limit the orphanage to DEFAULT_MAX_ORPHAN_TRANSACTIONS. */
virtual void LimitOrphans(FastRandomContext& rng) = 0;
/** Add any orphans that list a particular tx as a parent into the from peer's work set */
virtual void AddChildrenToWorkSet(const CTransaction& tx, FastRandomContext& rng) = 0;
/** Does this peer have any work to do? */
virtual bool HaveTxToReconsider(NodeId peer) = 0;
/** Get all children that spend from this tx and were received from nodeid. Sorted from most
* recent to least recent. */
virtual std::vector<CTransactionRef> GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const = 0;
/** Return how many entries exist in the orphange */
virtual size_t Size() const = 0;
/** Get all orphan transactions */
virtual std::vector<OrphanTxBase> GetOrphanTransactions() const = 0;
/** Get the total usage (weight) of all orphans. If an orphan has multiple announcers, its usage is
* only counted once within this total. */
int64_t TotalOrphanUsage() const { return m_total_orphan_usage; }
virtual int64_t TotalOrphanUsage() const = 0;
/** Total usage (weight) of orphans for which this peer is an announcer. If an orphan has multiple
* announcers, its weight will be accounted for in each PeerOrphanInfo, so the total of all
* peers' UsageByPeer() may be larger than TotalOrphanBytes(). */
int64_t UsageByPeer(NodeId peer) const {
auto peer_it = m_peer_orphanage_info.find(peer);
return peer_it == m_peer_orphanage_info.end() ? 0 : peer_it->second.m_total_usage;
}
virtual int64_t UsageByPeer(NodeId peer) const = 0;
/** Check consistency between PeerOrphanInfo and m_orphans. Recalculate counters and ensure they
* match what is cached. */
void SanityCheck() const;
protected:
struct OrphanTx : public OrphanTxBase {
NodeSeconds nTimeExpire;
size_t list_pos;
};
/** Total usage (weight) of all entries in m_orphans. */
int64_t m_total_orphan_usage{0};
/** Total number of <peer, tx> pairs. Can be larger than m_orphans.size() because multiple peers
* may have announced the same orphan. */
unsigned int m_total_announcements{0};
/** Map from wtxid to orphan transaction record. Limited by
* DEFAULT_MAX_ORPHAN_TRANSACTIONS */
std::map<Wtxid, OrphanTx> m_orphans;
struct PeerOrphanInfo {
/** List of transactions that should be reconsidered: added to in AddChildrenToWorkSet,
* removed from one-by-one with each call to GetTxToReconsider. The wtxids may refer to
* transactions that are no longer present in orphanage; these are lazily removed in
* GetTxToReconsider. */
std::set<Wtxid> m_work_set;
/** Total weight of orphans for which this peer is an announcer.
* If orphans are provided by different peers, its weight will be accounted for in each
* PeerOrphanInfo, so the total of all peers' m_total_usage may be larger than
* m_total_orphan_size. If a peer is removed as an announcer, even if the orphan still
* remains in the orphanage, this number will be decremented. */
int64_t m_total_usage{0};
};
std::map<NodeId, PeerOrphanInfo> m_peer_orphanage_info;
using OrphanMap = decltype(m_orphans);
struct IteratorComparator
{
template<typename I>
bool operator()(const I& a, const I& b) const
{
return a->first < b->first;
}
};
/** Index from the parents' COutPoint into the m_orphans. Used
* to remove orphan transactions from the m_orphans */
std::map<COutPoint, std::set<OrphanMap::iterator, IteratorComparator>> m_outpoint_to_orphan_it;
/** Orphan transactions in vector for quick random eviction */
std::vector<OrphanMap::iterator> m_orphan_list;
/** Timestamp for the next scheduled sweep of expired orphans */
NodeSeconds m_next_sweep{0s};
virtual void SanityCheck() const = 0;
};
/** Create a new TxOrphanage instance */
std::unique_ptr<TxOrphanage> MakeTxOrphanage() noexcept;
} // namespace node
#endif // BITCOIN_NODE_TXORPHANAGE_H

View File

@@ -279,11 +279,9 @@ static bool HasRelayPermissions(NodeId peer) { return peer == 0; }
static void CheckInvariants(const node::TxDownloadManagerImpl& txdownload_impl, size_t max_orphan_count)
{
const node::TxOrphanage& orphanage = txdownload_impl.m_orphanage;
// Orphanage usage should never exceed what is allowed
Assert(orphanage.Size() <= max_orphan_count);
txdownload_impl.m_orphanage.SanityCheck();
Assert(txdownload_impl.m_orphanage->Size() <= max_orphan_count);
txdownload_impl.m_orphanage->SanityCheck();
// We should never have more than the maximum in-flight requests out for a peer.
for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
@@ -349,7 +347,7 @@ FUZZ_TARGET(txdownloadman_impl, .init = initialize)
block.vtx.push_back(rand_tx);
txdownload_impl.BlockConnected(std::make_shared<CBlock>(block));
// Block transactions must be removed from orphanage
Assert(!txdownload_impl.m_orphanage.HaveTx(rand_tx->GetWitnessHash()));
Assert(!txdownload_impl.m_orphanage->HaveTx(rand_tx->GetWitnessHash()));
},
[&] {
txdownload_impl.BlockDisconnected();
@@ -401,7 +399,7 @@ FUZZ_TARGET(txdownloadman_impl, .init = initialize)
const auto& package = maybe_package->m_txns;
// Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage
Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256()));
Assert(txdownload_impl.m_orphanage.HaveTx(maybe_package->m_txns.back()->GetWitnessHash()));
Assert(txdownload_impl.m_orphanage->HaveTx(maybe_package->m_txns.back()->GetWitnessHash()));
// Package has not been rejected
Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package)));
// Neither is in m_lazy_recent_rejects

View File

@@ -37,7 +37,7 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};
SetMockTime(ConsumeTime(fuzzed_data_provider));
node::TxOrphanage orphanage;
auto orphanage = node::MakeTxOrphanage();
std::vector<COutPoint> outpoints; // Duplicates are tolerated
outpoints.reserve(200'000);
@@ -86,11 +86,11 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
// previous loop and potentially the parent of this tx.
if (ptx_potential_parent) {
// Set up future GetTxToReconsider call.
orphanage.AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
orphanage->AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
// Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
for (const auto& child : orphanage.GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
return input.prevout.hash == ptx_potential_parent->GetHash();
}));
@@ -101,60 +101,60 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * node::DEFAULT_MAX_ORPHAN_TRANSACTIONS)
{
NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
const auto total_bytes_start{orphanage.TotalOrphanUsage()};
const auto total_peer_bytes_start{orphanage.UsageByPeer(peer_id)};
const auto total_bytes_start{orphanage->TotalOrphanUsage()};
const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)};
const auto tx_weight{GetTransactionWeight(*tx)};
CallOneOf(
fuzzed_data_provider,
[&] {
{
CTransactionRef ref = orphanage.GetTxToReconsider(peer_id);
CTransactionRef ref = orphanage->GetTxToReconsider(peer_id);
if (ref) {
Assert(orphanage.HaveTx(ref->GetWitnessHash()));
Assert(orphanage->HaveTx(ref->GetWitnessHash()));
}
}
},
[&] {
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
// AddTx should return false if tx is too big or already have it
// tx weight is unknown, we only check when tx is already in orphanage
{
bool add_tx = orphanage.AddTx(tx, peer_id);
bool add_tx = orphanage->AddTx(tx, peer_id);
// have_tx == true -> add_tx == false
Assert(!have_tx || !add_tx);
if (add_tx) {
Assert(orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
Assert(orphanage.TotalOrphanUsage() == tx_weight + total_bytes_start);
Assert(orphanage->UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
Assert(orphanage->TotalOrphanUsage() == tx_weight + total_bytes_start);
Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT);
} else {
// Peer may have been added as an announcer.
if (orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start) {
Assert(orphanage.HaveTxFromPeer(wtxid, peer_id));
if (orphanage->UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start) {
Assert(orphanage->HaveTxFromPeer(wtxid, peer_id));
} else {
// Otherwise, there must not be any change to the peer byte count.
Assert(orphanage.UsageByPeer(peer_id) == total_peer_bytes_start);
Assert(orphanage->UsageByPeer(peer_id) == total_peer_bytes_start);
}
// Regardless, total bytes should not have changed.
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
Assert(orphanage->TotalOrphanUsage() == total_bytes_start);
}
}
have_tx = orphanage.HaveTx(tx->GetWitnessHash());
have_tx = orphanage->HaveTx(tx->GetWitnessHash());
{
bool add_tx = orphanage.AddTx(tx, peer_id);
bool add_tx = orphanage->AddTx(tx, peer_id);
// if have_tx is still false, it must be too big
Assert(!have_tx == (tx_weight > MAX_STANDARD_TX_WEIGHT));
Assert(!have_tx || !add_tx);
}
},
[&] {
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
bool have_tx_and_peer = orphanage.HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
// AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
{
bool added_announcer = orphanage.AddAnnouncer(tx->GetWitnessHash(), peer_id);
bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
// have_tx == false -> added_announcer == false
Assert(have_tx || !added_announcer);
// have_tx_and_peer == true -> added_announcer == false
@@ -162,43 +162,43 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
// Total bytes should not have changed. If peer was added as announcer, byte
// accounting must have been updated.
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
Assert(orphanage->TotalOrphanUsage() == total_bytes_start);
if (added_announcer) {
Assert(orphanage.UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
Assert(orphanage->UsageByPeer(peer_id) == tx_weight + total_peer_bytes_start);
} else {
Assert(orphanage.UsageByPeer(peer_id) == total_peer_bytes_start);
Assert(orphanage->UsageByPeer(peer_id) == total_peer_bytes_start);
}
}
},
[&] {
bool have_tx = orphanage.HaveTx(tx->GetWitnessHash());
bool have_tx_and_peer{orphanage.HaveTxFromPeer(wtxid, peer_id)};
bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)};
// EraseTx should return 0 if m_orphans doesn't have the tx
{
auto bytes_from_peer_before{orphanage.UsageByPeer(peer_id)};
Assert(have_tx == orphanage.EraseTx(tx->GetWitnessHash()));
auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)};
Assert(have_tx == orphanage->EraseTx(tx->GetWitnessHash()));
if (have_tx) {
Assert(orphanage.TotalOrphanUsage() == total_bytes_start - tx_weight);
Assert(orphanage->TotalOrphanUsage() == total_bytes_start - tx_weight);
if (have_tx_and_peer) {
Assert(orphanage.UsageByPeer(peer_id) == bytes_from_peer_before - tx_weight);
Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before - tx_weight);
} else {
Assert(orphanage.UsageByPeer(peer_id) == bytes_from_peer_before);
Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before);
}
} else {
Assert(orphanage.TotalOrphanUsage() == total_bytes_start);
Assert(orphanage->TotalOrphanUsage() == total_bytes_start);
}
}
have_tx = orphanage.HaveTx(tx->GetWitnessHash());
have_tx_and_peer = orphanage.HaveTxFromPeer(wtxid, peer_id);
have_tx = orphanage->HaveTx(tx->GetWitnessHash());
have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
// have_tx should be false and EraseTx should fail
{
Assert(!have_tx && !have_tx_and_peer && !orphanage.EraseTx(wtxid));
Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid));
}
},
[&] {
orphanage.EraseForPeer(peer_id);
Assert(!orphanage.HaveTxFromPeer(tx->GetWitnessHash(), peer_id));
Assert(orphanage.UsageByPeer(peer_id) == 0);
orphanage->EraseForPeer(peer_id);
Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id));
Assert(orphanage->UsageByPeer(peer_id) == 0);
},
[&] {
// Make a block out of txs and then EraseForBlock
@@ -208,17 +208,17 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history);
block.vtx.push_back(tx_to_remove);
}
orphanage.EraseForBlock(block);
orphanage->EraseForBlock(block);
for (const auto& tx_removed : block.vtx) {
Assert(!orphanage.HaveTx(tx_removed->GetWitnessHash()));
Assert(!orphanage.HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));
Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash()));
Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));
}
},
[&] {
// test mocktime and expiry
SetMockTime(ConsumeTime(fuzzed_data_provider));
orphanage.LimitOrphans(orphanage_rng);
Assert(orphanage.Size() <= node::DEFAULT_MAX_ORPHAN_TRANSACTIONS);
orphanage->LimitOrphans(orphanage_rng);
Assert(orphanage->Size() <= node::DEFAULT_MAX_ORPHAN_TRANSACTIONS);
});
}
@@ -228,9 +228,9 @@ FUZZ_TARGET(txorphan, .init = initialize_orphanage)
ptx_potential_parent = tx;
}
const bool have_tx{orphanage.HaveTx(tx->GetWitnessHash())};
const bool get_tx_nonnull{orphanage.GetTx(tx->GetWitnessHash()) != nullptr};
const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())};
const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr};
Assert(have_tx == get_tx_nonnull);
}
orphanage.SanityCheck();
orphanage->SanityCheck();
}

View File

@@ -21,14 +21,6 @@
BOOST_FIXTURE_TEST_SUITE(orphanage_tests, TestingSetup)
class TxOrphanageTest : public node::TxOrphanage
{
public:
TxOrphanageTest(FastRandomContext& rng) : m_rng{rng} {}
FastRandomContext& m_rng;
};
static void MakeNewKeyWithFastRandomContext(CKey& key, FastRandomContext& rand_ctx)
{
std::vector<unsigned char> keydata;
@@ -90,7 +82,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
// signature's R and S values have leading zeros.
m_rng.Reseed(uint256{33});
TxOrphanageTest orphanage{m_rng};
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
CKey key;
MakeNewKeyWithFastRandomContext(key, m_rng);
FillableSigningProvider keystore;
@@ -115,7 +107,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
tx.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey()));
auto ptx = MakeTransactionRef(tx);
orphanage.AddTx(ptx, i);
orphanage->AddTx(ptx, i);
orphans_added.emplace_back(ptx);
}
@@ -135,7 +127,7 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0, SIGHASH_ALL, empty));
auto ptx = MakeTransactionRef(tx);
orphanage.AddTx(ptx, i);
orphanage->AddTx(ptx, i);
orphans_added.emplace_back(ptx);
}
@@ -161,50 +153,50 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
for (unsigned int j = 1; j < tx.vin.size(); j++)
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), i));
BOOST_CHECK(!orphanage->AddTx(MakeTransactionRef(tx), i));
}
size_t expected_num_orphans = orphanage.Size();
size_t expected_num_orphans = orphanage->Size();
// Non-existent peer; nothing should be deleted
orphanage.EraseForPeer(/*peer=*/-1);
BOOST_CHECK_EQUAL(orphanage.Size(), expected_num_orphans);
orphanage->EraseForPeer(/*peer=*/-1);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_num_orphans);
// Each of first three peers stored
// two transactions each.
for (NodeId i = 0; i < 3; i++)
{
orphanage.EraseForPeer(i);
orphanage->EraseForPeer(i);
expected_num_orphans -= 2;
BOOST_CHECK(orphanage.Size() == expected_num_orphans);
BOOST_CHECK(orphanage->Size() == expected_num_orphans);
}
// Test LimitOrphanTxSize() function, nothing should timeout:
FastRandomContext rng{/*fDeterministic=*/true};
orphanage.LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage.Size(), expected_num_orphans);
orphanage->LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_num_orphans);
// Add one more orphan, check timeout logic
auto timeout_tx = MakeTransactionSpending(/*outpoints=*/{}, rng);
orphanage.AddTx(timeout_tx, 0);
orphanage->AddTx(timeout_tx, 0);
expected_num_orphans += 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_num_orphans);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_num_orphans);
// One second shy of expiration
SetMockTime(now + node::ORPHAN_TX_EXPIRE_TIME - 1s);
orphanage.LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage.Size(), expected_num_orphans);
orphanage->LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_num_orphans);
// Jump one more second, orphan should be timed out on limiting
SetMockTime(now + node::ORPHAN_TX_EXPIRE_TIME);
orphanage.LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage.Size(), 0);
orphanage->LimitOrphans(rng);
BOOST_CHECK_EQUAL(orphanage->Size(), 0);
}
BOOST_AUTO_TEST_CASE(same_txid_diff_witness)
{
FastRandomContext det_rand{true};
node::TxOrphanage orphanage;
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
NodeId peer{0};
std::vector<COutPoint> empty_outpoints;
@@ -218,31 +210,31 @@ BOOST_AUTO_TEST_CASE(same_txid_diff_witness)
const auto& mutated_wtxid = child_mutated->GetWitnessHash();
BOOST_CHECK(normal_wtxid != mutated_wtxid);
BOOST_CHECK(orphanage.AddTx(child_normal, peer));
BOOST_CHECK(orphanage->AddTx(child_normal, peer));
// EraseTx fails as transaction by this wtxid doesn't exist.
BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 0);
BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
BOOST_CHECK(orphanage.GetTx(normal_wtxid) == child_normal);
BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
BOOST_CHECK(orphanage.GetTx(mutated_wtxid) == nullptr);
BOOST_CHECK_EQUAL(orphanage->EraseTx(mutated_wtxid), 0);
BOOST_CHECK(orphanage->HaveTx(normal_wtxid));
BOOST_CHECK(orphanage->GetTx(normal_wtxid) == child_normal);
BOOST_CHECK(!orphanage->HaveTx(mutated_wtxid));
BOOST_CHECK(orphanage->GetTx(mutated_wtxid) == nullptr);
// Must succeed. Both transactions should be present in orphanage.
BOOST_CHECK(orphanage.AddTx(child_mutated, peer));
BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
BOOST_CHECK(orphanage.HaveTx(mutated_wtxid));
BOOST_CHECK(orphanage->AddTx(child_mutated, peer));
BOOST_CHECK(orphanage->HaveTx(normal_wtxid));
BOOST_CHECK(orphanage->HaveTx(mutated_wtxid));
// Outpoints map should track all entries: check that both are returned as children of the parent.
std::set<CTransactionRef> expected_children{child_normal, child_mutated};
BOOST_CHECK(EqualTxns(expected_children, orphanage.GetChildrenFromSamePeer(parent, peer)));
BOOST_CHECK(EqualTxns(expected_children, orphanage->GetChildrenFromSamePeer(parent, peer)));
// Erase by wtxid: mutated first
BOOST_CHECK_EQUAL(orphanage.EraseTx(mutated_wtxid), 1);
BOOST_CHECK(orphanage.HaveTx(normal_wtxid));
BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
BOOST_CHECK_EQUAL(orphanage->EraseTx(mutated_wtxid), 1);
BOOST_CHECK(orphanage->HaveTx(normal_wtxid));
BOOST_CHECK(!orphanage->HaveTx(mutated_wtxid));
BOOST_CHECK_EQUAL(orphanage.EraseTx(normal_wtxid), 1);
BOOST_CHECK(!orphanage.HaveTx(normal_wtxid));
BOOST_CHECK(!orphanage.HaveTx(mutated_wtxid));
BOOST_CHECK_EQUAL(orphanage->EraseTx(normal_wtxid), 1);
BOOST_CHECK(!orphanage->HaveTx(normal_wtxid));
BOOST_CHECK(!orphanage->HaveTx(mutated_wtxid));
}
@@ -272,34 +264,34 @@ BOOST_AUTO_TEST_CASE(get_children)
// All orphans provided by node1
{
node::TxOrphanage orphanage;
BOOST_CHECK(orphanage.AddTx(child_p1n0, node1));
BOOST_CHECK(orphanage.AddTx(child_p2n1, node1));
BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node1));
BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node1));
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
BOOST_CHECK(orphanage->AddTx(child_p1n0, node1));
BOOST_CHECK(orphanage->AddTx(child_p2n1, node1));
BOOST_CHECK(orphanage->AddTx(child_p1n0_p1n1, node1));
BOOST_CHECK(orphanage->AddTx(child_p1n0_p2n0, node1));
std::set<CTransactionRef> expected_parent1_children{child_p1n0, child_p1n0_p2n0, child_p1n0_p1n1};
std::set<CTransactionRef> expected_parent2_children{child_p2n1, child_p1n0_p2n0};
BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage.GetChildrenFromSamePeer(parent1, node1)));
BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage.GetChildrenFromSamePeer(parent2, node1)));
BOOST_CHECK(EqualTxns(expected_parent1_children, orphanage->GetChildrenFromSamePeer(parent1, node1)));
BOOST_CHECK(EqualTxns(expected_parent2_children, orphanage->GetChildrenFromSamePeer(parent2, node1)));
// The peer must match
BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent1, node2).empty());
BOOST_CHECK(orphanage.GetChildrenFromSamePeer(parent2, node2).empty());
BOOST_CHECK(orphanage->GetChildrenFromSamePeer(parent1, node2).empty());
BOOST_CHECK(orphanage->GetChildrenFromSamePeer(parent2, node2).empty());
// There shouldn't be any children of this tx in the orphanage
BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node1).empty());
BOOST_CHECK(orphanage.GetChildrenFromSamePeer(child_p1n0_p2n0, node2).empty());
BOOST_CHECK(orphanage->GetChildrenFromSamePeer(child_p1n0_p2n0, node1).empty());
BOOST_CHECK(orphanage->GetChildrenFromSamePeer(child_p1n0_p2n0, node2).empty());
}
// Orphans provided by node1 and node2
{
node::TxOrphanage orphanage;
BOOST_CHECK(orphanage.AddTx(child_p1n0, node1));
BOOST_CHECK(orphanage.AddTx(child_p2n1, node1));
BOOST_CHECK(orphanage.AddTx(child_p1n0_p1n1, node2));
BOOST_CHECK(orphanage.AddTx(child_p1n0_p2n0, node2));
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
BOOST_CHECK(orphanage->AddTx(child_p1n0, node1));
BOOST_CHECK(orphanage->AddTx(child_p2n1, node1));
BOOST_CHECK(orphanage->AddTx(child_p1n0_p1n1, node2));
BOOST_CHECK(orphanage->AddTx(child_p1n0_p2n0, node2));
// +----------------+---------------+----------------------------------+
// | | sender=node1 | sender=node2 |
@@ -312,53 +304,53 @@ BOOST_AUTO_TEST_CASE(get_children)
{
std::set<CTransactionRef> expected_parent1_node1{child_p1n0};
BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage.GetChildrenFromSamePeer(parent1, node1)));
BOOST_CHECK(EqualTxns(expected_parent1_node1, orphanage->GetChildrenFromSamePeer(parent1, node1)));
}
// Children of parent2 from node1:
{
std::set<CTransactionRef> expected_parent2_node1{child_p2n1};
BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage.GetChildrenFromSamePeer(parent2, node1)));
BOOST_CHECK(EqualTxns(expected_parent2_node1, orphanage->GetChildrenFromSamePeer(parent2, node1)));
}
// Children of parent1 from node2:
{
std::set<CTransactionRef> expected_parent1_node2{child_p1n0_p1n1, child_p1n0_p2n0};
BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage.GetChildrenFromSamePeer(parent1, node2)));
BOOST_CHECK(EqualTxns(expected_parent1_node2, orphanage->GetChildrenFromSamePeer(parent1, node2)));
}
// Children of parent2 from node2:
{
std::set<CTransactionRef> expected_parent2_node2{child_p1n0_p2n0};
BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage.GetChildrenFromSamePeer(parent2, node2)));
BOOST_CHECK(EqualTxns(expected_parent2_node2, orphanage->GetChildrenFromSamePeer(parent2, node2)));
}
}
}
BOOST_AUTO_TEST_CASE(too_large_orphan_tx)
{
node::TxOrphanage orphanage;
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
CMutableTransaction tx;
tx.vin.resize(1);
// check that txs larger than MAX_STANDARD_TX_WEIGHT are not added to the orphanage
BulkTransaction(tx, MAX_STANDARD_TX_WEIGHT + 4);
BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(tx)), MAX_STANDARD_TX_WEIGHT + 4);
BOOST_CHECK(!orphanage.AddTx(MakeTransactionRef(tx), 0));
BOOST_CHECK(!orphanage->AddTx(MakeTransactionRef(tx), 0));
tx.vout.clear();
BulkTransaction(tx, MAX_STANDARD_TX_WEIGHT);
BOOST_CHECK_EQUAL(GetTransactionWeight(CTransaction(tx)), MAX_STANDARD_TX_WEIGHT);
BOOST_CHECK(orphanage.AddTx(MakeTransactionRef(tx), 0));
BOOST_CHECK(orphanage->AddTx(MakeTransactionRef(tx), 0));
}
BOOST_AUTO_TEST_CASE(process_block)
{
FastRandomContext det_rand{true};
TxOrphanageTest orphanage{det_rand};
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
// Create outpoints that will be spent by transactions in the block
std::vector<COutPoint> outpoints;
@@ -373,10 +365,10 @@ BOOST_AUTO_TEST_CASE(process_block)
const NodeId node{0};
auto control_tx = MakeTransactionSpending({}, det_rand);
BOOST_CHECK(orphanage.AddTx(control_tx, node));
BOOST_CHECK(orphanage->AddTx(control_tx, node));
auto bo_tx_same_txid = MakeTransactionSpending({outpoints.at(0)}, det_rand);
BOOST_CHECK(orphanage.AddTx(bo_tx_same_txid, node));
BOOST_CHECK(orphanage->AddTx(bo_tx_same_txid, node));
block.vtx.emplace_back(bo_tx_same_txid);
// 2 transactions with the same txid but different witness
@@ -384,30 +376,30 @@ BOOST_AUTO_TEST_CASE(process_block)
block.vtx.emplace_back(b_tx_same_txid_diff_witness);
auto o_tx_same_txid_diff_witness = MakeMutation(b_tx_same_txid_diff_witness);
BOOST_CHECK(orphanage.AddTx(o_tx_same_txid_diff_witness, node));
BOOST_CHECK(orphanage->AddTx(o_tx_same_txid_diff_witness, node));
// 2 different transactions that spend the same input.
auto b_tx_conflict = MakeTransactionSpending({outpoints.at(2)}, det_rand);
block.vtx.emplace_back(b_tx_conflict);
auto o_tx_conflict = MakeTransactionSpending({outpoints.at(2)}, det_rand);
BOOST_CHECK(orphanage.AddTx(o_tx_conflict, node));
BOOST_CHECK(orphanage->AddTx(o_tx_conflict, node));
// 2 different transactions that have 1 overlapping input.
auto b_tx_conflict_partial = MakeTransactionSpending({outpoints.at(3), outpoints.at(4)}, det_rand);
block.vtx.emplace_back(b_tx_conflict_partial);
auto o_tx_conflict_partial_2 = MakeTransactionSpending({outpoints.at(4), outpoints.at(5)}, det_rand);
BOOST_CHECK(orphanage.AddTx(o_tx_conflict_partial_2, node));
BOOST_CHECK(orphanage->AddTx(o_tx_conflict_partial_2, node));
orphanage.EraseForBlock(block);
orphanage->EraseForBlock(block);
for (const auto& expected_removed : {bo_tx_same_txid, o_tx_same_txid_diff_witness, o_tx_conflict, o_tx_conflict_partial_2}) {
const auto& expected_removed_wtxid = expected_removed->GetWitnessHash();
BOOST_CHECK(!orphanage.HaveTx(expected_removed_wtxid));
BOOST_CHECK(!orphanage->HaveTx(expected_removed_wtxid));
}
// Only remaining tx is control_tx
BOOST_CHECK_EQUAL(orphanage.Size(), 1);
BOOST_CHECK(orphanage.HaveTx(control_tx->GetWitnessHash()));
BOOST_CHECK_EQUAL(orphanage->Size(), 1);
BOOST_CHECK(orphanage->HaveTx(control_tx->GetWitnessHash()));
}
BOOST_AUTO_TEST_CASE(multiple_announcers)
@@ -417,60 +409,60 @@ BOOST_AUTO_TEST_CASE(multiple_announcers)
const NodeId node2{2};
size_t expected_total_count{0};
FastRandomContext det_rand{true};
TxOrphanageTest orphanage{det_rand};
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
// Check accounting per peer.
// Check that EraseForPeer works with multiple announcers.
{
auto ptx = MakeTransactionSpending({}, det_rand);
const auto& wtxid = ptx->GetWitnessHash();
BOOST_CHECK(orphanage.AddTx(ptx, node0));
BOOST_CHECK(orphanage.HaveTx(wtxid));
BOOST_CHECK(orphanage->AddTx(ptx, node0));
BOOST_CHECK(orphanage->HaveTx(wtxid));
expected_total_count += 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
// Adding again should do nothing.
BOOST_CHECK(!orphanage.AddTx(ptx, node0));
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK(!orphanage->AddTx(ptx, node0));
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
// We can add another tx with the same txid but different witness.
auto ptx_mutated{MakeMutation(ptx)};
BOOST_CHECK(orphanage.AddTx(ptx_mutated, node0));
BOOST_CHECK(orphanage.HaveTx(ptx_mutated->GetWitnessHash()));
BOOST_CHECK(orphanage->AddTx(ptx_mutated, node0));
BOOST_CHECK(orphanage->HaveTx(ptx_mutated->GetWitnessHash()));
expected_total_count += 1;
BOOST_CHECK(!orphanage.AddTx(ptx, node0));
BOOST_CHECK(!orphanage->AddTx(ptx, node0));
// Adding a new announcer should not change overall accounting.
BOOST_CHECK(orphanage.AddAnnouncer(ptx->GetWitnessHash(), node2));
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK(orphanage->AddAnnouncer(ptx->GetWitnessHash(), node2));
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
// If we already have this announcer, AddAnnouncer returns false.
BOOST_CHECK(orphanage.HaveTxFromPeer(ptx->GetWitnessHash(), node2));
BOOST_CHECK(!orphanage.AddAnnouncer(ptx->GetWitnessHash(), node2));
BOOST_CHECK(orphanage->HaveTxFromPeer(ptx->GetWitnessHash(), node2));
BOOST_CHECK(!orphanage->AddAnnouncer(ptx->GetWitnessHash(), node2));
// Same with using AddTx for an existing tx, which is equivalent to using AddAnnouncer
BOOST_CHECK(!orphanage.AddTx(ptx, node1));
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK(!orphanage->AddTx(ptx, node1));
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
// if EraseForPeer is called for an orphan with multiple announcers, the orphanage should only
// erase that peer from the announcers set.
orphanage.EraseForPeer(node0);
BOOST_CHECK(orphanage.HaveTx(ptx->GetWitnessHash()));
BOOST_CHECK(!orphanage.HaveTxFromPeer(ptx->GetWitnessHash(), node0));
orphanage->EraseForPeer(node0);
BOOST_CHECK(orphanage->HaveTx(ptx->GetWitnessHash()));
BOOST_CHECK(!orphanage->HaveTxFromPeer(ptx->GetWitnessHash(), node0));
// node0 is the only one that announced ptx_mutated
BOOST_CHECK(!orphanage.HaveTx(ptx_mutated->GetWitnessHash()));
BOOST_CHECK(!orphanage->HaveTx(ptx_mutated->GetWitnessHash()));
expected_total_count -= 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
// EraseForPeer should delete the orphan if it's the only announcer left.
orphanage.EraseForPeer(node1);
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK(orphanage.HaveTx(ptx->GetWitnessHash()));
orphanage.EraseForPeer(node2);
orphanage->EraseForPeer(node1);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
BOOST_CHECK(orphanage->HaveTx(ptx->GetWitnessHash()));
orphanage->EraseForPeer(node2);
expected_total_count -= 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK(!orphanage.HaveTx(ptx->GetWitnessHash()));
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
BOOST_CHECK(!orphanage->HaveTx(ptx->GetWitnessHash()));
}
// Check that erasure for blocks removes for all peers.
@@ -478,18 +470,18 @@ BOOST_AUTO_TEST_CASE(multiple_announcers)
CBlock block;
auto tx_block = MakeTransactionSpending({}, det_rand);
block.vtx.emplace_back(tx_block);
BOOST_CHECK(orphanage.AddTx(tx_block, node0));
BOOST_CHECK(!orphanage.AddTx(tx_block, node1));
BOOST_CHECK(orphanage->AddTx(tx_block, node0));
BOOST_CHECK(!orphanage->AddTx(tx_block, node1));
expected_total_count += 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
orphanage.EraseForBlock(block);
orphanage->EraseForBlock(block);
expected_total_count -= 1;
BOOST_CHECK_EQUAL(orphanage.Size(), expected_total_count);
BOOST_CHECK_EQUAL(orphanage->Size(), expected_total_count);
}
}
BOOST_AUTO_TEST_CASE(peer_worksets)
@@ -498,7 +490,7 @@ BOOST_AUTO_TEST_CASE(peer_worksets)
const NodeId node1{1};
const NodeId node2{2};
FastRandomContext det_rand{true};
TxOrphanageTest orphanage{det_rand};
std::unique_ptr<node::TxOrphanage> orphanage{node::MakeTxOrphanage()};
// AddChildrenToWorkSet should pick an announcer randomly
{
auto tx_missing_parent = MakeTransactionSpending({}, det_rand);
@@ -506,18 +498,18 @@ BOOST_AUTO_TEST_CASE(peer_worksets)
const auto& orphan_wtxid = tx_orphan->GetWitnessHash();
// All 3 peers are announcers.
BOOST_CHECK(orphanage.AddTx(tx_orphan, node0));
BOOST_CHECK(!orphanage.AddTx(tx_orphan, node1));
BOOST_CHECK(orphanage.AddAnnouncer(orphan_wtxid, node2));
BOOST_CHECK(orphanage->AddTx(tx_orphan, node0));
BOOST_CHECK(!orphanage->AddTx(tx_orphan, node1));
BOOST_CHECK(orphanage->AddAnnouncer(orphan_wtxid, node2));
for (NodeId node = node0; node <= node2; ++node) {
BOOST_CHECK(orphanage.HaveTxFromPeer(orphan_wtxid, node));
BOOST_CHECK(orphanage->HaveTxFromPeer(orphan_wtxid, node));
}
// Parent accepted: child is added to 1 of 3 worksets.
orphanage.AddChildrenToWorkSet(*tx_missing_parent, det_rand);
int node0_reconsider = orphanage.HaveTxToReconsider(node0);
int node1_reconsider = orphanage.HaveTxToReconsider(node1);
int node2_reconsider = orphanage.HaveTxToReconsider(node2);
orphanage->AddChildrenToWorkSet(*tx_missing_parent, det_rand);
int node0_reconsider = orphanage->HaveTxToReconsider(node0);
int node1_reconsider = orphanage->HaveTxToReconsider(node1);
int node2_reconsider = orphanage->HaveTxToReconsider(node2);
BOOST_CHECK_EQUAL(node0_reconsider + node1_reconsider + node2_reconsider, 1);
NodeId assigned_peer;
@@ -531,15 +523,15 @@ BOOST_AUTO_TEST_CASE(peer_worksets)
}
// EraseForPeer also removes that tx from the workset.
orphanage.EraseForPeer(assigned_peer);
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node0), nullptr);
orphanage->EraseForPeer(assigned_peer);
BOOST_CHECK_EQUAL(orphanage->GetTxToReconsider(node0), nullptr);
// Delete this tx, clearing the orphanage.
BOOST_CHECK_EQUAL(orphanage.EraseTx(orphan_wtxid), 1);
BOOST_CHECK_EQUAL(orphanage.Size(), 0);
BOOST_CHECK_EQUAL(orphanage->EraseTx(orphan_wtxid), 1);
BOOST_CHECK_EQUAL(orphanage->Size(), 0);
for (NodeId node = node0; node <= node2; ++node) {
BOOST_CHECK_EQUAL(orphanage.GetTxToReconsider(node), nullptr);
BOOST_CHECK(!orphanage.HaveTxFromPeer(orphan_wtxid, node));
BOOST_CHECK_EQUAL(orphanage->GetTxToReconsider(node), nullptr);
BOOST_CHECK(!orphanage->HaveTxFromPeer(orphan_wtxid, node));
}
}
}

View File

@@ -80,7 +80,7 @@ static bool CheckOrphanBehavior(node::TxDownloadManagerImpl& txdownload_impl, co
return false;
}
if (expect_orphan != txdownload_impl.m_orphanage.HaveTx(tx->GetWitnessHash())) {
if (expect_orphan != txdownload_impl.m_orphanage->HaveTx(tx->GetWitnessHash())) {
err_msg = strprintf("unexpectedly %s tx in orphanage", expect_orphan ? "did not find" : "found");
return false;
}
@@ -162,7 +162,7 @@ BOOST_FIXTURE_TEST_CASE(tx_rejection_types, TestChain100Setup)
BOOST_CHECK_EQUAL(parent_txid_rejected, txdownload_impl.RecentRejectsFilter().contains(child_wtxid.ToUint256()));
// Unless rejected, the child should be in orphanage.
BOOST_CHECK_EQUAL(!parent_txid_rejected, txdownload_impl.m_orphanage.HaveTx(ptx_child->GetWitnessHash()));
BOOST_CHECK_EQUAL(!parent_txid_rejected, txdownload_impl.m_orphanage->HaveTx(ptx_child->GetWitnessHash()));
}
}
}