Skip to content

Commit 80067ac

Browse files
committed
Merge bitcoin/bitcoin#31829: p2p: improve TxOrphanage denial of service bounds
5002462 [bench] worst case LimitOrphans and EraseForBlock (glozow) 45c7a4b [functional test] orphan resolution works in the presence of DoSy peers (glozow) 835f5c7 [prep/test] restart instead of bumpmocktime between p2p_orphan_handling subtests (glozow) b113877 [fuzz] Add simulation fuzz test for TxOrphanage (Pieter Wuille) 03aaaed [prep] Return the made-reconsiderable announcements in AddChildrenToWorkSet (Pieter Wuille) ea29c43 [p2p] bump DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE to 3,000 (glozow) 24afee8 [fuzz] TxOrphanage protects peers that don't go over limit (glozow) a2878cf [unit test] strengthen GetChildrenFromSamePeer tests: results are in recency order (glozow) 7ce3b7e [unit test] basic TxOrphanage eviction and protection (glozow) 4d23d1d [cleanup] remove unused rng param from LimitOrphans (glozow) 067365d [p2p] overhaul TxOrphanage with smarter limits (glozow) 1a41e79 [refactor] create aliases for TxOrphanage Count and Usage (glozow) b50bd72 [prep] change return type of EraseTx to bool (glozow) 3da6d7f [prep/refactor] make TxOrphanage a virtual class implemented by TxOrphanageImpl (glozow) 77ebe8f [prep/test] have TxOrphanage remember its own limits in LimitOrphans (glozow) d0af423 [prep/refactor] move DEFAULT_MAX_ORPHAN_TRANSACTIONS to txorphanage.h (glozow) 5136522 [prep/config] remove -maxorphantx (glozow) 8dd24c2 [prep/test] modify test to not access TxOrphanage internals (glozow) 44f5327 [fuzz] add SeedRandomStateForTest(SeedRand::ZEROS) to txorphan (glozow) 15a4ec9 [prep/rpc] remove entry and expiry time from getorphantxs (glozow) 08e58fa [prep/refactor] move txorphanage to node namespace and directory (glozow) bb91d23 [txorphanage] change type of usage to int64_t (glozow) Pull request description: This PR is part of the orphan resolution project, see #27463. This design came from collaboration with sipa - thanks. We want to limit the CPU work and memory used by `TxOrphanage` to avoid denial of service attacks. On master, this is achieved by limiting the number of transactions in this data structure to 100, and the weight of each transaction to 400KWu (the largest standard tx) [0]. We always allow new orphans, but if the addition causes us to exceed 100, we evict one randomly. This is dead simple, but has problems: - It makes the orphanage trivially churnable: any one peer can render it useless by spamming us with lots of orphans. It's possible this is happening: "Looking at data from node alice on 2024-09-14 shows that we’re sometimes removing more than 100k orphans per minute. This feels like someone flooding us with orphans." [1] - Effectively, opportunistic 1p1c is useless in the presence of adversaries: it is *opportunistic* and pairs a low feerate tx with a child that happens to be in the orphanage. So if nothing is able to stay in orphanages, we can't expect 1p1cs to propagate. - This number is also often insufficient for the volume of orphans we handle: historical data show that overflows are pretty common, and there are times where "it seems like [the node] forgot about the orphans and re-requested them multiple times." [1] Just jacking up the `-maxorphantxs` number is not a good enough solution, because it doesn't solve the churnability problem, and the effective resource bounds scale poorly. This PR introduces numbers for {global, per-peer} {memory usage, announcements + number of inputs}, representing resource limits: - The (constant) **global latency score limit** is the number of unique (wtxid, peer) pairs in the orphanage + the number of inputs spent by those (deduplicated) transactions floor-divided by 10 [2]. This represents a cap on CPU or latency for any given operation, and does not change with the number of peers we have. Evictions must happen whenever this limit is reached. The primary goal of this limit is to ensure we do not spend more than a few ms on any call to `LimitOrphans` or `EraseForBlock`. - The (variable) **per-peer latency score limit** is the global latency score limit divided by the number of peers. Peers are allowed to exceed this limit provided the global announcement limit has not been reached. The per-peer announcement limit decreases with more peers. - The (constant) **per-peer memory usage reservation** is the amount of orphan weight [3] reserved per peer [4]. Reservation means that peers are effectively guaranteed this amount of space. Peers are allowed to exceed this limit provided the global usage limit is not reached. The primary goal of this limit is to ensure we don't oom. - The (variable) **global memory usage limit** is the number of peers multiplied by the per-peer reservation [5]. As such, the global memory usage limit scales up with the number of peers we have. Evictions must happen whenever this limit is reached. - We introduce a "Peer DoS Score" which is the maximum between its "CPU Score" and "Memory Score." The CPU score is the ratio between the number of orphans announced by this peer / peer announcement limit. The memory score is the total usage of all orphans announced by this peer / peer usage reservation. Eviction changes in a few ways: - It is triggered if either limit is exceeded. - On each iteration of the loop, instead of selecting a random orphan, we select a peer and delete 1 of its announcements. Specifically, we select the peer with the highest DoS score, which is the maximum between its CPU DoS score (based on announcements) and Memory DoS score (based on tx weight). After the peer has been selected, we evict the oldest orphan (non-reconsiderable sorted before reconsiderable). - Instead of evicting orphans, we evict announcements. An orphan is still in the orphanage as long as there is 1 peer announcer. Of course, over the course of several iteration loops, we may erase all announcers, thus erasing the orphan itself. The purpose of this change is to prevent a peer from being able to trigger eviction of another peer's orphans. This PR also: - Reimplements `TxOrphanage` as single multi-index container. - Effectively bounds the number of transactions that can be in a peer's work set by ensuring it is a subset of the peer's announcements. - Removes the `-maxorphantxs` config option, as the orphanage no longer limits by unique orphans. This means we can receive 1p1c packages in the presence of spammy peers. It also makes the orphanage more useful and increases our download capacity without drastically increasing orphanage resource usage. [0]: This means the effective memory limit in orphan weight is 100 * 400KWu = 40MWu [1]: https://delvingbitcoin.org/t/stats-on-orphanage-overflows/1421 [2]: Limit is 3000, which is equivalent to one max size ancestor package (24 transactions can be missing inputs) for each peer (default max connections is 125). [3]: Orphan weight is used in place of actual memory usage because something like "one maximally sized standard tx" is easier to reason about than "considering the bytes allocated for vin and vout vectors, it needs to be within N bytes..." etc. We can also consider a different formula to encapsulate more the memory overhead but still have an interface that is easy to reason about. [4]: The limit is 404KWu, which is the maximum size of an ancestor package. [5]: With 125 peers, this is 50.5MWu, which is a small increase from the existing limit of 40MWu. While the actual memory usage limit is higher (this number does not include the other memory used by `TxOrphanage` to store the outpoints map, etc.), this is within the same ballpark as the old limit. ACKs for top commit: marcofleon: ReACK 5002462 achow101: light ACK 5002462 instagibbs: ACK 5002462 theStack: Code-review ACK 5002462 Tree-SHA512: 270c11a2d116a1bf222358a1b4e25ffd1f01e24da958284fa8c4678bee5547f9e0554e87da7b7d5d5d172ca11da147f54a69b3436cc8f382debb6a45a90647fd
2 parents 672c85c + 5002462 commit 80067ac

24 files changed

+2657
-880
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
277277
node/timeoffsets.cpp
278278
node/transaction.cpp
279279
node/txdownloadman_impl.cpp
280+
node/txorphanage.cpp
280281
node/txreconciliation.cpp
281282
node/utxo_snapshot.cpp
282283
node/warnings.cpp
@@ -308,7 +309,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
308309
txdb.cpp
309310
txgraph.cpp
310311
txmempool.cpp
311-
txorphanage.cpp
312312
txrequest.cpp
313313
validation.cpp
314314
validationinterface.cpp

src/bench/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ add_executable(bench_bitcoin
4949
streams_findbyte.cpp
5050
strencodings.cpp
5151
txgraph.cpp
52+
txorphanage.cpp
5253
util_time.cpp
5354
verify_script.cpp
5455
xor.cpp

src/bench/txorphanage.cpp

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
// Copyright (c) The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <bench/bench.h>
6+
#include <consensus/amount.h>
7+
#include <net.h>
8+
#include <policy/policy.h>
9+
#include <primitives/transaction.h>
10+
#include <pubkey.h>
11+
#include <script/sign.h>
12+
#include <test/util/setup_common.h>
13+
#include <node/txorphanage.h>
14+
#include <util/check.h>
15+
#include <test/util/transaction_utils.h>
16+
17+
#include <cstdint>
18+
#include <memory>
19+
20+
static constexpr node::TxOrphanage::Usage TINY_TX_WEIGHT{240};
21+
static constexpr int64_t APPROX_WEIGHT_PER_INPUT{200};
22+
23+
// Creates a transaction with num_inputs inputs and 1 output, padded to target_weight. Use this function to maximize m_outpoint_to_orphan_it operations.
24+
// If num_inputs is 0, we maximize the number of inputs.
25+
static CTransactionRef MakeTransactionBulkedTo(unsigned int num_inputs, int64_t target_weight, FastRandomContext& det_rand)
26+
{
27+
CMutableTransaction tx;
28+
assert(target_weight >= 40 + APPROX_WEIGHT_PER_INPUT);
29+
if (!num_inputs) num_inputs = (target_weight - 40) / APPROX_WEIGHT_PER_INPUT;
30+
for (unsigned int i = 0; i < num_inputs; ++i) {
31+
tx.vin.emplace_back(Txid::FromUint256(det_rand.rand256()), 0);
32+
}
33+
assert(GetTransactionWeight(*MakeTransactionRef(tx)) <= target_weight);
34+
35+
tx.vout.resize(1);
36+
37+
// If necessary, pad the transaction to the target weight.
38+
if (GetTransactionWeight(*MakeTransactionRef(tx)) < target_weight - 4) {
39+
BulkTransaction(tx, target_weight);
40+
}
41+
return MakeTransactionRef(tx);
42+
}
43+
44+
// Constructs a transaction using a subset of inputs[start_input : start_input + num_inputs] up to the weight_limit.
45+
static CTransactionRef MakeTransactionSpendingUpTo(const std::vector<CTxIn>& inputs, unsigned int start_input, unsigned int num_inputs, int64_t weight_limit)
46+
{
47+
CMutableTransaction tx;
48+
for (unsigned int i{start_input}; i < start_input + num_inputs; ++i) {
49+
if (GetTransactionWeight(*MakeTransactionRef(tx)) + APPROX_WEIGHT_PER_INPUT >= weight_limit) break;
50+
tx.vin.emplace_back(inputs.at(i % inputs.size()));
51+
}
52+
assert(tx.vin.size() > 0);
53+
return MakeTransactionRef(tx);
54+
}
55+
static void OrphanageSinglePeerEviction(benchmark::Bench& bench)
56+
{
57+
FastRandomContext det_rand{true};
58+
59+
// Fill up announcements slots with tiny txns, followed by a single large one
60+
unsigned int NUM_TINY_TRANSACTIONS((node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE));
61+
62+
// Construct transactions to submit to orphanage: 1-in-1-out tiny transactions
63+
std::vector<CTransactionRef> tiny_txs;
64+
tiny_txs.reserve(NUM_TINY_TRANSACTIONS);
65+
for (unsigned int i{0}; i < NUM_TINY_TRANSACTIONS; ++i) {
66+
tiny_txs.emplace_back(MakeTransactionBulkedTo(1, TINY_TX_WEIGHT, det_rand));
67+
}
68+
auto large_tx = MakeTransactionBulkedTo(1, MAX_STANDARD_TX_WEIGHT, det_rand);
69+
assert(GetTransactionWeight(*large_tx) <= MAX_STANDARD_TX_WEIGHT);
70+
71+
const auto orphanage{node::MakeTxOrphanage(/*max_global_ann=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
72+
73+
// Populate the orphanage. To maximize the number of evictions, first fill up with tiny transactions, then add a huge one.
74+
NodeId peer{0};
75+
// Add tiny transactions until we are just about to hit the memory limit, up to the max number of announcements.
76+
// We use the same tiny transactions for all peers to minimize their contribution to the usage limit.
77+
int64_t total_weight_to_add{0};
78+
for (unsigned int txindex{0}; txindex < NUM_TINY_TRANSACTIONS; ++txindex) {
79+
const auto& tx{tiny_txs.at(txindex)};
80+
81+
total_weight_to_add += GetTransactionWeight(*tx);
82+
if (total_weight_to_add > orphanage->MaxGlobalUsage()) break;
83+
84+
assert(orphanage->AddTx(tx, peer));
85+
86+
// Sanity check: we should always be exiting at the point of hitting the weight limit.
87+
assert(txindex < NUM_TINY_TRANSACTIONS - 1);
88+
}
89+
90+
// In the real world, we always trim after each new tx.
91+
// If we need to trim already, that means the benchmark is not representative of what LimitOrphans may do in a single call.
92+
assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());
93+
assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
94+
assert(orphanage->TotalOrphanUsage() + TINY_TX_WEIGHT > orphanage->MaxGlobalUsage());
95+
96+
bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
97+
// Lastly, add the large transaction.
98+
const auto num_announcements_before_trim{orphanage->CountAnnouncements()};
99+
assert(orphanage->AddTx(large_tx, peer));
100+
orphanage->LimitOrphans();
101+
102+
// If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.
103+
const auto num_announcements_after_trim{orphanage->CountAnnouncements()};
104+
const auto num_evicted{num_announcements_before_trim - num_announcements_after_trim};
105+
106+
// The number of evictions is the same regardless of the number of peers. In both cases, we can exceed the
107+
// usage limit using 1 maximally-sized transaction.
108+
assert(num_evicted == MAX_STANDARD_TX_WEIGHT / TINY_TX_WEIGHT);
109+
});
110+
}
111+
static void OrphanageMultiPeerEviction(benchmark::Bench& bench)
112+
{
113+
// Best number is just below sqrt(DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE)
114+
static constexpr unsigned int NUM_PEERS{39};
115+
// All peers will have the same transactions. We want to be just under the weight limit, so divide the max usage limit by the number of unique transactions.
116+
static constexpr node::TxOrphanage::Count NUM_UNIQUE_TXNS{node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS};
117+
static constexpr node::TxOrphanage::Usage TOTAL_USAGE_LIMIT{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER * NUM_PEERS};
118+
// Subtract 4 because BulkTransaction rounds up and we must avoid going over the weight limit early.
119+
static constexpr node::TxOrphanage::Usage LARGE_TX_WEIGHT{TOTAL_USAGE_LIMIT / NUM_UNIQUE_TXNS - 4};
120+
static_assert(LARGE_TX_WEIGHT >= TINY_TX_WEIGHT * 2, "Tx is too small, increase NUM_PEERS");
121+
// The orphanage does not permit any transactions larger than 400'000, so this test will not work if the large tx is much larger.
122+
static_assert(LARGE_TX_WEIGHT <= MAX_STANDARD_TX_WEIGHT, "Tx is too large, decrease NUM_PEERS");
123+
124+
FastRandomContext det_rand{true};
125+
// Construct large transactions
126+
std::vector<CTransactionRef> shared_txs;
127+
shared_txs.reserve(NUM_UNIQUE_TXNS);
128+
for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {
129+
shared_txs.emplace_back(MakeTransactionBulkedTo(9, LARGE_TX_WEIGHT, det_rand));
130+
}
131+
std::vector<size_t> indexes;
132+
indexes.resize(NUM_UNIQUE_TXNS);
133+
std::iota(indexes.begin(), indexes.end(), 0);
134+
135+
const auto orphanage{node::MakeTxOrphanage(/*max_global_ann=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
136+
// Every peer sends the same transactions, all from shared_txs.
137+
// Each peer has 1 or 2 assigned transactions, which they must place as the last and second-to-last positions.
138+
// The assignments ensure that every transaction is in some peer's last 2 transactions, and is thus remains in the orphanage until the end of LimitOrphans.
139+
static_assert(NUM_UNIQUE_TXNS <= NUM_PEERS * 2);
140+
141+
// We need each peer to send some transactions so that the global limit (which is a function of the number of peers providing at least 1 announcement) rises.
142+
for (unsigned int i{0}; i < NUM_UNIQUE_TXNS; ++i) {
143+
for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
144+
const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};
145+
CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};
146+
147+
const auto& tx{shared_txs.at(indexes.at(i))};
148+
if (tx == reserved_last_tx) {
149+
// Skip
150+
} else if (reserved_second_to_last_tx && tx == reserved_second_to_last_tx) {
151+
// Skip
152+
} else {
153+
orphanage->AddTx(tx, peer);
154+
}
155+
}
156+
}
157+
158+
// Now add the final reserved transactions.
159+
for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
160+
const CTransactionRef& reserved_last_tx{shared_txs.at(peer)};
161+
CTransactionRef reserved_second_to_last_tx{peer < NUM_UNIQUE_TXNS - NUM_PEERS ? shared_txs.at(peer + NUM_PEERS) : nullptr};
162+
// Add the final reserved transactions.
163+
if (reserved_second_to_last_tx) {
164+
orphanage->AddTx(reserved_second_to_last_tx, peer);
165+
}
166+
orphanage->AddTx(reserved_last_tx, peer);
167+
}
168+
169+
assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_UNIQUE_TXNS);
170+
const auto total_usage{orphanage->TotalOrphanUsage()};
171+
const auto max_usage{orphanage->MaxGlobalUsage()};
172+
assert(max_usage - total_usage <= LARGE_TX_WEIGHT);
173+
assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
174+
175+
auto last_tx = MakeTransactionBulkedTo(0, max_usage - total_usage + 1, det_rand);
176+
177+
bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
178+
const auto num_announcements_before_trim{orphanage->CountAnnouncements()};
179+
// There is a small gap between the total usage and the max usage. Add a transaction to fill it.
180+
assert(orphanage->AddTx(last_tx, 0));
181+
orphanage->LimitOrphans();
182+
183+
// If there are multiple peers, note that they all have the same DoS score. We will evict only 1 item at a time for each new DoSiest peer.
184+
const auto num_evicted{num_announcements_before_trim - orphanage->CountAnnouncements() + 1};
185+
// The trimming happens as a round robin. In the first NUM_UNIQUE_TXNS - 2 rounds for each peer, only duplicates are evicted.
186+
// Once each peer has 2 transactions left, it's possible to select a peer whose oldest transaction is unique.
187+
assert(num_evicted >= (NUM_UNIQUE_TXNS - 2) * NUM_PEERS);
188+
});
189+
}
190+
191+
static void OrphanageEraseAll(benchmark::Bench& bench, bool block_or_disconnect)
192+
{
193+
FastRandomContext det_rand{true};
194+
const auto orphanage{node::MakeTxOrphanage(/*max_global_ann=*/node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE, /*reserved_peer_usage=*/node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER)};
195+
// This is an unrealistically large number of inputs for a block, as there is almost no room given to witness data,
196+
// outputs, and overhead for individual transactions. The entire block is 1 transaction with 20,000 inputs.
197+
constexpr unsigned int NUM_BLOCK_INPUTS{MAX_BLOCK_WEIGHT / APPROX_WEIGHT_PER_INPUT};
198+
const auto block_tx{MakeTransactionBulkedTo(NUM_BLOCK_INPUTS, MAX_BLOCK_WEIGHT - 4000, det_rand)};
199+
CBlock block;
200+
block.vtx.push_back(block_tx);
201+
202+
// Transactions with 9 inputs maximize the computation / LatencyScore ratio.
203+
constexpr unsigned int INPUTS_PER_TX{9};
204+
constexpr unsigned int NUM_PEERS{125};
205+
constexpr unsigned int NUM_TXNS_PER_PEER = node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS;
206+
207+
// Divide the block's inputs evenly among the peers.
208+
constexpr unsigned int INPUTS_PER_PEER = NUM_BLOCK_INPUTS / NUM_PEERS;
209+
static_assert(INPUTS_PER_PEER > 0);
210+
// All the block inputs are spent by the orphanage transactions. Each peer is assigned 76 of them.
211+
// Each peer has 24 transactions spending 9 inputs each, so jumping by 3 ensures we cover all of the inputs.
212+
static_assert(7 * NUM_TXNS_PER_PEER + INPUTS_PER_TX - 1 >= INPUTS_PER_PEER);
213+
214+
for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
215+
int64_t weight_left_for_peer{node::DEFAULT_RESERVED_ORPHAN_WEIGHT_PER_PEER};
216+
for (unsigned int txnum{0}; txnum < node::DEFAULT_MAX_ORPHANAGE_LATENCY_SCORE / NUM_PEERS; ++txnum) {
217+
// Transactions must be unique since they use different (though overlapping) inputs.
218+
const unsigned int start_input = peer * INPUTS_PER_PEER + txnum * 7;
219+
220+
// Note that we shouldn't be able to hit the weight limit with these small transactions.
221+
const int64_t weight_limit{std::min<int64_t>(weight_left_for_peer, MAX_STANDARD_TX_WEIGHT)};
222+
auto ptx = MakeTransactionSpendingUpTo(block_tx->vin, /*start_input=*/start_input, /*num_inputs=*/INPUTS_PER_TX, /*weight_limit=*/weight_limit);
223+
224+
assert(GetTransactionWeight(*ptx) <= MAX_STANDARD_TX_WEIGHT);
225+
assert(!orphanage->HaveTx(ptx->GetWitnessHash()));
226+
assert(orphanage->AddTx(ptx, peer));
227+
228+
weight_left_for_peer -= GetTransactionWeight(*ptx);
229+
if (weight_left_for_peer < TINY_TX_WEIGHT * 2) break;
230+
}
231+
}
232+
233+
// If these fail, it means this benchmark is not realistic because the orphanage would have been trimmed already.
234+
assert(orphanage->TotalLatencyScore() <= orphanage->MaxGlobalLatencyScore());
235+
assert(orphanage->TotalOrphanUsage() <= orphanage->MaxGlobalUsage());
236+
237+
// 3000 announcements (and unique transactions) in the orphanage.
238+
// They spend a total of 27,000 inputs (20,000 unique ones)
239+
assert(orphanage->CountAnnouncements() == NUM_PEERS * NUM_TXNS_PER_PEER);
240+
assert(orphanage->TotalLatencyScore() == orphanage->CountAnnouncements());
241+
242+
bench.epochs(1).epochIterations(1).run([&]() NO_THREAD_SAFETY_ANALYSIS {
243+
if (block_or_disconnect) {
244+
// Erase everything through EraseForBlock.
245+
// Every tx conflicts with this block.
246+
orphanage->EraseForBlock(block);
247+
assert(orphanage->CountAnnouncements() == 0);
248+
} else {
249+
// Erase everything through EraseForPeer.
250+
for (NodeId peer{0}; peer < NUM_PEERS; ++peer) {
251+
orphanage->EraseForPeer(peer);
252+
}
253+
assert(orphanage->CountAnnouncements() == 0);
254+
}
255+
});
256+
}
257+
258+
static void OrphanageEraseForBlock(benchmark::Bench& bench)
259+
{
260+
OrphanageEraseAll(bench, /*block_or_disconnect=*/true);
261+
}
262+
static void OrphanageEraseForPeer(benchmark::Bench& bench)
263+
{
264+
OrphanageEraseAll(bench, /*block_or_disconnect=*/false);
265+
}
266+
267+
BENCHMARK(OrphanageSinglePeerEviction, benchmark::PriorityLevel::LOW);
268+
BENCHMARK(OrphanageMultiPeerEviction, benchmark::PriorityLevel::LOW);
269+
BENCHMARK(OrphanageEraseForBlock, benchmark::PriorityLevel::LOW);
270+
BENCHMARK(OrphanageEraseForPeer, benchmark::PriorityLevel::LOW);

src/init.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,6 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
490490
argsman.AddArg("-allowignoredconf", strprintf("For backwards compatibility, treat an unused %s file in the datadir as a warning, not an error.", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
491491
argsman.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
492492
argsman.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE_MB), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
493-
argsman.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
494493
argsman.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY_HOURS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
495494
argsman.AddArg("-minimumchainwork=<hex>", strprintf("Minimum work assumed to exist on a valid chain in hex (default: %s, testnet3: %s, testnet4: %s, signet: %s)", defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnetChainParams->GetConsensus().nMinimumChainWork.GetHex(), testnet4ChainParams->GetConsensus().nMinimumChainWork.GetHex(), signetChainParams->GetConsensus().nMinimumChainWork.GetHex()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS);
496495
argsman.AddArg("-par=<n>", strprintf("Set the number of script verification threads (0 = auto, up to %d, <0 = leave that many cores free, default: %d)",

src/net_processing.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <node/protocol_version.h>
3636
#include <node/timeoffsets.h>
3737
#include <node/txdownloadman.h>
38+
#include <node/txorphanage.h>
3839
#include <node/txreconciliation.h>
3940
#include <node/warnings.h>
4041
#include <policy/feerate.h>
@@ -53,7 +54,6 @@
5354
#include <sync.h>
5455
#include <tinyformat.h>
5556
#include <txmempool.h>
56-
#include <txorphanage.h>
5757
#include <uint256.h>
5858
#include <util/check.h>
5959
#include <util/strencodings.h>
@@ -533,7 +533,7 @@ class PeerManagerImpl final : public PeerManager
533533
std::optional<std::string> FetchBlock(NodeId peer_id, const CBlockIndex& block_index) override
534534
EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
535535
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
536-
std::vector<TxOrphanage::OrphanTxBase> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
536+
std::vector<node::TxOrphanage::OrphanTxBase> GetOrphanTransactions() override EXCLUSIVE_LOCKS_REQUIRED(!m_tx_download_mutex);
537537
PeerManagerInfo GetInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
538538
void SendPings() override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
539539
void RelayTransaction(const Txid& txid, const Wtxid& wtxid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex);
@@ -1754,7 +1754,7 @@ bool PeerManagerImpl::GetNodeStateStats(NodeId nodeid, CNodeStateStats& stats) c
17541754
return true;
17551755
}
17561756

1757-
std::vector<TxOrphanage::OrphanTxBase> PeerManagerImpl::GetOrphanTransactions()
1757+
std::vector<node::TxOrphanage::OrphanTxBase> PeerManagerImpl::GetOrphanTransactions()
17581758
{
17591759
LOCK(m_tx_download_mutex);
17601760
return m_txdownloadman.GetOrphanTransactions();
@@ -1925,7 +1925,7 @@ PeerManagerImpl::PeerManagerImpl(CConnman& connman, AddrMan& addrman,
19251925
m_banman(banman),
19261926
m_chainman(chainman),
19271927
m_mempool(pool),
1928-
m_txdownloadman(node::TxDownloadOptions{pool, m_rng, opts.max_orphan_txs, opts.deterministic_rng}),
1928+
m_txdownloadman(node::TxDownloadOptions{pool, m_rng, opts.deterministic_rng}),
19291929
m_warnings{warnings},
19301930
m_opts{opts}
19311931
{

0 commit comments

Comments
 (0)