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