@@ -119,6 +119,9 @@ class TxOrphanageImpl final : public TxOrphanage {
119119 * a transaction that can be reconsidered and to remove entries that conflict with a block.*/
120120 std::unordered_map<COutPoint, std::set<Wtxid>, SaltedOutpointHasher> m_outpoint_to_orphan_it;
121121
122+ /* * Set of Wtxids for which (exactly) one announcement with m_reconsider=true exists. */
123+ std::set<Wtxid> m_reconsiderable_wtxids;
124+
122125 struct PeerDoSInfo {
123126 TxOrphanage::Usage m_total_usage{0 };
124127 TxOrphanage::Count m_count_announcements{0 };
@@ -261,6 +264,11 @@ void TxOrphanageImpl::Erase(Iter<Tag> it)
261264 }
262265 }
263266 }
267+
268+ // If this was the (unique) reconsiderable announcement for its wtxid, then the wtxid won't
269+ // have any reconsiderable announcements left after erasing.
270+ if (it->m_reconsider ) m_reconsiderable_wtxids.erase (it->m_tx ->GetWitnessHash ());
271+
264272 m_orphans.get <Tag>().erase (it);
265273}
266274
@@ -521,6 +529,9 @@ std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(cons
521529 const auto it_by_prev = m_outpoint_to_orphan_it.find (COutPoint (tx.GetHash (), i));
522530 if (it_by_prev != m_outpoint_to_orphan_it.end ()) {
523531 for (const auto & wtxid : it_by_prev->second ) {
532+ // If a reconsiderable announcement for this wtxid already exists, skip it.
533+ if (m_reconsiderable_wtxids.contains (wtxid)) continue ;
534+
524535 // Belt and suspenders, each entry in m_outpoint_to_orphan_it should always have at least 1 announcement.
525536 auto it = index_by_wtxid.lower_bound (ByWtxidView{wtxid, MIN_PEER});
526537 if (!Assume (it != index_by_wtxid.end ())) continue ;
@@ -537,10 +548,10 @@ std::vector<std::pair<Wtxid, NodeId>> TxOrphanageImpl::AddChildrenToWorkSet(cons
537548
538549 // Mark this orphan as ready to be reconsidered.
539550 static constexpr auto mark_reconsidered_modifier = [](auto & ann) { ann.m_reconsider = true ; };
540- if (!it->m_reconsider ) {
541- index_by_wtxid.modify (it, mark_reconsidered_modifier);
542- ret.emplace_back (wtxid, it->m_announcer );
543- }
551+ Assume (!it->m_reconsider );
552+ index_by_wtxid.modify (it, mark_reconsidered_modifier);
553+ ret.emplace_back (wtxid, it->m_announcer );
554+ m_reconsiderable_wtxids. insert (wtxid);
544555
545556 LogDebug (BCLog::TXPACKAGES, " added %s (wtxid=%s) to peer %d workset\n " ,
546557 it->m_tx ->GetHash ().ToString (), it->m_tx ->GetWitnessHash ().ToString (), it->m_announcer );
@@ -578,6 +589,9 @@ CTransactionRef TxOrphanageImpl::GetTxToReconsider(NodeId peer)
578589 // reconsidered again until there is a new reason to do so.
579590 static constexpr auto mark_reconsidered_modifier = [](auto & ann) { ann.m_reconsider = false ; };
580591 m_orphans.get <ByPeer>().modify (it, mark_reconsidered_modifier);
592+ // As there is exactly one m_reconsider announcement per reconsiderable wtxids, flipping
593+ // the m_reconsider flag means the wtxid is no longer reconsiderable.
594+ m_reconsiderable_wtxids.erase (it->m_tx ->GetWitnessHash ());
581595 return it->m_tx ;
582596 }
583597 return nullptr ;
@@ -680,6 +694,7 @@ void TxOrphanageImpl::SanityCheck() const
680694 std::unordered_map<NodeId, PeerDoSInfo> reconstructed_peer_info;
681695 std::map<Wtxid, std::pair<TxOrphanage::Usage, TxOrphanage::Count>> unique_wtxids_to_scores;
682696 std::set<COutPoint> all_outpoints;
697+ std::set<Wtxid> reconstructed_reconsiderable_wtxids;
683698
684699 for (auto it = m_orphans.begin (); it != m_orphans.end (); ++it) {
685700 for (const auto & input : it->m_tx ->vin ) {
@@ -691,9 +706,21 @@ void TxOrphanageImpl::SanityCheck() const
691706 peer_info.m_total_usage += it->GetMemUsage ();
692707 peer_info.m_count_announcements += 1 ;
693708 peer_info.m_total_latency_score += it->GetLatencyScore ();
709+
710+ if (it->m_reconsider ) {
711+ auto [_, added] = reconstructed_reconsiderable_wtxids.insert (it->m_tx ->GetWitnessHash ());
712+ // Check that there is only ever 1 announcement per wtxid with m_reconsider set.
713+ assert (added);
714+ }
694715 }
695716 assert (reconstructed_peer_info.size () == m_peer_orphanage_info.size ());
696717
718+ // Recalculated per-peer stats are identical to m_peer_orphanage_info
719+ assert (reconstructed_peer_info == m_peer_orphanage_info);
720+
721+ // Recalculated set of reconsiderable wtxids must match.
722+ assert (m_reconsiderable_wtxids == reconstructed_reconsiderable_wtxids);
723+
697724 // All outpoints exist in m_outpoint_to_orphan_it, all keys in m_outpoint_to_orphan_it correspond to some
698725 // orphan, and all wtxids referenced in m_outpoint_to_orphan_it are also in m_orphans.
699726 // This ensures m_outpoint_to_orphan_it is cleaned up.
0 commit comments