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) 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/txospenderindex.h>7#include <common/args.h>8#include <crypto/siphash.h>9#include <dbwrapper.h>10#include <flatfile.h>11#include <index/base.h>12#include <index/disktxpos.h>13#include <interfaces/chain.h>14#include <logging.h>15#include <node/blockstorage.h>16#include <primitives/block.h>17#include <primitives/transaction.h>18#include <random.h>19#include <serialize.h>20#include <streams.h>21#include <tinyformat.h>22#include <uint256.h>23#include <util/fs.h>24#include <validation.h>26#include <cstddef>27#include <cstdio>28#include <exception>29#include <ios>30#include <span>31#include <string>32#include <utility>33#include <vector>35/* The database is used to find the spending transaction of a given utxo.36* For every input of every transaction it stores a key that is a pair(siphash(input outpoint), transaction location on disk) and a zero-byte value.37* To find the spending transaction of an outpoint, we perform a range query on siphash(outpoint), and for each returned key load the transaction38* and return it if it does spend the provided outpoint.39*/41// LevelDB key prefix. We only have one key for now but it will make it easier to add others if needed.42constexpr uint8_t DB_TXOSPENDERINDEX{'s'};44std::unique_ptr<TxoSpenderIndex> g_txospenderindex;46struct DBKey {47uint64_t hash;48CDiskTxPos pos;50explicit DBKey(const uint64_t& hash_in, const CDiskTxPos& pos_in) : hash(hash_in), pos(pos_in) {}52SERIALIZE_METHODS(DBKey, obj)53{54uint8_t prefix{DB_TXOSPENDERINDEX};55READWRITE(prefix);56if (prefix != DB_TXOSPENDERINDEX) {57throw std::ios_base::failure("Invalid format for spender index DB key");58}59READWRITE(obj.hash);60READWRITE(obj.pos);61}62};64TxoSpenderIndex::TxoSpenderIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)65: BaseIndex(std::move(chain), "txospenderindex", "txospenderidx"), m_db{std::make_unique<DB>(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/false)}66{67if (!m_db->Read("siphash_key", m_siphash_key)) {68FastRandomContext rng(false);69m_siphash_key = {rng.rand64(), rng.rand64()};70m_db->Write("siphash_key", m_siphash_key, /*fSync=*/ true);71}72}74interfaces::Chain::NotifyOptions TxoSpenderIndex::CustomOptions()75{76interfaces::Chain::NotifyOptions options;77options.disconnect_data = true;78return options;79}81static uint64_t CreateKeyPrefix(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout)82{83return PresaltedSipHasher(siphash_key.first, siphash_key.second)(vout.hash.ToUint256(), vout.n);84}86static DBKey CreateKey(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout, const CDiskTxPos& pos)87{88return DBKey(CreateKeyPrefix(siphash_key, vout), pos);89}91void TxoSpenderIndex::WriteSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)92{93CDBBatch batch(*m_db);94for (const auto& [outpoint, pos] : items) {95DBKey key(CreateKey(m_siphash_key, outpoint, pos));96// The key encodes the spent outpoint hash and disk position. The value is only a marker.97// Older entries may contain serialized empty strings; FindSpender() reads only keys.98batch.Write(key, std::span<const std::byte>{});99}100m_db->WriteBatch(batch);101}104void TxoSpenderIndex::EraseSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)105{106CDBBatch batch(*m_db);107for (const auto& [outpoint, pos] : items) {108batch.Erase(CreateKey(m_siphash_key, outpoint, pos));109}110m_db->WriteBatch(batch);111}113static std::vector<std::pair<COutPoint, CDiskTxPos>> BuildSpenderPositions(const interfaces::BlockInfo& block)114{115std::vector<std::pair<COutPoint, CDiskTxPos>> items;116items.reserve(block.data->vtx.size());118CDiskTxPos pos({block.file_number, block.data_pos}, GetSizeOfCompactSize(block.data->vtx.size()));119for (const auto& tx : block.data->vtx) {120if (!tx->IsCoinBase()) {121for (const auto& input : tx->vin) {122items.emplace_back(input.prevout, pos);123}124}125pos.nTxOffset += ::GetSerializeSize(TX_WITH_WITNESS(*tx));126}128return items;129}132bool TxoSpenderIndex::CustomAppend(const interfaces::BlockInfo& block)133{134WriteSpenderInfos(BuildSpenderPositions(block));135return true;136}138bool TxoSpenderIndex::CustomRemove(const interfaces::BlockInfo& block)139{140EraseSpenderInfos(BuildSpenderPositions(block));141return true;142}144util::Expected<TxoSpender, std::string> TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos) const145{146AutoFile file{m_chainstate->m_blockman.OpenBlockFile(tx_pos, /*fReadOnly=*/true)};147if (file.IsNull()) {148return util::Unexpected("cannot open block");149}150CBlockHeader header;151TxoSpender spender;152try {153file >> header;154file.seek(tx_pos.nTxOffset, SEEK_CUR);155file >> TX_WITH_WITNESS(spender.tx);156spender.block_hash = header.GetHash();157return spender;158} catch (const std::exception& e) {159return util::Unexpected(e.what());160}161}163util::Expected<std::optional<TxoSpender>, std::string> TxoSpenderIndex::FindSpender(const COutPoint& txo) const164{165const uint64_t prefix{CreateKeyPrefix(m_siphash_key, txo)};166std::unique_ptr<CDBIterator> it(m_db->NewIterator());167DBKey key(prefix, CDiskTxPos());169// find all keys that start with the outpoint hash, load the transaction at the location specified in the key170// and return it if it does spend the provided outpoint171for (it->Seek(std::pair{DB_TXOSPENDERINDEX, prefix}); it->Valid() && it->GetKey(key) && key.hash == prefix; it->Next()) {172if (const auto spender{ReadTransaction(key.pos)}) {173for (const auto& input : spender->tx->vin) {174if (input.prevout == txo) {175return std::optional{*spender};176}177}178} else {179LogError("Deserialize or I/O error - %s", spender.error());180return util::Unexpected{strprintf("IO error finding spending tx for outpoint %s:%d.", txo.hash.GetHex(), txo.n)};181}182}183return util::Expected<std::optional<TxoSpender>, std::string>(std::nullopt);184}186BaseIndex::DB& TxoSpenderIndex::GetDB() const { return *m_db; }