block_index_tree.cpp
1 // Copyright (c) 2020-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 6 #include <chain.h> 7 #include <chainparams.h> 8 #include <flatfile.h> 9 #include <primitives/block.h> 10 #include <primitives/transaction.h> 11 #include <test/fuzz/FuzzedDataProvider.h> 12 #include <test/fuzz/fuzz.h> 13 #include <test/fuzz/util.h> 14 #include <test/util/setup_common.h> 15 #include <test/util/validation.h> 16 #include <validation.h> 17 18 #include <ranges> 19 #include <vector> 20 21 const TestingSetup* g_setup; 22 23 CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter) 24 { 25 CBlockHeader header; 26 header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>(); 27 header.hashPrevBlock = prev_hash; 28 header.hashMerkleRoot = uint256{}; // never used 29 header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>(); 30 header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked). 31 header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash 32 return header; 33 } 34 35 void initialize_block_index_tree() 36 { 37 static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); 38 g_setup = testing_setup.get(); 39 } 40 41 FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree) 42 { 43 FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); 44 SetMockTime(ConsumeTime(fuzzed_data_provider)); 45 auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman); 46 auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman); 47 CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0]; 48 int nonce_counter = 0; 49 std::vector<CBlockIndex*> blocks; 50 blocks.push_back(genesis); 51 bool abort_run{false}; 52 53 std::vector<CBlockIndex*> pruned_blocks; 54 55 LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 1000) 56 { 57 if (abort_run) break; 58 CallOneOf( 59 fuzzed_data_provider, 60 [&] { 61 // Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here. 62 LOCK(cs_main); 63 CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks); 64 if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) { 65 CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter); 66 CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header); 67 assert(index->nStatus & BLOCK_VALID_TREE); 68 assert(index->pprev == prev_block); 69 blocks.push_back(index); 70 } 71 }, 72 [&] { 73 // Receive a full block (valid or invalid) for an existing header, but don't attempt to connect it yet 74 LOCK(cs_main); 75 CBlockIndex* index = PickValue(fuzzed_data_provider, blocks); 76 // Must be new to us and not known to be invalid (e.g. because of an invalid ancestor). 77 if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) { 78 if (fuzzed_data_provider.ConsumeBool()) { // Invalid 79 BlockValidationState state; 80 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid"); 81 chainman.InvalidBlockFound(index, state); 82 } else { 83 size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000); 84 CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value. 85 block.vtx = std::vector<CTransactionRef>(nTx); 86 FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000)); 87 chainman.ReceivedBlockTransactions(block, index, pos); 88 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS); 89 assert(index->nStatus & BLOCK_HAVE_DATA); 90 } 91 } 92 }, 93 [&] { 94 // Simplified ActivateBestChain(): Try to move to a chain with more work - with the possibility of finding blocks to be invalid on the way 95 LOCK(cs_main); 96 auto& chain = chainman.ActiveChain(); 97 CBlockIndex* old_tip = chain.Tip(); 98 assert(old_tip); 99 do { 100 CBlockIndex* best_tip = chainman.FindMostWorkChain(); 101 assert(best_tip); // Should at least return current tip 102 if (best_tip == chain.Tip()) break; // Nothing to do 103 // Rewind chain to forking point 104 const CBlockIndex* fork = chain.FindFork(best_tip); 105 // If we can't go back to the fork point due to pruned data, abort this run. In reality, a pruned node would also currently just crash in this scenario. 106 // This is very unlikely to happen due to the minimum pruning threshold of 550MiB. 107 CBlockIndex* it = chain.Tip(); 108 while (it && it->nHeight != fork->nHeight) { 109 if (!(it->nStatus & BLOCK_HAVE_UNDO)) { 110 assert(blockman.m_have_pruned); 111 abort_run = true; 112 return; 113 } 114 it = it->pprev; 115 } 116 chain.SetTip(*chain[fork->nHeight]); 117 118 // Prepare new blocks to connect 119 std::vector<CBlockIndex*> to_connect; 120 it = best_tip; 121 while (it && it->nHeight != fork->nHeight) { 122 to_connect.push_back(it); 123 it = it->pprev; 124 } 125 // Connect blocks, possibly fail 126 for (CBlockIndex* block : to_connect | std::views::reverse) { 127 assert(!(block->nStatus & BLOCK_FAILED_VALID)); 128 assert(block->nStatus & BLOCK_HAVE_DATA); 129 if (!block->IsValid(BLOCK_VALID_SCRIPTS)) { 130 if (fuzzed_data_provider.ConsumeBool()) { // Invalid 131 BlockValidationState state; 132 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid"); 133 chainman.InvalidBlockFound(block, state); 134 // This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation 135 chainman.InvalidChainFound(to_connect.front()); 136 break; 137 } else { 138 block->RaiseValidity(BLOCK_VALID_SCRIPTS); 139 block->nStatus |= BLOCK_HAVE_UNDO; 140 } 141 } 142 chain.SetTip(*block); 143 chainman.ActiveChainstate().PruneBlockIndexCandidates(); 144 // ActivateBestChainStep may release cs_main / not connect all blocks in one go - but only if we have at least as much chain work as we had at the start. 145 if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) { 146 break; 147 } 148 } 149 } while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip)); 150 assert(chain.Tip()->nChainWork >= old_tip->nChainWork); 151 }, 152 [&] { 153 // Prune chain - dealing with block files is beyond the scope of this test, so just prune random blocks, making no assumptions 154 // about what blocks are pruned together because they are in the same block file. 155 // Also don't prune blocks outside of the chain for now - this would make the fuzzer crash because of the problem described in 156 // https://github.com/bitcoin/bitcoin/issues/31512 157 LOCK(cs_main); 158 auto& chain = chainman.ActiveChain(); 159 int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height()); 160 CBlockIndex* prune_block{chain[prune_height]}; 161 if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) { 162 blockman.m_have_pruned = true; 163 prune_block->nStatus &= ~BLOCK_HAVE_DATA; 164 prune_block->nStatus &= ~BLOCK_HAVE_UNDO; 165 prune_block->nFile = 0; 166 prune_block->nDataPos = 0; 167 prune_block->nUndoPos = 0; 168 auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev); 169 while (range.first != range.second) { 170 std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first; 171 range.first++; 172 if (_it->second == prune_block) { 173 blockman.m_blocks_unlinked.erase(_it); 174 } 175 } 176 pruned_blocks.push_back(prune_block); 177 } 178 }, 179 [&] { 180 // Download a previously pruned block 181 LOCK(cs_main); 182 size_t num_pruned = pruned_blocks.size(); 183 if (num_pruned == 0) return; 184 size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1); 185 CBlockIndex* index = pruned_blocks[i]; 186 assert(!(index->nStatus & BLOCK_HAVE_DATA)); 187 CBlock block; 188 block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value. 189 FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000)); 190 chainman.ReceivedBlockTransactions(block, index, pos); 191 assert(index->nStatus & BLOCK_VALID_TRANSACTIONS); 192 assert(index->nStatus & BLOCK_HAVE_DATA); 193 pruned_blocks.erase(pruned_blocks.begin() + i); 194 }); 195 } 196 if (!abort_run) { 197 chainman.CheckBlockIndex(); 198 } 199 200 // clean up global state changed by last iteration and prepare for next iteration 201 { 202 LOCK(cs_main); 203 genesis->nStatus |= BLOCK_HAVE_DATA; 204 genesis->nStatus |= BLOCK_HAVE_UNDO; 205 chainman.m_best_header = genesis; 206 chainman.ResetBestInvalid(); 207 chainman.nBlockSequenceId = 2; 208 chainman.ActiveChain().SetTip(*genesis); 209 chainman.ActiveChainstate().setBlockIndexCandidates.clear(); 210 chainman.m_cached_is_ibd = true; 211 blockman.m_blocks_unlinked.clear(); 212 blockman.m_have_pruned = false; 213 blockman.CleanupForFuzzing(); 214 // Delete all blocks but Genesis from block index 215 uint256 genesis_hash = genesis->GetBlockHash(); 216 for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) { 217 if (it->first != genesis_hash) { 218 it = blockman.m_block_index.erase(it); 219 } else { 220 ++it; 221 } 222 } 223 chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis); 224 assert(blockman.m_block_index.size() == 1); 225 assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1); 226 assert(chainman.ActiveChain().Height() == 0); 227 } 228 }