LCOV - code coverage report
Current view: top level - src/index - spentindex.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 0 80 0.0 %
Date: 2026-06-25 07:23:51 Functions: 0 14 0.0 %

          Line data    Source code
       1             : // Copyright (c) 2026 The Dash 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/spentindex.h>
       6             : 
       7             : #include <chain.h>
       8             : #include <chainparams.h>
       9             : #include <index/addressindex_util.h>
      10             : #include <logging.h>
      11             : #include <node/blockstorage.h>
      12             : #include <primitives/block.h>
      13             : #include <primitives/transaction.h>
      14             : #include <script/script.h>
      15             : #include <tinyformat.h>
      16             : #include <undo.h>
      17             : #include <util/system.h>
      18             : 
      19             : constexpr uint8_t DB_SPENTINDEX{'p'};
      20             : 
      21             : std::unique_ptr<SpentIndex> g_spentindex;
      22             : 
      23           0 : SpentIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
      24           0 :     BaseIndex::DB(gArgs.GetDataDirNet() / "indexes" / "spentindex", n_cache_size, f_memory, f_wipe)
      25           0 : {
      26           0 : }
      27             : 
      28           0 : bool SpentIndex::DB::WriteBatch(const std::vector<CSpentIndexEntry>& entries)
      29             : {
      30           0 :     CDBBatch batch(*this);
      31           0 :     for (const auto& [key, value] : entries) {
      32           0 :         if (value.IsNull()) {
      33             :             // Null value means delete entry (used during disconnect)
      34           0 :             batch.Erase(std::make_pair(DB_SPENTINDEX, key));
      35           0 :         } else {
      36           0 :             batch.Write(std::make_pair(DB_SPENTINDEX, key), value);
      37             :         }
      38             :     }
      39           0 :     return CDBWrapper::WriteBatch(batch);
      40           0 : }
      41             : 
      42           0 : bool SpentIndex::DB::ReadSpentIndex(const CSpentIndexKey& key, CSpentIndexValue& value)
      43             : {
      44           0 :     return Read(std::make_pair(DB_SPENTINDEX, key), value);
      45             : }
      46             : 
      47           0 : bool SpentIndex::DB::EraseSpentIndex(const std::vector<CSpentIndexKey>& keys)
      48             : {
      49           0 :     CDBBatch batch(*this);
      50           0 :     for (const auto& key : keys) {
      51           0 :         batch.Erase(std::make_pair(DB_SPENTINDEX, key));
      52             :     }
      53           0 :     return CDBWrapper::WriteBatch(batch);
      54           0 : }
      55             : 
      56           0 : SpentIndex::SpentIndex(size_t n_cache_size, bool f_memory, bool f_wipe) :
      57           0 :     m_db(std::make_unique<SpentIndex::DB>(n_cache_size, f_memory, f_wipe))
      58           0 : {
      59           0 : }
      60             : 
      61           0 : SpentIndex::~SpentIndex() = default;
      62             : 
      63           0 : bool SpentIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
      64             : {
      65             :     // Skip genesis block (no inputs to index)
      66           0 :     if (pindex->nHeight == 0) {
      67           0 :         return true;
      68             :     }
      69             : 
      70             :     // Read undo data for this block to get information about spent outputs
      71           0 :     CBlockUndo blockundo;
      72           0 :     if (!node::UndoReadFromDisk(blockundo, pindex)) {
      73           0 :         return error("%s: Failed to read undo data for block %s at height %d", __func__,
      74           0 :                      pindex->GetBlockHash().ToString(), pindex->nHeight);
      75             :     }
      76             : 
      77           0 :     std::vector<CSpentIndexEntry> entries;
      78             : 
      79             :     // Process each non-coinbase transaction
      80             :     // blockundo.vtxundo[i] corresponds to block.vtx[i+1] (coinbase is skipped in undo data)
      81           0 :     if (blockundo.vtxundo.size() != block.vtx.size() - 1) {
      82           0 :         return error("%s: Undo data size mismatch for block %s (expected %zu, got %zu)", __func__,
      83           0 :                      pindex->GetBlockHash().ToString(), block.vtx.size() - 1, blockundo.vtxundo.size());
      84             :     }
      85             : 
      86           0 :     for (size_t i = 0; i < blockundo.vtxundo.size(); i++) {
      87           0 :         const CTransactionRef& tx = block.vtx[i + 1]; // +1 to skip coinbase
      88           0 :         const CTxUndo& txundo = blockundo.vtxundo[i];
      89           0 :         const uint256 txhash = tx->GetHash();
      90             : 
      91             :         // Process each input
      92           0 :         if (tx->vin.size() != txundo.vprevout.size()) {
      93           0 :             return error("%s: Undo data mismatch for tx %s", __func__, txhash.ToString());
      94             :         }
      95             : 
      96           0 :         for (size_t j = 0; j < tx->vin.size(); j++) {
      97           0 :             const CTxIn& input = tx->vin[j];
      98           0 :             const Coin& coin = txundo.vprevout[j];
      99           0 :             const CTxOut& prevout = coin.out;
     100             : 
     101           0 :             AddressType address_type{AddressType::UNKNOWN};
     102           0 :             uint160 address_bytes;
     103           0 :             AddressBytesFromScript(prevout.scriptPubKey, address_type, address_bytes);
     104             : 
     105             :             // Create spent index entry: spent output -> spending tx info
     106           0 :             CSpentIndexKey key(input.prevout.hash, input.prevout.n);
     107           0 :             CSpentIndexValue value(txhash, j, pindex->nHeight, prevout.nValue, address_type, address_bytes);
     108             : 
     109           0 :             entries.emplace_back(key, value);
     110           0 :         }
     111           0 :     }
     112             : 
     113           0 :     return m_db->WriteBatch(entries);
     114           0 : }
     115             : 
     116           0 : bool SpentIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
     117             : {
     118           0 :     assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
     119             : 
     120             :     // Erase spent index entries for blocks being rewound
     121           0 :     for (const CBlockIndex* pindex = current_tip; pindex != new_tip; pindex = pindex->pprev) {
     122             :         // Skip genesis block
     123           0 :         if (pindex->nHeight == 0) continue;
     124             : 
     125             :         // Read block to get transactions
     126           0 :         CBlock block;
     127           0 :         if (!node::ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     128           0 :             return error("%s: Failed to read block %s from disk during rewind", __func__,
     129           0 :                          pindex->GetBlockHash().ToString());
     130             :         }
     131             : 
     132           0 :         std::vector<CSpentIndexKey> keys_to_erase;
     133             : 
     134             :         // Process each non-coinbase transaction
     135           0 :         for (size_t i = 1; i < block.vtx.size(); i++) {
     136           0 :             const CTransactionRef& tx = block.vtx[i];
     137             : 
     138             :             // Erase spent index entries for each input
     139           0 :             for (const CTxIn& input : tx->vin) {
     140           0 :                 CSpentIndexKey key(input.prevout.hash, input.prevout.n);
     141           0 :                 keys_to_erase.push_back(key);
     142             :             }
     143           0 :         }
     144             : 
     145           0 :         if (!keys_to_erase.empty() && !m_db->EraseSpentIndex(keys_to_erase)) {
     146           0 :             return error("%s: Failed to erase spent index during rewind", __func__);
     147             :         }
     148           0 :     }
     149             : 
     150             :     // Call base class Rewind to update the best block pointer
     151           0 :     return BaseIndex::Rewind(current_tip, new_tip);
     152           0 : }
     153             : 
     154           0 : BaseIndex::DB& SpentIndex::GetDB() const { return *m_db; }
     155             : 
     156           0 : bool SpentIndex::GetSpentInfo(const CSpentIndexKey& key, CSpentIndexValue& value) const
     157             : {
     158           0 :     return m_db->ReadSpentIndex(key, value);
     159             : }

Generated by: LCOV version 1.16