Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2020-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.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/time.h>16#include <test/util/validation.h>17#include <validation.h>19#include <ranges>20#include <vector>22const TestingSetup* g_setup;24CBlockHeader ConsumeBlockHeader(FuzzedDataProvider& provider, uint256 prev_hash, int& nonce_counter)25{26CBlockHeader header;27header.nVersion = provider.ConsumeIntegral<decltype(header.nVersion)>();28header.hashPrevBlock = prev_hash;29header.hashMerkleRoot = uint256{}; // never used30header.nTime = provider.ConsumeIntegral<decltype(header.nTime)>();31header.nBits = Params().GenesisBlock().nBits; // not fuzzed because not used (validation is mocked).32header.nNonce = nonce_counter++; // prevent creating multiple block headers with the same hash33return header;34}36void initialize_block_index_tree()37{38static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();39g_setup = testing_setup.get();40}42FUZZ_TARGET(block_index_tree, .init = initialize_block_index_tree)43{44FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());45FakeNodeClock clock{ConsumeTime(fuzzed_data_provider)};46auto& chainman = static_cast<TestChainstateManager&>(*g_setup->m_node.chainman);47auto& blockman = static_cast<TestBlockManager&>(chainman.m_blockman);48CBlockIndex* genesis = chainman.ActiveChainstate().m_chain[0];49int nonce_counter = 0;50std::vector<CBlockIndex*> blocks;51blocks.push_back(genesis);52bool abort_run{false};54std::vector<CBlockIndex*> pruned_blocks;56LIMITED_WHILE (fuzzed_data_provider.ConsumeBool(), 1000) {57if (abort_run) break;58CallOneOf(59fuzzed_data_provider,60[&] {61// Receive a header building on an existing valid one. This assumes headers are valid, so PoW is not relevant here.62LOCK(cs_main);63CBlockIndex* prev_block = PickValue(fuzzed_data_provider, blocks);64if (!(prev_block->nStatus & BLOCK_FAILED_VALID)) {65CBlockHeader header = ConsumeBlockHeader(fuzzed_data_provider, prev_block->GetBlockHash(), nonce_counter);66CBlockIndex* index = blockman.AddToBlockIndex(header, chainman.m_best_header);67assert(index->nStatus & BLOCK_VALID_TREE);68assert(index->pprev == prev_block);69blocks.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 yet74LOCK(cs_main);75CBlockIndex* 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).77if (index->nTx == 0 && !(index->nStatus & BLOCK_FAILED_VALID)) {78if (fuzzed_data_provider.ConsumeBool()) { // Invalid79BlockValidationState state;80state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");81chainman.InvalidBlockFound(index, state);82} else {83size_t nTx = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 1000);84CBlock block; // Dummy block, so that ReceivedBlockTransactions can infer a nTx value.85block.vtx = std::vector<CTransactionRef>(nTx);86FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));87chainman.ReceivedBlockTransactions(block, index, pos);88assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);89assert(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 way95LOCK(cs_main);96auto& chain = chainman.ActiveChain();97CBlockIndex* old_tip = chain.Tip();98assert(old_tip);99do {100CBlockIndex* best_tip = chainman.FindMostWorkChain();101assert(best_tip); // Should at least return current tip102if (best_tip == chain.Tip()) break; // Nothing to do103// Rewind chain to forking point104const 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.107CBlockIndex* it = chain.Tip();108while (it && it->nHeight != fork->nHeight) {109if (!(it->nStatus & BLOCK_HAVE_UNDO)) {110assert(blockman.m_have_pruned);111abort_run = true;112return;113}114it = it->pprev;115}116chain.SetTip(*chain[fork->nHeight]);118// Prepare new blocks to connect119std::vector<CBlockIndex*> to_connect;120it = best_tip;121while (it && it->nHeight != fork->nHeight) {122to_connect.push_back(it);123it = it->pprev;124}125// Connect blocks, possibly fail126for (CBlockIndex* block : to_connect | std::views::reverse) {127assert(!(block->nStatus & BLOCK_FAILED_VALID));128assert(block->nStatus & BLOCK_HAVE_DATA);129if (!block->IsValid(BLOCK_VALID_SCRIPTS)) {130if (fuzzed_data_provider.ConsumeBool()) { // Invalid131BlockValidationState state;132state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "consensus-invalid");133chainman.InvalidBlockFound(block, state);134// This results in duplicate calls to InvalidChainFound, but mirrors the behavior in validation135chainman.InvalidChainFound(to_connect.front());136break;137} else {138block->RaiseValidity(BLOCK_VALID_SCRIPTS);139block->nStatus |= BLOCK_HAVE_UNDO;140}141}142chain.SetTip(*block);143chainman.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.145if (block->nChainWork > old_tip->nChainWork && fuzzed_data_provider.ConsumeBool()) {146break;147}148}149} while (node::CBlockIndexWorkComparator()(chain.Tip(), old_tip));150assert(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 assumptions154// 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 in156// https://github.com/bitcoin/bitcoin/issues/31512157LOCK(cs_main);158auto& chain = chainman.ActiveChain();159int prune_height = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, chain.Height());160CBlockIndex* prune_block{chain[prune_height]};161if (prune_block != chain.Tip() && (prune_block->nStatus & BLOCK_HAVE_DATA)) {162blockman.m_have_pruned = true;163prune_block->nStatus &= ~BLOCK_HAVE_DATA;164prune_block->nStatus &= ~BLOCK_HAVE_UNDO;165prune_block->nFile = 0;166prune_block->nDataPos = 0;167prune_block->nUndoPos = 0;168auto range = blockman.m_blocks_unlinked.equal_range(prune_block->pprev);169while (range.first != range.second) {170std::multimap<CBlockIndex*, CBlockIndex*>::iterator _it = range.first;171range.first++;172if (_it->second == prune_block) {173blockman.m_blocks_unlinked.erase(_it);174}175}176pruned_blocks.push_back(prune_block);177}178},179[&] {180// Download a previously pruned block181LOCK(cs_main);182size_t num_pruned = pruned_blocks.size();183if (num_pruned == 0) return;184size_t i = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, num_pruned - 1);185CBlockIndex* index = pruned_blocks[i];186assert(!(index->nStatus & BLOCK_HAVE_DATA));187CBlock block;188block.vtx = std::vector<CTransactionRef>(index->nTx); // Set the number of tx to the prior value.189FlatFilePos pos(0, fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1000));190chainman.ReceivedBlockTransactions(block, index, pos);191assert(index->nStatus & BLOCK_VALID_TRANSACTIONS);192assert(index->nStatus & BLOCK_HAVE_DATA);193pruned_blocks.erase(pruned_blocks.begin() + i);194});195}196if (!abort_run) {197chainman.CheckBlockIndex();198}200// clean up global state changed by last iteration and prepare for next iteration201{202LOCK(cs_main);203genesis->nStatus |= BLOCK_HAVE_DATA;204genesis->nStatus |= BLOCK_HAVE_UNDO;205chainman.m_best_header = genesis;206chainman.ResetBestInvalid();207chainman.nBlockSequenceId = 2;208chainman.ActiveChain().SetTip(*genesis);209chainman.ActiveChainstate().setBlockIndexCandidates.clear();210chainman.m_cached_is_ibd = true;211blockman.m_blocks_unlinked.clear();212blockman.m_have_pruned = false;213blockman.CleanupForFuzzing();214// Delete all blocks but Genesis from block index215uint256 genesis_hash = genesis->GetBlockHash();216for (auto it = blockman.m_block_index.begin(); it != blockman.m_block_index.end();) {217if (it->first != genesis_hash) {218it = blockman.m_block_index.erase(it);219} else {220++it;221}222}223chainman.ActiveChainstate().TryAddBlockIndexCandidate(genesis);224assert(blockman.m_block_index.size() == 1);225assert(chainman.ActiveChainstate().setBlockIndexCandidates.size() == 1);226assert(chainman.ActiveChain().Height() == 0);227}228}
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.