/root/bitcoin/src/test/fuzz/txdownloadman.cpp
| Line | Count | Source | 
| 1 |  | // Copyright (c) 2023 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/validation.h> | 
| 6 |  | #include <node/context.h> | 
| 7 |  | #include <node/mempool_args.h> | 
| 8 |  | #include <node/miner.h> | 
| 9 |  | #include <node/txdownloadman.h> | 
| 10 |  | #include <node/txdownloadman_impl.h> | 
| 11 |  | #include <test/fuzz/FuzzedDataProvider.h> | 
| 12 |  | #include <test/fuzz/fuzz.h> | 
| 13 |  | #include <test/fuzz/util.h> | 
| 14 |  | #include <test/fuzz/util/mempool.h> | 
| 15 |  | #include <test/util/mining.h> | 
| 16 |  | #include <test/util/script.h> | 
| 17 |  | #include <test/util/setup_common.h> | 
| 18 |  | #include <test/util/txmempool.h> | 
| 19 |  | #include <util/hasher.h> | 
| 20 |  | #include <util/rbf.h> | 
| 21 |  | #include <util/time.h> | 
| 22 |  | #include <txmempool.h> | 
| 23 |  | #include <validation.h> | 
| 24 |  | #include <validationinterface.h> | 
| 25 |  |  | 
| 26 |  | namespace { | 
| 27 |  |  | 
| 28 |  | const TestingSetup* g_setup; | 
| 29 |  |  | 
| 30 |  | constexpr size_t NUM_COINS{50}; | 
| 31 |  | COutPoint COINS[NUM_COINS]; | 
| 32 |  |  | 
| 33 |  | static TxValidationResult TESTED_TX_RESULTS[] = { | 
| 34 |  |     // Skip TX_RESULT_UNSET | 
| 35 |  |     TxValidationResult::TX_CONSENSUS, | 
| 36 |  |     TxValidationResult::TX_INPUTS_NOT_STANDARD, | 
| 37 |  |     TxValidationResult::TX_NOT_STANDARD, | 
| 38 |  |     TxValidationResult::TX_MISSING_INPUTS, | 
| 39 |  |     TxValidationResult::TX_PREMATURE_SPEND, | 
| 40 |  |     TxValidationResult::TX_WITNESS_MUTATED, | 
| 41 |  |     TxValidationResult::TX_WITNESS_STRIPPED, | 
| 42 |  |     TxValidationResult::TX_CONFLICT, | 
| 43 |  |     TxValidationResult::TX_MEMPOOL_POLICY, | 
| 44 |  |     // Skip TX_NO_MEMPOOL | 
| 45 |  |     TxValidationResult::TX_RECONSIDERABLE, | 
| 46 |  |     TxValidationResult::TX_UNKNOWN, | 
| 47 |  | }; | 
| 48 |  |  | 
| 49 |  | // Precomputed transactions. Some may conflict with each other. | 
| 50 |  | std::vector<CTransactionRef> TRANSACTIONS; | 
| 51 |  |  | 
| 52 |  | // Limit the total number of peers because we don't expect coverage to change much with lots more peers. | 
| 53 |  | constexpr int NUM_PEERS = 16; | 
| 54 |  |  | 
| 55 |  | // Precomputed random durations (positive and negative, each ~exponentially distributed). | 
| 56 |  | std::chrono::microseconds TIME_SKIPS[128]; | 
| 57 |  |  | 
| 58 |  | static CTransactionRef MakeTransactionSpending(const std::vector<COutPoint>& outpoints, size_t num_outputs, bool add_witness) | 
| 59 | 120k | { | 
| 60 | 120k |     CMutableTransaction tx; | 
| 61 |  |     // If no outpoints are given, create a random one. | 
| 62 | 536k |     for (const auto& outpoint : outpoints) { | 
| 63 | 536k |         tx.vin.emplace_back(outpoint); | 
| 64 | 536k |     } | 
| 65 | 120k |     if (add_witness) { | 
| 66 | 77.1k |         tx.vin[0].scriptWitness.stack.push_back({1}); | 
| 67 | 77.1k |     } | 
| 68 | 23.1M |     for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE); | 
| 69 | 120k |     return MakeTransactionRef(tx); | 
| 70 | 120k | } | 
| 71 |  | static std::vector<COutPoint> PickCoins(FuzzedDataProvider& fuzzed_data_provider) | 
| 72 | 120k | { | 
| 73 | 120k |     std::vector<COutPoint> ret; | 
| 74 | 120k |     ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); | 
| 75 | 416k |     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10) { | 
| 76 | 416k |         ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); | 
| 77 | 416k |     } | 
| 78 | 120k |     return ret; | 
| 79 | 120k | } | 
| 80 |  |  | 
| 81 |  | void initialize() | 
| 82 | 0 | { | 
| 83 | 0 |     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); | 
| 84 | 0 |     g_setup = testing_setup.get(); | 
| 85 | 0 |     for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) { | 
| 86 | 0 |         COINS[i] = COutPoint{Txid::FromUint256((HashWriter() << i).GetHash()), i}; | 
| 87 | 0 |     } | 
| 88 | 0 |     size_t outpoints_index = 0; | 
| 89 |  |     // 2 transactions same txid different witness | 
| 90 | 0 |     { | 
| 91 | 0 |         auto tx1{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/false)}; | 
| 92 | 0 |         auto tx2{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/true)}; | 
| 93 | 0 |         Assert(tx1->GetHash() == tx2->GetHash()); | 
| 94 | 0 |         TRANSACTIONS.emplace_back(tx1); | 
| 95 | 0 |         TRANSACTIONS.emplace_back(tx2); | 
| 96 | 0 |         outpoints_index += 1; | 
| 97 | 0 |     } | 
| 98 |  |     // 2 parents 1 child | 
| 99 | 0 |     { | 
| 100 | 0 |         auto tx_parent_1{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/true)}; | 
| 101 | 0 |         TRANSACTIONS.emplace_back(tx_parent_1); | 
| 102 | 0 |         auto tx_parent_2{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/false)}; | 
| 103 | 0 |         TRANSACTIONS.emplace_back(tx_parent_2); | 
| 104 | 0 |         TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent_1->GetHash(), 0}, COutPoint{tx_parent_2->GetHash(), 0}}, | 
| 105 | 0 |                                                             /*num_outputs=*/1, /*add_witness=*/true)); | 
| 106 | 0 |     } | 
| 107 |  |     // 1 parent 2 children | 
| 108 | 0 |     { | 
| 109 | 0 |         auto tx_parent{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/2, /*add_witness=*/true)}; | 
| 110 | 0 |         TRANSACTIONS.emplace_back(tx_parent); | 
| 111 | 0 |         TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 0}}, | 
| 112 | 0 |                                                             /*num_outputs=*/1, /*add_witness=*/true)); | 
| 113 | 0 |         TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 1}}, | 
| 114 | 0 |                                                             /*num_outputs=*/1, /*add_witness=*/true)); | 
| 115 | 0 |     } | 
| 116 |  |     // chain of 5 segwit | 
| 117 | 0 |     { | 
| 118 | 0 |         COutPoint& last_outpoint = COINS[outpoints_index++]; | 
| 119 | 0 |         for (auto i{0}; i < 5; ++i) { | 
| 120 | 0 |             auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/true)}; | 
| 121 | 0 |             TRANSACTIONS.emplace_back(tx); | 
| 122 | 0 |             last_outpoint = COutPoint{tx->GetHash(), 0}; | 
| 123 | 0 |         } | 
| 124 | 0 |     } | 
| 125 |  |     // chain of 5 non-segwit | 
| 126 | 0 |     { | 
| 127 | 0 |         COutPoint& last_outpoint = COINS[outpoints_index++]; | 
| 128 | 0 |         for (auto i{0}; i < 5; ++i) { | 
| 129 | 0 |             auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/false)}; | 
| 130 | 0 |             TRANSACTIONS.emplace_back(tx); | 
| 131 | 0 |             last_outpoint = COutPoint{tx->GetHash(), 0}; | 
| 132 | 0 |         } | 
| 133 | 0 |     } | 
| 134 |  |     // Also create a loose tx for each outpoint. Some of these transactions conflict with the above | 
| 135 |  |     // or have the same txid. | 
| 136 | 0 |     for (const auto& outpoint : COINS) { | 
| 137 | 0 |         TRANSACTIONS.emplace_back(MakeTransactionSpending({outpoint}, /*num_outputs=*/1, /*add_witness=*/true)); | 
| 138 | 0 |     } | 
| 139 |  |  | 
| 140 |  |     // Create random-looking time jumps | 
| 141 | 0 |     int i = 0; | 
| 142 |  |     // TIME_SKIPS[N] for N=0..15 is just N microseconds. | 
| 143 | 0 |     for (; i < 16; ++i) { | 
| 144 | 0 |         TIME_SKIPS[i] = std::chrono::microseconds{i}; | 
| 145 | 0 |     } | 
| 146 |  |     // TIME_SKIPS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to | 
| 147 |  |     // 198.416453 seconds. | 
| 148 | 0 |     for (; i < 128; ++i) { | 
| 149 | 0 |         int diff_bits = ((i - 10) * 2) / 9; | 
| 150 | 0 |         uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits)); | 
| 151 | 0 |         TIME_SKIPS[i] = TIME_SKIPS[i - 1] + std::chrono::microseconds{diff}; | 
| 152 | 0 |     } | 
| 153 | 0 | } | 
| 154 |  |  | 
| 155 |  | void CheckPackageToValidate(const node::PackageToValidate& package_to_validate, NodeId peer) | 
| 156 | 3.90k | { | 
| 157 | 3.90k |     Assert(package_to_validate.m_senders.size() == 2); | 
| 158 | 3.90k |     Assert(package_to_validate.m_senders.front() == peer); | 
| 159 | 3.90k |     Assert(package_to_validate.m_senders.back() < NUM_PEERS); | 
| 160 |  |  | 
| 161 |  |     // Package is a 1p1c | 
| 162 | 3.90k |     const auto& package = package_to_validate.m_txns; | 
| 163 | 3.90k |     Assert(IsChildWithParents(package)); | 
| 164 | 3.90k |     Assert(package.size() == 2); | 
| 165 | 3.90k | } | 
| 166 |  |  | 
| 167 |  | FUZZ_TARGET(txdownloadman, .init = initialize) | 
| 168 | 559 | { | 
| 169 | 559 |     SeedRandomStateForTest(SeedRand::ZEROS); | 
| 170 | 559 |     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | 
| 171 | 559 |     SetMockTime(ConsumeTime(fuzzed_data_provider)); | 
| 172 |  |  | 
| 173 |  |     // Initialize txdownloadman | 
| 174 | 559 |     bilingual_str error; | 
| 175 | 559 |     CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; | 
| 176 | 559 |     FastRandomContext det_rand{true}; | 
| 177 | 559 |     node::TxDownloadManager txdownloadman{node::TxDownloadOptions{pool, det_rand, true}}; | 
| 178 |  |  | 
| 179 | 559 |     std::chrono::microseconds time{244466666}; | 
| 180 |  |  | 
| 181 | 559 |     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) | 
| 182 | 163k |     { | 
| 183 | 163k |         NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); | 
| 184 |  |  | 
| 185 |  |         // Transaction can be one of the premade ones or a randomly generated one | 
| 186 | 163k |         auto rand_tx = fuzzed_data_provider.ConsumeBool() ? | 
| 187 | 120k |             MakeTransactionSpending(PickCoins(fuzzed_data_provider), | 
| 188 | 120k |                                     /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), | 
| 189 | 120k |                                     /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : | 
| 190 | 163k |             TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); | 
| 191 |  |  | 
| 192 | 163k |         CallOneOf( | 
| 193 | 163k |             fuzzed_data_provider, | 
| 194 | 163k |             [&] { | 
| 195 | 18.1k |                 node::TxDownloadConnectionInfo info{ | 
| 196 | 18.1k |                     .m_preferred = fuzzed_data_provider.ConsumeBool(), | 
| 197 | 18.1k |                     .m_relay_permissions = fuzzed_data_provider.ConsumeBool(), | 
| 198 | 18.1k |                     .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() | 
| 199 | 18.1k |                 }; | 
| 200 | 18.1k |                 txdownloadman.ConnectedPeer(rand_peer, info); | 
| 201 | 18.1k |             }, | 
| 202 | 163k |             [&] { | 
| 203 | 9.26k |                 txdownloadman.DisconnectedPeer(rand_peer); | 
| 204 | 9.26k |                 txdownloadman.CheckIsEmpty(rand_peer); | 
| 205 | 9.26k |             }, | 
| 206 | 163k |             [&] { | 
| 207 | 932 |                 txdownloadman.ActiveTipChange(); | 
| 208 | 932 |             }, | 
| 209 | 163k |             [&] { | 
| 210 | 10.4k |                 CBlock block; | 
| 211 | 10.4k |                 block.vtx.push_back(rand_tx); | 
| 212 | 10.4k |                 txdownloadman.BlockConnected(std::make_shared<CBlock>(block)); | 
| 213 | 10.4k |             }, | 
| 214 | 163k |             [&] { | 
| 215 | 5.71k |                 txdownloadman.BlockDisconnected(); | 
| 216 | 5.71k |             }, | 
| 217 | 163k |             [&] { | 
| 218 | 12.5k |                 txdownloadman.MempoolAcceptedTx(rand_tx); | 
| 219 | 12.5k |             }, | 
| 220 | 163k |             [&] { | 
| 221 | 47.6k |                 TxValidationState state; | 
| 222 | 47.6k |                 state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); | 
| 223 | 47.6k |                 bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; | 
| 224 |  |  | 
| 225 | 47.6k |                 node::RejectedTxTodo todo = txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); | 
| 226 | 47.6k |                 Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); | 
| 227 | 47.6k |             }, | 
| 228 | 163k |             [&] { | 
| 229 | 29.8k |                 auto gtxid = fuzzed_data_provider.ConsumeBool() ? | 
| 230 | 23.6k |                              GenTxid{rand_tx->GetHash()} : | 
| 231 | 29.8k |                              GenTxid{rand_tx->GetWitnessHash()}; | 
| 232 | 29.8k |                 txdownloadman.AddTxAnnouncement(rand_peer, gtxid, time); | 
| 233 | 29.8k |             }, | 
| 234 | 163k |             [&] { | 
| 235 | 8.55k |                 txdownloadman.GetRequestsToSend(rand_peer, time); | 
| 236 | 8.55k |             }, | 
| 237 | 163k |             [&] { | 
| 238 | 11.0k |                 txdownloadman.ReceivedTx(rand_peer, rand_tx); | 
| 239 | 11.0k |                 const auto& [should_validate, maybe_package] = txdownloadman.ReceivedTx(rand_peer, rand_tx); | 
| 240 |  |                 // The only possible results should be: | 
| 241 |  |                 // - Don't validate the tx, no package. | 
| 242 |  |                 // - Don't validate the tx, package. | 
| 243 |  |                 // - Validate the tx, no package. | 
| 244 |  |                 // The only combination that doesn't make sense is validate both tx and package. | 
| 245 | 11.0k |                 Assert(!(should_validate && maybe_package.has_value())); | 
| 246 | 11.0k |                 if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer); | 
| 247 | 11.0k |             }, | 
| 248 | 163k |             [&] { | 
| 249 | 2.03k |                 txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); | 
| 250 | 2.03k |             }, | 
| 251 | 163k |             [&] { | 
| 252 | 7.16k |                 const bool expect_work{txdownloadman.HaveMoreWork(rand_peer)}; | 
| 253 | 7.16k |                 const auto ptx = txdownloadman.GetTxToReconsider(rand_peer); | 
| 254 |  |                 // expect_work=true doesn't necessarily mean the next item from the workset isn't a | 
| 255 |  |                 // nullptr, as the transaction could have been removed from orphanage without being | 
| 256 |  |                 // removed from the peer's workset. | 
| 257 | 7.16k |                 if (ptx) { | 
| 258 |  |                     // However, if there was a non-null tx in the workset, HaveMoreWork should have | 
| 259 |  |                     // returned true. | 
| 260 | 1.49k |                     Assert(expect_work); | 
| 261 | 1.49k |                 } | 
| 262 | 7.16k |             }); | 
| 263 |  |         // Jump forwards or backwards | 
| 264 | 163k |         auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); | 
| 265 | 163k |         if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; | 
| 266 | 163k |         time += time_skip; | 
| 267 | 163k |     } | 
| 268 |  |     // Disconnect everybody, check that all data structures are empty. | 
| 269 | 9.50k |     for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { | 
| 270 | 8.94k |         txdownloadman.DisconnectedPeer(nodeid); | 
| 271 | 8.94k |         txdownloadman.CheckIsEmpty(nodeid); | 
| 272 | 8.94k |     } | 
| 273 | 559 |     txdownloadman.CheckIsEmpty(); | 
| 274 | 559 | } | 
| 275 |  |  | 
| 276 |  | // Give node 0 relay permissions, and nobody else. This helps us remember who is a RelayPermissions | 
| 277 |  | // peer without tracking anything (this is only for the txdownload_impl target). | 
| 278 | 0 | static bool HasRelayPermissions(NodeId peer) { return peer == 0; } | 
| 279 |  |  | 
| 280 |  | static void CheckInvariants(const node::TxDownloadManagerImpl& txdownload_impl) | 
| 281 | 0 | { | 
| 282 | 0 |     txdownload_impl.m_orphanage->SanityCheck(); | 
| 283 |  |     // We should never have more than the maximum in-flight requests out for a peer. | 
| 284 | 0 |     for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { | 
| 285 | 0 |         if (!HasRelayPermissions(peer)) { | 
| 286 | 0 |             Assert(txdownload_impl.m_txrequest.Count(peer) <= node::MAX_PEER_TX_ANNOUNCEMENTS); | 
| 287 | 0 |         } | 
| 288 | 0 |     } | 
| 289 | 0 |     txdownload_impl.m_txrequest.SanityCheck(); | 
| 290 | 0 | } | 
| 291 |  |  | 
| 292 |  | FUZZ_TARGET(txdownloadman_impl, .init = initialize) | 
| 293 | 0 | { | 
| 294 | 0 |     SeedRandomStateForTest(SeedRand::ZEROS); | 
| 295 | 0 |     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | 
| 296 | 0 |     SetMockTime(ConsumeTime(fuzzed_data_provider)); | 
| 297 |  |  | 
| 298 |  |     // Initialize a TxDownloadManagerImpl | 
| 299 | 0 |     bilingual_str error; | 
| 300 | 0 |     CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; | 
| 301 | 0 |     FastRandomContext det_rand{true}; | 
| 302 | 0 |     node::TxDownloadManagerImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, true}}; | 
| 303 |  | 
 | 
| 304 | 0 |     std::chrono::microseconds time{244466666}; | 
| 305 |  | 
 | 
| 306 | 0 |     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) | 
| 307 | 0 |     { | 
| 308 | 0 |         NodeId rand_peer = fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(0, NUM_PEERS - 1); | 
| 309 |  |  | 
| 310 |  |         // Transaction can be one of the premade ones or a randomly generated one | 
| 311 | 0 |         auto rand_tx = fuzzed_data_provider.ConsumeBool() ? | 
| 312 | 0 |             MakeTransactionSpending(PickCoins(fuzzed_data_provider), | 
| 313 | 0 |                                     /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), | 
| 314 | 0 |                                     /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : | 
| 315 | 0 |             TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); | 
| 316 |  | 
 | 
| 317 | 0 |         CallOneOf( | 
| 318 | 0 |             fuzzed_data_provider, | 
| 319 | 0 |             [&] { | 
| 320 | 0 |                 node::TxDownloadConnectionInfo info{ | 
| 321 | 0 |                     .m_preferred = fuzzed_data_provider.ConsumeBool(), | 
| 322 | 0 |                     .m_relay_permissions = HasRelayPermissions(rand_peer), | 
| 323 | 0 |                     .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() | 
| 324 | 0 |                 }; | 
| 325 | 0 |                 txdownload_impl.ConnectedPeer(rand_peer, info); | 
| 326 | 0 |             }, | 
| 327 | 0 |             [&] { | 
| 328 | 0 |                 txdownload_impl.DisconnectedPeer(rand_peer); | 
| 329 | 0 |                 txdownload_impl.CheckIsEmpty(rand_peer); | 
| 330 | 0 |             }, | 
| 331 | 0 |             [&] { | 
| 332 | 0 |                 txdownload_impl.ActiveTipChange(); | 
| 333 |  |                 // After a block update, nothing should be in the rejection caches | 
| 334 | 0 |                 for (const auto& tx : TRANSACTIONS) { | 
| 335 | 0 |                     Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetWitnessHash().ToUint256())); | 
| 336 | 0 |                     Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetHash().ToUint256())); | 
| 337 | 0 |                     Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetWitnessHash().ToUint256())); | 
| 338 | 0 |                     Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetHash().ToUint256())); | 
| 339 | 0 |                 } | 
| 340 | 0 |             }, | 
| 341 | 0 |             [&] { | 
| 342 | 0 |                 CBlock block; | 
| 343 | 0 |                 block.vtx.push_back(rand_tx); | 
| 344 | 0 |                 txdownload_impl.BlockConnected(std::make_shared<CBlock>(block)); | 
| 345 |  |                 // Block transactions must be removed from orphanage | 
| 346 | 0 |                 Assert(!txdownload_impl.m_orphanage->HaveTx(rand_tx->GetWitnessHash())); | 
| 347 | 0 |             }, | 
| 348 | 0 |             [&] { | 
| 349 | 0 |                 txdownload_impl.BlockDisconnected(); | 
| 350 | 0 |                 Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetWitnessHash().ToUint256())); | 
| 351 | 0 |                 Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetHash().ToUint256())); | 
| 352 | 0 |             }, | 
| 353 | 0 |             [&] { | 
| 354 | 0 |                 txdownload_impl.MempoolAcceptedTx(rand_tx); | 
| 355 | 0 |             }, | 
| 356 | 0 |             [&] { | 
| 357 | 0 |                 TxValidationState state; | 
| 358 | 0 |                 state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); | 
| 359 | 0 |                 bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; | 
| 360 |  | 
 | 
| 361 | 0 |                 bool reject_contains_wtxid{txdownload_impl.RecentRejectsFilter().contains(rand_tx->GetWitnessHash().ToUint256())}; | 
| 362 |  | 
 | 
| 363 | 0 |                 node::RejectedTxTodo todo = txdownload_impl.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); | 
| 364 | 0 |                 Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); | 
| 365 | 0 |                 if (!reject_contains_wtxid) Assert(todo.m_unique_parents.size() <= rand_tx->vin.size()); | 
| 366 | 0 |             }, | 
| 367 | 0 |             [&] { | 
| 368 | 0 |                 auto gtxid = fuzzed_data_provider.ConsumeBool() ? | 
| 369 | 0 |                              GenTxid{rand_tx->GetHash()} : | 
| 370 | 0 |                              GenTxid{rand_tx->GetWitnessHash()}; | 
| 371 | 0 |                 txdownload_impl.AddTxAnnouncement(rand_peer, gtxid, time); | 
| 372 | 0 |             }, | 
| 373 | 0 |             [&] { | 
| 374 | 0 |                 const auto getdata_requests = txdownload_impl.GetRequestsToSend(rand_peer, time); | 
| 375 |  |                 // TxDownloadManager should not be telling us to request things we already have. | 
| 376 |  |                 // Exclude m_lazy_recent_rejects_reconsiderable because it may request low-feerate parent of orphan. | 
| 377 | 0 |                 for (const auto& gtxid : getdata_requests) { | 
| 378 | 0 |                     Assert(!txdownload_impl.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)); | 
| 379 | 0 |                 } | 
| 380 | 0 |             }, | 
| 381 | 0 |             [&] { | 
| 382 | 0 |                 const auto& [should_validate, maybe_package] = txdownload_impl.ReceivedTx(rand_peer, rand_tx); | 
| 383 |  |                 // The only possible results should be: | 
| 384 |  |                 // - Don't validate the tx, no package. | 
| 385 |  |                 // - Don't validate the tx, package. | 
| 386 |  |                 // - Validate the tx, no package. | 
| 387 |  |                 // The only combination that doesn't make sense is validate both tx and package. | 
| 388 | 0 |                 Assert(!(should_validate && maybe_package.has_value())); | 
| 389 | 0 |                 if (should_validate) { | 
| 390 | 0 |                     Assert(!txdownload_impl.AlreadyHaveTx(rand_tx->GetWitnessHash(), /*include_reconsiderable=*/true)); | 
| 391 | 0 |                 } | 
| 392 | 0 |                 if (maybe_package.has_value()) { | 
| 393 | 0 |                     CheckPackageToValidate(*maybe_package, rand_peer); | 
| 394 |  | 
 | 
| 395 | 0 |                     const auto& package = maybe_package->m_txns; | 
| 396 |  |                     // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage | 
| 397 | 0 |                     Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256())); | 
| 398 | 0 |                     Assert(txdownload_impl.m_orphanage->HaveTx(maybe_package->m_txns.back()->GetWitnessHash())); | 
| 399 |  |                     // Package has not been rejected | 
| 400 | 0 |                     Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package))); | 
| 401 |  |                     // Neither is in m_lazy_recent_rejects | 
| 402 | 0 |                     Assert(!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256())); | 
| 403 | 0 |                     Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())); | 
| 404 | 0 |                 } | 
| 405 | 0 |             }, | 
| 406 | 0 |             [&] { | 
| 407 | 0 |                 txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); | 
| 408 | 0 |             }, | 
| 409 | 0 |             [&] { | 
| 410 | 0 |                 const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)}; | 
| 411 | 0 |                 const auto ptx{txdownload_impl.GetTxToReconsider(rand_peer)}; | 
| 412 |  |                 // expect_work=true doesn't necessarily mean the next item from the workset isn't a | 
| 413 |  |                 // nullptr, as the transaction could have been removed from orphanage without being | 
| 414 |  |                 // removed from the peer's workset. | 
| 415 | 0 |                 if (ptx) { | 
| 416 |  |                     // However, if there was a non-null tx in the workset, HaveMoreWork should have | 
| 417 |  |                     // returned true. | 
| 418 | 0 |                     Assert(expect_work); | 
| 419 | 0 |                     Assert(txdownload_impl.AlreadyHaveTx(ptx->GetWitnessHash(), /*include_reconsiderable=*/false)); | 
| 420 |  |                     // Presumably we have validated this tx. Use "missing inputs" to keep it in the | 
| 421 |  |                     // orphanage longer. Later iterations might call MempoolAcceptedTx or | 
| 422 |  |                     // MempoolRejectedTx with a different error. | 
| 423 | 0 |                     TxValidationState state_missing_inputs; | 
| 424 | 0 |                     state_missing_inputs.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); | 
| 425 | 0 |                     txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool()); | 
| 426 | 0 |                 } | 
| 427 | 0 |             }); | 
| 428 |  | 
 | 
| 429 | 0 |         auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); | 
| 430 | 0 |         if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; | 
| 431 | 0 |         time += time_skip; | 
| 432 | 0 |     } | 
| 433 | 0 |     CheckInvariants(txdownload_impl); | 
| 434 |  |     // Disconnect everybody, check that all data structures are empty. | 
| 435 | 0 |     for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { | 
| 436 | 0 |         txdownload_impl.DisconnectedPeer(nodeid); | 
| 437 | 0 |         txdownload_impl.CheckIsEmpty(nodeid); | 
| 438 | 0 |     } | 
| 439 | 0 |     txdownload_impl.CheckIsEmpty(); | 
| 440 | 0 | } | 
| 441 |  |  | 
| 442 |  | } // namespace |