Bitcoin Core 31.1.0
P2P Digital Currency
Loading...
Searching...
No Matches
txorphan.cpp
Go to the documentation of this file.
1// Copyright (c) 2022-present 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 <consensus/amount.h>
7#include <net_processing.h>
8#include <node/eviction.h>
9#include <node/txorphanage.h>
10#include <policy/policy.h>
12#include <script/script.h>
13#include <sync.h>
15#include <test/fuzz/fuzz.h>
16#include <test/fuzz/util.h>
18#include <uint256.h>
19#include <util/check.h>
20#include <util/feefrac.h>
21#include <util/time.h>
22
23#include <algorithm>
24#include <bitset>
25#include <cmath>
26#include <cstdint>
27#include <iostream>
28#include <memory>
29#include <set>
30#include <utility>
31#include <vector>
32
34{
35 static const auto testing_setup = MakeNoLogFileContext();
36}
37
39{
41 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
42 FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};
43 SetMockTime(ConsumeTime(fuzzed_data_provider));
44
45 auto orphanage = node::MakeTxOrphanage();
46 std::vector<COutPoint> outpoints; // Duplicates are tolerated
47 outpoints.reserve(200'000);
48
49 // initial outpoints used to construct transactions later
50 for (uint8_t i = 0; i < 4; i++) {
51 outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
52 }
53
54 CTransactionRef ptx_potential_parent = nullptr;
55
56 std::vector<CTransactionRef> tx_history;
57
58 LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 1000)
59 {
60 // construct transaction
61 const CTransactionRef tx = [&] {
63 const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
64 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
65 // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
66 // running any transaction validation logic before adding transactions to the orphanage
67 tx_mut.vin.reserve(num_in);
68 for (uint32_t i = 0; i < num_in; i++) {
69 auto& prevout = PickValue(fuzzed_data_provider, outpoints);
70 // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
71 tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
72 }
73 // output amount will not affect txorphanage
74 tx_mut.vout.reserve(num_out);
75 for (uint32_t i = 0; i < num_out; i++) {
76 tx_mut.vout.emplace_back(CAmount{0}, CScript{});
77 }
78 auto new_tx = MakeTransactionRef(tx_mut);
79 // add newly constructed outpoints to the coin pool
80 for (uint32_t i = 0; i < num_out; i++) {
81 outpoints.emplace_back(new_tx->GetHash(), i);
82 }
83 return new_tx;
84 }();
85
86 tx_history.push_back(tx);
87
88 const auto wtxid{tx->GetWitnessHash()};
89
90 // Trigger orphanage functions that are called using parents. ptx_potential_parent is a tx we constructed in a
91 // previous loop and potentially the parent of this tx.
92 if (ptx_potential_parent) {
93 // Set up future GetTxToReconsider call.
94 orphanage->AddChildrenToWorkSet(*ptx_potential_parent, orphanage_rng);
95
96 // Check that all txns returned from GetChildrenFrom* are indeed a direct child of this tx.
97 NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
98 for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) {
99 assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) {
100 return input.prevout.hash == ptx_potential_parent->GetHash();
101 }));
102 }
103 }
104
105 // trigger orphanage functions
106 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000)
107 {
108 NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>();
109 const auto total_bytes_start{orphanage->TotalOrphanUsage()};
110 const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)};
111 const auto tx_weight{GetTransactionWeight(*tx)};
112
113 CallOneOf(
114 fuzzed_data_provider,
115 [&] {
116 {
117 CTransactionRef ref = orphanage->GetTxToReconsider(peer_id);
118 if (ref) {
119 Assert(orphanage->HaveTx(ref->GetWitnessHash()));
120 }
121 }
122 },
123 [&] {
124 bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
125 bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
126 // AddTx should return false if tx is too big or already have it
127 // tx weight is unknown, we only check when tx is already in orphanage
128 {
129 bool add_tx = orphanage->AddTx(tx, peer_id);
130 // have_tx == true -> add_tx == false
131 Assert(!have_tx || !add_tx);
132 // have_tx_and_peer == true -> add_tx == false
133 Assert(!have_tx_and_peer || !add_tx);
134 // After AddTx, the orphanage may trim itself, so the peer's usage may have gone up or down.
135
136 if (add_tx) {
137 Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT);
138 } else {
139 // Peer may have been added as an announcer.
140 if (orphanage->UsageByPeer(peer_id) > total_peer_bytes_start) {
141 Assert(orphanage->HaveTxFromPeer(wtxid, peer_id));
142 }
143
144 // If announcement was added, total bytes does not increase.
145 // However, if eviction was triggered, the value may decrease.
146 Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);
147 }
148 }
149 // We are not guaranteed to have_tx after AddTx. There are a few possible reasons:
150 // - tx itself exceeds the per-peer memory usage limit, so LimitOrphans had to remove it immediately
151 // - tx itself exceeds the per-peer latency score limit, so LimitOrphans had to remove it immediately
152 // - the orphanage needed trim and all other announcements from this peer are reconsiderable
153 },
154 [&] {
155 bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
156 bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
157 // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
158 {
159 bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
160 // have_tx == false -> added_announcer == false
161 Assert(have_tx || !added_announcer);
162 // have_tx_and_peer == true -> added_announcer == false
163 Assert(!have_tx_and_peer || !added_announcer);
164
165 // If announcement was added, total bytes does not increase.
166 // However, if eviction was triggered, the value may decrease.
167 Assert(orphanage->TotalOrphanUsage() <= total_bytes_start);
168 }
169 },
170 [&] {
171 bool have_tx = orphanage->HaveTx(tx->GetWitnessHash());
172 bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)};
173 // EraseTx should return 0 if m_orphans doesn't have the tx
174 {
175 auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)};
176 Assert(have_tx == orphanage->EraseTx(tx->GetWitnessHash()));
177 // After EraseTx, the orphanage may trim itself, so all peers' usage may have gone up or down.
178 if (have_tx) {
179 if (!have_tx_and_peer) {
180 Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before);
181 }
182 }
183 }
184 have_tx = orphanage->HaveTx(tx->GetWitnessHash());
185 have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
186 // have_tx should be false and EraseTx should fail
187 {
188 Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid));
189 }
190 },
191 [&] {
192 orphanage->EraseForPeer(peer_id);
193 Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id));
194 Assert(orphanage->UsageByPeer(peer_id) == 0);
195 },
196 [&] {
197 // Make a block out of txs and then EraseForBlock
198 CBlock block;
199 int64_t block_weight{0};
200 int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000);
201 for (int i{0}; i < num_txs; ++i) {
202 auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history);
203 const auto tx_weight = GetTransactionWeight(*tx_to_remove);
204 if (block_weight + tx_weight > MAX_BLOCK_WEIGHT) break;
205 block_weight += tx_weight;
206 block.vtx.push_back(tx_to_remove);
207 }
208 orphanage->EraseForBlock(block);
209 for (const auto& tx_removed : block.vtx) {
210 Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash()));
211 Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));
212 }
213 }
214 );
215 }
216
217 // Set tx as potential parent to be used for future GetChildren() calls.
218 if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {
219 ptx_potential_parent = tx;
220 }
221
222 const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())};
223 const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr};
224 Assert(have_tx == get_tx_nonnull);
225 }
226 orphanage->SanityCheck();
227}
228
229FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage)
230{
232 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
233 FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)};
234 SetMockTime(ConsumeTime(fuzzed_data_provider));
235
236 // We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and
237 // should therefore never have any orphans evicted.
238 const unsigned int MAX_PEERS = 125;
239 const unsigned int num_peers = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS);
240 // Generate a vector of bools for whether each peer is protected from eviction
241 std::bitset<MAX_PEERS> protected_peers;
242 for (unsigned int i = 0; i < num_peers; i++) {
243 protected_peers.set(i, fuzzed_data_provider.ConsumeBool());
244 }
245
246 // Params for orphanage.
247 const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000);
248 const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000);
249 auto orphanage = node::MakeTxOrphanage(global_latency_score_limit, per_peer_weight_reservation);
250
251 // The actual limit, MaxPeerLatencyScore(), may be higher, since TxOrphanage only counts peers
252 // that have announced an orphan. The honest peer will not experience evictions if it never
253 // exceeds this.
254 const unsigned int honest_latency_limit = global_latency_score_limit / num_peers;
255 // Honest peer will not experience evictions if it never exceeds this.
256 const int64_t honest_mem_limit = per_peer_weight_reservation;
257
258 std::vector<COutPoint> outpoints; // Duplicates are tolerated
259 outpoints.reserve(400);
260
261 // initial outpoints used to construct transactions later
262 for (uint8_t i = 0; i < 4; i++) {
263 outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
264 }
265
266 // These are honest peer's live announcements. We expect them to be protected from eviction.
267 std::set<Wtxid> protected_wtxids;
268
269 LIMITED_WHILE(outpoints.size() < 400 && fuzzed_data_provider.ConsumeBool(), 1000)
270 {
271 // construct transaction
272 const CTransactionRef tx = [&] {
273 CMutableTransaction tx_mut;
274 const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
275 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
276 // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
277 // running any transaction validation logic before adding transactions to the orphanage
278 tx_mut.vin.reserve(num_in);
279 for (uint32_t i = 0; i < num_in; i++) {
280 auto& prevout = PickValue(fuzzed_data_provider, outpoints);
281 // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
282 tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
283 }
284 // output amount or spendability will not affect txorphanage
285 tx_mut.vout.reserve(num_out);
286 for (uint32_t i = 0; i < num_out; i++) {
287 const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000);
288 if (payload_size) {
289 tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size));
290 } else {
291 tx_mut.vout.emplace_back(0, CScript{});
292 }
293 }
294 auto new_tx = MakeTransactionRef(tx_mut);
295 // add newly constructed outpoints to the coin pool
296 for (uint32_t i = 0; i < num_out; i++) {
297 outpoints.emplace_back(new_tx->GetHash(), i);
298 }
299 return new_tx;
300 }();
301
302 const auto wtxid{tx->GetWitnessHash()};
303
304 // orphanage functions
305 LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 10 * global_latency_score_limit)
306 {
307 NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, num_peers - 1);
308 const auto tx_weight{GetTransactionWeight(*tx)};
309
310 // This protected peer will never send orphans that would
311 // exceed their own personal allotment, so is never evicted.
312 const bool peer_is_protected{protected_peers[peer_id]};
313
314 CallOneOf(
315 fuzzed_data_provider,
316 [&] { // AddTx
317 bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
318 if (peer_is_protected && !have_tx_and_peer &&
319 (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
320 orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
321 // We never want our protected peer oversized or over-announced
322 } else {
323 orphanage->AddTx(tx, peer_id);
324 if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
325 protected_wtxids.insert(wtxid);
326 }
327 }
328 },
329 [&] { // AddAnnouncer
330 bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
331 // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
332 {
333 if (peer_is_protected && !have_tx_and_peer &&
334 (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
335 orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
336 // We never want our protected peer oversized
337 } else {
338 orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
339 if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
340 protected_wtxids.insert(wtxid);
341 }
342 }
343 }
344 },
345 [&] { // EraseTx
346 if (protected_wtxids.contains(tx->GetWitnessHash())) {
347 protected_wtxids.erase(wtxid);
348 }
349 orphanage->EraseTx(wtxid);
350 Assert(!orphanage->HaveTx(wtxid));
351 },
352 [&] { // EraseForPeer
353 if (!protected_peers[peer_id]) {
354 orphanage->EraseForPeer(peer_id);
355 Assert(orphanage->UsageByPeer(peer_id) == 0);
356 Assert(orphanage->LatencyScoreFromPeer(peer_id) == 0);
357 Assert(orphanage->AnnouncementsFromPeer(peer_id) == 0);
358 }
359 }
360 );
361 }
362 }
363
364 orphanage->SanityCheck();
365 // All of the honest peer's announcements are still present.
366 for (const auto& wtxid : protected_wtxids) {
367 Assert(orphanage->HaveTx(wtxid));
368 }
369}
370
371FUZZ_TARGET(txorphanage_sim)
372{
374 // This is a comprehensive simulation fuzz test, which runs through a scenario involving up to
375 // 16 transactions (which may have simple or complex topology, and may have duplicate txids
376 // with distinct wtxids, and up to 16 peers. The scenario is performed both on a real
377 // TxOrphanage object and the behavior is compared with a naive reimplementation (just a vector
378 // of announcements) where possible, and tested for desired properties where not possible.
379
380 //
381 // 1. Setup.
382 //
383
386 static constexpr unsigned NUM_TX = 16;
389 static constexpr unsigned NUM_PEERS = 16;
392 static constexpr unsigned MAX_ANN = 64;
393
394 FuzzedDataProvider provider(buffer.data(), buffer.size());
397 InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
398
399 //
400 // 2. Construct an interesting set of 16 transactions.
401 //
402
403 // - Pick a topological order among the transactions.
404 std::vector<unsigned> txorder(NUM_TX);
405 std::iota(txorder.begin(), txorder.end(), unsigned{0});
406 std::shuffle(txorder.begin(), txorder.end(), rng);
407 // - Pick a set of dependencies (pair<child_index, parent_index>).
408 std::vector<std::pair<unsigned, unsigned>> deps;
409 deps.reserve((NUM_TX * (NUM_TX - 1)) / 2);
410 for (unsigned p = 0; p < NUM_TX - 1; ++p) {
411 for (unsigned c = p + 1; c < NUM_TX; ++c) {
412 deps.emplace_back(c, p);
413 }
414 }
415 std::shuffle(deps.begin(), deps.end(), rng);
416 deps.resize(provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * 4 - 1));
417 // - Construct the actual transactions.
418 std::set<Wtxid> wtxids;
419 std::vector<CTransactionRef> txn(NUM_TX);
420 node::TxOrphanage::Usage total_usage{0};
421 for (unsigned t = 0; t < NUM_TX; ++t) {
423 if (t > 0 && rng.randrange(4) == 0) {
424 // Occasionally duplicate the previous transaction, so that repetitions of the same
425 // txid are possible (with different wtxid).
426 tx = CMutableTransaction(*txn[txorder[t - 1]]);
427 } else {
428 tx.version = 1;
429 tx.nLockTime = 0xffffffff;
430 // Construct 1 to 16 outputs.
431 auto num_outputs = rng.randrange<unsigned>(1 << rng.randrange<unsigned>(5)) + 1;
432 for (unsigned output = 0; output < num_outputs; ++output) {
433 CScript scriptpubkey;
434 scriptpubkey.resize(provider.ConsumeIntegralInRange<unsigned>(20, 34));
435 tx.vout.emplace_back(CAmount{0}, std::move(scriptpubkey));
436 }
437 // Construct inputs (one for each dependency).
438 for (auto& [child, parent] : deps) {
439 if (child == t) {
440 auto& partx = txn[txorder[parent]];
441 assert(partx->version == 1);
442 COutPoint outpoint(partx->GetHash(), rng.randrange<size_t>(partx->vout.size()));
443 tx.vin.emplace_back(outpoint);
444 tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
445 }
446 }
447 // Construct fallback input in case there are no dependencies.
448 if (tx.vin.empty()) {
449 COutPoint outpoint(Txid::FromUint256(rng.rand256()), rng.randrange<size_t>(16));
450 tx.vin.emplace_back(outpoint);
451 tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
452 }
453 }
454 // Optionally modify the witness (allowing wtxid != txid), and certainly when the wtxid
455 // already exists.
456 while (wtxids.contains(CTransaction(tx).GetWitnessHash()) || rng.randrange(4) == 0) {
457 auto& input = tx.vin[rng.randrange(tx.vin.size())];
458 if (rng.randbool()) {
459 input.scriptWitness.stack.resize(1);
460 input.scriptWitness.stack[0].resize(rng.randrange(100));
461 } else {
462 input.scriptWitness.stack.resize(0);
463 }
464 }
465 // Convert to CTransactionRef.
466 txn[txorder[t]] = MakeTransactionRef(std::move(tx));
467 wtxids.insert(txn[txorder[t]]->GetWitnessHash());
468 auto weight = GetTransactionWeight(*txn[txorder[t]]);
470 total_usage += GetTransactionWeight(*txn[txorder[t]]);
471 }
472
473 //
474 // 3. Initialize real orphanage
475 //
476
477 auto max_global_latency_score = provider.ConsumeIntegralInRange<node::TxOrphanage::Count>(NUM_PEERS, MAX_ANN);
478 auto reserved_peer_usage = provider.ConsumeIntegralInRange<node::TxOrphanage::Usage>(1, total_usage);
479 auto real = node::MakeTxOrphanage(max_global_latency_score, reserved_peer_usage);
480
481 //
482 // 4. Functions and data structures for the simulation.
483 //
484
487 struct SimAnnouncement
488 {
489 unsigned tx;
490 NodeId announcer;
491 bool reconsider{false};
492 SimAnnouncement(unsigned tx_in, NodeId announcer_in, bool reconsider_in) noexcept :
493 tx(tx_in), announcer(announcer_in), reconsider(reconsider_in) {}
494 };
498 std::vector<SimAnnouncement> sim_announcements;
499
501 auto read_tx_fn = [&]() -> unsigned { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX - 1); };
503 auto read_peer_fn = [&]() -> NodeId { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_PEERS - 1); };
505 auto read_tx_peer_fn = [&]() -> std::pair<unsigned, NodeId> {
506 auto code = provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * NUM_PEERS - 1);
507 return {code % NUM_TX, code / NUM_TX};
508 };
510 auto have_tx_fn = [&](unsigned tx) -> bool {
511 for (auto& ann : sim_announcements) {
512 if (ann.tx == tx) return true;
513 }
514 return false;
515 };
517 auto count_peers_fn = [&]() -> unsigned {
518 std::bitset<NUM_PEERS> mask;
519 for (auto& ann : sim_announcements) {
520 mask.set(ann.announcer);
521 }
522 return mask.count();
523 };
525 auto have_reconsiderable_fn = [&](unsigned tx) -> bool {
526 for (auto& ann : sim_announcements) {
527 if (ann.reconsider && ann.tx == tx) return true;
528 }
529 return false;
530 };
532 auto have_reconsider_fn = [&](NodeId peer) -> bool {
533 for (auto& ann : sim_announcements) {
534 if (ann.reconsider && ann.announcer == peer) return true;
535 }
536 return false;
537 };
539 auto find_announce_wtxid_fn = [&](const Wtxid& wtxid, NodeId peer) -> std::vector<SimAnnouncement>::iterator {
540 for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
541 if (txn[it->tx]->GetWitnessHash() == wtxid && it->announcer == peer) return it;
542 }
543 return sim_announcements.end();
544 };
546 auto find_announce_fn = [&](unsigned tx, NodeId peer) {
547 for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
548 if (it->tx == tx && it->announcer == peer) return it;
549 }
550 return sim_announcements.end();
551 };
553 auto dos_score_fn = [&](NodeId peer, int32_t max_count, int32_t max_usage) -> FeeFrac {
554 int64_t count{0};
555 int64_t usage{0};
556 for (auto& ann : sim_announcements) {
557 if (ann.announcer != peer) continue;
558 count += 1 + (txn[ann.tx]->vin.size() / 10);
559 usage += GetTransactionWeight(*txn[ann.tx]);
560 }
561 return std::max(FeeFrac{count, max_count}, FeeFrac{usage, max_usage});
562 };
563
564 //
565 // 5. Run through a scenario of mutators on both real and simulated orphanage.
566 //
567
568 LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
569 int command = provider.ConsumeIntegralInRange<uint8_t>(0, 15);
570 while (true) {
571 if (sim_announcements.size() < MAX_ANN && command-- == 0) {
572 // AddTx
573 auto [tx, peer] = read_tx_peer_fn();
574 bool added = real->AddTx(txn[tx], peer);
575 bool sim_have_tx = have_tx_fn(tx);
576 assert(added == !sim_have_tx);
577 if (find_announce_fn(tx, peer) == sim_announcements.end()) {
578 sim_announcements.emplace_back(tx, peer, false);
579 }
580 break;
581 } else if (sim_announcements.size() < MAX_ANN && command-- == 0) {
582 // AddAnnouncer
583 auto [tx, peer] = read_tx_peer_fn();
584 bool added = real->AddAnnouncer(txn[tx]->GetWitnessHash(), peer);
585 bool sim_have_tx = have_tx_fn(tx);
586 auto sim_it = find_announce_fn(tx, peer);
587 assert(added == (sim_it == sim_announcements.end() && sim_have_tx));
588 if (added) {
589 sim_announcements.emplace_back(tx, peer, false);
590 }
591 break;
592 } else if (command-- == 0) {
593 // EraseTx
594 auto tx = read_tx_fn();
595 bool erased = real->EraseTx(txn[tx]->GetWitnessHash());
596 bool sim_have = have_tx_fn(tx);
597 assert(erased == sim_have);
598 std::erase_if(sim_announcements, [&](auto& ann) { return ann.tx == tx; });
599 break;
600 } else if (command-- == 0) {
601 // EraseForPeer
602 auto peer = read_peer_fn();
603 real->EraseForPeer(peer);
604 std::erase_if(sim_announcements, [&](auto& ann) { return ann.announcer == peer; });
605 break;
606 } else if (command-- == 0) {
607 // EraseForBlock
608 auto pattern = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << NUM_TX) - 1);
609 CBlock block;
610 std::set<COutPoint> spent;
611 for (unsigned tx = 0; tx < NUM_TX; ++tx) {
612 if ((pattern >> tx) & 1) {
613 block.vtx.emplace_back(txn[tx]);
614 for (auto& txin : block.vtx.back()->vin) {
615 spent.insert(txin.prevout);
616 }
617 }
618 }
619 std::shuffle(block.vtx.begin(), block.vtx.end(), rng);
620 real->EraseForBlock(block);
621 std::erase_if(sim_announcements, [&](auto& ann) {
622 for (auto& txin : txn[ann.tx]->vin) {
623 if (spent.contains(txin.prevout)) return true;
624 }
625 return false;
626 });
627 break;
628 } else if (command-- == 0) {
629 // AddChildrenToWorkSet
630 auto tx = read_tx_fn();
631 FastRandomContext rand_ctx(rng.rand256());
632 auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx);
634 std::set<Wtxid> child_wtxids;
635 for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) {
636 if (!have_tx_fn(child_tx)) continue;
637 if (have_reconsiderable_fn(child_tx)) continue;
638 bool child_of = false;
639 for (auto& txin : txn[child_tx]->vin) {
640 if (txin.prevout.hash == txn[tx]->GetHash()) {
641 child_of = true;
642 break;
643 }
644 }
645 if (child_of) {
646 child_wtxids.insert(txn[child_tx]->GetWitnessHash());
647 }
648 }
649 for (auto& [wtxid, peer] : added) {
650 // Wtxid must be a child of tx that is not yet reconsiderable.
651 auto child_wtxid_it = child_wtxids.find(wtxid);
652 assert(child_wtxid_it != child_wtxids.end());
653 // Announcement must exist.
654 auto sim_ann_it = find_announce_wtxid_fn(wtxid, peer);
655 assert(sim_ann_it != sim_announcements.end());
656 // Announcement must not yet be reconsiderable.
657 assert(sim_ann_it->reconsider == false);
658 // Make reconsiderable.
659 sim_ann_it->reconsider = true;
660 // Remove from child_wtxids map, to disallow it being reported a second time in added.
661 child_wtxids.erase(wtxid);
662 }
663 // Verify that AddChildrenToWorkSet does not select announcements that were already reconsiderable:
664 // Check all child wtxids which did not occur at least once in the result were already reconsiderable
665 // due to a previous AddChildrenToWorkSet.
666 assert(child_wtxids.empty());
667 break;
668 } else if (command-- == 0) {
669 // GetTxToReconsider.
670 auto peer = read_peer_fn();
671 auto result = real->GetTxToReconsider(peer);
672 if (result) {
673 // A transaction was found. It must have a corresponding reconsiderable
674 // announcement from peer.
675 auto sim_ann_it = find_announce_wtxid_fn(result->GetWitnessHash(), peer);
676 assert(sim_ann_it != sim_announcements.end());
677 assert(sim_ann_it->announcer == peer);
678 assert(sim_ann_it->reconsider);
679 // Make it non-reconsiderable.
680 sim_ann_it->reconsider = false;
681 } else {
682 // No reconsiderable transaction was found from peer. Verify that it does not
683 // have any.
684 assert(!have_reconsider_fn(peer));
685 }
686 break;
687 }
688 }
689 // Always trim after each command if needed.
690 const auto max_ann = max_global_latency_score / std::max<unsigned>(1, count_peers_fn());
691 const auto max_mem = reserved_peer_usage;
692 while (true) {
693 // Count global usage and number of peers.
694 node::TxOrphanage::Usage total_usage{0};
695 node::TxOrphanage::Count total_latency_score = sim_announcements.size();
696 for (unsigned tx = 0; tx < NUM_TX; ++tx) {
697 if (have_tx_fn(tx)) {
698 total_usage += GetTransactionWeight(*txn[tx]);
699 total_latency_score += txn[tx]->vin.size() / 10;
700 }
701 }
702 auto num_peers = count_peers_fn();
703 bool oversized = (total_usage > reserved_peer_usage * num_peers) ||
704 (total_latency_score > real->MaxGlobalLatencyScore());
705 if (!oversized) break;
706 // Find worst peer.
707 FeeFrac worst_dos_score{0, 1};
708 unsigned worst_peer = unsigned(-1);
709 for (unsigned peer = 0; peer < NUM_PEERS; ++peer) {
710 auto dos_score = dos_score_fn(peer, max_ann, max_mem);
711 // Use >= so that the more recent peer (higher NodeId) wins in case of
712 // ties.
713 if (dos_score >= worst_dos_score) {
714 worst_dos_score = dos_score;
715 worst_peer = peer;
716 }
717 }
718 assert(worst_peer != unsigned(-1));
719 assert(worst_dos_score >> FeeFrac(1, 1));
720 // Find oldest announcement from worst_peer, preferring non-reconsiderable ones.
721 bool done{false};
722 for (int reconsider = 0; reconsider < 2; ++reconsider) {
723 for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
724 if (it->announcer != worst_peer || it->reconsider != reconsider) continue;
725 sim_announcements.erase(it);
726 done = true;
727 break;
728 }
729 if (done) break;
730 }
731 assert(done);
732 }
733 // We must now be within limits, otherwise LimitOrphans should have continued further.
734 // We don't check the contents of the orphanage until the end to make fuzz runs faster.
735 assert(real->TotalLatencyScore() <= real->MaxGlobalLatencyScore());
736 assert(real->TotalOrphanUsage() <= real->MaxGlobalUsage());
737 }
738
739 //
740 // 6. Perform a full comparison between the real orphanage's inspectors and the simulation.
741 //
742
743 real->SanityCheck();
744
745
746 auto all_orphans = real->GetOrphanTransactions();
747 node::TxOrphanage::Usage orphan_usage{0};
748 std::vector<node::TxOrphanage::Usage> usage_by_peer(NUM_PEERS);
749 node::TxOrphanage::Count unique_orphans{0};
750 std::vector<node::TxOrphanage::Count> count_by_peer(NUM_PEERS);
751 node::TxOrphanage::Count total_latency_score = sim_announcements.size();
752 for (unsigned tx = 0; tx < NUM_TX; ++tx) {
753 bool sim_have_tx = have_tx_fn(tx);
754 if (sim_have_tx) {
755 orphan_usage += GetTransactionWeight(*txn[tx]);
756 total_latency_score += txn[tx]->vin.size() / 10;
757 }
758 unique_orphans += sim_have_tx;
759 auto orphans_it = std::find_if(all_orphans.begin(), all_orphans.end(), [&](auto& orph) { return orph.tx->GetWitnessHash() == txn[tx]->GetWitnessHash(); });
760 // GetOrphanTransactions (OrphanBase existence)
761 assert((orphans_it != all_orphans.end()) == sim_have_tx);
762 // HaveTx
763 bool have_tx = real->HaveTx(txn[tx]->GetWitnessHash());
764 assert(have_tx == sim_have_tx);
765 // GetTx
766 auto txref = real->GetTx(txn[tx]->GetWitnessHash());
767 assert(!!txref == sim_have_tx);
768 if (sim_have_tx) assert(txref->GetWitnessHash() == txn[tx]->GetWitnessHash());
769
770 for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
771 auto it_sim_ann = find_announce_fn(tx, peer);
772 bool sim_have_ann = it_sim_ann != sim_announcements.end();
773 if (sim_have_ann) usage_by_peer[peer] += GetTransactionWeight(*txn[tx]);
774 count_by_peer[peer] += sim_have_ann;
775 // GetOrphanTransactions (announcers presence)
776 if (sim_have_ann) assert(sim_have_tx);
777 if (sim_have_tx) assert(orphans_it->announcers.count(peer) == sim_have_ann);
778 // HaveTxFromPeer
779 bool have_ann = real->HaveTxFromPeer(txn[tx]->GetWitnessHash(), peer);
780 assert(sim_have_ann == have_ann);
781 // GetChildrenFromSamePeer
782 auto children_from_peer = real->GetChildrenFromSamePeer(txn[tx], peer);
783 auto it = children_from_peer.rbegin();
784 for (int phase = 0; phase < 2; ++phase) {
785 // First expect all children which have reconsiderable announcement from peer, then the others.
786 for (auto& ann : sim_announcements) {
787 if (ann.announcer != peer) continue;
788 if (ann.reconsider != (phase == 1)) continue;
789 bool matching_parent{false};
790 for (const auto& vin : txn[ann.tx]->vin) {
791 if (vin.prevout.hash == txn[tx]->GetHash()) matching_parent = true;
792 }
793 if (!matching_parent) continue;
794 // Found an announcement from peer which is a child of txn[tx].
795 assert(it != children_from_peer.rend());
796 assert((*it)->GetWitnessHash() == txn[ann.tx]->GetWitnessHash());
797 ++it;
798 }
799 }
800 assert(it == children_from_peer.rend());
801 }
802 }
803 // TotalOrphanUsage
804 assert(orphan_usage == real->TotalOrphanUsage());
805 for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
806 bool sim_have_reconsider = have_reconsider_fn(peer);
807 // HaveTxToReconsider
808 bool have_reconsider = real->HaveTxToReconsider(peer);
809 assert(have_reconsider == sim_have_reconsider);
810 // UsageByPeer
811 assert(usage_by_peer[peer] == real->UsageByPeer(peer));
812 // AnnouncementsFromPeer
813 assert(count_by_peer[peer] == real->AnnouncementsFromPeer(peer));
814 }
815 // CountAnnouncements
816 assert(sim_announcements.size() == real->CountAnnouncements());
817 // CountUniqueOrphans
818 assert(unique_orphans == real->CountUniqueOrphans());
819 // MaxGlobalLatencyScore
820 assert(max_global_latency_score == real->MaxGlobalLatencyScore());
821 // ReservedPeerUsage
822 assert(reserved_peer_usage == real->ReservedPeerUsage());
823 // MaxPeerLatencyScore
824 auto present_peers = count_peers_fn();
825 assert(max_global_latency_score / std::max<unsigned>(1, present_peers) == real->MaxPeerLatencyScore());
826 // MaxGlobalUsage
827 assert(reserved_peer_usage * std::max<unsigned>(1, present_peers) == real->MaxGlobalUsage());
828 // TotalLatencyScore.
829 assert(real->TotalLatencyScore() == total_latency_score);
830}
int64_t CAmount
Amount in satoshis (Can be negative).
Definition amount.h:12
const auto command
#define Assert(val)
Identity function.
Definition check.h:113
Definition block.h:74
std::vector< CTransactionRef > vtx
Definition block.h:77
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:29
Serialized script, used inside transaction inputs and outputs.
Definition script.h:405
The basic transaction that is broadcasted on the network and contained in blocks.
static const uint32_t SEQUENCE_FINAL
Setting nSequence to this value for every input in a transaction disables nLockTime/IsFinalTx().
Definition transaction.h:76
Fast randomness source.
Definition random.h:386
T ConsumeIntegralInRange(T min, T max)
xoroshiro128++ PRNG.
Definition random.h:425
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition random.h:254
uint256 rand256() noexcept
generate a random uint256.
Definition random.h:317
bool randbool() noexcept
Generate a random boolean.
Definition random.h:325
unsigned int Count
Definition txorphanage.h:41
void resize(size_type new_size)
Definition prevector.h:276
static transaction_identifier FromUint256(const uint256 &id)
256-bit opaque blob.
Definition uint256.h:195
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition validation.h:132
static const unsigned int MAX_BLOCK_WEIGHT
The maximum allowed weight for a block, see BIP 141 (network rule).
Definition consensus.h:15
#define FUZZ_TARGET(...)
Definition fuzz.h:35
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition fuzz.h:22
std::unique_ptr< TxOrphanage > MakeTxOrphanage() noexcept
Create a new TxOrphanage instance.
int64_t NodeId
Definition net.h:103
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we're willing to relay/mine.
Definition policy.h:37
static CTransactionRef MakeTransactionRef(Tx &&txIn)
std::shared_ptr< const CTransaction > CTransactionRef
@ OP_RETURN
Definition script.h:111
std::unique_ptr< T > MakeNoLogFileContext(const ChainType chain_type=ChainType::REGTEST, TestOpts opts={})
Make a test setup that has disk access to the debug.log file disabled.
A mutable version of CTransaction.
std::vector< CTxOut > vout
std::vector< CTxIn > vin
Data structure storing a fee and size, ordered by increasing fee/size.
Definition feefrac.h:40
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition util.cpp:34
auto & PickValue(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
Definition util.h:47
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition util.h:167
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition util.h:35
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition random.cpp:19
@ ZEROS
Seed with a compile time constant of zeros.
Definition random.h:19
static int count
transaction_identifier< true > Wtxid
Wtxid commits to all transaction fields including the witness.
void initialize_orphanage()
Definition txorphan.cpp:33
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition time.cpp:44
assert(!tx.IsCoinBase())