[p2p] overhaul TxOrphanage with smarter limits

This is largely a reimplementation using boost::multi_index_container.
All the same public methods are available. It has an index by outpoint,
per-peer tracking, peer worksets, etc.

A few differences:
- Limits have changed: instead of a global limit of 100 unique orphans,
  we have a maximum number of announcements (which can include duplicate
orphans) and a global memory limit which scales with the number of
peers.
- The maximum announcements limit is 100 to match the original limit,
  but this is actually a stricter limit because the announcement count
is not de-duplicated.
- Eviction strategy: when global limits are reached, a per-peer limit
  comes into play. While limits are exceeded, we choose the peer whose
“DoS score” (max usage / limit ratio for announcements and memory
limits) is highest and evict announcements by entry time, sorting
non-reconsiderable ones before reconsiderable ones. Since announcements
are unique by (wtxid, peer), as long as 1 announcement remains for a
transaction, it remains in the orphanage.
- This eviction strategy means no peer can influence the eviction of
  another peer’s orphans.
- Also, since global limits are a multiple of per-peer limits, as long
  as a peer does not exceed its limits, its orphans are protected from
eviction.
- Orphans no longer expire, since older announcements are generally
  removed before newer ones.
- GetChildrenFromSamePeer returns the transactions from newest to
  oldest.

Co-authored-by: Pieter Wuille <pieter@wuille.net>
This commit is contained in:
glozow
2025-04-02 17:29:38 -04:00
parent 1a41e7962d
commit 067365d2a8
4 changed files with 614 additions and 338 deletions

View File

@@ -170,27 +170,6 @@ BOOST_AUTO_TEST_CASE(DoS_mapOrphans)
expected_num_orphans -= 2;
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);
// Add one more orphan, check timeout logic
auto timeout_tx = MakeTransactionSpending(/*outpoints=*/{}, rng);
orphanage->AddTx(timeout_tx, 0);
expected_num_orphans += 1;
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);
// 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);
}
BOOST_AUTO_TEST_CASE(same_txid_diff_witness)