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 : }
|