Inside Bitcoin
Opening the selected file…
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 <index/txindex.h>7#include <chain.h>8#include <common/args.h>9#include <crypto/siphash.h>10#include <dbwrapper.h>11#include <flatfile.h>12#include <index/base.h>13#include <index/disktxpos.h>14#include <index/txindex_key.h>15#include <interfaces/chain.h>16#include <node/blockstorage.h>17#include <primitives/block.h>18#include <primitives/transaction.h>19#include <random.h>20#include <serialize.h>21#include <streams.h>22#include <sync.h>23#include <uint256.h>24#include <util/fs.h>25#include <util/log.h>26#include <validation.h>28#include <algorithm>29#include <array>30#include <cassert>31#include <cstdint>32#include <cstdio>33#include <exception>34#include <functional>35#include <memory>36#include <optional>37#include <string>38#include <utility>39#include <vector>41std::unique_ptr<TxIndex> g_txindex;43namespace {44SipHasher13UJ ReadOrCreateTxidHasher(CDBWrapper& db)45{46std::pair<uint64_t, uint64_t> salt;47if (!db.Read(txindex::DB_TXID_HASH_SALT, salt)) {48FastRandomContext rng{};49salt = {rng.rand64(), rng.rand64()};50db.Write(txindex::DB_TXID_HASH_SALT, salt, /*fSync=*/true);51}52return SipHasher13UJ{salt.first, salt.second};53}54} // namespace56/** Access to the txindex database (indexes/txindex/) */57class TxIndex::DB : public BaseIndex::DB58{59public:60explicit DB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);62/// Write a block of transaction positions to the DB.63void WriteTxs(const interfaces::BlockInfo& block);65/// Used to hash the txid to compute the prefix.66const SipHasher13UJ m_hasher;68/// Whether the database contains any legacy ('t' + txid) entries.69const bool m_has_legacy;71CBlockLocator ReadBestBlock() const override;72void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator) override;74private:75DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy);76};78static fs::path TxIndexDBPath() { return gArgs.GetDataDirNet() / "indexes" / "txindex"; }80TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :81// Bloom filters are built for every key but only consulted by point reads,82// which iterators bypass: the per-tx hashed ('x') lookups seek with an83// iterator, and the 's'/'h' point reads are at most one per block against a84// tiny keyspace. Only the legacy entries' per-tx point lookups benefit, so85// enable the filters only for databases still containing them.86DB(n_cache_size, f_memory, f_wipe,87/*has_legacy=*/!f_memory && !f_wipe && CDBWrapper::HasKeyStartingWith(TxIndexDBPath(), txindex::DB_TXINDEX))88{}90TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy) :91BaseIndex::DB(TxIndexDBPath(), n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/has_legacy),92m_hasher{ReadOrCreateTxidHasher(*this)},93m_has_legacy{has_legacy}94{}96CBlockLocator TxIndex::DB::ReadBestBlock() const97{98CBlockLocator locator;99if (Read(txindex::DB_BEST_BLOCK_V2, locator)) {100return locator;101}102// If we don't have a locator yet, start from the legacy best block.103return BaseIndex::DB::ReadBestBlock();104}106void TxIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)107{108batch.Write(txindex::DB_BEST_BLOCK_V2, locator);109}111void TxIndex::DB::WriteTxs(const interfaces::BlockInfo& block)112{113// A block may be submitted again after it was already indexed, e.g. when it114// reconnects after a reorg or is re-processed after an unclean shutdown. It115// keeps its original sequence number, so skip it to avoid duplicate entries.116if (Exists(txindex::BlockHashKey{block.hash})) return;118uint32_t block_seq{0};119Read(txindex::DB_NEXT_BLOCK_SEQ, block_seq);121CDBBatch batch(*this);122batch.Write(txindex::BlockHashKey{block.hash}, block_seq);123batch.Write(txindex::BlockSeqKey{block_seq}, block.hash);124batch.Write(txindex::DB_NEXT_BLOCK_SEQ, block_seq + 1);125uint32_t tx_offset_in_block{txindex::BLOCK_HEADER_SIZE + GetSizeOfCompactSize(block.data->vtx.size())};126for (const auto& tx : block.data->vtx) {127const txindex::DBKey key{txindex::CreateKeyPrefix(m_hasher, tx->GetHash()),128txindex::BlockTxPosition{block_seq, tx_offset_in_block}};129batch.Write(key, txindex::EMPTY_VALUE);130tx_offset_in_block += tx->ComputeTotalSize();131}132WriteBatch(batch);133}135TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)136: BaseIndex(std::move(chain), "txindex", "txidx"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))137{138if (m_db->m_has_legacy) {139LogInfo("txindex contains entries in the legacy format, which uses excessive disk space. "140"To reclaim disk space, stop the node, delete %s and restart to rebuild the index.",141fs::PathToString(TxIndexDBPath()));142}143}145TxIndex::~TxIndex() = default;147bool TxIndex::CustomAppend(const interfaces::BlockInfo& block)148{149// Exclude genesis block transaction because outputs are not spendable.150if (block.height == 0) return true;152assert(block.data);153m_db->WriteTxs(block);154return true;155}157BaseIndex::DB& TxIndex::GetDB() const { return *m_db; }159std::optional<TxIndexResult> TxIndex::FindTx(const Txid& tx_hash) const160{161struct Candidate {162FlatFilePos tx_position;163uint256 block_hash;164uint32_t block_seq;165//! Whether this candidate's block is currently in the active chain.166//! Active chain candidates are attempted first, so duplicate entries167//! in both active and stale blocks will always return the active block hash.168bool in_active_chain;169};170std::vector<Candidate> candidates;171{172std::unique_ptr<CDBIterator> it{m_db->NewIterator()};173const txindex::TxHashKeyPrefix prefix{txindex::CreateKeyPrefix(m_db->m_hasher, tx_hash)};174txindex::DBKey key{prefix, {}};175for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {176uint256 candidate_block_hash;177if (!m_db->Read(txindex::BlockSeqKey{key.pos.block_seq}, candidate_block_hash)) {178LogWarning("Block sequence %u not found for txid %s", key.pos.block_seq, tx_hash.ToString());179continue;180}181LOCK(cs_main);182const CBlockIndex* block_index{m_chainstate->m_blockman.LookupBlockIndex(candidate_block_hash)};183if (!block_index) {184LogWarning("Block index entry %s not found for txid %s", candidate_block_hash.ToString(), tx_hash.ToString());185continue;186}187if (!(block_index->nStatus & BLOCK_HAVE_DATA)) continue;188const FlatFilePos tx_position{block_index->nFile, block_index->nDataPos + key.pos.tx_offset_in_block};189candidates.emplace_back(tx_position, candidate_block_hash, key.pos.block_seq, m_chainstate->m_chain.Contains(*block_index));190}191}193// Prefer active-chain matches, then later-connected blocks.194std::ranges::sort(candidates, std::greater{}, [](const Candidate& c) {195return std::pair{c.in_active_chain, c.block_seq};196});198for (const auto& candidate : candidates) {199AutoFile file{m_chainstate->m_blockman.OpenBlockFile(candidate.tx_position, /*fReadOnly=*/true)};200if (file.IsNull()) {201LogWarning("OpenBlockFile failed for txid %s", tx_hash.ToString());202continue;203}204CTransactionRef tx;205try {206file >> TX_WITH_WITNESS(tx);207} catch (const std::exception& e) {208LogWarning("Deserialize or I/O error - %s", e.what());209continue;210}211if (tx->GetHash() == tx_hash) {212return TxIndexResult{candidate.block_hash, std::move(tx)};213}214}215// Fall back to legacy if no hashed entry matched. This makes misses pay an216// extra lookup, but keeps existing full-txid entries readable after upgrade.217return m_db->m_has_legacy ? FindLegacyTx(tx_hash) : std::nullopt;218}220std::optional<TxIndexResult> TxIndex::FindLegacyTx(const Txid& tx_hash) const221{222CDiskTxPos postx;223if (!m_db->Read(txindex::LegacyTxKey(tx_hash), postx)) {224return std::nullopt;225}227AutoFile file{m_chainstate->m_blockman.OpenBlockFile(postx, /*fReadOnly=*/true)};228if (file.IsNull()) {229LogError("OpenBlockFile failed");230return std::nullopt;231}232CBlockHeader header;233CTransactionRef tx;234try {235file >> header;236file.seek(postx.nTxOffset, SEEK_CUR);237file >> TX_WITH_WITNESS(tx);238} catch (const std::exception& e) {239LogError("Deserialize or I/O error - %s", e.what());240return std::nullopt;241}242if (tx->GetHash() != tx_hash) {243LogError("txid mismatch");244return std::nullopt;245}246return TxIndexResult{header.GetHash(), std::move(tx)};247}