[refactor] make TxOrphanage keep itself trimmed

This commit is contained in:
glozow
2025-07-03 16:06:21 -04:00
parent d1fac25ff3
commit af7402ccfa
6 changed files with 125 additions and 166 deletions

View File

@@ -72,15 +72,6 @@ static bool EqualTxns(const std::set<CTransactionRef>& set_txns, const std::vect
return true;
}
unsigned int CheckNumEvictions(node::TxOrphanage& orphanage)
{
const auto original_total_count{orphanage.CountAnnouncements()};
orphanage.LimitOrphans();
assert(orphanage.TotalLatencyScore() <= orphanage.MaxGlobalLatencyScore());
assert(orphanage.TotalOrphanUsage() <= orphanage.MaxGlobalUsage());
return original_total_count - orphanage.CountAnnouncements();
}
BOOST_AUTO_TEST_CASE(peer_dos_limits)
{
FastRandomContext det_rand{true};
@@ -106,30 +97,22 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
auto orphanage_low_ann = node::MakeTxOrphanage(/*max_global_ann=*/1, /*reserved_peer_usage=*/TX_SIZE * 10);
auto orphanage_low_mem = node::MakeTxOrphanage(/*max_global_ann=*/10, /*reserved_peer_usage=*/TX_SIZE);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_mem), 0);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_ann), 0);
// Add the first transaction
orphanage_low_ann->AddTx(txns.at(0), peer);
orphanage_low_mem->AddTx(txns.at(0), peer);
// Add more. One of the limits is exceeded, so LimitOrphans evicts 1.
orphanage_low_ann->AddTx(txns.at(1), peer);
BOOST_CHECK(orphanage_low_ann->TotalLatencyScore() > orphanage_low_ann->MaxGlobalLatencyScore());
BOOST_CHECK(orphanage_low_ann->TotalOrphanUsage() <= orphanage_low_ann->MaxGlobalUsage());
orphanage_low_mem->AddTx(txns.at(1), peer);
BOOST_CHECK(orphanage_low_mem->TotalLatencyScore() <= orphanage_low_mem->MaxGlobalLatencyScore());
BOOST_CHECK(orphanage_low_mem->TotalOrphanUsage() > orphanage_low_mem->MaxGlobalUsage());
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_mem), 1);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_ann), 1);
// The older transaction is evicted.
BOOST_CHECK(!orphanage_low_ann->HaveTx(txns.at(0)->GetWitnessHash()));
BOOST_CHECK(!orphanage_low_mem->HaveTx(txns.at(0)->GetWitnessHash()));
BOOST_CHECK(orphanage_low_ann->HaveTx(txns.at(1)->GetWitnessHash()));
BOOST_CHECK(orphanage_low_mem->HaveTx(txns.at(1)->GetWitnessHash()));
orphanage_low_ann->SanityCheck();
orphanage_low_mem->SanityCheck();
}
// Single peer: latency score includes inputs
@@ -138,8 +121,6 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
NodeId peer{10};
auto orphanage_low_ann = node::MakeTxOrphanage(/*max_global_ann=*/5, /*reserved_peer_usage=*/TX_SIZE * 1000);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_ann), 0);
// Add the first transaction
orphanage_low_ann->AddTx(txns.at(0), peer);
@@ -151,14 +132,11 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
auto ptx = MakeTransactionSpending(outpoints_45, det_rand);
orphanage_low_ann->AddTx(ptx, peer);
BOOST_CHECK(orphanage_low_ann->TotalLatencyScore() > orphanage_low_ann->MaxGlobalLatencyScore());
BOOST_CHECK(orphanage_low_ann->LatencyScoreFromPeer(peer) > orphanage_low_ann->MaxPeerLatencyScore());
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage_low_ann), 1);
// The older transaction is evicted.
BOOST_CHECK(!orphanage_low_ann->HaveTx(txns.at(0)->GetWitnessHash()));
BOOST_CHECK(orphanage_low_ann->HaveTx(ptx->GetWitnessHash()));
orphanage_low_ann->SanityCheck();
}
// Single peer: eviction order is FIFO on non-reconsiderable, then reconsiderable orphans.
@@ -191,15 +169,14 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// Add 1 more orphan, causing the orphanage to be oversize. child1 is evicted.
orphanage->AddTx(children.at(3), peer);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK(orphanage->HaveTx(children.at(0)->GetWitnessHash()));
BOOST_CHECK(!orphanage->HaveTx(children.at(1)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(2)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(3)->GetWitnessHash()));
orphanage->SanityCheck();
// Add 1 more... child2 is evicted.
orphanage->AddTx(children.at(4), peer);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK(orphanage->HaveTx(children.at(0)->GetWitnessHash()));
BOOST_CHECK(!orphanage->HaveTx(children.at(2)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(3)->GetWitnessHash()));
@@ -213,7 +190,6 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// child5 is evicted immediately because it is the only non-reconsiderable orphan.
orphanage->AddTx(children.at(5), peer);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK(orphanage->HaveTx(children.at(0)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(3)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(4)->GetWitnessHash()));
@@ -222,7 +198,6 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// Transactions are marked non-reconsiderable again when returned through GetTxToReconsider
BOOST_CHECK_EQUAL(orphanage->GetTxToReconsider(peer), children.at(0));
orphanage->AddTx(children.at(6), peer);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK(!orphanage->HaveTx(children.at(0)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(3)->GetWitnessHash()));
BOOST_CHECK(orphanage->HaveTx(children.at(4)->GetWitnessHash()));
@@ -232,6 +207,8 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// reconsideration earlier.
BOOST_CHECK_EQUAL(orphanage->GetTxToReconsider(peer), children.at(3));
BOOST_CHECK_EQUAL(orphanage->GetTxToReconsider(peer), children.at(4));
orphanage->SanityCheck();
}
// Multiple peers: when limit is exceeded, we choose the DoSiest peer and evict their oldest transaction.
@@ -247,8 +224,8 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// No evictions happen before the global limit is reached.
for (unsigned int i{0}; i < max_announcements; ++i) {
orphanage->AddTx(txns.at(i), peer_dosy);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 0);
}
orphanage->SanityCheck();
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer_dosy), max_announcements);
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer1), 0);
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer2), 0);
@@ -260,7 +237,6 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
orphanage->AddTx(txns.at(max_announcements + i), peer1);
// The announcement limit per peer has halved, but LimitOrphans does not evict beyond what is necessary to
// bring the total announcements within its global limit.
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK(orphanage->AnnouncementsFromPeer(peer_dosy) > orphanage->MaxPeerLatencyScore());
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer1), i + 1);
@@ -276,9 +252,6 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
BOOST_CHECK(orphanage->HaveTxFromPeer(txns.at(i)->GetWitnessHash(), peer_dosy));
orphanage->AddTx(txns.at(i), peer2);
// Announcement limit is by entry, not by unique orphans
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
// peer_dosy is still the only one getting evicted
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer_dosy), max_announcements - i - 1);
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer1), num_from_peer1);
@@ -291,15 +264,18 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// With 6 peers, each can add 10, and still only peer_dosy's orphans are evicted.
const unsigned int max_per_peer{max_announcements / 6};
const unsigned int num_announcements{orphanage->CountAnnouncements()};
for (NodeId peer{3}; peer < 6; ++peer) {
for (unsigned int i{0}; i < max_per_peer; ++i) {
// Each addition causes 1 eviction.
orphanage->AddTx(txns.at(peer * max_per_peer + i), peer);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 1);
BOOST_CHECK_EQUAL(orphanage->CountAnnouncements(), num_announcements);
}
}
for (NodeId peer{0}; peer < 6; ++peer) {
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer), max_per_peer);
}
orphanage->SanityCheck();
}
// Limits change as more peers are added.
@@ -355,6 +331,8 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
BOOST_CHECK_EQUAL(orphanage->ReservedPeerUsage(), node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER);
BOOST_CHECK_EQUAL(orphanage->MaxGlobalUsage(), node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER);
BOOST_CHECK_EQUAL(orphanage->MaxPeerLatencyScore(), node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE);
orphanage->SanityCheck();
}
// Test eviction of multiple transactions at a time
@@ -378,17 +356,17 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
}
BOOST_CHECK(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
BOOST_CHECK(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 0);
// Add the large transaction. This should cause evictions of all the previous 10 transactions from that peer.
orphanage->AddTx(ptx_large, peer_large);
BOOST_CHECK_EQUAL(CheckNumEvictions(*orphanage), 10);
// peer_normal should still have 10 transactions, and peer_large should have 1.
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer_normal), 10);
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(peer_large), 1);
BOOST_CHECK(orphanage->HaveTxFromPeer(ptx_large->GetWitnessHash(), peer_large));
BOOST_CHECK_EQUAL(orphanage->CountAnnouncements(), 11);
orphanage->SanityCheck();
}
// Test that latency score includes number of inputs.
@@ -432,6 +410,8 @@ BOOST_AUTO_TEST_CASE(peer_dos_limits)
// Peer 1 sent 5 of the 10 transactions with many inputs
BOOST_CHECK_EQUAL(orphanage->AnnouncementsFromPeer(1), 5);
BOOST_CHECK_EQUAL(orphanage->LatencyScoreFromPeer(1), 5 + 5 * 5);
orphanage->SanityCheck();
}
}
BOOST_AUTO_TEST_CASE(DoS_mapOrphans)