txdownloadman.cpp
1 // Copyright (c) 2023-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/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 { 60 CMutableTransaction tx; 61 // If no outpoints are given, create a random one. 62 for (const auto& outpoint : outpoints) { 63 tx.vin.emplace_back(outpoint); 64 } 65 if (add_witness) { 66 tx.vin[0].scriptWitness.stack.push_back({1}); 67 } 68 for (size_t o = 0; o < num_outputs; ++o) tx.vout.emplace_back(CENT, P2WSH_OP_TRUE); 69 return MakeTransactionRef(tx); 70 } 71 static std::vector<COutPoint> PickCoins(FuzzedDataProvider& fuzzed_data_provider) 72 { 73 std::vector<COutPoint> ret; 74 ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); 75 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10) { 76 ret.push_back(fuzzed_data_provider.PickValueInArray(COINS)); 77 } 78 return ret; 79 } 80 81 void initialize() 82 { 83 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 84 g_setup = testing_setup.get(); 85 for (uint32_t i = 0; i < uint32_t{NUM_COINS}; ++i) { 86 COINS[i] = COutPoint{Txid::FromUint256((HashWriter() << i).GetHash()), i}; 87 } 88 size_t outpoints_index = 0; 89 // 2 transactions same txid different witness 90 { 91 auto tx1{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/false)}; 92 auto tx2{MakeTransactionSpending({COINS[outpoints_index]}, /*num_outputs=*/5, /*add_witness=*/true)}; 93 Assert(tx1->GetHash() == tx2->GetHash()); 94 TRANSACTIONS.emplace_back(tx1); 95 TRANSACTIONS.emplace_back(tx2); 96 outpoints_index += 1; 97 } 98 // 2 parents 1 child 99 { 100 auto tx_parent_1{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/true)}; 101 TRANSACTIONS.emplace_back(tx_parent_1); 102 auto tx_parent_2{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/1, /*add_witness=*/false)}; 103 TRANSACTIONS.emplace_back(tx_parent_2); 104 TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent_1->GetHash(), 0}, COutPoint{tx_parent_2->GetHash(), 0}}, 105 /*num_outputs=*/1, /*add_witness=*/true)); 106 } 107 // 1 parent 2 children 108 { 109 auto tx_parent{MakeTransactionSpending({COINS[outpoints_index++]}, /*num_outputs=*/2, /*add_witness=*/true)}; 110 TRANSACTIONS.emplace_back(tx_parent); 111 TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 0}}, 112 /*num_outputs=*/1, /*add_witness=*/true)); 113 TRANSACTIONS.emplace_back(MakeTransactionSpending({COutPoint{tx_parent->GetHash(), 1}}, 114 /*num_outputs=*/1, /*add_witness=*/true)); 115 } 116 // chain of 5 segwit 117 { 118 COutPoint& last_outpoint = COINS[outpoints_index++]; 119 for (auto i{0}; i < 5; ++i) { 120 auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/true)}; 121 TRANSACTIONS.emplace_back(tx); 122 last_outpoint = COutPoint{tx->GetHash(), 0}; 123 } 124 } 125 // chain of 5 non-segwit 126 { 127 COutPoint& last_outpoint = COINS[outpoints_index++]; 128 for (auto i{0}; i < 5; ++i) { 129 auto tx{MakeTransactionSpending({last_outpoint}, /*num_outputs=*/1, /*add_witness=*/false)}; 130 TRANSACTIONS.emplace_back(tx); 131 last_outpoint = COutPoint{tx->GetHash(), 0}; 132 } 133 } 134 // Also create a loose tx for each outpoint. Some of these transactions conflict with the above 135 // or have the same txid. 136 for (const auto& outpoint : COINS) { 137 TRANSACTIONS.emplace_back(MakeTransactionSpending({outpoint}, /*num_outputs=*/1, /*add_witness=*/true)); 138 } 139 140 // Create random-looking time jumps 141 int i = 0; 142 // TIME_SKIPS[N] for N=0..15 is just N microseconds. 143 for (; i < 16; ++i) { 144 TIME_SKIPS[i] = std::chrono::microseconds{i}; 145 } 146 // TIME_SKIPS[N] for N=16..127 has randomly-looking but roughly exponentially increasing values up to 147 // 198.416453 seconds. 148 for (; i < 128; ++i) { 149 int diff_bits = ((i - 10) * 2) / 9; 150 uint64_t diff = 1 + (CSipHasher(0, 0).Write(i).Finalize() >> (64 - diff_bits)); 151 TIME_SKIPS[i] = TIME_SKIPS[i - 1] + std::chrono::microseconds{diff}; 152 } 153 } 154 155 void CheckPackageToValidate(const node::PackageToValidate& package_to_validate, NodeId peer) 156 { 157 Assert(package_to_validate.m_senders.size() == 2); 158 Assert(package_to_validate.m_senders.front() == peer); 159 Assert(package_to_validate.m_senders.back() < NUM_PEERS); 160 161 // Package is a 1p1c 162 const auto& package = package_to_validate.m_txns; 163 Assert(IsChildWithParents(package)); 164 Assert(package.size() == 2); 165 } 166 167 FUZZ_TARGET(txdownloadman, .init = initialize) 168 { 169 SeedRandomStateForTest(SeedRand::ZEROS); 170 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 171 SetMockTime(ConsumeTime(fuzzed_data_provider)); 172 173 // Initialize txdownloadman 174 bilingual_str error; 175 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; 176 FastRandomContext det_rand{true}; 177 node::TxDownloadManager txdownloadman{node::TxDownloadOptions{pool, det_rand, true}}; 178 179 std::chrono::microseconds time{244466666}; 180 181 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) 182 { 183 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 auto rand_tx = fuzzed_data_provider.ConsumeBool() ? 187 MakeTransactionSpending(PickCoins(fuzzed_data_provider), 188 /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), 189 /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : 190 TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); 191 192 CallOneOf( 193 fuzzed_data_provider, 194 [&] { 195 node::TxDownloadConnectionInfo info{ 196 .m_preferred = fuzzed_data_provider.ConsumeBool(), 197 .m_relay_permissions = fuzzed_data_provider.ConsumeBool(), 198 .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() 199 }; 200 txdownloadman.ConnectedPeer(rand_peer, info); 201 }, 202 [&] { 203 txdownloadman.DisconnectedPeer(rand_peer); 204 txdownloadman.CheckIsEmpty(rand_peer); 205 }, 206 [&] { 207 txdownloadman.ActiveTipChange(); 208 }, 209 [&] { 210 CBlock block; 211 block.vtx.push_back(rand_tx); 212 txdownloadman.BlockConnected(std::make_shared<CBlock>(block)); 213 }, 214 [&] { 215 txdownloadman.BlockDisconnected(); 216 }, 217 [&] { 218 txdownloadman.MempoolAcceptedTx(rand_tx); 219 }, 220 [&] { 221 TxValidationState state; 222 state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); 223 bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; 224 225 node::RejectedTxTodo todo = txdownloadman.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); 226 Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); 227 }, 228 [&] { 229 auto gtxid = fuzzed_data_provider.ConsumeBool() ? 230 GenTxid{rand_tx->GetHash()} : 231 GenTxid{rand_tx->GetWitnessHash()}; 232 txdownloadman.AddTxAnnouncement(rand_peer, gtxid, time); 233 }, 234 [&] { 235 txdownloadman.GetRequestsToSend(rand_peer, time); 236 }, 237 [&] { 238 txdownloadman.ReceivedTx(rand_peer, rand_tx); 239 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 Assert(!(should_validate && maybe_package.has_value())); 246 if (maybe_package.has_value()) CheckPackageToValidate(*maybe_package, rand_peer); 247 }, 248 [&] { 249 txdownloadman.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); 250 }, 251 [&] { 252 const bool expect_work{txdownloadman.HaveMoreWork(rand_peer)}; 253 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 if (ptx) { 258 // However, if there was a non-null tx in the workset, HaveMoreWork should have 259 // returned true. 260 Assert(expect_work); 261 } 262 }); 263 // Jump forwards or backwards 264 auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); 265 if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; 266 time += time_skip; 267 } 268 // Disconnect everybody, check that all data structures are empty. 269 for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { 270 txdownloadman.DisconnectedPeer(nodeid); 271 txdownloadman.CheckIsEmpty(nodeid); 272 } 273 txdownloadman.CheckIsEmpty(); 274 } 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 static bool HasRelayPermissions(NodeId peer) { return peer == 0; } 279 280 static void CheckInvariants(const node::TxDownloadManagerImpl& txdownload_impl) 281 { 282 txdownload_impl.m_orphanage->SanityCheck(); 283 // We should never have more than the maximum in-flight requests out for a peer. 284 for (NodeId peer = 0; peer < NUM_PEERS; ++peer) { 285 if (!HasRelayPermissions(peer)) { 286 Assert(txdownload_impl.m_txrequest.Count(peer) <= node::MAX_PEER_TX_ANNOUNCEMENTS); 287 } 288 } 289 txdownload_impl.m_txrequest.SanityCheck(); 290 } 291 292 FUZZ_TARGET(txdownloadman_impl, .init = initialize) 293 { 294 SeedRandomStateForTest(SeedRand::ZEROS); 295 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 296 SetMockTime(ConsumeTime(fuzzed_data_provider)); 297 298 // Initialize a TxDownloadManagerImpl 299 bilingual_str error; 300 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; 301 FastRandomContext det_rand{true}; 302 node::TxDownloadManagerImpl txdownload_impl{node::TxDownloadOptions{pool, det_rand, true}}; 303 304 std::chrono::microseconds time{244466666}; 305 306 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 500) 307 { 308 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 auto rand_tx = fuzzed_data_provider.ConsumeBool() ? 312 MakeTransactionSpending(PickCoins(fuzzed_data_provider), 313 /*num_outputs=*/fuzzed_data_provider.ConsumeIntegralInRange(1, 500), 314 /*add_witness=*/fuzzed_data_provider.ConsumeBool()) : 315 TRANSACTIONS.at(fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, TRANSACTIONS.size() - 1)); 316 317 CallOneOf( 318 fuzzed_data_provider, 319 [&] { 320 node::TxDownloadConnectionInfo info{ 321 .m_preferred = fuzzed_data_provider.ConsumeBool(), 322 .m_relay_permissions = HasRelayPermissions(rand_peer), 323 .m_wtxid_relay = fuzzed_data_provider.ConsumeBool() 324 }; 325 txdownload_impl.ConnectedPeer(rand_peer, info); 326 }, 327 [&] { 328 txdownload_impl.DisconnectedPeer(rand_peer); 329 txdownload_impl.CheckIsEmpty(rand_peer); 330 }, 331 [&] { 332 txdownload_impl.ActiveTipChange(); 333 // After a block update, nothing should be in the rejection caches 334 for (const auto& tx : TRANSACTIONS) { 335 Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetWitnessHash().ToUint256())); 336 Assert(!txdownload_impl.RecentRejectsFilter().contains(tx->GetHash().ToUint256())); 337 Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetWitnessHash().ToUint256())); 338 Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(tx->GetHash().ToUint256())); 339 } 340 }, 341 [&] { 342 CBlock block; 343 block.vtx.push_back(rand_tx); 344 txdownload_impl.BlockConnected(std::make_shared<CBlock>(block)); 345 // Block transactions must be removed from orphanage 346 Assert(!txdownload_impl.m_orphanage->HaveTx(rand_tx->GetWitnessHash())); 347 }, 348 [&] { 349 txdownload_impl.BlockDisconnected(); 350 Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetWitnessHash().ToUint256())); 351 Assert(!txdownload_impl.RecentConfirmedTransactionsFilter().contains(rand_tx->GetHash().ToUint256())); 352 }, 353 [&] { 354 txdownload_impl.MempoolAcceptedTx(rand_tx); 355 }, 356 [&] { 357 TxValidationState state; 358 state.Invalid(fuzzed_data_provider.PickValueInArray(TESTED_TX_RESULTS), ""); 359 bool first_time_failure{fuzzed_data_provider.ConsumeBool()}; 360 361 bool reject_contains_wtxid{txdownload_impl.RecentRejectsFilter().contains(rand_tx->GetWitnessHash().ToUint256())}; 362 363 node::RejectedTxTodo todo = txdownload_impl.MempoolRejectedTx(rand_tx, state, rand_peer, first_time_failure); 364 Assert(first_time_failure || !todo.m_should_add_extra_compact_tx); 365 if (!reject_contains_wtxid) Assert(todo.m_unique_parents.size() <= rand_tx->vin.size()); 366 }, 367 [&] { 368 auto gtxid = fuzzed_data_provider.ConsumeBool() ? 369 GenTxid{rand_tx->GetHash()} : 370 GenTxid{rand_tx->GetWitnessHash()}; 371 txdownload_impl.AddTxAnnouncement(rand_peer, gtxid, time); 372 }, 373 [&] { 374 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 for (const auto& gtxid : getdata_requests) { 378 Assert(!txdownload_impl.AlreadyHaveTx(gtxid, /*include_reconsiderable=*/false)); 379 } 380 }, 381 [&] { 382 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 Assert(!(should_validate && maybe_package.has_value())); 389 if (should_validate) { 390 Assert(!txdownload_impl.AlreadyHaveTx(rand_tx->GetWitnessHash(), /*include_reconsiderable=*/true)); 391 } 392 if (maybe_package.has_value()) { 393 CheckPackageToValidate(*maybe_package, rand_peer); 394 395 const auto& package = maybe_package->m_txns; 396 // Parent is in m_lazy_recent_rejects_reconsiderable and child is in m_orphanage 397 Assert(txdownload_impl.RecentRejectsReconsiderableFilter().contains(rand_tx->GetWitnessHash().ToUint256())); 398 Assert(txdownload_impl.m_orphanage->HaveTx(maybe_package->m_txns.back()->GetWitnessHash())); 399 // Package has not been rejected 400 Assert(!txdownload_impl.RecentRejectsReconsiderableFilter().contains(GetPackageHash(package))); 401 // Neither is in m_lazy_recent_rejects 402 Assert(!txdownload_impl.RecentRejectsFilter().contains(package.front()->GetWitnessHash().ToUint256())); 403 Assert(!txdownload_impl.RecentRejectsFilter().contains(package.back()->GetWitnessHash().ToUint256())); 404 } 405 }, 406 [&] { 407 txdownload_impl.ReceivedNotFound(rand_peer, {rand_tx->GetWitnessHash()}); 408 }, 409 [&] { 410 const bool expect_work{txdownload_impl.HaveMoreWork(rand_peer)}; 411 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 if (ptx) { 416 // However, if there was a non-null tx in the workset, HaveMoreWork should have 417 // returned true. 418 Assert(expect_work); 419 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 TxValidationState state_missing_inputs; 424 state_missing_inputs.Invalid(TxValidationResult::TX_MISSING_INPUTS, ""); 425 txdownload_impl.MempoolRejectedTx(ptx, state_missing_inputs, rand_peer, fuzzed_data_provider.ConsumeBool()); 426 } 427 }); 428 429 auto time_skip = fuzzed_data_provider.PickValueInArray(TIME_SKIPS); 430 if (fuzzed_data_provider.ConsumeBool()) time_skip *= -1; 431 time += time_skip; 432 } 433 CheckInvariants(txdownload_impl); 434 // Disconnect everybody, check that all data structures are empty. 435 for (NodeId nodeid = 0; nodeid < NUM_PEERS; ++nodeid) { 436 txdownload_impl.DisconnectedPeer(nodeid); 437 txdownload_impl.CheckIsEmpty(nodeid); 438 } 439 txdownload_impl.CheckIsEmpty(); 440 } 441 442 } // namespace