LCOV - code coverage report
Current view: top level - src/index - txospenderindex.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 90.7 % 86 78
Test Date: 2026-02-25 05:45:00 Functions: 100.0 % 14 14
Branches: 51.0 % 104 53

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 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/txospenderindex.h>
       6                 :             : 
       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>
      25                 :             : 
      26                 :             : #include <cstdio>
      27                 :             : #include <exception>
      28                 :             : #include <ios>
      29                 :             : #include <span>
      30                 :             : #include <string>
      31                 :             : #include <utility>
      32                 :             : #include <vector>
      33                 :             : 
      34                 :             : /* The database is used to find the spending transaction of a given utxo.
      35                 :             :  * For every input of every transaction it stores a key that is a pair(siphash(input outpoint), transaction location on disk) and an empty value.
      36                 :             :  * To find the spending transaction of an outpoint, we perform a range query on siphash(outpoint), and for each returned key load the transaction
      37                 :             :  * and return it if it does spend the provided outpoint.
      38                 :             :  */
      39                 :             : 
      40                 :             : // LevelDB key prefix. We only have one key for now but it will make it easier to add others if needed.
      41                 :             : constexpr uint8_t DB_TXOSPENDERINDEX{'s'};
      42                 :             : 
      43                 :             : std::unique_ptr<TxoSpenderIndex> g_txospenderindex;
      44                 :             : 
      45                 :             : struct DBKey {
      46                 :             :     uint64_t hash;
      47                 :             :     CDiskTxPos pos;
      48                 :             : 
      49                 :          81 :     explicit DBKey(const uint64_t& hash_in, const CDiskTxPos& pos_in) : hash(hash_in), pos(pos_in) {}
      50                 :             : 
      51                 :         142 :     SERIALIZE_METHODS(DBKey, obj)
      52                 :             :     {
      53                 :          71 :         uint8_t prefix{DB_TXOSPENDERINDEX};
      54                 :          71 :         READWRITE(prefix);
      55         [ -  + ]:          71 :         if (prefix != DB_TXOSPENDERINDEX) {
      56         [ #  # ]:           0 :             throw std::ios_base::failure("Invalid format for spender index DB key");
      57                 :             :         }
      58                 :          71 :         READWRITE(obj.hash);
      59                 :          71 :         READWRITE(obj.pos);
      60                 :          71 :     }
      61                 :             : };
      62                 :             : 
      63                 :          17 : TxoSpenderIndex::TxoSpenderIndex(std::unique_ptr<interfaces::Chain> chain, size_t n_cache_size, bool f_memory, bool f_wipe)
      64   [ +  -  +  -  :         136 :     : BaseIndex(std::move(chain), "txospenderindex"), m_db{std::make_unique<DB>(gArgs.GetDataDirNet() / "indexes" / "txospenderindex" / "db", n_cache_size, f_memory, f_wipe)}
          +  -  +  -  +  
             -  +  +  +  
                      - ]
      65                 :             : {
      66   [ +  -  +  + ]:          15 :     if (!m_db->Read("siphash_key", m_siphash_key)) {
      67                 :           5 :         FastRandomContext rng(false);
      68         [ +  - ]:           5 :         m_siphash_key = {rng.rand64(), rng.rand64()};
      69         [ +  - ]:           5 :         m_db->Write("siphash_key", m_siphash_key, /*fSync=*/ true);
      70                 :           5 :     }
      71                 :          17 : }
      72                 :             : 
      73                 :         943 : interfaces::Chain::NotifyOptions TxoSpenderIndex::CustomOptions()
      74                 :             : {
      75                 :         943 :     interfaces::Chain::NotifyOptions options;
      76                 :         943 :     options.disconnect_data = true;
      77                 :         943 :     return options;
      78                 :             : }
      79                 :             : 
      80                 :          81 : static uint64_t CreateKeyPrefix(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout)
      81                 :             : {
      82                 :          81 :     return PresaltedSipHasher(siphash_key.first, siphash_key.second)(vout.hash.ToUint256(), vout.n);
      83                 :             : }
      84                 :             : 
      85                 :          44 : static DBKey CreateKey(std::pair<uint64_t, uint64_t> siphash_key, const COutPoint& vout, const CDiskTxPos& pos)
      86                 :             : {
      87                 :          44 :     return DBKey(CreateKeyPrefix(siphash_key, vout), pos);
      88                 :             : }
      89                 :             : 
      90                 :         926 : void TxoSpenderIndex::WriteSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
      91                 :             : {
      92                 :         926 :     CDBBatch batch(*m_db);
      93         [ +  + ]:         964 :     for (const auto& [outpoint, pos] : items) {
      94                 :          38 :         DBKey key(CreateKey(m_siphash_key, outpoint, pos));
      95                 :             :         // key is hash(spent outpoint) | disk pos, value is empty
      96         [ +  - ]:          38 :         batch.Write(key, "");
      97                 :             :     }
      98         [ +  - ]:         926 :     m_db->WriteBatch(batch);
      99                 :         926 : }
     100                 :             : 
     101                 :             : 
     102                 :           4 : void TxoSpenderIndex::EraseSpenderInfos(const std::vector<std::pair<COutPoint, CDiskTxPos>>& items)
     103                 :             : {
     104                 :           4 :     CDBBatch batch(*m_db);
     105         [ +  + ]:          10 :     for (const auto& [outpoint, pos] : items) {
     106         [ +  - ]:           6 :         batch.Erase(CreateKey(m_siphash_key, outpoint, pos));
     107                 :             :     }
     108         [ +  - ]:           4 :     m_db->WriteBatch(batch);
     109                 :           4 : }
     110                 :             : 
     111                 :         930 : static std::vector<std::pair<COutPoint, CDiskTxPos>> BuildSpenderPositions(const interfaces::BlockInfo& block)
     112                 :             : {
     113                 :         930 :     std::vector<std::pair<COutPoint, CDiskTxPos>> items;
     114   [ -  +  +  - ]:         930 :     items.reserve(block.data->vtx.size());
     115                 :             : 
     116   [ -  +  -  + ]:         930 :     CDiskTxPos pos({block.file_number, block.data_pos}, GetSizeOfCompactSize(block.data->vtx.size()));
     117         [ +  + ]:        1902 :     for (const auto& tx : block.data->vtx) {
     118         [ +  + ]:         972 :         if (!tx->IsCoinBase()) {
     119         [ +  + ]:          86 :             for (const auto& input : tx->vin) {
     120         [ +  - ]:          44 :                 items.emplace_back(input.prevout, pos);
     121                 :             :             }
     122                 :             :         }
     123                 :         972 :         pos.nTxOffset += ::GetSerializeSize(TX_WITH_WITNESS(*tx));
     124                 :             :     }
     125                 :             : 
     126                 :         930 :     return items;
     127                 :           0 : }
     128                 :             : 
     129                 :             : 
     130                 :         926 : bool TxoSpenderIndex::CustomAppend(const interfaces::BlockInfo& block)
     131                 :             : {
     132         [ +  - ]:         926 :     WriteSpenderInfos(BuildSpenderPositions(block));
     133                 :         926 :     return true;
     134                 :             : }
     135                 :             : 
     136                 :           4 : bool TxoSpenderIndex::CustomRemove(const interfaces::BlockInfo& block)
     137                 :             : {
     138         [ +  - ]:           4 :     EraseSpenderInfos(BuildSpenderPositions(block));
     139                 :           4 :     return true;
     140                 :             : }
     141                 :             : 
     142                 :          20 : util::Expected<TxoSpender, std::string> TxoSpenderIndex::ReadTransaction(const CDiskTxPos& tx_pos) const
     143                 :             : {
     144                 :          20 :     AutoFile file{m_chainstate->m_blockman.OpenBlockFile(tx_pos, /*fReadOnly=*/true)};
     145         [ -  + ]:          20 :     if (file.IsNull()) {
     146         [ #  # ]:           0 :         return util::Unexpected("cannot open block");
     147                 :             :     }
     148                 :          20 :     CBlockHeader header;
     149                 :          20 :     TxoSpender spender;
     150                 :          20 :     try {
     151         [ +  - ]:          20 :         file >> header;
     152         [ +  - ]:          20 :         file.seek(tx_pos.nTxOffset, SEEK_CUR);
     153         [ +  - ]:          20 :         file >> TX_WITH_WITNESS(spender.tx);
     154         [ +  - ]:          20 :         spender.block_hash = header.GetHash();
     155                 :          20 :         return spender;
     156         [ -  - ]:           0 :     } catch (const std::exception& e) {
     157         [ -  - ]:           0 :         return util::Unexpected(e.what());
     158                 :           0 :     }
     159                 :          20 : }
     160                 :             : 
     161                 :          37 : util::Expected<std::optional<TxoSpender>, std::string> TxoSpenderIndex::FindSpender(const COutPoint& txo) const
     162                 :             : {
     163                 :          37 :     const uint64_t prefix{CreateKeyPrefix(m_siphash_key, txo)};
     164         [ +  - ]:          37 :     std::unique_ptr<CDBIterator> it(m_db->NewIterator());
     165                 :          37 :     DBKey key(prefix, CDiskTxPos());
     166                 :             : 
     167                 :             :     // find all keys that start with the outpoint hash, load the transaction at the location specified in the key
     168                 :             :     // and return it if it does spend the provided outpoint
     169   [ +  -  +  -  :          37 :     for (it->Seek(std::pair{DB_TXOSPENDERINDEX, prefix}); it->Valid() && it->GetKey(key) && key.hash == prefix; it->Next()) {
          +  +  +  -  +  
                -  +  + ]
     170   [ +  -  +  - ]:          20 :         if (const auto spender{ReadTransaction(key.pos)}) {
     171         [ +  - ]:          20 :             for (const auto& input : spender->tx->vin) {
     172         [ +  - ]:          20 :                 if (input.prevout == txo) {
     173                 :          20 :                     return std::optional{*spender};
     174                 :             :                 }
     175                 :             :             }
     176                 :             :         } else {
     177         [ #  # ]:           0 :             LogError("Deserialize or I/O error - %s", spender.error());
     178   [ #  #  #  # ]:           0 :             return util::Unexpected{strprintf("IO error finding spending tx for outpoint %s:%d.", txo.hash.GetHex(), txo.n)};
     179         [ -  - ]:          20 :         }
     180                 :             :     }
     181                 :          17 :     return util::Expected<std::optional<TxoSpender>, std::string>(std::nullopt);
     182                 :          37 : }
     183                 :             : 
     184                 :          87 : BaseIndex::DB& TxoSpenderIndex::GetDB() const { return *m_db; }
        

Generated by: LCOV version 2.0-1