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) 2017-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 <chain.h>7#include <chainparams.h>8#include <common/args.h>9#include <consensus/amount.h>10#include <consensus/validation.h>11#include <crypto/hex_base.h>12#include <dbwrapper.h>13#include <flatfile.h>14#include <index/disktxpos.h>15#include <index/txindex.h>16#include <index/txindex_key.h>17#include <interfaces/chain.h>18#include <key.h>19#include <node/blockstorage.h>20#include <primitives/block.h>21#include <script/script.h>22#include <streams.h>23#include <sync.h>24#include <test/util/setup_common.h>25#include <util/byte_units.h>26#include <util/check.h>27#include <util/strencodings.h>28#include <validation.h>30#include <cstdint>31#include <memory>32#include <string>33#include <string_view>34#include <utility>35#include <vector>37#include <boost/test/unit_test.hpp>39BOOST_AUTO_TEST_SUITE(txindex_tests)41// Grants tests access to the otherwise non-public txindex database handle.42class TxIndexTest43{44public:45static CDBWrapper& GetDB(const TxIndex& txindex) { return txindex.GetDB(); }46static CBlockLocator ReadBestBlock(const TxIndex& txindex) { return txindex.GetDB().ReadBestBlock(); }47static void WriteBestBlock(const TxIndex& txindex, const CBlockLocator& locator)48{49auto& db{txindex.GetDB()};50CDBBatch batch{db};51db.WriteBestBlock(batch, locator);52db.WriteBatch(batch);53}54};56namespace {58SipHasher13UJ ReadHasher(const CDBWrapper& db)59{60std::pair<uint64_t, uint64_t> salt;61BOOST_REQUIRE(db.Read(txindex::DB_TXID_HASH_SALT, salt));62return SipHasher13UJ{salt.first, salt.second};63}65std::vector<txindex::BlockTxPosition> BucketPositions(CDBWrapper& db, txindex::TxHashKeyPrefix prefix)66{67std::vector<txindex::BlockTxPosition> positions;68std::unique_ptr<CDBIterator> it{db.NewIterator()};69txindex::DBKey key{prefix, {}};70for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {71positions.push_back(key.pos);72}73return positions;74}76FlatFilePos BlockFilePos(const ChainstateManager& chainman, uint32_t height)77{78LOCK(cs_main);79const CBlockIndex* block_index{chainman.ActiveChain()[height]};80BOOST_REQUIRE(block_index);81return {block_index->nFile, block_index->nDataPos};82}84uint256 LookupTx(const TxIndex& txindex, const Txid& txid)85{86const auto result{txindex.FindTx(txid)};87BOOST_REQUIRE(result);88BOOST_CHECK(result->tx->GetHash() == txid);89return result->block_hash;90}92void InvalidateBlock(ChainstateManager& chainman, const uint256& block_hash)93{94CBlockIndex* block_index{WITH_LOCK(cs_main, return chainman.m_blockman.LookupBlockIndex(block_hash))};95BOOST_REQUIRE(block_index);96BlockValidationState state;97BOOST_REQUIRE(chainman.ActiveChainstate().InvalidateBlock(state, block_index));98}100} // namespace102BOOST_AUTO_TEST_CASE(txindex_position_encoding)103{104constexpr struct { txindex::BlockTxPosition position; std::string_view encoded; } test_vectors[]{105{{0, 0}, "00000000"},106{{1, 2}, "01000002"},107{{10'000'000, 123}, "83e1ac0000007b"},108{{456, 3'999'999}, "82483d08ff"},109};111for (const auto& [position, encoded] : test_vectors) {112BOOST_CHECK_EQUAL(HexStr(DataStream{} << position), encoded);114txindex::BlockTxPosition decoded;115BOOST_CHECK((DataStream{ParseHex(encoded)} >> decoded).empty());116BOOST_CHECK(decoded == position);117}119// Pin the full key encodings, including the type prefixes.120BOOST_CHECK_EQUAL(HexStr(DataStream{} << txindex::BlockSeqKey{1}), "7301");121BOOST_CHECK_EQUAL(HexStr(DataStream{} << txindex::DBKey{0x0102030405, {1, 2}}),122"78010203040501000002");124BOOST_CHECK_EQUAL(txindex::BLOCK_HEADER_SIZE, GetSerializeSize(CBlockHeader{}));125}127BOOST_AUTO_TEST_CASE(txindex_hash_prefix)128{129BOOST_CHECK_EQUAL(130txindex::CreateKeyPrefix(131SipHasher13UJ{0x0706050403020100ULL, 0x0F0E0D0C0B0A0908ULL},132Txid{"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"}),1330xc67d87b08cULL);134}136BOOST_FIXTURE_TEST_CASE(txindex_initial_sync, TestChain100Setup)137{138TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/true);139BOOST_REQUIRE(txindex.Init());141// Transaction should not be found in the index before it is started.142for (const auto& txn : m_coinbase_txns) {143BOOST_CHECK(!txindex.FindTx(txn->GetHash()));144}146// BlockUntilSyncedToCurrentChain should return false before txindex is started.147BOOST_CHECK(!txindex.BlockUntilSyncedToCurrentChain());149txindex.Sync();151// Check that txindex excludes genesis block transactions.152const CBlock& genesis_block = Params().GenesisBlock();153for (const auto& txn : genesis_block.vtx) {154BOOST_CHECK(!txindex.FindTx(txn->GetHash()));155}157// Check that txindex has all txs that were in the chain before it started.158for (const auto& txn : m_coinbase_txns) {159LookupTx(txindex, txn->GetHash());160}162// Check that new transactions in new blocks make it into the index.163for (int i = 0; i < 10; i++) {164CScript coinbase_script_pub_key = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));165std::vector<CMutableTransaction> no_txns;166const CBlock& block = CreateAndProcessBlock(no_txns, coinbase_script_pub_key);167const CTransaction& txn = *block.vtx[0];169BOOST_CHECK(txindex.BlockUntilSyncedToCurrentChain());170LookupTx(txindex, txn.GetHash());171}173// shutdown sequence (c.f. Shutdown() in init.cpp)174txindex.Stop();175}177BOOST_FIXTURE_TEST_CASE(txindex_collision_scan_path, TestChain100Setup)178{179// On-disk, so the legacy-entry probe at construction runs against a fresh180// database, as it would on a node whose index was created by this version.181TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);182BOOST_REQUIRE(txindex.Init());183txindex.Sync();185CDBWrapper& db{TxIndexTest::GetDB(txindex)};186const SipHasher13UJ hasher{ReadHasher(db)};188// Lookups scan candidates in descending sequence order, so entries of189// later-connected blocks are tried first. Forge a colliding entry under the190// first coinbase's prefix pointing at the last coinbase, so looking up the191// first tx must scan that false positive first.192const Txid fake_txid{m_coinbase_txns.back()->GetHash()};193const Txid target_txid{m_coinbase_txns.front()->GetHash()};194const auto fake_prefix{txindex::CreateKeyPrefix(hasher, fake_txid)};195const auto target_prefix{txindex::CreateKeyPrefix(hasher, target_txid)};196// Distinct prefixes guarantee the target's bucket initially holds only the target.197BOOST_REQUIRE(fake_prefix != target_prefix);199// Read the last coinbase's encoded position straight from its bucket.200const auto fake_bucket{BucketPositions(db, fake_prefix)};201BOOST_REQUIRE_EQUAL(fake_bucket.size(), 1U);202const txindex::BlockTxPosition fake_pos{fake_bucket.front()};204db.Write(txindex::DBKey{target_prefix, fake_pos}, txindex::EMPTY_VALUE);206// The target's bucket now holds the real target first (lower sequence207// number), then the forged false positive, which the descending scan tries first.208const auto target_bucket{BucketPositions(db, target_prefix)};209BOOST_REQUIRE_EQUAL(target_bucket.size(), 2U);210BOOST_CHECK(target_bucket[0] != fake_pos);211BOOST_CHECK(target_bucket[1] == fake_pos);213LookupTx(txindex, target_txid);215// A database created fresh by this version cannot contain legacy entries, so216// lookups skip the legacy fallback: drop the last coinbase's hashed entry and217// re-add it under the old 't' + txid schema (a physical CDiskTxPos), then218// confirm the lookup misses even though the legacy row exists.219// BlockTxPosition offsets are from the block start (header included), while220// the legacy CDiskTxPos.nTxOffset is measured after the header.221const CDiskTxPos fake_physical{BlockFilePos(*m_node.chainman, fake_pos.block_seq + 1), fake_pos.tx_offset_in_block - txindex::BLOCK_HEADER_SIZE};222db.Erase(txindex::DBKey{fake_prefix, fake_pos});223db.Write(txindex::LegacyTxKey(fake_txid), fake_physical);224BOOST_CHECK(!txindex.FindTx(fake_txid));226txindex.Stop();227}229BOOST_FIXTURE_TEST_CASE(txindex_legacy_fallback, TestChain100Setup)230{231// Seed the on-disk database with a legacy ('t' + txid) entry before the index232// is opened, as if it had been written by a pre-hashing version.233const Txid legacy_txid{m_coinbase_txns.front()->GetHash()};234// The block at height 1 holds only the coinbase, so the tx starts right after235// the header and the 1-byte tx count.236const CDiskTxPos legacy_pos{BlockFilePos(*m_node.chainman, 1), 1};237{238CDBWrapper db{DBParams{.path = gArgs.GetDataDirNet() / "indexes" / "txindex", .cache_bytes = 1_MiB}};239db.Write(txindex::LegacyTxKey(legacy_txid), legacy_pos);240}242TxIndex txindex(interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB, /*f_memory=*/false);243BOOST_REQUIRE(txindex.Init());244txindex.Sync();246// Drop the hashed entries so only the legacy row remains, then confirm the247// lookup succeeds through the fallback.248CDBWrapper& db{TxIndexTest::GetDB(txindex)};249const auto prefix{txindex::CreateKeyPrefix(ReadHasher(db), legacy_txid)};250const auto bucket{BucketPositions(db, prefix)};
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.