txorphan.cpp
1 // Copyright (c) 2022 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 <policy/policy.h> 10 #include <primitives/transaction.h> 11 #include <script/script.h> 12 #include <sync.h> 13 #include <test/fuzz/FuzzedDataProvider.h> 14 #include <test/fuzz/fuzz.h> 15 #include <test/fuzz/util.h> 16 #include <test/util/setup_common.h> 17 #include <txorphanage.h> 18 #include <uint256.h> 19 #include <util/check.h> 20 #include <util/time.h> 21 22 #include <cstdint> 23 #include <memory> 24 #include <set> 25 #include <utility> 26 #include <vector> 27 28 void initialize_orphanage() 29 { 30 static const auto testing_setup = MakeNoLogFileContext(); 31 } 32 33 FUZZ_TARGET(txorphan, .init = initialize_orphanage) 34 { 35 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 36 FastRandomContext limit_orphans_rng{/*fDeterministic=*/true}; 37 SetMockTime(ConsumeTime(fuzzed_data_provider)); 38 39 TxOrphanage orphanage; 40 std::vector<COutPoint> outpoints; 41 // initial outpoints used to construct transactions later 42 for (uint8_t i = 0; i < 4; i++) { 43 outpoints.emplace_back(Txid::FromUint256(uint256{i}), 0); 44 } 45 // if true, allow duplicate input when constructing tx 46 const bool duplicate_input = fuzzed_data_provider.ConsumeBool(); 47 48 LIMITED_WHILE(outpoints.size() < 200'000 && fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) 49 { 50 // construct transaction 51 const CTransactionRef tx = [&] { 52 CMutableTransaction tx_mut; 53 const auto num_in = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); 54 const auto num_out = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(1, outpoints.size()); 55 // pick unique outpoints from outpoints as input 56 for (uint32_t i = 0; i < num_in; i++) { 57 auto& prevout = PickValue(fuzzed_data_provider, outpoints); 58 tx_mut.vin.emplace_back(prevout); 59 // pop the picked outpoint if duplicate input is not allowed 60 if (!duplicate_input) { 61 std::swap(prevout, outpoints.back()); 62 outpoints.pop_back(); 63 } 64 } 65 // output amount will not affect txorphanage 66 for (uint32_t i = 0; i < num_out; i++) { 67 tx_mut.vout.emplace_back(CAmount{0}, CScript{}); 68 } 69 // restore previously popped outpoints 70 for (auto& in : tx_mut.vin) { 71 outpoints.push_back(in.prevout); 72 } 73 auto new_tx = MakeTransactionRef(tx_mut); 74 // add newly constructed transaction to outpoints 75 for (uint32_t i = 0; i < num_out; i++) { 76 outpoints.emplace_back(new_tx->GetHash(), i); 77 } 78 return new_tx; 79 }(); 80 81 // trigger orphanage functions 82 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10 * DEFAULT_MAX_ORPHAN_TRANSACTIONS) 83 { 84 NodeId peer_id = fuzzed_data_provider.ConsumeIntegral<NodeId>(); 85 86 CallOneOf( 87 fuzzed_data_provider, 88 [&] { 89 orphanage.AddChildrenToWorkSet(*tx); 90 }, 91 [&] { 92 { 93 CTransactionRef ref = orphanage.GetTxToReconsider(peer_id); 94 if (ref) { 95 bool have_tx = orphanage.HaveTx(GenTxid::Txid(ref->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(ref->GetWitnessHash())); 96 Assert(have_tx); 97 } 98 } 99 }, 100 [&] { 101 bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); 102 // AddTx should return false if tx is too big or already have it 103 // tx weight is unknown, we only check when tx is already in orphanage 104 { 105 bool add_tx = orphanage.AddTx(tx, peer_id); 106 // have_tx == true -> add_tx == false 107 Assert(!have_tx || !add_tx); 108 } 109 have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); 110 { 111 bool add_tx = orphanage.AddTx(tx, peer_id); 112 // if have_tx is still false, it must be too big 113 Assert(!have_tx == (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT)); 114 Assert(!have_tx || !add_tx); 115 } 116 }, 117 [&] { 118 bool have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); 119 // EraseTx should return 0 if m_orphans doesn't have the tx 120 { 121 Assert(have_tx == orphanage.EraseTx(tx->GetHash())); 122 } 123 have_tx = orphanage.HaveTx(GenTxid::Txid(tx->GetHash())) || orphanage.HaveTx(GenTxid::Wtxid(tx->GetWitnessHash())); 124 // have_tx should be false and EraseTx should fail 125 { 126 Assert(!have_tx && !orphanage.EraseTx(tx->GetHash())); 127 } 128 }, 129 [&] { 130 orphanage.EraseForPeer(peer_id); 131 }, 132 [&] { 133 // test mocktime and expiry 134 SetMockTime(ConsumeTime(fuzzed_data_provider)); 135 auto limit = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); 136 orphanage.LimitOrphans(limit, limit_orphans_rng); 137 Assert(orphanage.Size() <= limit); 138 }); 139 } 140 } 141 }