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.5#include <addresstype.h>6#include <blockfilter.h>7#include <chain.h>8#include <chainparams.h>9#include <coins.h>10#include <common/args.h>11#include <consensus/validation.h>12#include <index/base.h>13#include <index/blockfilterindex.h>14#include <index/coinstatsindex.h>15#include <index/txindex.h>16#include <index/txospenderindex.h>17#include <interfaces/chain.h>18#include <kernel/types.h>19#include <key.h>20#include <node/context.h>21#include <primitives/block.h>22#include <script/script.h>23#include <sync.h>24#include <test/util/mining.h>25#include <test/util/setup_common.h>26#include <test/util/time.h>27#include <test/util/validation.h>28#include <tinyformat.h>29#include <util/byte_units.h>30#include <util/check.h>31#include <util/fs.h>32#include <validation.h>34#include <boost/test/unit_test.hpp>36#include <chrono>37#include <functional>38#include <future>39#include <memory>40#include <string>41#include <thread>42#include <utility>43#include <vector>45using kernel::ChainstateRole;47using IndexFactory = std::function<std::unique_ptr<BaseIndex>(node::NodeContext&)>;49static const std::vector<std::pair<std::string, IndexFactory>> INDEX_FACTORIES{50{"coinstatsindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {51return std::make_unique<CoinStatsIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},52{"txindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {53return std::make_unique<TxIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},54{"txospenderindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {55return std::make_unique<TxoSpenderIndex>(interfaces::MakeChain(node), /*n_cache_size=*/1_MiB); }},56{"blockfilterindex", [](node::NodeContext& node) -> std::unique_ptr<BaseIndex> {57return std::make_unique<BlockFilterIndex>(interfaces::MakeChain(node), BlockFilterType::BASIC, /*n_cache_size=*/1_MiB); }},58};60// Tests of generic BaseIndex functionality that is independent of which61// concrete index is being used.62BOOST_AUTO_TEST_SUITE(baseindex_tests)64// Test that the index does not commit ahead of the chainstate's last65// flushed block. If it did, a subsequent unclean shutdown would corrupt66// the index, because during reverting it would require blocks that were67// never flushed to disk.68BOOST_FIXTURE_TEST_CASE(baseindex_no_commit_ahead_of_flush, TestChain100Setup)69{70Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();71for (const auto& [index_name, make_index] : INDEX_FACTORIES) {72BOOST_TEST_INFO_SCOPE(index_name);73const int tip_height{WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight)};74auto sync_index = [&](bool do_flush, int expected_sync_height, int expected_commit_height) {75auto index{make_index(m_node)};76BOOST_REQUIRE(index->Init());77index->Sync();78if (do_flush) {79chainstate.ForceFlushStateToDisk();80m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();81}82BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, expected_sync_height);83index->Stop();84// Reload index to see which block data was actually committed.85BOOST_REQUIRE(index->Init());86BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, expected_commit_height);88// Drain in-flight validation callbacks before destroying the index.89m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();90// shutdown sequence (c.f. Shutdown() in init.cpp)91index->Stop();92};94// Part 1: Sync, then "crash" (stop without flushing). Models a node that95// started up, had its index catch up, but never flushed before going down.96// The end-of-sync Commit() runs at the chain tip but m_last_flushed_block97// is null, so it is skipped.98sync_index(false, tip_height, 0);100// Part 2: Restart cleanly. Sync, force a chainstate flush, and drain the101// validation queue so the index's ChainStateFlushed callback runs.102// Now m_last_flushed_block == tip and the index can commit.103sync_index(true, tip_height, tip_height);105// Part 3: Connect a new block on the chain without flushing106// (m_last_flushed_block stays at tip_height). For a real node this would107// happen in parallel with Sync(). Here we do it before Sync() to make the108// race state deterministic.109CreateAndProcessBlock({}, CScript() << OP_TRUE);110sync_index(false, tip_height + 1, tip_height);111}112}114// Test shutdown between BlockConnected and ChainStateFlushed notifications,115// make sure index is not corrupted and reloads at the last committed height.116BOOST_FIXTURE_TEST_CASE(index_unclean_shutdown, TestChain100Setup)117{118Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();119const CChainParams& params = Params();120const int tip_height{WITH_LOCK(cs_main, return chainstate.m_chain.Height())};121chainstate.ForceFlushStateToDisk();122// Drain the notification before registering any index.123m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();124for (const auto& [index_name, make_index] : INDEX_FACTORIES) {125BOOST_TEST_INFO_SCOPE(index_name);126{127auto index{make_index(m_node)};128BOOST_REQUIRE(index->Init());129index->Sync();130std::shared_ptr<const CBlock> new_block;131CBlockIndex* new_block_index = nullptr;132{133const CScript script_pub_key{CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG};134const CBlock block = this->CreateBlock({}, script_pub_key);136new_block = std::make_shared<CBlock>(block);138LOCK(cs_main);139BlockValidationState state;140BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));141BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));142CCoinsViewCache view(&chainstate.CoinsTip());143BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));144}145// Send block connected notification, then stop the index without146// sending a chainstate flushed notification. Prior to #24138, this147// would cause the index to be corrupted and fail to reload.148ValidationInterfaceTest::BlockConnected(ChainstateRole{}, *index, new_block, new_block_index);149index->Stop();150}152{153auto index{make_index(m_node)};154BOOST_REQUIRE(index->Init());155// Make sure the index reloads from the pre-crash commit.156BOOST_CHECK_EQUAL(index->GetSummary().best_block_height, tip_height);157BOOST_REQUIRE(index->StartBackgroundSync());158index->Stop();159}160}161}163class IndexReorgCrash : public BaseIndex164{165private:166FakeNodeClock& m_clock;167std::unique_ptr<BaseIndex::DB> m_db;168std::shared_future<void> m_blocker;169int m_blocking_height;171public:172explicit IndexReorgCrash(std::unique_ptr<interfaces::Chain> chain, std::shared_future<void> blocker, int blocking_height, FakeNodeClock& clock)173: BaseIndex(std::move(chain), "test index", "testidx"), m_clock(clock), m_blocker(blocker), m_blocking_height(blocking_height)174{175const fs::path path = gArgs.GetDataDirNet() / "index";176fs::create_directories(path);177m_db = std::make_unique<BaseIndex::DB>(path / "db", /*n_cache_size=*/0, /*f_memory=*/true, /*f_wipe=*/false);178}180bool AllowPrune() const override { return false; }181BaseIndex::DB& GetDB() const override { return *m_db; }183bool CustomAppend(const interfaces::BlockInfo& block) override184{185// Simulate a delay so new blocks can get connected during the initial sync186if (block.height == m_blocking_height) m_blocker.wait();188// Move mock time forward so the best index gets updated only when we are not at the blocking height189if (block.height == m_blocking_height - 1 || block.height > m_blocking_height) {190m_clock += 31s;191}193return true;194}195};197BOOST_FIXTURE_TEST_CASE(index_reorg_crash, TestChain100Setup)198{199std::promise<void> promise;200std::shared_future<void> blocker(promise.get_future());201int blocking_height = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->nHeight);203IndexReorgCrash index{interfaces::MakeChain(m_node), blocker, blocking_height, m_clock};204BOOST_REQUIRE(index.Init());205BOOST_REQUIRE(index.StartBackgroundSync());207auto func_wait_until = [&](int height, std::chrono::milliseconds timeout) {208auto deadline = std::chrono::steady_clock::now() + timeout;209while (index.GetSummary().best_block_height < height) {210if (std::chrono::steady_clock::now() > deadline) {211BOOST_FAIL(strprintf("Timeout waiting for index height %d (current: %d)", height, index.GetSummary().best_block_height));212return;213}214std::this_thread::sleep_for(100ms);215}216};218// Wait until the index is one block before the fork point219func_wait_until(blocking_height - 1, /*timeout=*/5s);221// Create a fork to trigger the reorg222std::vector<std::shared_ptr<CBlock>> fork;223const CBlockIndex* prev_tip = WITH_LOCK(cs_main, return m_node.chainman->ActiveChain().Tip()->pprev);224BOOST_REQUIRE(BuildChain(m_node, prev_tip, GetScriptForDestination(PKHash(GenerateRandomKey().GetPubKey())), 3, fork));226for (const auto& block : fork) {227BOOST_REQUIRE(m_node.chainman->ProcessNewBlock(block, /*force_processing=*/true, /*min_pow_checked=*/true, nullptr));228}230// The index thread is blocked and not done231BOOST_CHECK(!index.GetSummary().synced);233// Unblock the index thread so it can process the reorg234promise.set_value();235// Wait for the index to reach the new tip236func_wait_until(blocking_height + 2, 5s);238// Drain unused BlockConnected events, to avoid unsafe memory races during destruction239m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();240// shutdown sequence (c.f. Shutdown() in init.cpp)241index.Stop();242}244BOOST_AUTO_TEST_SUITE_END()
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.