diff --git a/src/node/txdownloadman_impl.cpp b/src/node/txdownloadman_impl.cpp index 1c106c9eb7..6abc7f8d90 100644 --- a/src/node/txdownloadman_impl.cpp +++ b/src/node/txdownloadman_impl.cpp @@ -97,7 +97,7 @@ void TxDownloadManagerImpl::ActiveTipChange() void TxDownloadManagerImpl::BlockConnected(const std::shared_ptr& 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(>xid)) { - 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 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 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> 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 TxDownloadManagerImpl::GetOrphanTransactions() const { - return m_orphanage.GetOrphanTransactions(); + return m_orphanage->GetOrphanTransactions(); } } // namespace node diff --git a/src/node/txdownloadman_impl.h b/src/node/txdownloadman_impl.h index 245e251c3f..b10c71d5d2 100644 --- a/src/node/txdownloadman_impl.h +++ b/src/node/txdownloadman_impl.h @@ -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 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. */ diff --git a/src/node/txorphanage.cpp b/src/node/txorphanage.cpp index 86e7c04332..e230cbc1ba 100644 --- a/src/node/txorphanage.cpp +++ b/src/node/txorphanage.cpp @@ -12,8 +12,85 @@ #include -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 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 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 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 m_peer_orphanage_info; + + using OrphanMap = decltype(m_orphans); + + struct IteratorComparator + { + template + 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> m_outpoint_to_orphan_it; + + /** Orphan transactions in vector for quick random eviction */ + std::vector 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 GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const override; + size_t Size() const override { return m_orphans.size(); } + std::vector 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::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()}; @@ -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 vOrphanErase; @@ -273,7 +351,7 @@ void TxOrphanage::EraseForBlock(const CBlock& block) } } -std::vector TxOrphanage::GetChildrenFromSamePeer(const CTransactionRef& parent, NodeId nodeid) const +std::vector 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 TxOrphanage::GetChildrenFromSamePeer(const CTransac return children_found; } -std::vector TxOrphanage::GetOrphanTransactions() const +std::vector TxOrphanageImpl::GetOrphanTransactions() const { std::vector ret; ret.reserve(m_orphans.size()); @@ -323,7 +401,13 @@ std::vector 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 MakeTxOrphanage() noexcept +{ + return std::make_unique(); +} + } // namespace node diff --git a/src/node/txorphanage.h b/src/node/txorphanage.h index 48eafd081d..6a13b6239b 100644 --- a/src/node/txorphanage.h +++ b/src/node/txorphanage.h @@ -15,7 +15,7 @@ #include #include -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 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 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 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 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 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 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 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 m_peer_orphanage_info; - - using OrphanMap = decltype(m_orphans); - - struct IteratorComparator - { - template - 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> m_outpoint_to_orphan_it; - - /** Orphan transactions in vector for quick random eviction */ - std::vector 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 MakeTxOrphanage() noexcept; + } // namespace node #endif // BITCOIN_NODE_TXORPHANAGE_H diff --git a/src/test/fuzz/txdownloadman.cpp b/src/test/fuzz/txdownloadman.cpp index 277fc7161d..0c07e1010d 100644 --- a/src/test/fuzz/txdownloadman.cpp +++ b/src/test/fuzz/txdownloadman.cpp @@ -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(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 diff --git a/src/test/fuzz/txorphan.cpp b/src/test/fuzz/txorphan.cpp index c3521ff520..954281fb39 100644 --- a/src/test/fuzz/txorphan.cpp +++ b/src/test/fuzz/txorphan.cpp @@ -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 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(); - 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(); - 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(); } diff --git a/src/test/orphanage_tests.cpp b/src/test/orphanage_tests.cpp index 1ab22cb3e8..09f6ec63d7 100644 --- a/src/test/orphanage_tests.cpp +++ b/src/test/orphanage_tests.cpp @@ -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 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 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 orphanage{node::MakeTxOrphanage()}; NodeId peer{0}; std::vector 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 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 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 expected_parent1_children{child_p1n0, child_p1n0_p2n0, child_p1n0_p1n1}; std::set 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 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 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 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 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 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 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 orphanage{node::MakeTxOrphanage()}; // Create outpoints that will be spent by transactions in the block std::vector 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 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 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)); } } } diff --git a/src/test/txdownload_tests.cpp b/src/test/txdownload_tests.cpp index 8130e443ed..8b36714878 100644 --- a/src/test/txdownload_tests.cpp +++ b/src/test/txdownload_tests.cpp @@ -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())); } } }