partially_downloaded_block.cpp
1 // Copyright (c) 2023-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or https://opensource.org/license/mit. 4 5 #include <blockencodings.h> 6 #include <consensus/merkle.h> 7 #include <consensus/validation.h> 8 #include <primitives/block.h> 9 #include <primitives/transaction.h> 10 #include <test/fuzz/FuzzedDataProvider.h> 11 #include <test/fuzz/fuzz.h> 12 #include <test/fuzz/util.h> 13 #include <test/fuzz/util/mempool.h> 14 #include <test/util/setup_common.h> 15 #include <test/util/time.h> 16 #include <test/util/txmempool.h> 17 #include <txmempool.h> 18 #include <util/check.h> 19 #include <util/time.h> 20 #include <util/translation.h> 21 22 #include <cstddef> 23 #include <cstdint> 24 #include <limits> 25 #include <memory> 26 #include <optional> 27 #include <set> 28 #include <vector> 29 30 namespace { 31 const TestingSetup* g_setup; 32 } // namespace 33 34 void initialize_pdb() 35 { 36 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 37 g_setup = testing_setup.get(); 38 } 39 40 PartiallyDownloadedBlock::IsBlockMutatedFn FuzzedIsBlockMutated(bool result) 41 { 42 return [result](const CBlock& block, bool) { 43 return result; 44 }; 45 } 46 47 FUZZ_TARGET(partially_downloaded_block, .init = initialize_pdb) 48 { 49 SeedRandomStateForTest(SeedRand::ZEROS); 50 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 51 NodeClockContext clock_ctx{ConsumeTime(fuzzed_data_provider)}; 52 53 auto block{ConsumeDeserializable<CBlock>(fuzzed_data_provider, TX_WITH_WITNESS)}; 54 if (!block || block->vtx.size() == 0 || 55 block->vtx.size() >= std::numeric_limits<uint16_t>::max()) { 56 return; 57 } 58 59 CBlockHeaderAndShortTxIDs cmpctblock{*block, fuzzed_data_provider.ConsumeIntegral<uint64_t>()}; 60 61 bilingual_str error; 62 CTxMemPool pool{MemPoolOptionsForTest(g_setup->m_node), error}; 63 Assert(error.empty()); 64 PartiallyDownloadedBlock pdb{&pool}; 65 66 // Set of available transactions (mempool or extra_txn) 67 std::set<uint16_t> available; 68 // The coinbase is always available 69 available.insert(0); 70 71 std::vector<std::pair<Wtxid, CTransactionRef>> extra_txn; 72 for (size_t i = 1; i < block->vtx.size(); ++i) { 73 auto tx{block->vtx[i]}; 74 75 bool add_to_extra_txn{fuzzed_data_provider.ConsumeBool()}; 76 bool add_to_mempool{fuzzed_data_provider.ConsumeBool()}; 77 78 if (add_to_extra_txn) { 79 extra_txn.emplace_back(tx->GetWitnessHash(), tx); 80 available.insert(i); 81 } 82 83 if (add_to_mempool && !pool.exists(tx->GetHash())) { 84 LOCK2(cs_main, pool.cs); 85 TryAddToMempool(pool, ConsumeTxMemPoolEntry(fuzzed_data_provider, *tx)); 86 available.insert(i); 87 } 88 } 89 90 auto init_status{pdb.InitData(cmpctblock, extra_txn)}; 91 92 std::vector<CTransactionRef> missing; 93 // Whether we skipped a transaction that should be included in `missing`. 94 // FillBlock should never return READ_STATUS_OK if that is the case. 95 bool skipped_missing{false}; 96 for (size_t i = 0; i < cmpctblock.BlockTxCount(); i++) { 97 // If init_status == READ_STATUS_OK then a available transaction in the 98 // compact block (i.e. IsTxAvailable(i) == true) implies that we marked 99 // that transaction as available above (i.e. available.contains(i)). 100 // The reverse is not true, due to possible compact block short id 101 // collisions (i.e. available.contains(i) does not imply 102 // IsTxAvailable(i) == true). 103 if (init_status == READ_STATUS_OK) { 104 assert(!pdb.IsTxAvailable(i) || available.contains(i)); 105 } 106 107 bool skip{fuzzed_data_provider.ConsumeBool()}; 108 if (!pdb.IsTxAvailable(i) && !skip) { 109 missing.push_back(block->vtx[i]); 110 } 111 112 skipped_missing |= (!pdb.IsTxAvailable(i) && skip); 113 } 114 115 bool segwit_active{fuzzed_data_provider.ConsumeBool()}; 116 117 // Mock IsBlockMutated 118 bool fail_block_mutated{fuzzed_data_provider.ConsumeBool()}; 119 pdb.m_check_block_mutated_mock = FuzzedIsBlockMutated(fail_block_mutated); 120 121 CBlock reconstructed_block; 122 auto fill_status{pdb.FillBlock(reconstructed_block, missing, segwit_active)}; 123 switch (fill_status) { 124 case READ_STATUS_OK: 125 assert(!skipped_missing); 126 assert(!fail_block_mutated); 127 assert(block->GetHash() == reconstructed_block.GetHash()); 128 break; 129 case READ_STATUS_FAILED: 130 assert(fail_block_mutated); 131 break; 132 case READ_STATUS_INVALID: 133 break; 134 } 135 }