/root/bitcoin/src/test/fuzz/txorphan.cpp
| Line | Count | Source | 
| 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> | 
| 14 |  | #include <test/fuzz/FuzzedDataProvider.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 |  |  | 
| 33 |  | void initialize_orphanage() | 
| 34 | 0 | { | 
| 35 | 0 |     static const auto testing_setup = MakeNoLogFileContext(); | 
| 36 | 0 | } | 
| 37 |  |  | 
| 38 |  | FUZZ_TARGET(txorphan, .init = initialize_orphanage) | 
| 39 | 0 | { | 
| 40 | 0 |     SeedRandomStateForTest(SeedRand::ZEROS); | 
| 41 | 0 |     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | 
| 42 | 0 |     FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; | 
| 43 | 0 |     SetMockTime(ConsumeTime(fuzzed_data_provider)); | 
| 44 |  | 
 | 
| 45 | 0 |     auto orphanage = node::MakeTxOrphanage(); | 
| 46 | 0 |     std::vector<COutPoint> outpoints; // Duplicates are tolerated | 
| 47 | 0 |     outpoints.reserve(200'000); | 
| 48 |  |  | 
| 49 |  |     // initial outpoints used to construct transactions later | 
| 50 | 0 |     for (uint8_t i = 0; i < 4; i++) { | 
| 51 | 0 |         outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); | 
| 52 | 0 |     } | 
| 53 |  | 
 | 
| 54 | 0 |     CTransactionRef ptx_potential_parent = nullptr; | 
| 55 |  | 
 | 
| 56 | 0 |     std::vector<CTransactionRef> tx_history; | 
| 57 |  | 
 | 
| 58 | 0 |     LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 1000) | 
| 59 | 0 |     { | 
| 60 |  |         // construct transaction | 
| 61 | 0 |         const CTransactionRef tx = [&] { | 
| 62 | 0 |             CMutableTransaction tx_mut; | 
| 63 | 0 |             const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); | 
| 64 | 0 |             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 | 0 |             tx_mut.vin.reserve(num_in); | 
| 68 | 0 |             for (uint32_t i = 0; i < num_in; i++) { | 
| 69 | 0 |                 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 | 0 |                 tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); | 
| 72 | 0 |             } | 
| 73 |  |             // output amount will not affect txorphanage | 
| 74 | 0 |             tx_mut.vout.reserve(num_out); | 
| 75 | 0 |             for (uint32_t i = 0; i < num_out; i++) { | 
| 76 | 0 |                 tx_mut.vout.emplace_back(CAmount{0}, CScript{}); | 
| 77 | 0 |             } | 
| 78 | 0 |             auto new_tx = MakeTransactionRef(tx_mut); | 
| 79 |  |             // add newly constructed outpoints to the coin pool | 
| 80 | 0 |             for (uint32_t i = 0; i < num_out; i++) { | 
| 81 | 0 |                 outpoints.emplace_back(new_tx->GetHash(), i); | 
| 82 | 0 |             } | 
| 83 | 0 |             return new_tx; | 
| 84 | 0 |         }(); | 
| 85 |  | 
 | 
| 86 | 0 |         tx_history.push_back(tx); | 
| 87 |  | 
 | 
| 88 | 0 |         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 | 0 |         if (ptx_potential_parent) { | 
| 93 |  |             // Set up future GetTxToReconsider call. | 
| 94 | 0 |             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 | 0 |             NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); | 
| 98 | 0 |             for (const auto& child : orphanage->GetChildrenFromSamePeer(ptx_potential_parent, peer_id)) { | 
| 99 | 0 |                 assert(std::any_of(child->vin.cbegin(), child->vin.cend(), [&](const auto& input) { | 
| 100 | 0 |                     return input.prevout.hash == ptx_potential_parent->GetHash(); | 
| 101 | 0 |                 })); | 
| 102 | 0 |             } | 
| 103 | 0 |         } | 
| 104 |  |  | 
| 105 |  |         // trigger orphanage functions | 
| 106 | 0 |         LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000) | 
| 107 | 0 |         { | 
| 108 | 0 |             NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); | 
| 109 | 0 |             const auto total_bytes_start{orphanage->TotalOrphanUsage()}; | 
| 110 | 0 |             const auto total_peer_bytes_start{orphanage->UsageByPeer(peer_id)}; | 
| 111 | 0 |             const auto tx_weight{GetTransactionWeight(*tx)}; | 
| 112 |  | 
 | 
| 113 | 0 |             CallOneOf( | 
| 114 | 0 |                 fuzzed_data_provider, | 
| 115 | 0 |                 [&] { | 
| 116 | 0 |                     { | 
| 117 | 0 |                         CTransactionRef ref = orphanage->GetTxToReconsider(peer_id); | 
| 118 | 0 |                         if (ref) { | 
| 119 | 0 |                             Assert(orphanage->HaveTx(ref->GetWitnessHash())); | 
| 120 | 0 |                         } | 
| 121 | 0 |                     } | 
| 122 | 0 |                 }, | 
| 123 | 0 |                 [&] { | 
| 124 | 0 |                     bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); | 
| 125 | 0 |                     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 | 0 |                     { | 
| 129 | 0 |                         bool add_tx = orphanage->AddTx(tx, peer_id); | 
| 130 |  |                         // have_tx == true -> add_tx == false | 
| 131 | 0 |                         Assert(!have_tx || !add_tx); | 
| 132 |  |                         // have_tx_and_peer == true -> add_tx == false | 
| 133 | 0 |                         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 | 0 |                         if (add_tx) { | 
| 137 | 0 |                             Assert(tx_weight <= MAX_STANDARD_TX_WEIGHT); | 
| 138 | 0 |                         } else { | 
| 139 |  |                             // Peer may have been added as an announcer. | 
| 140 | 0 |                             if (orphanage->UsageByPeer(peer_id) > total_peer_bytes_start) { | 
| 141 | 0 |                                 Assert(orphanage->HaveTxFromPeer(wtxid, peer_id)); | 
| 142 | 0 |                             } | 
| 143 |  |  | 
| 144 |  |                             // If announcement was added, total bytes does not increase. | 
| 145 |  |                             // However, if eviction was triggered, the value may decrease. | 
| 146 | 0 |                             Assert(orphanage->TotalOrphanUsage() <= total_bytes_start); | 
| 147 | 0 |                         } | 
| 148 | 0 |                     } | 
| 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 | 0 |                 }, | 
| 154 | 0 |                 [&] { | 
| 155 | 0 |                     bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); | 
| 156 | 0 |                     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 | 0 |                     { | 
| 159 | 0 |                         bool added_announcer = orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id); | 
| 160 |  |                         // have_tx == false -> added_announcer == false | 
| 161 | 0 |                         Assert(have_tx || !added_announcer); | 
| 162 |  |                         // have_tx_and_peer == true -> added_announcer == false | 
| 163 | 0 |                         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 | 0 |                         Assert(orphanage->TotalOrphanUsage() <= total_bytes_start); | 
| 168 | 0 |                     } | 
| 169 | 0 |                 }, | 
| 170 | 0 |                 [&] { | 
| 171 | 0 |                     bool have_tx = orphanage->HaveTx(tx->GetWitnessHash()); | 
| 172 | 0 |                     bool have_tx_and_peer{orphanage->HaveTxFromPeer(wtxid, peer_id)}; | 
| 173 |  |                     // EraseTx should return 0 if m_orphans doesn't have the tx | 
| 174 | 0 |                     { | 
| 175 | 0 |                         auto bytes_from_peer_before{orphanage->UsageByPeer(peer_id)}; | 
| 176 | 0 |                         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 | 0 |                         if (have_tx) { | 
| 179 | 0 |                             if (!have_tx_and_peer) { | 
| 180 | 0 |                                 Assert(orphanage->UsageByPeer(peer_id) == bytes_from_peer_before); | 
| 181 | 0 |                             } | 
| 182 | 0 |                         } | 
| 183 | 0 |                     } | 
| 184 | 0 |                     have_tx = orphanage->HaveTx(tx->GetWitnessHash()); | 
| 185 | 0 |                     have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); | 
| 186 |  |                     // have_tx should be false and EraseTx should fail | 
| 187 | 0 |                     { | 
| 188 | 0 |                         Assert(!have_tx && !have_tx_and_peer && !orphanage->EraseTx(wtxid)); | 
| 189 | 0 |                     } | 
| 190 | 0 |                 }, | 
| 191 | 0 |                 [&] { | 
| 192 | 0 |                     orphanage->EraseForPeer(peer_id); | 
| 193 | 0 |                     Assert(!orphanage->HaveTxFromPeer(tx->GetWitnessHash(), peer_id)); | 
| 194 | 0 |                     Assert(orphanage->UsageByPeer(peer_id) == 0); | 
| 195 | 0 |                 }, | 
| 196 | 0 |                 [&] { | 
| 197 |  |                     // Make a block out of txs and then EraseForBlock | 
| 198 | 0 |                     CBlock block; | 
| 199 | 0 |                     int num_txs = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 1000); | 
| 200 | 0 |                     for (int i{0}; i < num_txs; ++i) { | 
| 201 | 0 |                         auto& tx_to_remove = PickValue(fuzzed_data_provider, tx_history); | 
| 202 | 0 |                         block.vtx.push_back(tx_to_remove); | 
| 203 | 0 |                     } | 
| 204 | 0 |                     orphanage->EraseForBlock(block); | 
| 205 | 0 |                     for (const auto& tx_removed : block.vtx) { | 
| 206 | 0 |                         Assert(!orphanage->HaveTx(tx_removed->GetWitnessHash())); | 
| 207 | 0 |                         Assert(!orphanage->HaveTxFromPeer(tx_removed->GetWitnessHash(), peer_id)); | 
| 208 | 0 |                     } | 
| 209 | 0 |                 } | 
| 210 | 0 |             ); | 
| 211 | 0 |         } | 
| 212 |  |  | 
| 213 |  |         // Set tx as potential parent to be used for future GetChildren() calls. | 
| 214 | 0 |         if (!ptx_potential_parent || fuzzed_data_provider.ConsumeBool()) { | 
| 215 | 0 |             ptx_potential_parent = tx; | 
| 216 | 0 |         } | 
| 217 |  | 
 | 
| 218 | 0 |         const bool have_tx{orphanage->HaveTx(tx->GetWitnessHash())}; | 
| 219 | 0 |         const bool get_tx_nonnull{orphanage->GetTx(tx->GetWitnessHash()) != nullptr}; | 
| 220 | 0 |         Assert(have_tx == get_tx_nonnull); | 
| 221 | 0 |     } | 
| 222 | 0 |     orphanage->SanityCheck(); | 
| 223 | 0 | } | 
| 224 |  |  | 
| 225 |  | FUZZ_TARGET(txorphan_protected, .init = initialize_orphanage) | 
| 226 | 0 | { | 
| 227 | 0 |     SeedRandomStateForTest(SeedRand::ZEROS); | 
| 228 | 0 |     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | 
| 229 | 0 |     FastRandomContext orphanage_rng{ConsumeUInt256(fuzzed_data_provider)}; | 
| 230 | 0 |     SetMockTime(ConsumeTime(fuzzed_data_provider)); | 
| 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 | 0 |     const unsigned int MAX_PEERS = 125; | 
| 235 | 0 |     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 | 0 |     std::bitset<MAX_PEERS> protected_peers; | 
| 238 | 0 |     for (unsigned int i = 0; i < num_peers; i++) { | 
| 239 | 0 |         protected_peers.set(i, fuzzed_data_provider.ConsumeBool()); | 
| 240 | 0 |     } | 
| 241 |  |  | 
| 242 |  |     // Params for orphanage. | 
| 243 | 0 |     const unsigned int global_latency_score_limit = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(num_peers, 6'000); | 
| 244 | 0 |     const int64_t per_peer_weight_reservation = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(1, 4'040'000); | 
| 245 | 0 |     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 | 0 |     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 | 0 |     const int64_t honest_mem_limit = per_peer_weight_reservation; | 
| 253 |  | 
 | 
| 254 | 0 |     std::vector<COutPoint> outpoints; // Duplicates are tolerated | 
| 255 | 0 |     outpoints.reserve(400); | 
| 256 |  |  | 
| 257 |  |     // initial outpoints used to construct transactions later | 
| 258 | 0 |     for (uint8_t i = 0; i < 4; i++) { | 
| 259 | 0 |         outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); | 
| 260 | 0 |     } | 
| 261 |  |  | 
| 262 |  |     // These are honest peer's live announcements. We expect them to be protected from eviction. | 
| 263 | 0 |     std::set<Wtxid> protected_wtxids; | 
| 264 |  | 
 | 
| 265 | 0 |     LIMITED_WHILE(outpoints.size() < 400 && fuzzed_data_provider.ConsumeBool(), 1000) | 
| 266 | 0 |     { | 
| 267 |  |         // construct transaction | 
| 268 | 0 |         const CTransactionRef tx = [&] { | 
| 269 | 0 |             CMutableTransaction tx_mut; | 
| 270 | 0 |             const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); | 
| 271 | 0 |             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 | 0 |             tx_mut.vin.reserve(num_in); | 
| 275 | 0 |             for (uint32_t i = 0; i < num_in; i++) { | 
| 276 | 0 |                 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 | 0 |                 tx_mut.vin.emplace_back(prevout, CScript{}, fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, CTxIn::SEQUENCE_FINAL)); | 
| 279 | 0 |             } | 
| 280 |  |             // output amount or spendability will not affect txorphanage | 
| 281 | 0 |             tx_mut.vout.reserve(num_out); | 
| 282 | 0 |             for (uint32_t i = 0; i < num_out; i++) { | 
| 283 | 0 |                 const auto payload_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100000); | 
| 284 | 0 |                 if (payload_size) { | 
| 285 | 0 |                     tx_mut.vout.emplace_back(0, CScript() << OP_RETURN << std::vector<unsigned char>(payload_size)); | 
| 286 | 0 |                 } else { | 
| 287 | 0 |                     tx_mut.vout.emplace_back(0, CScript{}); | 
| 288 | 0 |                 } | 
| 289 | 0 |             } | 
| 290 | 0 |             auto new_tx = MakeTransactionRef(tx_mut); | 
| 291 |  |             // add newly constructed outpoints to the coin pool | 
| 292 | 0 |             for (uint32_t i = 0; i < num_out; i++) { | 
| 293 | 0 |                 outpoints.emplace_back(new_tx->GetHash(), i); | 
| 294 | 0 |             } | 
| 295 | 0 |             return new_tx; | 
| 296 | 0 |         }(); | 
| 297 |  | 
 | 
| 298 | 0 |         const auto wtxid{tx->GetWitnessHash()}; | 
| 299 |  |  | 
| 300 |  |         // orphanage functions | 
| 301 | 0 |         LIMITED_WHILE(fuzzed_data_provider.remaining_bytes(), 10 * global_latency_score_limit) | 
| 302 | 0 |         { | 
| 303 | 0 |             NodeId peer_id = fuzzed_data_provider.ConsumeIntegralInRange<NodeId>(0, num_peers - 1); | 
| 304 | 0 |             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 | 0 |             const bool peer_is_protected{protected_peers[peer_id]}; | 
| 309 |  | 
 | 
| 310 | 0 |             CallOneOf( | 
| 311 | 0 |                 fuzzed_data_provider, | 
| 312 | 0 |                 [&] { // AddTx | 
| 313 | 0 |                     bool have_tx_and_peer = orphanage->HaveTxFromPeer(wtxid, peer_id); | 
| 314 | 0 |                     if (peer_is_protected && !have_tx_and_peer && | 
| 315 | 0 |                         (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit || | 
| 316 | 0 |                         orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) { | 
| 317 |  |                         // We never want our protected peer oversized or over-announced | 
| 318 | 0 |                     } else { | 
| 319 | 0 |                         orphanage->AddTx(tx, peer_id); | 
| 320 | 0 |                         if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { | 
| 321 | 0 |                             protected_wtxids.insert(wtxid); | 
| 322 | 0 |                         } | 
| 323 | 0 |                     } | 
| 324 | 0 |                 }, | 
| 325 | 0 |                 [&] { // AddAnnouncer | 
| 326 | 0 |                     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 | 0 |                     { | 
| 329 | 0 |                         if (peer_is_protected && !have_tx_and_peer && | 
| 330 | 0 |                             (orphanage->UsageByPeer(peer_id) + tx_weight > honest_mem_limit || | 
| 331 | 0 |                             orphanage->LatencyScoreFromPeer(peer_id) + (tx->vin.size() / 10) + 1 > honest_latency_limit)) { | 
| 332 |  |                             // We never want our protected peer oversized | 
| 333 | 0 |                         } else { | 
| 334 | 0 |                             orphanage->AddAnnouncer(tx->GetWitnessHash(), peer_id); | 
| 335 | 0 |                             if (peer_is_protected && orphanage->HaveTxFromPeer(wtxid, peer_id)) { | 
| 336 | 0 |                                 protected_wtxids.insert(wtxid); | 
| 337 | 0 |                             } | 
| 338 | 0 |                         } | 
| 339 | 0 |                     } | 
| 340 | 0 |                 }, | 
| 341 | 0 |                 [&] { // EraseTx | 
| 342 | 0 |                     if (protected_wtxids.count(tx->GetWitnessHash())) { | 
| 343 | 0 |                         protected_wtxids.erase(wtxid); | 
| 344 | 0 |                     } | 
| 345 | 0 |                     orphanage->EraseTx(wtxid); | 
| 346 | 0 |                     Assert(!orphanage->HaveTx(wtxid)); | 
| 347 | 0 |                 }, | 
| 348 | 0 |                 [&] { // EraseForPeer | 
| 349 | 0 |                     if (!protected_peers[peer_id]) { | 
| 350 | 0 |                         orphanage->EraseForPeer(peer_id); | 
| 351 | 0 |                         Assert(orphanage->UsageByPeer(peer_id) == 0); | 
| 352 | 0 |                         Assert(orphanage->LatencyScoreFromPeer(peer_id) == 0); | 
| 353 | 0 |                         Assert(orphanage->AnnouncementsFromPeer(peer_id) == 0); | 
| 354 | 0 |                     } | 
| 355 | 0 |                 } | 
| 356 | 0 |             ); | 
| 357 | 0 |         } | 
| 358 | 0 |     } | 
| 359 |  | 
 | 
| 360 | 0 |     orphanage->SanityCheck(); | 
| 361 |  |     // All of the honest peer's announcements are still present. | 
| 362 | 0 |     for (const auto& wtxid : protected_wtxids) { | 
| 363 | 0 |         Assert(orphanage->HaveTx(wtxid)); | 
| 364 | 0 |     } | 
| 365 | 0 | } | 
| 366 |  |  | 
| 367 |  | FUZZ_TARGET(txorphanage_sim) | 
| 368 | 0 | { | 
| 369 | 0 |     SeedRandomStateForTest(SeedRand::ZEROS); | 
| 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 |  |  | 
| 380 |  |     /** The total number of transactions this simulation uses (not all of which will necessarily | 
| 381 |  |      *  be present in the orphanage at once). */ | 
| 382 | 0 |     static constexpr unsigned NUM_TX = 16; | 
| 383 |  |     /** The number of peers this simulation uses (not all of which will necessarily be present in | 
| 384 |  |      *  the orphanage at once). */ | 
| 385 | 0 |     static constexpr unsigned NUM_PEERS = 16; | 
| 386 |  |     /** The maximum number of announcements this simulation uses (which may be higher than the | 
| 387 |  |      *  number permitted inside the orphanage). */ | 
| 388 | 0 |     static constexpr unsigned MAX_ANN = 64; | 
| 389 |  | 
 | 
| 390 | 0 |     FuzzedDataProvider provider(buffer.data(), buffer.size()); | 
| 391 |  |     /** Local RNG. Only used for topology/sizes of the transaction set, the order of transactions | 
| 392 |  |      *  in EraseForBlock, and for the randomized passed to AddChildrenToWorkSet. */ | 
| 393 | 0 |     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 | 0 |     std::vector<unsigned> txorder(NUM_TX); | 
| 401 | 0 |     std::iota(txorder.begin(), txorder.end(), unsigned{0}); | 
| 402 | 0 |     std::shuffle(txorder.begin(), txorder.end(), rng); | 
| 403 |  |     // - Pick a set of dependencies (pair<child_index, parent_index>). | 
| 404 | 0 |     std::vector<std::pair<unsigned, unsigned>> deps; | 
| 405 | 0 |     deps.reserve((NUM_TX * (NUM_TX - 1)) / 2); | 
| 406 | 0 |     for (unsigned p = 0; p < NUM_TX - 1; ++p) { | 
| 407 | 0 |         for (unsigned c = p + 1; c < NUM_TX; ++c) { | 
| 408 | 0 |             deps.emplace_back(c, p); | 
| 409 | 0 |         } | 
| 410 | 0 |     } | 
| 411 | 0 |     std::shuffle(deps.begin(), deps.end(), rng); | 
| 412 | 0 |     deps.resize(provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * 4 - 1)); | 
| 413 |  |     // - Construct the actual transactions. | 
| 414 | 0 |     std::set<Wtxid> wtxids; | 
| 415 | 0 |     std::vector<CTransactionRef> txn(NUM_TX); | 
| 416 | 0 |     node::TxOrphanage::Usage total_usage{0}; | 
| 417 | 0 |     for (unsigned t = 0; t < NUM_TX; ++t) { | 
| 418 | 0 |         CMutableTransaction tx; | 
| 419 | 0 |         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 | 0 |             tx = CMutableTransaction(*txn[txorder[t - 1]]); | 
| 423 | 0 |         } else { | 
| 424 | 0 |             tx.version = 1; | 
| 425 | 0 |             tx.nLockTime = 0xffffffff; | 
| 426 |  |             // Construct 1 to 16 outputs. | 
| 427 | 0 |             auto num_outputs = rng.randrange<unsigned>(1 << rng.randrange<unsigned>(5)) + 1; | 
| 428 | 0 |             for (unsigned output = 0; output < num_outputs; ++output) { | 
| 429 | 0 |                 CScript scriptpubkey; | 
| 430 | 0 |                 scriptpubkey.resize(provider.ConsumeIntegralInRange<unsigned>(20, 34)); | 
| 431 | 0 |                 tx.vout.emplace_back(CAmount{0}, std::move(scriptpubkey)); | 
| 432 | 0 |             } | 
| 433 |  |             // Construct inputs (one for each dependency). | 
| 434 | 0 |             for (auto& [child, parent] : deps) { | 
| 435 | 0 |                 if (child == t) { | 
| 436 | 0 |                     auto& partx = txn[txorder[parent]]; | 
| 437 | 0 |                     assert(partx->version == 1); | 
| 438 | 0 |                     COutPoint outpoint(partx->GetHash(), rng.randrange<size_t>(partx->vout.size())); | 
| 439 | 0 |                     tx.vin.emplace_back(outpoint); | 
| 440 | 0 |                     tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200)); | 
| 441 | 0 |                 } | 
| 442 | 0 |             } | 
| 443 |  |             // Construct fallback input in case there are no dependencies. | 
| 444 | 0 |             if (tx.vin.empty()) { | 
| 445 | 0 |                 COutPoint outpoint(Txid::FromUint256(rng.rand256()), rng.randrange<size_t>(16)); | 
| 446 | 0 |                 tx.vin.emplace_back(outpoint); | 
| 447 | 0 |                 tx.vin.back().scriptSig.resize(provider.ConsumeIntegralInRange<unsigned>(16, 200)); | 
| 448 | 0 |             } | 
| 449 | 0 |         } | 
| 450 |  |         // Optionally modify the witness (allowing wtxid != txid), and certainly when the wtxid | 
| 451 |  |         // already exists. | 
| 452 | 0 |         while (wtxids.contains(CTransaction(tx).GetWitnessHash()) || rng.randrange(4) == 0) { | 
| 453 | 0 |             auto& input = tx.vin[rng.randrange(tx.vin.size())]; | 
| 454 | 0 |             if (rng.randbool()) { | 
| 455 | 0 |                 input.scriptWitness.stack.resize(1); | 
| 456 | 0 |                 input.scriptWitness.stack[0].resize(rng.randrange(100)); | 
| 457 | 0 |             } else { | 
| 458 | 0 |                 input.scriptWitness.stack.resize(0); | 
| 459 | 0 |             } | 
| 460 | 0 |         } | 
| 461 |  |         // Convert to CTransactionRef. | 
| 462 | 0 |         txn[txorder[t]] = MakeTransactionRef(std::move(tx)); | 
| 463 | 0 |         wtxids.insert(txn[txorder[t]]->GetWitnessHash()); | 
| 464 | 0 |         auto weight = GetTransactionWeight(*txn[txorder[t]]); | 
| 465 | 0 |         assert(weight < MAX_STANDARD_TX_WEIGHT); | 
| 466 | 0 |         total_usage += GetTransactionWeight(*txn[txorder[t]]); | 
| 467 | 0 |     } | 
| 468 |  |  | 
| 469 |  |     // | 
| 470 |  |     // 3. Initialize real orphanage | 
| 471 |  |     // | 
| 472 |  |  | 
| 473 | 0 |     auto max_global_latency_score = provider.ConsumeIntegralInRange<node::TxOrphanage::Count>(NUM_PEERS, MAX_ANN); | 
| 474 | 0 |     auto reserved_peer_usage = provider.ConsumeIntegralInRange<node::TxOrphanage::Usage>(1, total_usage); | 
| 475 | 0 |     auto real = node::MakeTxOrphanage(max_global_latency_score, reserved_peer_usage); | 
| 476 |  |  | 
| 477 |  |     // | 
| 478 |  |     // 4. Functions and data structures for the simulation. | 
| 479 |  |     // | 
| 480 |  |  | 
| 481 |  |     /** Data structure representing one announcement (pair of (tx, peer), plus whether it's | 
| 482 |  |      *  reconsiderable or not. */ | 
| 483 | 0 |     struct SimAnnouncement | 
| 484 | 0 |     { | 
| 485 | 0 |         unsigned tx; | 
| 486 | 0 |         NodeId announcer; | 
| 487 | 0 |         bool reconsider{false}; | 
| 488 | 0 |         SimAnnouncement(unsigned tx_in, NodeId announcer_in, bool reconsider_in) noexcept : | 
| 489 | 0 |             tx(tx_in), announcer(announcer_in), reconsider(reconsider_in) {} | 
| 490 | 0 |     }; | 
| 491 |  |     /** The entire simulated orphanage is represented by this list of announcements, in | 
| 492 |  |      *  announcement order (unlike TxOrphanageImpl which uses a sequence number to represent | 
| 493 |  |      *  announcement order). New announcements are added to the back. */ | 
| 494 | 0 |     std::vector<SimAnnouncement> sim_announcements; | 
| 495 |  |  | 
| 496 |  |     /** Consume a transaction (index into txn) from provider. */ | 
| 497 | 0 |     auto read_tx_fn = [&]() -> unsigned { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX - 1); }; | 
| 498 |  |     /** Consume a NodeId from provider. */ | 
| 499 | 0 |     auto read_peer_fn = [&]() -> NodeId { return provider.ConsumeIntegralInRange<unsigned>(0, NUM_PEERS - 1); }; | 
| 500 |  |     /** Consume both a transaction (index into txn) and a NodeId from provider. */ | 
| 501 | 0 |     auto read_tx_peer_fn = [&]() -> std::pair<unsigned, NodeId> { | 
| 502 | 0 |         auto code = provider.ConsumeIntegralInRange<unsigned>(0, NUM_TX * NUM_PEERS - 1); | 
| 503 | 0 |         return {code % NUM_TX, code / NUM_TX}; | 
| 504 | 0 |     }; | 
| 505 |  |     /** Determine if we have any announcements of the given transaction in the simulation. */ | 
| 506 | 0 |     auto have_tx_fn = [&](unsigned tx) -> bool { | 
| 507 | 0 |         for (auto& ann : sim_announcements) { | 
| 508 | 0 |             if (ann.tx == tx) return true; | 
| 509 | 0 |         } | 
| 510 | 0 |         return false; | 
| 511 | 0 |     }; | 
| 512 |  |     /** Count the number of peers in the simulation. */ | 
| 513 | 0 |     auto count_peers_fn = [&]() -> unsigned { | 
| 514 | 0 |         std::bitset<NUM_PEERS> mask; | 
| 515 | 0 |         for (auto& ann : sim_announcements) { | 
| 516 | 0 |             mask.set(ann.announcer); | 
| 517 | 0 |         } | 
| 518 | 0 |         return mask.count(); | 
| 519 | 0 |     }; | 
| 520 |  |     /** Determine if we have any reconsiderable announcements of a given transaction. */ | 
| 521 | 0 |     auto have_reconsiderable_fn = [&](unsigned tx) -> bool { | 
| 522 | 0 |         for (auto& ann : sim_announcements) { | 
| 523 | 0 |             if (ann.reconsider && ann.tx == tx) return true; | 
| 524 | 0 |         } | 
| 525 | 0 |         return false; | 
| 526 | 0 |     }; | 
| 527 |  |     /** Determine if a peer has any transactions to reconsider. */ | 
| 528 | 0 |     auto have_reconsider_fn = [&](NodeId peer) -> bool { | 
| 529 | 0 |         for (auto& ann : sim_announcements) { | 
| 530 | 0 |             if (ann.reconsider && ann.announcer == peer) return true; | 
| 531 | 0 |         } | 
| 532 | 0 |         return false; | 
| 533 | 0 |     }; | 
| 534 |  |     /** Get an iterator to an existing (wtxid, peer) pair in the simulation. */ | 
| 535 | 0 |     auto find_announce_wtxid_fn = [&](const Wtxid& wtxid, NodeId peer) -> std::vector<SimAnnouncement>::iterator { | 
| 536 | 0 |         for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { | 
| 537 | 0 |             if (txn[it->tx]->GetWitnessHash() == wtxid && it->announcer == peer) return it; | 
| 538 | 0 |         } | 
| 539 | 0 |         return sim_announcements.end(); | 
| 540 | 0 |     }; | 
| 541 |  |     /** Get an iterator to an existing (tx, peer) pair in the simulation. */ | 
| 542 | 0 |     auto find_announce_fn = [&](unsigned tx, NodeId peer) { | 
| 543 | 0 |         for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { | 
| 544 | 0 |             if (it->tx == tx && it->announcer == peer) return it; | 
| 545 | 0 |         } | 
| 546 | 0 |         return sim_announcements.end(); | 
| 547 | 0 |     }; | 
| 548 |  |     /** Compute a peer's DoS score according to simulation data. */ | 
| 549 | 0 |     auto dos_score_fn = [&](NodeId peer, int32_t max_count, int32_t max_usage) -> FeeFrac { | 
| 550 | 0 |         int64_t count{0}; | 
| 551 | 0 |         int64_t usage{0}; | 
| 552 | 0 |         for (auto& ann : sim_announcements) { | 
| 553 | 0 |             if (ann.announcer != peer) continue; | 
| 554 | 0 |             count += 1 + (txn[ann.tx]->vin.size() / 10); | 
| 555 | 0 |             usage += GetTransactionWeight(*txn[ann.tx]); | 
| 556 | 0 |         } | 
| 557 | 0 |         return std::max(FeeFrac{count, max_count}, FeeFrac{usage, max_usage}); | 
| 558 | 0 |     }; | 
| 559 |  |  | 
| 560 |  |     // | 
| 561 |  |     // 5. Run through a scenario of mutators on both real and simulated orphanage. | 
| 562 |  |     // | 
| 563 |  | 
 | 
| 564 | 0 |     LIMITED_WHILE(provider.remaining_bytes() > 0, 200) { | 
| 565 | 0 |         int command = provider.ConsumeIntegralInRange<uint8_t>(0, 15); | 
| 566 | 0 |         while (true) { | 
| 567 | 0 |             if (sim_announcements.size() < MAX_ANN && command-- == 0) { | 
| 568 |  |                 // AddTx | 
| 569 | 0 |                 auto [tx, peer] = read_tx_peer_fn(); | 
| 570 | 0 |                 bool added = real->AddTx(txn[tx], peer); | 
| 571 | 0 |                 bool sim_have_tx = have_tx_fn(tx); | 
| 572 | 0 |                 assert(added == !sim_have_tx); | 
| 573 | 0 |                 if (find_announce_fn(tx, peer) == sim_announcements.end()) { | 
| 574 | 0 |                     sim_announcements.emplace_back(tx, peer, false); | 
| 575 | 0 |                 } | 
| 576 | 0 |                 break; | 
| 577 | 0 |             } else if (sim_announcements.size() < MAX_ANN && command-- == 0) { | 
| 578 |  |                 // AddAnnouncer | 
| 579 | 0 |                 auto [tx, peer] = read_tx_peer_fn(); | 
| 580 | 0 |                 bool added = real->AddAnnouncer(txn[tx]->GetWitnessHash(), peer); | 
| 581 | 0 |                 bool sim_have_tx = have_tx_fn(tx); | 
| 582 | 0 |                 auto sim_it = find_announce_fn(tx, peer); | 
| 583 | 0 |                 assert(added == (sim_it == sim_announcements.end() && sim_have_tx)); | 
| 584 | 0 |                 if (added) { | 
| 585 | 0 |                     sim_announcements.emplace_back(tx, peer, false); | 
| 586 | 0 |                 } | 
| 587 | 0 |                 break; | 
| 588 | 0 |             } else if (command-- == 0) { | 
| 589 |  |                 // EraseTx | 
| 590 | 0 |                 auto tx = read_tx_fn(); | 
| 591 | 0 |                 bool erased = real->EraseTx(txn[tx]->GetWitnessHash()); | 
| 592 | 0 |                 bool sim_have = have_tx_fn(tx); | 
| 593 | 0 |                 assert(erased == sim_have); | 
| 594 | 0 |                 std::erase_if(sim_announcements, [&](auto& ann) { return ann.tx == tx; }); | 
| 595 | 0 |                 break; | 
| 596 | 0 |            } else if (command-- == 0) { | 
| 597 |  |                 // EraseForPeer | 
| 598 | 0 |                 auto peer = read_peer_fn(); | 
| 599 | 0 |                 real->EraseForPeer(peer); | 
| 600 | 0 |                 std::erase_if(sim_announcements, [&](auto& ann) { return ann.announcer == peer; }); | 
| 601 | 0 |                 break; | 
| 602 | 0 |             } else if (command-- == 0) { | 
| 603 |  |                 // EraseForBlock | 
| 604 | 0 |                 auto pattern = provider.ConsumeIntegralInRange<uint64_t>(0, (uint64_t{1} << NUM_TX) - 1); | 
| 605 | 0 |                 CBlock block; | 
| 606 | 0 |                 std::set<COutPoint> spent; | 
| 607 | 0 |                 for (unsigned tx = 0; tx < NUM_TX; ++tx) { | 
| 608 | 0 |                     if ((pattern >> tx) & 1) { | 
| 609 | 0 |                         block.vtx.emplace_back(txn[tx]); | 
| 610 | 0 |                         for (auto& txin : block.vtx.back()->vin) { | 
| 611 | 0 |                             spent.insert(txin.prevout); | 
| 612 | 0 |                         } | 
| 613 | 0 |                     } | 
| 614 | 0 |                 } | 
| 615 | 0 |                 std::shuffle(block.vtx.begin(), block.vtx.end(), rng); | 
| 616 | 0 |                 real->EraseForBlock(block); | 
| 617 | 0 |                 std::erase_if(sim_announcements, [&](auto& ann) { | 
| 618 | 0 |                     for (auto& txin : txn[ann.tx]->vin) { | 
| 619 | 0 |                         if (spent.count(txin.prevout)) return true; | 
| 620 | 0 |                     } | 
| 621 | 0 |                     return false; | 
| 622 | 0 |                 }); | 
| 623 | 0 |                 break; | 
| 624 | 0 |             } else if (command-- == 0) { | 
| 625 |  |                 // AddChildrenToWorkSet | 
| 626 | 0 |                 auto tx = read_tx_fn(); | 
| 627 | 0 |                 FastRandomContext rand_ctx(rng.rand256()); | 
| 628 | 0 |                 auto added = real->AddChildrenToWorkSet(*txn[tx], rand_ctx); | 
| 629 |  |                 /** Set of not-already-reconsiderable child wtxids. */ | 
| 630 | 0 |                 std::set<Wtxid> child_wtxids; | 
| 631 | 0 |                 for (unsigned child_tx = 0; child_tx < NUM_TX; ++child_tx) { | 
| 632 | 0 |                     if (!have_tx_fn(child_tx)) continue; | 
| 633 | 0 |                     if (have_reconsiderable_fn(child_tx)) continue; | 
| 634 | 0 |                     bool child_of = false; | 
| 635 | 0 |                     for (auto& txin : txn[child_tx]->vin) { | 
| 636 | 0 |                         if (txin.prevout.hash == txn[tx]->GetHash()) { | 
| 637 | 0 |                             child_of = true; | 
| 638 | 0 |                             break; | 
| 639 | 0 |                         } | 
| 640 | 0 |                     } | 
| 641 | 0 |                     if (child_of) { | 
| 642 | 0 |                         child_wtxids.insert(txn[child_tx]->GetWitnessHash()); | 
| 643 | 0 |                     } | 
| 644 | 0 |                 } | 
| 645 | 0 |                 for (auto& [wtxid, peer] : added) { | 
| 646 |  |                     // Wtxid must be a child of tx that is not yet reconsiderable. | 
| 647 | 0 |                     auto child_wtxid_it = child_wtxids.find(wtxid); | 
| 648 | 0 |                     assert(child_wtxid_it != child_wtxids.end()); | 
| 649 |  |                     // Announcement must exist. | 
| 650 | 0 |                     auto sim_ann_it = find_announce_wtxid_fn(wtxid, peer); | 
| 651 | 0 |                     assert(sim_ann_it != sim_announcements.end()); | 
| 652 |  |                     // Announcement must not yet be reconsiderable. | 
| 653 | 0 |                     assert(sim_ann_it->reconsider == false); | 
| 654 |  |                     // Make reconsiderable. | 
| 655 | 0 |                     sim_ann_it->reconsider = true; | 
| 656 |  |                     // Remove from child_wtxids map, to disallow it being reported a second time in added. | 
| 657 | 0 |                     child_wtxids.erase(wtxid); | 
| 658 | 0 |                 } | 
| 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 | 0 |                 assert(child_wtxids.empty()); | 
| 663 | 0 |                 break; | 
| 664 | 0 |             } else if (command-- == 0) { | 
| 665 |  |                 // GetTxToReconsider. | 
| 666 | 0 |                 auto peer = read_peer_fn(); | 
| 667 | 0 |                 auto result = real->GetTxToReconsider(peer); | 
| 668 | 0 |                 if (result) { | 
| 669 |  |                     // A transaction was found. It must have a corresponding reconsiderable | 
| 670 |  |                     // announcement from peer. | 
| 671 | 0 |                     auto sim_ann_it = find_announce_wtxid_fn(result->GetWitnessHash(), peer); | 
| 672 | 0 |                     assert(sim_ann_it != sim_announcements.end()); | 
| 673 | 0 |                     assert(sim_ann_it->announcer == peer); | 
| 674 | 0 |                     assert(sim_ann_it->reconsider); | 
| 675 |  |                     // Make it non-reconsiderable. | 
| 676 | 0 |                     sim_ann_it->reconsider = false; | 
| 677 | 0 |                 } else { | 
| 678 |  |                     // No reconsiderable transaction was found from peer. Verify that it does not | 
| 679 |  |                     // have any. | 
| 680 | 0 |                     assert(!have_reconsider_fn(peer)); | 
| 681 | 0 |                 } | 
| 682 | 0 |                 break; | 
| 683 | 0 |             } | 
| 684 | 0 |         } | 
| 685 |  |         // Always trim after each command if needed. | 
| 686 | 0 |         const auto max_ann = max_global_latency_score / std::max<unsigned>(1, count_peers_fn()); | 
| 687 | 0 |         const auto max_mem = reserved_peer_usage; | 
| 688 | 0 |         while (true) { | 
| 689 |  |             // Count global usage and number of peers. | 
| 690 | 0 |             node::TxOrphanage::Usage total_usage{0}; | 
| 691 | 0 |             node::TxOrphanage::Count total_latency_score = sim_announcements.size(); | 
| 692 | 0 |             for (unsigned tx = 0; tx < NUM_TX; ++tx) { | 
| 693 | 0 |                 if (have_tx_fn(tx)) { | 
| 694 | 0 |                     total_usage += GetTransactionWeight(*txn[tx]); | 
| 695 | 0 |                     total_latency_score += txn[tx]->vin.size() / 10; | 
| 696 | 0 |                 } | 
| 697 | 0 |             } | 
| 698 | 0 |             auto num_peers = count_peers_fn(); | 
| 699 | 0 |             bool oversized = (total_usage > reserved_peer_usage * num_peers) || | 
| 700 | 0 |                                 (total_latency_score > real->MaxGlobalLatencyScore()); | 
| 701 | 0 |             if (!oversized) break; | 
| 702 |  |             // Find worst peer. | 
| 703 | 0 |             FeeFrac worst_dos_score{0, 1}; | 
| 704 | 0 |             unsigned worst_peer = unsigned(-1); | 
| 705 | 0 |             for (unsigned peer = 0; peer < NUM_PEERS; ++peer) { | 
| 706 | 0 |                 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 | 0 |                 if (dos_score >= worst_dos_score) { | 
| 710 | 0 |                     worst_dos_score = dos_score; | 
| 711 | 0 |                     worst_peer = peer; | 
| 712 | 0 |                 } | 
| 713 | 0 |             } | 
| 714 | 0 |             assert(worst_peer != unsigned(-1)); | 
| 715 | 0 |             assert(worst_dos_score >> FeeFrac(1, 1)); | 
| 716 |  |             // Find oldest announcement from worst_peer, preferring non-reconsiderable ones. | 
| 717 | 0 |             bool done{false}; | 
| 718 | 0 |             for (int reconsider = 0; reconsider < 2; ++reconsider) { | 
| 719 | 0 |                 for (auto it = sim_announcements.begin(); it != sim_announcements.end(); ++it) { | 
| 720 | 0 |                     if (it->announcer != worst_peer || it->reconsider != reconsider) continue; | 
| 721 | 0 |                     sim_announcements.erase(it); | 
| 722 | 0 |                     done = true; | 
| 723 | 0 |                     break; | 
| 724 | 0 |                 } | 
| 725 | 0 |                 if (done) break; | 
| 726 | 0 |             } | 
| 727 | 0 |             assert(done); | 
| 728 | 0 |         } | 
| 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 | 0 |         assert(real->TotalLatencyScore() <= real->MaxGlobalLatencyScore()); | 
| 732 | 0 |         assert(real->TotalOrphanUsage() <= real->MaxGlobalUsage()); | 
| 733 | 0 |     } | 
| 734 |  |  | 
| 735 |  |     // | 
| 736 |  |     // 6. Perform a full comparison between the real orphanage's inspectors and the simulation. | 
| 737 |  |     // | 
| 738 |  |  | 
| 739 | 0 |     real->SanityCheck(); | 
| 740 |  |  | 
| 741 |  | 
 | 
| 742 | 0 |     auto all_orphans = real->GetOrphanTransactions(); | 
| 743 | 0 |     node::TxOrphanage::Usage orphan_usage{0}; | 
| 744 | 0 |     std::vector<node::TxOrphanage::Usage> usage_by_peer(NUM_PEERS); | 
| 745 | 0 |     node::TxOrphanage::Count unique_orphans{0}; | 
| 746 | 0 |     std::vector<node::TxOrphanage::Count> count_by_peer(NUM_PEERS); | 
| 747 | 0 |     node::TxOrphanage::Count total_latency_score = sim_announcements.size(); | 
| 748 | 0 |     for (unsigned tx = 0; tx < NUM_TX; ++tx) { | 
| 749 | 0 |         bool sim_have_tx = have_tx_fn(tx); | 
| 750 | 0 |         if (sim_have_tx) { | 
| 751 | 0 |             orphan_usage += GetTransactionWeight(*txn[tx]); | 
| 752 | 0 |             total_latency_score += txn[tx]->vin.size() / 10; | 
| 753 | 0 |         } | 
| 754 | 0 |         unique_orphans += sim_have_tx; | 
| 755 | 0 |         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 | 0 |         assert((orphans_it != all_orphans.end()) == sim_have_tx); | 
| 758 |  |         // HaveTx | 
| 759 | 0 |         bool have_tx = real->HaveTx(txn[tx]->GetWitnessHash()); | 
| 760 | 0 |         assert(have_tx == sim_have_tx); | 
| 761 |  |         // GetTx | 
| 762 | 0 |         auto txref = real->GetTx(txn[tx]->GetWitnessHash()); | 
| 763 | 0 |         assert(!!txref == sim_have_tx); | 
| 764 | 0 |         if (sim_have_tx) assert(txref->GetWitnessHash() == txn[tx]->GetWitnessHash()); | 
| 765 |  |  | 
| 766 | 0 |         for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { | 
| 767 | 0 |             auto it_sim_ann = find_announce_fn(tx, peer); | 
| 768 | 0 |             bool sim_have_ann = it_sim_ann != sim_announcements.end(); | 
| 769 | 0 |             if (sim_have_ann) usage_by_peer[peer] += GetTransactionWeight(*txn[tx]); | 
| 770 | 0 |             count_by_peer[peer] += sim_have_ann; | 
| 771 |  |             // GetOrphanTransactions (announcers presence) | 
| 772 | 0 |             if (sim_have_ann) assert(sim_have_tx); | 
| 773 | 0 |             if (sim_have_tx) assert(orphans_it->announcers.count(peer) == sim_have_ann); | 
| 774 |  |             // HaveTxFromPeer | 
| 775 | 0 |             bool have_ann = real->HaveTxFromPeer(txn[tx]->GetWitnessHash(), peer); | 
| 776 | 0 |             assert(sim_have_ann == have_ann); | 
| 777 |  |             // GetChildrenFromSamePeer | 
| 778 | 0 |             auto children_from_peer = real->GetChildrenFromSamePeer(txn[tx], peer); | 
| 779 | 0 |             auto it = children_from_peer.rbegin(); | 
| 780 | 0 |             for (int phase = 0; phase < 2; ++phase) { | 
| 781 |  |                 // First expect all children which have reconsiderable announcement from peer, then the others. | 
| 782 | 0 |                 for (auto& ann : sim_announcements) { | 
| 783 | 0 |                     if (ann.announcer != peer) continue; | 
| 784 | 0 |                     if (ann.reconsider != (phase == 1)) continue; | 
| 785 | 0 |                     bool matching_parent{false}; | 
| 786 | 0 |                     for (const auto& vin : txn[ann.tx]->vin) { | 
| 787 | 0 |                         if (vin.prevout.hash == txn[tx]->GetHash()) matching_parent = true; | 
| 788 | 0 |                     } | 
| 789 | 0 |                     if (!matching_parent) continue; | 
| 790 |  |                     // Found an announcement from peer which is a child of txn[tx]. | 
| 791 | 0 |                     assert(it != children_from_peer.rend()); | 
| 792 | 0 |                     assert((*it)->GetWitnessHash() == txn[ann.tx]->GetWitnessHash()); | 
| 793 | 0 |                     ++it; | 
| 794 | 0 |                 } | 
| 795 | 0 |             } | 
| 796 | 0 |             assert(it == children_from_peer.rend()); | 
| 797 | 0 |         } | 
| 798 | 0 |     } | 
| 799 |  |     // TotalOrphanUsage | 
| 800 | 0 |     assert(orphan_usage == real->TotalOrphanUsage()); | 
| 801 | 0 |     for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { | 
| 802 | 0 |         bool sim_have_reconsider = have_reconsider_fn(peer); | 
| 803 |  |         // HaveTxToReconsider | 
| 804 | 0 |         bool have_reconsider = real->HaveTxToReconsider(peer); | 
| 805 | 0 |         assert(have_reconsider == sim_have_reconsider); | 
| 806 |  |         // UsageByPeer | 
| 807 | 0 |         assert(usage_by_peer[peer] == real->UsageByPeer(peer)); | 
| 808 |  |         // AnnouncementsFromPeer | 
| 809 | 0 |         assert(count_by_peer[peer] == real->AnnouncementsFromPeer(peer)); | 
| 810 | 0 |     } | 
| 811 |  |     // CountAnnouncements | 
| 812 | 0 |     assert(sim_announcements.size() == real->CountAnnouncements()); | 
| 813 |  |     // CountUniqueOrphans | 
| 814 | 0 |     assert(unique_orphans == real->CountUniqueOrphans()); | 
| 815 |  |     // MaxGlobalLatencyScore | 
| 816 | 0 |     assert(max_global_latency_score == real->MaxGlobalLatencyScore()); | 
| 817 |  |     // ReservedPeerUsage | 
| 818 | 0 |     assert(reserved_peer_usage == real->ReservedPeerUsage()); | 
| 819 |  |     // MaxPeerLatencyScore | 
| 820 | 0 |     auto present_peers = count_peers_fn(); | 
| 821 | 0 |     assert(max_global_latency_score / std::max<unsigned>(1, present_peers) == real->MaxPeerLatencyScore()); | 
| 822 |  |     // MaxGlobalUsage | 
| 823 | 0 |     assert(reserved_peer_usage * std::max<unsigned>(1, present_peers) == real->MaxGlobalUsage()); | 
| 824 |  |     // TotalLatencyScore. | 
| 825 | 0 |     assert(real->TotalLatencyScore() == total_latency_score); | 
| 826 | 0 | } |