Skip to content

Commit ed24e01

Browse files
sipaglozow
authored andcommitted
[optimization] Maintain at most 1 reconsiderable announcement per wtxid
This introduces an invariant that TxOrphanageImpl never holds more than one announcement with m_reconsider=true for a given wtxid. This avoids duplicate work, both in the caller might otherwise reconsider the same transaction multiple times before it is ready, and internally in AddChildrenToWorkSet, which might otherwise iterate over all announcements multiple times.
1 parent af7402c commit ed24e01

File tree

2 files changed

+38
-16
lines changed

2 files changed

+38
-16
lines changed

src/node/txorphanage.cpp

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

src/test/fuzz/txorphan.cpp

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -626,11 +626,11 @@ FUZZ_TARGET(txorphanage_sim)
626626
auto tx = read_tx_fn();
627627
FastRandomContext rand_ctx(rng.rand256());
628628
auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx);
629-
/** Map of all child wtxids, with value whether they already have a reconsiderable
630-
announcement from some peer. */
631-
std::map<Wtxid, bool> child_wtxids;
629+
/** Set of not-already-reconsiderable child wtxids. */
630+
std::set<Wtxid> child_wtxids;
632631
for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) {
633632
if (!have_tx_fn(child_tx)) continue;
633+
if (have_reconsiderable_fn(child_tx)) continue;
634634
bool child_of = false;
635635
for (auto& txin : txn[child_tx]->vin) {
636636
if (txin.prevout.hash == txn[tx]->GetHash()) {
@@ -639,11 +639,11 @@ FUZZ_TARGET(txorphanage_sim)
639639
}
640640
}
641641
if (child_of) {
642-
child_wtxids[txn[child_tx]->GetWitnessHash()] = have_reconsiderable_fn(child_tx);
642+
child_wtxids.insert(txn[child_tx]->GetWitnessHash());
643643
}
644644
}
645645
for (auto& [wtxid, peer] : added) {
646-
// Wtxid must be a child of tx.
646+
// Wtxid must be a child of tx that is not yet reconsiderable.
647647
auto child_wtxid_it = child_wtxids.find(wtxid);
648648
assert(child_wtxid_it != child_wtxids.end());
649649
// Announcement must exist.
@@ -653,18 +653,13 @@ FUZZ_TARGET(txorphanage_sim)
653653
assert(sim_ann_it->reconsider == false);
654654
// Make reconsiderable.
655655
sim_ann_it->reconsider = true;
656-
}
657-
for (auto& [wtxid, peer] : added) {
658-
// Remove from child_wtxids map, so we can check that only already-reconsiderable
659-
// ones are missing from the result.
656+
// Remove from child_wtxids map, to disallow it being reported a second time in added.
660657
child_wtxids.erase(wtxid);
661658
}
662659
// Verify that AddChildrenToWorkSet does not select announcements that were already reconsiderable:
663660
// Check all child wtxids which did not occur at least once in the result were already reconsiderable
664661
// due to a previous AddChildrenToWorkSet.
665-
for (auto& [wtxid, already_reconsider] : child_wtxids) {
666-
assert(already_reconsider);
667-
}
662+
assert(child_wtxids.empty());
668663
break;
669664
} else if (command-- == 0) {
670665
// GetTxToReconsider.

0 commit comments

Comments
 (0)