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