Bitcoin Core  31.0.0
P2P Digital Currency
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>
6 #include <consensus/validation.h>
7 #include <net_processing.h>
8 #include <node/eviction.h>
9 #include <node/txorphanage.h>
10 #include <policy/policy.h>
11 #include <primitives/transaction.h>
12 #include <script/script.h>
13 #include <sync.h>
15 #include <test/fuzz/fuzz.h>
16 #include <test/fuzz/util.h>
17 #include <test/util/setup_common.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());
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 = [&] {
62  CMutableTransaction tx_mut;
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.
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
107  {
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(
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  int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000);
200  for (int i{0}; i < num_txs; ++i) {
201  auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history);
202  block.vtx.push_back(tx_to_remove);
203  }
204  orphanage->EraseForBlock(block);
205  for (const auto& tx_removed : block.vtx) {
206  Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash()));
207  Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id));
208  }
209  }
210  );
211  }
212 
213  // Set tx as potential parent to be used for future GetChildren() calls.
214  if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) {
215  ptx_potential_parent = tx;
216  }
217 
218  const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())};
219  const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr};
220  Assert(have_tx == get_tx_nonnull);
221  }
222  orphanage->SanityCheck();
223 }
224 
225 FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage)
226 {
228  FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
231 
232  // We have num_peers peers. Some subset of them will never exceed their reserved weight or announcement count, and
233  // should therefore never have any orphans evicted.
234  const unsigned int MAX_PEERS = 125;
235  const unsigned int num_peers = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(1, MAX_PEERS);
236  // Generate a vector of bools for whether each peer is protected from eviction
237  std::bitset<MAX_PEERS> protected_peers;
238  for (unsigned int i = 0; i < num_peers; i++) {
239  protected_peers.set(i, fuzzed_data_provider.ConsumeBool());
240  }
241 
242  // Params for orphanage.
243  const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000);
244  const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000);
245  auto orphanage = node::MakeTxOrphanage(global_latency_score_limit, per_peer_weight_reservation);
246 
247  // The actual limit, MaxPeerLatencyScore(), may be higher, since TxOrphanage only counts peers
248  // that have announced an orphan. The honest peer will not experience evictions if it never
249  // exceeds this.
250  const unsigned int honest_latency_limit = global_latency_score_limit / num_peers;
251  // Honest peer will not experience evictions if it never exceeds this.
252  const int64_t honest_mem_limit = per_peer_weight_reservation;
253 
254  std::vector<COutPoint> outpoints; // Duplicates are tolerated
255  outpoints.reserve(400);
256 
257  // initial outpoints used to construct transactions later
258  for (uint8_t i = 0; i < 4; i++) {
259  outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0);
260  }
261 
262  // These are honest peer's live announcements. We expect them to be protected from eviction.
263  std::set<Wtxid> protected_wtxids;
264 
265  LIMITED_WHILE(outpoints.size() < 400 && fuzzed_data_provider.ConsumeBool(), 1000)
266  {
267  // construct transaction
268  const CTransactionRef tx = [&] {
269  CMutableTransaction tx_mut;
270  const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size());
271  const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, 256);
272  // pick outpoints from outpoints as input. We allow input duplicates on purpose, given we are not
273  // running any transaction validation logic before adding transactions to the orphanage
274  tx_mut.vin.reserve(num_in);
275  for (uint32_t i = 0; i < num_in; i++) {
276  auto& prevout = PickValue(fuzzed_data_provider, outpoints);
277  // try making transactions unique by setting a random nSequence, but allow duplicate transactions if they happen
278  tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL));
279  }
280  // output amount or spendability will not affect txorphanage
281  tx_mut.vout.reserve(num_out);
282  for (uint32_t i = 0; i < num_out; i++) {
283  const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000);
284  if (payload_size) {
285  tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size));
286  } else {
287  tx_mut.vout.emplace_back(0, CScript{});
288  }
289  }
290  auto new_tx = MakeTransactionRef(tx_mut);
291  // add newly constructed outpoints to the coin pool
292  for (uint32_t i = 0; i < num_out; i++) {
293  outpoints.emplace_back(new_tx->GetHash(), i);
294  }
295  return new_tx;
296  }();
297 
298  const auto wtxid{tx->GetWitnessHash()};
299 
300  // orphanage functions
301  LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 10 * global_latency_score_limit)
302  {
303  NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, num_peers - 1);
304  const auto tx_weight{GetTransactionWeight(*tx)};
305 
306  // This protected peer will never send orphans that would
307  // exceed their own personal allotment, so is never evicted.
308  const bool peer_is_protected{protected_peers[peer_id]};
309 
310  CallOneOf(
312  [&] { // AddTx
313  bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id);
314  if (peer_is_protected && !have_tx_and_peer &&
315  (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
316  orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
317  // We never want our protected peer oversized or over-announced
318  } else {
319  orphanage->AddTx(tx, peer_id);
320  if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
321  protected_wtxids.insert(wtxid);
322  }
323  }
324  },
325  [&] { // AddAnnouncer
326  bool have_tx_and_peer = orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id);
327  // AddAnnouncer should return false if tx doesn't exist or we already HaveTxFromPeer.
328  {
329  if (peer_is_protected && !have_tx_and_peer &&
330  (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit ||
331  orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) {
332  // We never want our protected peer oversized
333  } else {
334  orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id);
335  if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) {
336  protected_wtxids.insert(wtxid);
337  }
338  }
339  }
340  },
341  [&] { // EraseTx
342  if (protected_wtxids.contains(tx->GetWitnessHash())) {
343  protected_wtxids.erase(wtxid);
344  }
345  orphanage->EraseTx(wtxid);
346  Assert(!orphanage->HaveTx(wtxid));
347  },
348  [&] { // EraseForPeer
349  if (!protected_peers[peer_id]) {
350  orphanage->EraseForPeer(peer_id);
351  Assert(orphanage->UsageByPeer(peer_id) == 0);
352  Assert(orphanage->LatencyScoreFromPeer(peer_id) == 0);
353  Assert(orphanage->AnnouncementsFromPeer(peer_id) == 0);
354  }
355  }
356  );
357  }
358  }
359 
360  orphanage->SanityCheck();
361  // All of the honest peer's announcements are still present.
362  for (const auto& wtxid : protected_wtxids) {
363  Assert(orphanage->HaveTx(wtxid));
364  }
365 }
366 
367 FUZZ_TARGET(txorphanage_sim)
368 {
370  // This is a comprehensive simulation fuzz test, which runs through a scenario involving up to
371  // 16 transactions (which may have simple or complex topology, and may have duplicate txids
372  // with distinct wtxids, and up to 16 peers. The scenario is performed both on a real
373  // TxOrphanage object and the behavior is compared with a naive reimplementation (just a vector
374  // of announcements) where possible, and tested for desired properties where not possible.
375 
376  //
377  // 1. Setup.
378  //
379 
382  static constexpr unsigned NUM_TX = 16;
385  static constexpr unsigned NUM_PEERS = 16;
388  static constexpr unsigned MAX_ANN = 64;
389 
390  FuzzedDataProvider provider(buffer.data(), buffer.size());
393  InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
394 
395  //
396  // 2. Construct an interesting set of 16 transactions.
397  //
398 
399  // - Pick a topological order among the transactions.
400  std::vector<unsigned> txorder(NUM_TX);
401  std::iota(txorder.begin(), txorder.end(), unsigned{0});
402  std::shuffle(txorder.begin(), txorder.end(), rng);
403  // - Pick a set of dependencies (pair<child_index, parent_index>).
404  std::vector<std::pair<unsigned, unsigned>> deps;
405  deps.reserve((NUM_TX * (NUM_TX - 1)) / 2);
406  for (unsigned p = 0; p < NUM_TX - 1; ++p) {
407  for (unsigned c = p + 1; c < NUM_TX; ++c) {
408  deps.emplace_back(c, p);
409  }
410  }
411  std::shuffle(deps.begin(), deps.end(), rng);
412  deps.resize(provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * 4 - 1));
413  // - Construct the actual transactions.
414  std::set<Wtxid> wtxids;
415  std::vector<CTransactionRef> txn(NUM_TX);
416  node::TxOrphanage::Usage total_usage{0};
417  for (unsigned t = 0; t < NUM_TX; ++t) {
419  if (t > 0 && rng.randrange(4) == 0) {
420  // Occasionally duplicate the previous transaction, so that repetitions of the same
421  // txid are possible (with different wtxid).
422  tx = CMutableTransaction(*txn[txorder[t - 1]]);
423  } else {
424  tx.version = 1;
425  tx.nLockTime = 0xffffffff;
426  // Construct 1 to 16 outputs.
427  auto num_outputs = rng.randrange<unsigned>(1 << rng.randrange<unsigned>(5)) + 1;
428  for (unsigned output = 0; output < num_outputs; ++output) {
429  CScript scriptpubkey;
430  scriptpubkey.resize(provider.ConsumeIntegralInRange<unsigned>(20, 34));
431  tx.vout.emplace_back(CAmount{0}, std::move(scriptpubkey));
432  }
433  // Construct inputs (one for each dependency).
434  for (auto& [child, parent] : deps) {
435  if (child == t) {
436  auto& partx = txn[txorder[parent]];
437  assert(partx->version == 1);
438  COutPoint outpoint(partx->GetHash(), rng.randrange<size_t>(partx->vout.size()));
439  tx.vin.emplace_back(outpoint);
440  tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
441  }
442  }
443  // Construct fallback input in case there are no dependencies.
444  if (tx.vin.empty()) {
445  COutPoint outpoint(Txid::FromUint256(rng.rand256()), rng.randrange<size_t>(16));
446  tx.vin.emplace_back(outpoint);
447  tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200));
448  }
449  }
450  // Optionally modify the witness (allowing wtxid != txid), and certainly when the wtxid
451  // already exists.
452  while (wtxids.contains(CTransaction(tx).GetWitnessHash()) || rng.randrange(4) == 0) {
453  auto& input = tx.vin[rng.randrange(tx.vin.size())];
454  if (rng.randbool()) {
455  input.scriptWitness.stack.resize(1);
456  input.scriptWitness.stack[0].resize(rng.randrange(100));
457  } else {
458  input.scriptWitness.stack.resize(0);
459  }
460  }
461  // Convert to CTransactionRef.
462  txn[txorder[t]] = MakeTransactionRef(std::move(tx));
463  wtxids.insert(txn[txorder[t]]->GetWitnessHash());
464  auto weight = GetTransactionWeight(*txn[txorder[t]]);
465  assert(weight < MAX_STANDARD_TX_WEIGHT);
466  total_usage += GetTransactionWeight(*txn[txorder[t]]);
467  }
468 
469  //
470  // 3. Initialize real orphanage
471  //
472 
473  auto max_global_latency_score = provider.ConsumeIntegralInRange<node::TxOrphanage::Count>(NUM_PEERS, MAX_ANN);
474  auto reserved_peer_usage = provider.ConsumeIntegralInRange<node::TxOrphanage::Usage>(1, total_usage);
475  auto real = node::MakeTxOrphanage(max_global_latency_score, reserved_peer_usage);
476 
477  //
478  // 4. Functions and data structures for the simulation.
479  //
480 
483  struct SimAnnouncement
484  {
485  unsigned tx;
486  NodeId announcer;
487  bool reconsider{false};
488  SimAnnouncement(unsigned tx_in, NodeId announcer_in, bool reconsider_in) noexcept :
489  tx(tx_in), announcer(announcer_in), reconsider(reconsider_in) {}
490  };
494  std::vector<SimAnnouncement> sim_announcements;
495 
497  auto read_tx_fn = [&]() -> unsigned { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX - 1); };
499  auto read_peer_fn = [&]() -> NodeId { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_PEERS - 1); };
501  auto read_tx_peer_fn = [&]() -> std::pair<unsigned, NodeId> {
502  auto code = provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * NUM_PEERS - 1);
503  return {code % NUM_TX, code / NUM_TX};
504  };
506  auto have_tx_fn = [&](unsigned tx) -> bool {
507  for (auto& ann : sim_announcements) {
508  if (ann.tx == tx) return true;
509  }
510  return false;
511  };
513  auto count_peers_fn = [&]() -> unsigned {
514  std::bitset<NUM_PEERS> mask;
515  for (auto& ann : sim_announcements) {
516  mask.set(ann.announcer);
517  }
518  return mask.count();
519  };
521  auto have_reconsiderable_fn = [&](unsigned tx) -> bool {
522  for (auto& ann : sim_announcements) {
523  if (ann.reconsider && ann.tx == tx) return true;
524  }
525  return false;
526  };
528  auto have_reconsider_fn = [&](NodeId peer) -> bool {
529  for (auto& ann : sim_announcements) {
530  if (ann.reconsider && ann.announcer == peer) return true;
531  }
532  return false;
533  };
535  auto find_announce_wtxid_fn = [&](const Wtxid& wtxid, NodeId peer) -> std::vector<SimAnnouncement>::iterator {
536  for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
537  if (txn[it->tx]->GetWitnessHash() == wtxid && it->announcer == peer) return it;
538  }
539  return sim_announcements.end();
540  };
542  auto find_announce_fn = [&](unsigned tx, NodeId peer) {
543  for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
544  if (it->tx == tx && it->announcer == peer) return it;
545  }
546  return sim_announcements.end();
547  };
549  auto dos_score_fn = [&](NodeId peer, int32_t max_count, int32_t max_usage) -> FeeFrac {
550  int64_t count{0};
551  int64_t usage{0};
552  for (auto& ann : sim_announcements) {
553  if (ann.announcer != peer) continue;
554  count += 1 + (txn[ann.tx]->vin.size() / 10);
555  usage += GetTransactionWeight(*txn[ann.tx]);
556  }
557  return std::max(FeeFrac{count, max_count}, FeeFrac{usage, max_usage});
558  };
559 
560  //
561  // 5. Run through a scenario of mutators on both real and simulated orphanage.
562  //
563 
564  LIMITED_WHILE(provider.remaining_bytes() > 0, 200) {
565  int command = provider.ConsumeIntegralInRange<uint8_t>(0, 15);
566  while (true) {
567  if (sim_announcements.size() < MAX_ANN && command-- == 0) {
568  // AddTx
569  auto [tx, peer] = read_tx_peer_fn();
570  bool added = real->AddTx(txn[tx], peer);
571  bool sim_have_tx = have_tx_fn(tx);
572  assert(added == !sim_have_tx);
573  if (find_announce_fn(tx, peer) == sim_announcements.end()) {
574  sim_announcements.emplace_back(tx, peer, false);
575  }
576  break;
577  } else if (sim_announcements.size() < MAX_ANN && command-- == 0) {
578  // AddAnnouncer
579  auto [tx, peer] = read_tx_peer_fn();
580  bool added = real->AddAnnouncer(txn[tx]->GetWitnessHash(), peer);
581  bool sim_have_tx = have_tx_fn(tx);
582  auto sim_it = find_announce_fn(tx, peer);
583  assert(added == (sim_it == sim_announcements.end() && sim_have_tx));
584  if (added) {
585  sim_announcements.emplace_back(tx, peer, false);
586  }
587  break;
588  } else if (command-- == 0) {
589  // EraseTx
590  auto tx = read_tx_fn();
591  bool erased = real->EraseTx(txn[tx]->GetWitnessHash());
592  bool sim_have = have_tx_fn(tx);
593  assert(erased == sim_have);
594  std::erase_if(sim_announcements, [&](auto& ann) { return ann.tx == tx; });
595  break;
596  } else if (command-- == 0) {
597  // EraseForPeer
598  auto peer = read_peer_fn();
599  real->EraseForPeer(peer);
600  std::erase_if(sim_announcements, [&](auto& ann) { return ann.announcer == peer; });
601  break;
602  } else if (command-- == 0) {
603  // EraseForBlock
604  auto pattern = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << NUM_TX) - 1);
605  CBlock block;
606  std::set<COutPoint> spent;
607  for (unsigned tx = 0; tx < NUM_TX; ++tx) {
608  if ((pattern >> tx) & 1) {
609  block.vtx.emplace_back(txn[tx]);
610  for (auto& txin : block.vtx.back()->vin) {
611  spent.insert(txin.prevout);
612  }
613  }
614  }
615  std::shuffle(block.vtx.begin(), block.vtx.end(), rng);
616  real->EraseForBlock(block);
617  std::erase_if(sim_announcements, [&](auto& ann) {
618  for (auto& txin : txn[ann.tx]->vin) {
619  if (spent.contains(txin.prevout)) return true;
620  }
621  return false;
622  });
623  break;
624  } else if (command-- == 0) {
625  // AddChildrenToWorkSet
626  auto tx = read_tx_fn();
627  FastRandomContext rand_ctx(rng.rand256());
628  auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx);
630  std::set<Wtxid> child_wtxids;
631  for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) {
632  if (!have_tx_fn(child_tx)) continue;
633  if (have_reconsiderable_fn(child_tx)) continue;
634  bool child_of = false;
635  for (auto& txin : txn[child_tx]->vin) {
636  if (txin.prevout.hash == txn[tx]->GetHash()) {
637  child_of = true;
638  break;
639  }
640  }
641  if (child_of) {
642  child_wtxids.insert(txn[child_tx]->GetWitnessHash());
643  }
644  }
645  for (auto& [wtxid, peer] : added) {
646  // Wtxid must be a child of tx that is not yet reconsiderable.
647  auto child_wtxid_it = child_wtxids.find(wtxid);
648  assert(child_wtxid_it != child_wtxids.end());
649  // Announcement must exist.
650  auto sim_ann_it = find_announce_wtxid_fn(wtxid, peer);
651  assert(sim_ann_it != sim_announcements.end());
652  // Announcement must not yet be reconsiderable.
653  assert(sim_ann_it->reconsider == false);
654  // Make reconsiderable.
655  sim_ann_it->reconsider = true;
656  // Remove from child_wtxids map, to disallow it being reported a second time in added.
657  child_wtxids.erase(wtxid);
658  }
659  // Verify that AddChildrenToWorkSet does not select announcements that were already reconsiderable:
660  // Check all child wtxids which did not occur at least once in the result were already reconsiderable
661  // due to a previous AddChildrenToWorkSet.
662  assert(child_wtxids.empty());
663  break;
664  } else if (command-- == 0) {
665  // GetTxToReconsider.
666  auto peer = read_peer_fn();
667  auto result = real->GetTxToReconsider(peer);
668  if (result) {
669  // A transaction was found. It must have a corresponding reconsiderable
670  // announcement from peer.
671  auto sim_ann_it = find_announce_wtxid_fn(result->GetWitnessHash(), peer);
672  assert(sim_ann_it != sim_announcements.end());
673  assert(sim_ann_it->announcer == peer);
674  assert(sim_ann_it->reconsider);
675  // Make it non-reconsiderable.
676  sim_ann_it->reconsider = false;
677  } else {
678  // No reconsiderable transaction was found from peer. Verify that it does not
679  // have any.
680  assert(!have_reconsider_fn(peer));
681  }
682  break;
683  }
684  }
685  // Always trim after each command if needed.
686  const auto max_ann = max_global_latency_score / std::max<unsigned>(1, count_peers_fn());
687  const auto max_mem = reserved_peer_usage;
688  while (true) {
689  // Count global usage and number of peers.
690  node::TxOrphanage::Usage total_usage{0};
691  node::TxOrphanage::Count total_latency_score = sim_announcements.size();
692  for (unsigned tx = 0; tx < NUM_TX; ++tx) {
693  if (have_tx_fn(tx)) {
694  total_usage += GetTransactionWeight(*txn[tx]);
695  total_latency_score += txn[tx]->vin.size() / 10;
696  }
697  }
698  auto num_peers = count_peers_fn();
699  bool oversized = (total_usage > reserved_peer_usage * num_peers) ||
700  (total_latency_score > real->MaxGlobalLatencyScore());
701  if (!oversized) break;
702  // Find worst peer.
703  FeeFrac worst_dos_score{0, 1};
704  unsigned worst_peer = unsigned(-1);
705  for (unsigned peer = 0; peer < NUM_PEERS; ++peer) {
706  auto dos_score = dos_score_fn(peer, max_ann, max_mem);
707  // Use >= so that the more recent peer (higher NodeId) wins in case of
708  // ties.
709  if (dos_score >= worst_dos_score) {
710  worst_dos_score = dos_score;
711  worst_peer = peer;
712  }
713  }
714  assert(worst_peer != unsigned(-1));
715  assert(worst_dos_score >> FeeFrac(1, 1));
716  // Find oldest announcement from worst_peer, preferring non-reconsiderable ones.
717  bool done{false};
718  for (int reconsider = 0; reconsider < 2; ++reconsider) {
719  for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) {
720  if (it->announcer != worst_peer || it->reconsider != reconsider) continue;
721  sim_announcements.erase(it);
722  done = true;
723  break;
724  }
725  if (done) break;
726  }
727  assert(done);
728  }
729  // We must now be within limits, otherwise LimitOrphans should have continued further.
730  // We don't check the contents of the orphanage until the end to make fuzz runs faster.
731  assert(real->TotalLatencyScore() <= real->MaxGlobalLatencyScore());
732  assert(real->TotalOrphanUsage() <= real->MaxGlobalUsage());
733  }
734 
735  //
736  // 6. Perform a full comparison between the real orphanage's inspectors and the simulation.
737  //
738 
739  real->SanityCheck();
740 
741 
742  auto all_orphans = real->GetOrphanTransactions();
743  node::TxOrphanage::Usage orphan_usage{0};
744  std::vector<node::TxOrphanage::Usage> usage_by_peer(NUM_PEERS);
745  node::TxOrphanage::Count unique_orphans{0};
746  std::vector<node::TxOrphanage::Count> count_by_peer(NUM_PEERS);
747  node::TxOrphanage::Count total_latency_score = sim_announcements.size();
748  for (unsigned tx = 0; tx < NUM_TX; ++tx) {
749  bool sim_have_tx = have_tx_fn(tx);
750  if (sim_have_tx) {
751  orphan_usage += GetTransactionWeight(*txn[tx]);
752  total_latency_score += txn[tx]->vin.size() / 10;
753  }
754  unique_orphans += sim_have_tx;
755  auto orphans_it = std::find_if(all_orphans.begin(), all_orphans.end(), [&](auto& orph) { return orph.tx->GetWitnessHash() == txn[tx]->GetWitnessHash(); });
756  // GetOrphanTransactions (OrphanBase existence)
757  assert((orphans_it != all_orphans.end()) == sim_have_tx);
758  // HaveTx
759  bool have_tx = real->HaveTx(txn[tx]->GetWitnessHash());
760  assert(have_tx == sim_have_tx);
761  // GetTx
762  auto txref = real->GetTx(txn[tx]->GetWitnessHash());
763  assert(!!txref == sim_have_tx);
764  if (sim_have_tx) assert(txref->GetWitnessHash() == txn[tx]->GetWitnessHash());
765 
766  for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
767  auto it_sim_ann = find_announce_fn(tx, peer);
768  bool sim_have_ann = it_sim_ann != sim_announcements.end();
769  if (sim_have_ann) usage_by_peer[peer] += GetTransactionWeight(*txn[tx]);
770  count_by_peer[peer] += sim_have_ann;
771  // GetOrphanTransactions (announcers presence)
772  if (sim_have_ann) assert(sim_have_tx);
773  if (sim_have_tx) assert(orphans_it->announcers.count(peer) == sim_have_ann);
774  // HaveTxFromPeer
775  bool have_ann = real->HaveTxFromPeer(txn[tx]->GetWitnessHash(), peer);
776  assert(sim_have_ann == have_ann);
777  // GetChildrenFromSamePeer
778  auto children_from_peer = real->GetChildrenFromSamePeer(txn[tx], peer);
779  auto it = children_from_peer.rbegin();
780  for (int phase = 0; phase < 2; ++phase) {
781  // First expect all children which have reconsiderable announcement from peer, then the others.
782  for (auto& ann : sim_announcements) {
783  if (ann.announcer != peer) continue;
784  if (ann.reconsider != (phase == 1)) continue;
785  bool matching_parent{false};
786  for (const auto& vin : txn[ann.tx]->vin) {
787  if (vin.prevout.hash == txn[tx]->GetHash()) matching_parent = true;
788  }
789  if (!matching_parent) continue;
790  // Found an announcement from peer which is a child of txn[tx].
791  assert(it != children_from_peer.rend());
792  assert((*it)->GetWitnessHash() == txn[ann.tx]->GetWitnessHash());
793  ++it;
794  }
795  }
796  assert(it == children_from_peer.rend());
797  }
798  }
799  // TotalOrphanUsage
800  assert(orphan_usage == real->TotalOrphanUsage());
801  for (NodeId peer = 0; peer < NUM_PEERS; ++peer) {
802  bool sim_have_reconsider = have_reconsider_fn(peer);
803  // HaveTxToReconsider
804  bool have_reconsider = real->HaveTxToReconsider(peer);
805  assert(have_reconsider == sim_have_reconsider);
806  // UsageByPeer
807  assert(usage_by_peer[peer] == real->UsageByPeer(peer));
808  // AnnouncementsFromPeer
809  assert(count_by_peer[peer] == real->AnnouncementsFromPeer(peer));
810  }
811  // CountAnnouncements
812  assert(sim_announcements.size() == real->CountAnnouncements());
813  // CountUniqueOrphans
814  assert(unique_orphans == real->CountUniqueOrphans());
815  // MaxGlobalLatencyScore
816  assert(max_global_latency_score == real->MaxGlobalLatencyScore());
817  // ReservedPeerUsage
818  assert(reserved_peer_usage == real->ReservedPeerUsage());
819  // MaxPeerLatencyScore
820  auto present_peers = count_peers_fn();
821  assert(max_global_latency_score / std::max<unsigned>(1, present_peers) == real->MaxPeerLatencyScore());
822  // MaxGlobalUsage
823  assert(reserved_peer_usage * std::max<unsigned>(1, present_peers) == real->MaxGlobalUsage());
824  // TotalLatencyScore.
825  assert(real->TotalLatencyScore() == total_latency_score);
826 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:403
void resize(size_type new_size)
Definition: prevector.h:276
assert(!tx.IsCoinBase())
Definition: block.h:73
void initialize_orphanage()
Definition: txorphan.cpp:33
std::vector< CTxIn > vin
Definition: transaction.h:359
static const uint32_t SEQUENCE_FINAL
Setting nSequence to this value for every input in a transaction disables nLockTime/IsFinalTx().
Definition: transaction.h:76
#define expect(bit)
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
static int32_t GetTransactionWeight(const CTransaction &tx)
Definition: validation.h:132
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
Fast randomness source.
Definition: random.h:385
xoroshiro128++ PRNG.
Definition: random.h:424
int64_t NodeId
Definition: net.h:103
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:28
unsigned int Count
Definition: txorphanage.h:41
std::vector< CTxOut > vout
Definition: transaction.h:360
NodeSeconds ConsumeTime(FuzzedDataProvider &fuzzed_data_provider, const std::optional< int64_t > &min, const std::optional< int64_t > &max) noexcept
Definition: util.cpp:34
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
std::unique_ptr< TxOrphanage > MakeTxOrphanage() noexcept
Create a new TxOrphanage instance.
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
static constexpr int32_t MAX_STANDARD_TX_WEIGHT
The maximum weight for transactions we&#39;re willing to relay/mine.
Definition: policy.h:37
std::vector< CTransactionRef > vtx
Definition: block.h:77
Data structure storing a fee and size, ordered by increasing fee/size.
Definition: feefrac.h:39
const auto command
auto result
Definition: common-types.h:74
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:404
static transaction_identifier FromUint256(const uint256 &id)
FuzzedDataProvider & fuzzed_data_provider
Definition: fees.cpp:38
static int count
void SetMockTime(int64_t nMockTimeIn)
DEPRECATED Use SetMockTime with chrono type.
Definition: time.cpp:44
A mutable version of CTransaction.
Definition: transaction.h:357
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
auto & PickValue(FuzzedDataProvider &fuzzed_data_provider, Collection &col)
Definition: util.h:47
The basic transaction that is broadcasted on the network and contained in blocks. ...
Definition: transaction.h:280
T ConsumeIntegralInRange(T min, T max)
Seed with a compile time constant of zeros.
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition: util.h:167
FUZZ_TARGET(txorphan,.init=initialize_orphanage)
Definition: txorphan.cpp:38
#define Assert(val)
Identity function.
Definition: check.h:113
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.
Definition: setup_common.h:250
transaction_identifier represents the two canonical transaction identifier types (txid, wtxid).