LCOV - code coverage report
Current view: top level - src/index - txindex.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 83.1 % 118 98
Test Date: 2026-08-19 07:20:03 Functions: 100.0 % 14 14
Branches: 49.4 % 164 81

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2017-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <index/txindex.h>
       6                 :             : 
       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>
      27                 :             : 
      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>
      40                 :             : 
      41                 :             : std::unique_ptr<TxIndex> g_txindex;
      42                 :             : 
      43                 :             : namespace {
      44                 :          10 : SipHasher13UJ ReadOrCreateTxidHasher(CDBWrapper& db)
      45                 :             : {
      46                 :          10 :     std::pair<uint64_t, uint64_t> salt;
      47         [ +  + ]:          10 :     if (!db.Read(txindex::DB_TXID_HASH_SALT, salt)) {
      48                 :           7 :         FastRandomContext rng{};
      49         [ +  - ]:           7 :         salt = {rng.rand64(), rng.rand64()};
      50         [ +  - ]:           7 :         db.Write(txindex::DB_TXID_HASH_SALT, salt, /*fSync=*/true);
      51                 :           7 :     }
      52                 :          10 :     return SipHasher13UJ{salt.first, salt.second};
      53                 :             : }
      54                 :             : } // namespace
      55                 :             : 
      56                 :             : /** Access to the txindex database (indexes/txindex/) */
      57                 :             : class TxIndex::DB : public BaseIndex::DB
      58                 :             : {
      59                 :             : public:
      60                 :             :     explicit DB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
      61                 :             : 
      62                 :             :     /// Write a block of transaction positions to the DB.
      63                 :             :     void WriteTxs(const interfaces::BlockInfo& block);
      64                 :             : 
      65                 :             :     /// Used to hash the txid to compute the prefix.
      66                 :             :     const SipHasher13UJ m_hasher;
      67                 :             : 
      68                 :             :     /// Whether the database contains any legacy ('t' + txid) entries.
      69                 :             :     const bool m_has_legacy;
      70                 :             : 
      71                 :             :     CBlockLocator ReadBestBlock() const override;
      72                 :             :     void WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator) override;
      73                 :             : 
      74                 :             : private:
      75                 :             :     DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy);
      76                 :             : };
      77                 :             : 
      78   [ +  -  +  - ]:          95 : static fs::path TxIndexDBPath() { return gArgs.GetDataDirNet() / "indexes" / "txindex"; }
      79                 :             : 
      80                 :          10 : TxIndex::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 an
      83                 :             :     // iterator, and the 's'/'h' point reads are at most one per block against a
      84                 :             :     // tiny keyspace. Only the legacy entries' per-tx point lookups benefit, so
      85                 :             :     // enable the filters only for databases still containing them.
      86                 :             :     DB(n_cache_size, f_memory, f_wipe,
      87   [ +  +  +  -  :          25 :        /*has_legacy=*/!f_memory && !f_wipe && CDBWrapper::HasKeyStartingWith(TxIndexDBPath(), txindex::DB_TXINDEX))
          +  +  +  -  +  
                +  -  - ]
      88                 :          10 : {}
      89                 :             : 
      90                 :          10 : TxIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe, bool has_legacy) :
      91                 :           0 :     BaseIndex::DB(TxIndexDBPath(), n_cache_size, f_memory, f_wipe, /*f_obfuscate=*/false, /*f_bloom=*/has_legacy),
      92                 :          10 :     m_hasher{ReadOrCreateTxidHasher(*this)},
      93   [ +  -  +  - ]:          20 :     m_has_legacy{has_legacy}
      94                 :          10 : {}
      95                 :             : 
      96                 :          14 : CBlockLocator TxIndex::DB::ReadBestBlock() const
      97                 :             : {
      98                 :          14 :     CBlockLocator locator;
      99   [ +  -  +  + ]:          14 :     if (Read(txindex::DB_BEST_BLOCK_V2, locator)) {
     100                 :           4 :         return locator;
     101                 :             :     }
     102                 :             :     // If we don't have a locator yet, start from the legacy best block.
     103         [ +  - ]:          10 :     return BaseIndex::DB::ReadBestBlock();
     104                 :          14 : }
     105                 :             : 
     106                 :           2 : void TxIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
     107                 :             : {
     108                 :           2 :     batch.Write(txindex::DB_BEST_BLOCK_V2, locator);
     109                 :           2 : }
     110                 :             : 
     111                 :         818 : void TxIndex::DB::WriteTxs(const interfaces::BlockInfo& block)
     112                 :             : {
     113                 :             :     // A block may be submitted again after it was already indexed, e.g. when it
     114                 :             :     // reconnects after a reorg or is re-processed after an unclean shutdown. It
     115                 :             :     // keeps its original sequence number, so skip it to avoid duplicate entries.
     116         [ +  + ]:         818 :     if (Exists(txindex::BlockHashKey{block.hash})) return;
     117                 :             : 
     118                 :         616 :     uint32_t block_seq{0};
     119                 :         616 :     Read(txindex::DB_NEXT_BLOCK_SEQ, block_seq);
     120                 :             : 
     121                 :         616 :     CDBBatch batch(*this);
     122         [ +  - ]:         616 :     batch.Write(txindex::BlockHashKey{block.hash}, block_seq);
     123         [ +  - ]:         616 :     batch.Write(txindex::BlockSeqKey{block_seq}, block.hash);
     124         [ +  - ]:         616 :     batch.Write(txindex::DB_NEXT_BLOCK_SEQ, block_seq + 1);
     125   [ -  +  -  + ]:         616 :     uint32_t tx_offset_in_block{txindex::BLOCK_HEADER_SIZE + GetSizeOfCompactSize(block.data->vtx.size())};
     126         [ +  + ]:        1234 :     for (const auto& tx : block.data->vtx) {
     127         [ +  - ]:         618 :         const txindex::DBKey key{txindex::CreateKeyPrefix(m_hasher, tx->GetHash()),
     128                 :         618 :                                  txindex::BlockTxPosition{block_seq, tx_offset_in_block}};
     129         [ +  - ]:         618 :         batch.Write(key, txindex::EMPTY_VALUE);
     130         [ +  - ]:         618 :         tx_offset_in_block += tx->ComputeTotalSize();
     131                 :             :     }
     132         [ +  - ]:         616 :     WriteBatch(batch);
     133                 :         616 : }
     134                 :             : 
     135                 :          10 : TxIndex::TxIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
     136   [ +  -  +  -  :          10 :     : BaseIndex(std::move(chain), "txindex", "txidx"), m_db(std::make_unique<TxIndex::DB>(n_cache_size, f_memory, f_wipe))
                   +  - ]
     137                 :             : {
     138         [ +  + ]:          10 :     if (m_db->m_has_legacy) {
     139   [ +  -  +  - ]:           3 :         LogInfo("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.",
     141                 :             :                 fs::PathToString(TxIndexDBPath()));
     142                 :             :     }
     143                 :          10 : }
     144                 :             : 
     145                 :          15 : TxIndex::~TxIndex() = default;
     146                 :             : 
     147                 :         826 : bool TxIndex::CustomAppend(const interfaces::BlockInfo& block)
     148                 :             : {
     149                 :             :     // Exclude genesis block transaction because outputs are not spendable.
     150         [ +  + ]:         826 :     if (block.height == 0) return true;
     151                 :             : 
     152         [ -  + ]:         818 :     assert(block.data);
     153                 :         818 :     m_db->WriteTxs(block);
     154                 :         818 :     return true;
     155                 :             : }
     156                 :             : 
     157                 :          22 : BaseIndex::DB& TxIndex::GetDB() const { return *m_db; }
     158                 :             : 
     159                 :         218 : std::optional<TxIndexResult> TxIndex::FindTx(const Txid& tx_hash) const
     160                 :             : {
     161                 :         218 :     struct Candidate {
     162                 :             :         FlatFilePos tx_position;
     163                 :             :         uint256 block_hash;
     164                 :             :         uint32_t block_seq;
     165                 :             :         //! Whether this candidate's block is currently in the active chain.
     166                 :             :         //! Active chain candidates are attempted first, so duplicate entries
     167                 :             :         //! in both active and stale blocks will always return the active block hash.
     168                 :             :         bool in_active_chain;
     169                 :             :     };
     170                 :         218 :     std::vector<Candidate> candidates;
     171                 :         218 :     {
     172   [ +  -  +  - ]:         218 :         std::unique_ptr<CDBIterator> it{m_db->NewIterator()};
     173         [ +  - ]:         218 :         const txindex::TxHashKeyPrefix prefix{txindex::CreateKeyPrefix(m_db->m_hasher, tx_hash)};
     174                 :         218 :         txindex::DBKey key{prefix, {}};
     175   [ +  -  +  -  :         336 :         for (it->Seek(key); it->Valid() && it->GetKey(key) && key.hash_prefix == prefix; it->Next()) {
          +  -  +  +  +  
             -  +  -  +  
                      + ]
     176                 :         118 :             uint256 candidate_block_hash;
     177   [ -  +  +  - ]:         118 :             if (!m_db->Read(txindex::BlockSeqKey{key.pos.block_seq}, candidate_block_hash)) {
     178   [ #  #  #  # ]:           0 :                 LogWarning("Block sequence %u not found for txid %s", key.pos.block_seq, tx_hash.ToString());
     179                 :           0 :                 continue;
     180                 :             :             }
     181         [ +  - ]:         118 :             LOCK(cs_main);
     182         [ +  - ]:         118 :             const CBlockIndex* block_index{m_chainstate->m_blockman.LookupBlockIndex(candidate_block_hash)};
     183         [ -  + ]:         118 :             if (!block_index) {
     184   [ #  #  #  #  :           0 :                 LogWarning("Block index entry %s not found for txid %s", candidate_block_hash.ToString(), tx_hash.ToString());
                   #  # ]
     185                 :           0 :                 continue;
     186                 :             :             }
     187         [ -  + ]:         118 :             if (!(block_index->nStatus & BLOCK_HAVE_DATA)) continue;
     188         [ +  - ]:         118 :             const FlatFilePos tx_position{block_index->nFile, block_index->nDataPos + key.pos.tx_offset_in_block};
     189         [ +  - ]:         118 :             candidates.emplace_back(tx_position, candidate_block_hash, key.pos.block_seq, m_chainstate->m_chain.Contains(*block_index));
     190                 :         118 :         }
     191                 :         218 :     }
     192                 :             : 
     193                 :             :     // Prefer active-chain matches, then later-connected blocks.
     194                 :         218 :     std::ranges::sort(candidates, std::greater{}, [](const Candidate& c) {
     195                 :           4 :         return std::pair{c.in_active_chain, c.block_seq};
     196                 :             :     });
     197                 :             : 
     198         [ +  + ]:         219 :     for (const auto& candidate : candidates) {
     199         [ +  - ]:         116 :         AutoFile file{m_chainstate->m_blockman.OpenBlockFile(candidate.tx_position, /*fReadOnly=*/true)};
     200         [ -  + ]:         116 :         if (file.IsNull()) {
     201   [ #  #  #  # ]:           0 :             LogWarning("OpenBlockFile failed for txid %s", tx_hash.ToString());
     202                 :           0 :             continue;
     203                 :             :         }
     204                 :         116 :         CTransactionRef tx;
     205                 :         116 :         try {
     206         [ +  - ]:         116 :             file >> TX_WITH_WITNESS(tx);
     207         [ -  - ]:           0 :         } catch (const std::exception& e) {
     208         [ -  - ]:           0 :             LogWarning("Deserialize or I/O error - %s", e.what());
     209                 :           0 :             continue;
     210         [ -  - ]:           0 :         }
     211   [ +  +  +  - ]:         116 :         if (tx->GetHash() == tx_hash) {
     212                 :         115 :             return TxIndexResult{candidate.block_hash, std::move(tx)};
     213                 :             :         }
     214                 :         116 :     }
     215                 :             :     // Fall back to legacy if no hashed entry matched. This makes misses pay an
     216                 :             :     // extra lookup, but keeps existing full-txid entries readable after upgrade.
     217   [ +  +  +  - ]:         103 :     return m_db->m_has_legacy ? FindLegacyTx(tx_hash) : std::nullopt;
     218                 :         218 : }
     219                 :             : 
     220                 :           1 : std::optional<TxIndexResult> TxIndex::FindLegacyTx(const Txid& tx_hash) const
     221                 :             : {
     222                 :           1 :     CDiskTxPos postx;
     223         [ -  + ]:           1 :     if (!m_db->Read(txindex::LegacyTxKey(tx_hash), postx)) {
     224                 :           0 :         return std::nullopt;
     225                 :             :     }
     226                 :             : 
     227                 :           1 :     AutoFile file{m_chainstate->m_blockman.OpenBlockFile(postx, /*fReadOnly=*/true)};
     228         [ -  + ]:           1 :     if (file.IsNull()) {
     229         [ #  # ]:           0 :         LogError("OpenBlockFile failed");
     230                 :           0 :         return std::nullopt;
     231                 :             :     }
     232                 :           1 :     CBlockHeader header;
     233                 :           1 :     CTransactionRef tx;
     234                 :           1 :     try {
     235         [ +  - ]:           1 :         file >> header;
     236         [ +  - ]:           1 :         file.seek(postx.nTxOffset, SEEK_CUR);
     237         [ +  - ]:           1 :         file >> TX_WITH_WITNESS(tx);
     238         [ -  - ]:           0 :     } catch (const std::exception& e) {
     239         [ -  - ]:           0 :         LogError("Deserialize or I/O error - %s", e.what());
     240                 :           0 :         return std::nullopt;
     241                 :           0 :     }
     242         [ +  - ]:           1 :     if (tx->GetHash() != tx_hash) {
     243         [ #  # ]:           0 :         LogError("txid mismatch");
     244                 :           0 :         return std::nullopt;
     245                 :             :     }
     246         [ +  - ]:           1 :     return TxIndexResult{header.GetHash(), std::move(tx)};
     247                 :           1 : }
        

Generated by: LCOV version 2.0-1