LCOV - code coverage report
Current view: top level - src/index - addressindex.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 187 223 83.9 %
Date: 2026-06-25 07:23:43 Functions: 16 19 84.2 %

          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/addressindex.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <clientversion.h>
       9             : #include <hash.h>
      10             : #include <index/addressindex_util.h>
      11             : #include <logging.h>
      12             : #include <node/blockstorage.h>
      13             : #include <tinyformat.h>
      14             : #include <undo.h>
      15             : #include <util/system.h>
      16             : 
      17             : constexpr uint8_t DB_ADDRESSINDEX{'a'};
      18             : constexpr uint8_t DB_ADDRESSUNSPENTINDEX{'u'};
      19             : 
      20             : std::unique_ptr<AddressIndex> g_addressindex;
      21             : 
      22          20 : AddressIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
      23          10 :     BaseIndex::DB(gArgs.GetDataDirNet() / "indexes" / "addressindex", n_cache_size, f_memory, f_wipe)
      24          10 : {
      25          20 : }
      26             : 
      27        2008 : bool AddressIndex::DB::WriteBatch(const std::vector<CAddressIndexEntry>& address_entries,
      28             :                                   const std::vector<CAddressUnspentIndexEntry>& unspent_entries)
      29             : {
      30        2008 :     CDBBatch batch(*this);
      31             : 
      32             :     // Write address transaction history
      33        4295 :     for (const auto& [key, value] : address_entries) {
      34        2287 :         batch.Write(std::make_pair(DB_ADDRESSINDEX, key), value);
      35             :     }
      36             : 
      37             :     // Write address unspent outputs (handles both adds and deletes)
      38        6582 :     for (const auto& [key, value] : unspent_entries) {
      39        2287 :         if (value.IsNull()) {
      40             :             // Null value means delete entry
      41          96 :             batch.Erase(std::make_pair(DB_ADDRESSUNSPENTINDEX, key));
      42          96 :         } else {
      43        2191 :             batch.Write(std::make_pair(DB_ADDRESSUNSPENTINDEX, key), value);
      44             :         }
      45             :     }
      46             : 
      47        2008 :     return CDBWrapper::WriteBatch(batch);
      48        2008 : }
      49             : 
      50          32 : bool AddressIndex::DB::ReadAddressIndex(const uint160& address_hash, const AddressType type,
      51             :                                         std::vector<CAddressIndexEntry>& entries, const int32_t start, const int32_t end)
      52             : {
      53          32 :     std::unique_ptr<CDBIterator> pcursor(NewIterator());
      54             : 
      55          32 :     if (start > 0 && end > 0) {
      56           4 :         pcursor->Seek(std::make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, address_hash, start)));
      57           4 :     } else {
      58          28 :         pcursor->Seek(std::make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, address_hash)));
      59             :     }
      60             : 
      61         320 :     while (pcursor->Valid()) {
      62         320 :         std::pair<uint8_t, CAddressIndexKey> key;
      63         626 :         if (pcursor->GetKey(key) && key.first == DB_ADDRESSINDEX && key.second.m_address_type == type &&
      64         306 :             key.second.m_address_bytes == address_hash) {
      65         292 :             if (end > 0 && key.second.m_block_height > end) {
      66           4 :                 break;
      67             :             }
      68             :             CAmount value;
      69         288 :             if (pcursor->GetValue(value)) {
      70         288 :                 entries.emplace_back(key.second, value);
      71         288 :                 pcursor->Next();
      72         288 :             } else {
      73           0 :                 return error("failed to get address index value");
      74             :             }
      75         288 :         } else {
      76          28 :             break;
      77             :         }
      78             :     }
      79             : 
      80          32 :     return true;
      81          32 : }
      82             : 
      83           8 : bool AddressIndex::DB::ReadAddressUnspentIndex(const uint160& address_hash, const AddressType type,
      84             :                                                std::vector<CAddressUnspentIndexEntry>& entries, const bool height_sort)
      85             : {
      86           8 :     std::unique_ptr<CDBIterator> pcursor(NewIterator());
      87             : 
      88           8 :     pcursor->Seek(std::make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, address_hash)));
      89             : 
      90          20 :     while (pcursor->Valid()) {
      91          20 :         std::pair<uint8_t, CAddressUnspentKey> key;
      92          38 :         if (pcursor->GetKey(key) && key.first == DB_ADDRESSUNSPENTINDEX && key.second.m_address_type == type &&
      93          18 :             key.second.m_address_bytes == address_hash) {
      94          12 :             CAddressUnspentValue value;
      95          12 :             if (pcursor->GetValue(value)) {
      96          12 :                 entries.emplace_back(key.second, value);
      97          12 :                 pcursor->Next();
      98          12 :             } else {
      99           0 :                 return error("failed to get address unspent value");
     100             :             }
     101          12 :         } else {
     102           8 :             break;
     103             :         }
     104             :     }
     105             : 
     106           8 :     if (height_sort) {
     107           8 :         std::sort(entries.begin(), entries.end(),
     108           5 :                   [](const CAddressUnspentIndexEntry& a, const CAddressUnspentIndexEntry& b) {
     109           5 :                       return a.second.m_block_height < b.second.m_block_height;
     110             :                   });
     111           8 :     }
     112             : 
     113           8 :     return true;
     114           8 : }
     115             : 
     116           0 : bool AddressIndex::DB::EraseAddressIndex(const std::vector<CAddressIndexEntry>& entries)
     117             : {
     118           0 :     CDBBatch batch(*this);
     119             : 
     120           0 :     for (const auto& [key, _] : entries) {
     121           0 :         batch.Erase(std::make_pair(DB_ADDRESSINDEX, key));
     122             :     }
     123             : 
     124           0 :     return CDBWrapper::WriteBatch(batch);
     125           0 : }
     126             : 
     127           0 : bool AddressIndex::DB::UpdateAddressUnspentIndex(const std::vector<CAddressUnspentIndexEntry>& entries)
     128             : {
     129           0 :     CDBBatch batch(*this);
     130             : 
     131           0 :     for (const auto& [key, value] : entries) {
     132           0 :         if (value.IsNull()) {
     133           0 :             batch.Erase(std::make_pair(DB_ADDRESSUNSPENTINDEX, key));
     134           0 :         } else {
     135           0 :             batch.Write(std::make_pair(DB_ADDRESSUNSPENTINDEX, key), value);
     136             :         }
     137             :     }
     138             : 
     139           0 :     return CDBWrapper::WriteBatch(batch);
     140           0 : }
     141             : 
     142           6 : bool AddressIndex::DB::RewindBatch(const std::vector<CAddressIndexEntry>& address_entries,
     143             :                                    const std::vector<CAddressUnspentIndexEntry>& unspent_entries)
     144             : {
     145           6 :     CDBBatch batch(*this);
     146             : 
     147          30 :     for (const auto& [key, _] : address_entries) {
     148          24 :         batch.Erase(std::make_pair(DB_ADDRESSINDEX, key));
     149             :     }
     150             : 
     151          54 :     for (const auto& [key, value] : unspent_entries) {
     152          24 :         if (value.IsNull()) {
     153          18 :             batch.Erase(std::make_pair(DB_ADDRESSUNSPENTINDEX, key));
     154          18 :         } else {
     155           6 :             batch.Write(std::make_pair(DB_ADDRESSUNSPENTINDEX, key), value);
     156             :         }
     157             :     }
     158             : 
     159           6 :     return CDBWrapper::WriteBatch(batch);
     160           6 : }
     161             : 
     162          20 : AddressIndex::AddressIndex(size_t n_cache_size, bool f_memory, bool f_wipe) :
     163          10 :     m_db(std::make_unique<AddressIndex::DB>(n_cache_size, f_memory, f_wipe))
     164          20 : {
     165          20 : }
     166             : 
     167          20 : AddressIndex::~AddressIndex() = default;
     168             : 
     169        2016 : bool AddressIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
     170             : {
     171             :     // Skip genesis block (no inputs to index)
     172        2016 :     if (pindex->nHeight == 0) {
     173           8 :         return true;
     174             :     }
     175             : 
     176             :     // Read undo data for this block to get information about spent outputs
     177        2008 :     CBlockUndo blockundo;
     178        2008 :     if (!node::UndoReadFromDisk(blockundo, pindex)) {
     179           0 :         return error("%s: Failed to read undo data for block %s at height %d", __func__,
     180           0 :                      pindex->GetBlockHash().ToString(), pindex->nHeight);
     181             :     }
     182             : 
     183        2008 :     std::vector<CAddressIndexEntry> addressIndex;
     184        2008 :     std::vector<CAddressUnspentIndexEntry> addressUnspentIndex;
     185             : 
     186             :     // Process each non-coinbase transaction
     187             :     // blockundo.vtxundo[i] corresponds to block.vtx[i+1] (coinbase is skipped in undo data)
     188        2008 :     if (blockundo.vtxundo.size() != block.vtx.size() - 1) {
     189           0 :         return error("%s: Undo data size mismatch for block %s (expected %zu, got %zu)", __func__,
     190           0 :                      pindex->GetBlockHash().ToString(), block.vtx.size() - 1, blockundo.vtxundo.size());
     191             :     }
     192             : 
     193        2101 :     for (size_t i = 0; i < blockundo.vtxundo.size(); i++) {
     194          93 :         const CTransactionRef& tx = block.vtx[i + 1]; // +1 to skip coinbase
     195          93 :         const CTxUndo& txundo = blockundo.vtxundo[i];
     196          93 :         const uint256 txhash = tx->GetHash();
     197             : 
     198             :         // Verify undo data matches transaction
     199          93 :         if (tx->vin.size() != txundo.vprevout.size()) {
     200           0 :             return error("%s: Undo data mismatch for tx %s", __func__, txhash.ToString());
     201             :         }
     202             : 
     203             :         // Process inputs (spending activity)
     204         189 :         for (size_t j = 0; j < tx->vin.size(); j++) {
     205          96 :             const CTxIn& input = tx->vin[j];
     206          96 :             const Coin& coin = txundo.vprevout[j];
     207          96 :             const CTxOut& prevout = coin.out;
     208             : 
     209          96 :             AddressType address_type{AddressType::UNKNOWN};
     210          96 :             uint160 address_bytes;
     211          96 :             if (!AddressBytesFromScript(prevout.scriptPubKey, address_type, address_bytes)) {
     212           0 :                 continue;
     213             :             }
     214             : 
     215             :             // Record spending activity
     216          96 :             addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, j, true),
     217          96 :                                       prevout.nValue * -1);
     218             : 
     219             :             // Remove from unspent index
     220         288 :             addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, input.prevout.hash,
     221          96 :                                                                 input.prevout.n),
     222          96 :                                              CAddressUnspentValue() // Null value means delete
     223             :             );
     224          96 :         }
     225             : 
     226             :         // Process outputs (receiving activity)
     227         276 :         for (size_t k = 0; k < tx->vout.size(); k++) {
     228         183 :             const CTxOut& out = tx->vout[k];
     229             : 
     230         183 :             AddressType address_type{AddressType::UNKNOWN};
     231         183 :             uint160 address_bytes;
     232         183 :             if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
     233           0 :                 continue;
     234             :             }
     235             : 
     236             :             // Record receiving activity
     237         183 :             addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, k, false),
     238         183 :                                       out.nValue);
     239             : 
     240             :             // Add to unspent index
     241         366 :             addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, txhash, k),
     242         183 :                                              CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight));
     243         183 :         }
     244          93 :     }
     245             : 
     246             :     // Also process coinbase outputs (receiving activity only)
     247        2008 :     const CTransactionRef& coinbase = block.vtx[0];
     248        2008 :     const uint256 coinbase_hash = coinbase->GetHash();
     249        4016 :     for (size_t k = 0; k < coinbase->vout.size(); k++) {
     250        2008 :         const CTxOut& out = coinbase->vout[k];
     251             : 
     252        2008 :         AddressType address_type{AddressType::UNKNOWN};
     253        2008 :         uint160 address_bytes;
     254        2008 :         if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
     255           0 :             continue;
     256             :         }
     257             : 
     258             :         // Record receiving activity for coinbase
     259        2008 :         addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, 0, coinbase_hash, k, false),
     260        2008 :                                   out.nValue);
     261             : 
     262             :         // Add coinbase outputs to unspent index
     263        4016 :         addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, coinbase_hash, k),
     264        2008 :                                          CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight));
     265        2008 :     }
     266             : 
     267        2008 :     return m_db->WriteBatch(addressIndex, addressUnspentIndex);
     268        2016 : }
     269             : 
     270           6 : bool AddressIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
     271             : {
     272           6 :     assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
     273             : 
     274             :     // Rewind the unspent index by processing blocks in reverse
     275             :     // We need to undo all operations from current_tip back to (but not including) new_tip
     276          12 :     for (const CBlockIndex* pindex = current_tip; pindex != new_tip; pindex = pindex->pprev) {
     277           6 :         CBlock block;
     278           6 :         if (!node::ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     279           0 :             return error("%s: Failed to read block %s from disk during rewind", __func__,
     280           0 :                          pindex->GetBlockHash().ToString());
     281             :         }
     282             : 
     283           6 :         CBlockUndo blockundo;
     284           6 :         if (pindex->nHeight > 0 && !node::UndoReadFromDisk(blockundo, pindex)) {
     285           0 :             return error("%s: Failed to read undo data for block %s during rewind", __func__,
     286           0 :                          pindex->GetBlockHash().ToString());
     287             :         }
     288             : 
     289           6 :         std::vector<CAddressIndexEntry> addressIndex;
     290           6 :         std::vector<CAddressUnspentIndexEntry> addressUnspentIndex;
     291             : 
     292             :         // Process transactions in reverse to undo them (matching DisconnectBlock order).
     293             :         // This is critical: for intra-block spends (tx1 creates output, tx2 spends it),
     294             :         // reverse order ensures spends are undone before outputs, preventing phantom UTXOs.
     295             :         // blockundo.vtxundo[i] corresponds to block.vtx[i+1] (coinbase skipped)
     296           6 :         if (blockundo.vtxundo.size() != block.vtx.size() - 1) {
     297           0 :             return error("%s: Undo data size mismatch for block %s (expected %zu, got %zu)", __func__,
     298           0 :                          pindex->GetBlockHash().ToString(), block.vtx.size() - 1, blockundo.vtxundo.size());
     299             :         }
     300             : 
     301          12 :         for (size_t i = blockundo.vtxundo.size(); i-- > 0;) {
     302           6 :             const CTransactionRef& tx = block.vtx[i + 1];
     303           6 :             const CTxUndo& txundo = blockundo.vtxundo[i];
     304           6 :             const uint256 txhash = tx->GetHash();
     305             : 
     306             :             // Undo outputs (remove from unspent index and transaction history)
     307          18 :             for (size_t k = 0; k < tx->vout.size(); k++) {
     308          12 :                 const CTxOut& out = tx->vout[k];
     309             : 
     310          12 :                 AddressType address_type{AddressType::UNKNOWN};
     311          12 :                 uint160 address_bytes;
     312             : 
     313          12 :                 if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
     314           0 :                     continue;
     315             :                 }
     316             : 
     317             :                 // Remove receiving activity from history
     318          24 :                 addressIndex.push_back(std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight,
     319          12 :                                                                        i + 1, txhash, k, false),
     320          12 :                                                       out.nValue));
     321             : 
     322             :                 // Remove from unspent index (mark for deletion)
     323          24 :                 addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(address_type, address_bytes, txhash, k),
     324          12 :                                                              CAddressUnspentValue() // null value signals deletion
     325             :                                                              ));
     326          12 :             }
     327             : 
     328             :             // Undo inputs (restore to unspent index, remove spending from history)
     329           6 :             if (tx->vin.size() != txundo.vprevout.size()) {
     330           0 :                 return error("%s: Undo data mismatch for tx %s", __func__, txhash.ToString());
     331             :             }
     332             : 
     333          12 :             for (size_t j = 0; j < tx->vin.size(); j++) {
     334           6 :                 const CTxIn& input = tx->vin[j];
     335           6 :                 const Coin& coin = txundo.vprevout[j];
     336           6 :                 const CTxOut& prevout = coin.out;
     337             : 
     338           6 :                 AddressType address_type{AddressType::UNKNOWN};
     339           6 :                 uint160 address_bytes;
     340             : 
     341           6 :                 if (!AddressBytesFromScript(prevout.scriptPubKey, address_type, address_bytes)) {
     342           0 :                     continue;
     343             :                 }
     344             : 
     345             :                 // Remove spending activity from history
     346           6 :                 addressIndex.push_back(
     347           6 :                     std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, j, true),
     348           6 :                                    prevout.nValue * -1));
     349             : 
     350             :                 // Restore to unspent index
     351           6 :                 addressUnspentIndex.push_back(
     352          12 :                     std::make_pair(CAddressUnspentKey(address_type, address_bytes, input.prevout.hash, input.prevout.n),
     353           6 :                                    CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, coin.nHeight)));
     354           6 :             }
     355             :         }
     356             : 
     357             :         // Process coinbase outputs (remove from indices)
     358           6 :         if (!block.vtx.empty()) {
     359           6 :             const CTransactionRef& coinbase_tx = block.vtx[0];
     360           6 :             const uint256 cb_hash = coinbase_tx->GetHash();
     361             : 
     362          12 :             for (size_t k = 0; k < coinbase_tx->vout.size(); k++) {
     363           6 :                 const CTxOut& out = coinbase_tx->vout[k];
     364             : 
     365           6 :                 AddressType address_type{AddressType::UNKNOWN};
     366           6 :                 uint160 address_bytes;
     367             : 
     368           6 :                 if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
     369           0 :                     continue;
     370             :                 }
     371             : 
     372             :                 // Remove coinbase receiving activity
     373           6 :                 addressIndex.push_back(
     374           6 :                     std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, 0, cb_hash, k, false),
     375           6 :                                    out.nValue));
     376             : 
     377             :                 // Remove from unspent index
     378          12 :                 addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(address_type, address_bytes, cb_hash, k),
     379           6 :                                                              CAddressUnspentValue() // null value signals deletion
     380             :                                                              ));
     381           6 :             }
     382           6 :         }
     383             : 
     384             :         // Apply both rewind updates in a single batch to avoid leaving the index half-rewound.
     385           6 :         if (!m_db->RewindBatch(addressIndex, addressUnspentIndex)) {
     386           0 :             return error("%s: Failed to apply address index rewind batch", __func__);
     387             :         }
     388           6 :     }
     389             : 
     390             :     // Call base class Rewind to update the best block pointer
     391           6 :     return BaseIndex::Rewind(current_tip, new_tip);
     392           6 : }
     393             : 
     394          70 : BaseIndex::DB& AddressIndex::GetDB() const { return *m_db; }
     395             : 
     396          32 : bool AddressIndex::GetAddressIndex(const uint160& address_hash, const AddressType type,
     397             :                                    std::vector<CAddressIndexEntry>& entries, const int32_t start, const int32_t end) const
     398             : {
     399          32 :     return m_db->ReadAddressIndex(address_hash, type, entries, start, end);
     400             : }
     401             : 
     402           8 : bool AddressIndex::GetAddressUnspentIndex(const uint160& address_hash, const AddressType type,
     403             :                                           std::vector<CAddressUnspentIndexEntry>& entries, const bool height_sort) const
     404             : {
     405           8 :     return m_db->ReadAddressUnspentIndex(address_hash, type, entries, height_sort);
     406             : }

Generated by: LCOV version 1.16