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 0 : AddressIndex::DB::DB(size_t n_cache_size, bool f_memory, bool f_wipe) :
23 0 : BaseIndex::DB(gArgs.GetDataDirNet() / "indexes" / "addressindex", n_cache_size, f_memory, f_wipe)
24 0 : {
25 0 : }
26 :
27 0 : bool AddressIndex::DB::WriteBatch(const std::vector<CAddressIndexEntry>& address_entries,
28 : const std::vector<CAddressUnspentIndexEntry>& unspent_entries)
29 : {
30 0 : CDBBatch batch(*this);
31 :
32 : // Write address transaction history
33 0 : for (const auto& [key, value] : address_entries) {
34 0 : batch.Write(std::make_pair(DB_ADDRESSINDEX, key), value);
35 : }
36 :
37 : // Write address unspent outputs (handles both adds and deletes)
38 0 : for (const auto& [key, value] : unspent_entries) {
39 0 : if (value.IsNull()) {
40 : // Null value means delete entry
41 0 : batch.Erase(std::make_pair(DB_ADDRESSUNSPENTINDEX, key));
42 0 : } else {
43 0 : batch.Write(std::make_pair(DB_ADDRESSUNSPENTINDEX, key), value);
44 : }
45 : }
46 :
47 0 : return CDBWrapper::WriteBatch(batch);
48 0 : }
49 :
50 0 : 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 0 : std::unique_ptr<CDBIterator> pcursor(NewIterator());
54 :
55 0 : if (start > 0 && end > 0) {
56 0 : pcursor->Seek(std::make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, address_hash, start)));
57 0 : } else {
58 0 : pcursor->Seek(std::make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, address_hash)));
59 : }
60 :
61 0 : while (pcursor->Valid()) {
62 0 : std::pair<uint8_t, CAddressIndexKey> key;
63 0 : if (pcursor->GetKey(key) && key.first == DB_ADDRESSINDEX && key.second.m_address_type == type &&
64 0 : key.second.m_address_bytes == address_hash) {
65 0 : if (end > 0 && key.second.m_block_height > end) {
66 0 : break;
67 : }
68 : CAmount value;
69 0 : if (pcursor->GetValue(value)) {
70 0 : entries.emplace_back(key.second, value);
71 0 : pcursor->Next();
72 0 : } else {
73 0 : return error("failed to get address index value");
74 : }
75 0 : } else {
76 0 : break;
77 : }
78 : }
79 :
80 0 : return true;
81 0 : }
82 :
83 0 : bool AddressIndex::DB::ReadAddressUnspentIndex(const uint160& address_hash, const AddressType type,
84 : std::vector<CAddressUnspentIndexEntry>& entries, const bool height_sort)
85 : {
86 0 : std::unique_ptr<CDBIterator> pcursor(NewIterator());
87 :
88 0 : pcursor->Seek(std::make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, address_hash)));
89 :
90 0 : while (pcursor->Valid()) {
91 0 : std::pair<uint8_t, CAddressUnspentKey> key;
92 0 : if (pcursor->GetKey(key) && key.first == DB_ADDRESSUNSPENTINDEX && key.second.m_address_type == type &&
93 0 : key.second.m_address_bytes == address_hash) {
94 0 : CAddressUnspentValue value;
95 0 : if (pcursor->GetValue(value)) {
96 0 : entries.emplace_back(key.second, value);
97 0 : pcursor->Next();
98 0 : } else {
99 0 : return error("failed to get address unspent value");
100 : }
101 0 : } else {
102 0 : break;
103 : }
104 : }
105 :
106 0 : if (height_sort) {
107 0 : std::sort(entries.begin(), entries.end(),
108 0 : [](const CAddressUnspentIndexEntry& a, const CAddressUnspentIndexEntry& b) {
109 0 : return a.second.m_block_height < b.second.m_block_height;
110 : });
111 0 : }
112 :
113 0 : return true;
114 0 : }
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 0 : bool AddressIndex::DB::RewindBatch(const std::vector<CAddressIndexEntry>& address_entries,
143 : const std::vector<CAddressUnspentIndexEntry>& unspent_entries)
144 : {
145 0 : CDBBatch batch(*this);
146 :
147 0 : for (const auto& [key, _] : address_entries) {
148 0 : batch.Erase(std::make_pair(DB_ADDRESSINDEX, key));
149 : }
150 :
151 0 : for (const auto& [key, value] : unspent_entries) {
152 0 : if (value.IsNull()) {
153 0 : batch.Erase(std::make_pair(DB_ADDRESSUNSPENTINDEX, key));
154 0 : } else {
155 0 : batch.Write(std::make_pair(DB_ADDRESSUNSPENTINDEX, key), value);
156 : }
157 : }
158 :
159 0 : return CDBWrapper::WriteBatch(batch);
160 0 : }
161 :
162 0 : AddressIndex::AddressIndex(size_t n_cache_size, bool f_memory, bool f_wipe) :
163 0 : m_db(std::make_unique<AddressIndex::DB>(n_cache_size, f_memory, f_wipe))
164 0 : {
165 0 : }
166 :
167 0 : AddressIndex::~AddressIndex() = default;
168 :
169 0 : bool AddressIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
170 : {
171 : // Skip genesis block (no inputs to index)
172 0 : if (pindex->nHeight == 0) {
173 0 : return true;
174 : }
175 :
176 : // Read undo data for this block to get information about spent outputs
177 0 : CBlockUndo blockundo;
178 0 : 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 0 : std::vector<CAddressIndexEntry> addressIndex;
184 0 : 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 0 : 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 0 : for (size_t i = 0; i < blockundo.vtxundo.size(); i++) {
194 0 : const CTransactionRef& tx = block.vtx[i + 1]; // +1 to skip coinbase
195 0 : const CTxUndo& txundo = blockundo.vtxundo[i];
196 0 : const uint256 txhash = tx->GetHash();
197 :
198 : // Verify undo data matches transaction
199 0 : 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 0 : for (size_t j = 0; j < tx->vin.size(); j++) {
205 0 : const CTxIn& input = tx->vin[j];
206 0 : const Coin& coin = txundo.vprevout[j];
207 0 : const CTxOut& prevout = coin.out;
208 :
209 0 : AddressType address_type{AddressType::UNKNOWN};
210 0 : uint160 address_bytes;
211 0 : if (!AddressBytesFromScript(prevout.scriptPubKey, address_type, address_bytes)) {
212 0 : continue;
213 : }
214 :
215 : // Record spending activity
216 0 : addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, j, true),
217 0 : prevout.nValue * -1);
218 :
219 : // Remove from unspent index
220 0 : addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, input.prevout.hash,
221 0 : input.prevout.n),
222 0 : CAddressUnspentValue() // Null value means delete
223 : );
224 0 : }
225 :
226 : // Process outputs (receiving activity)
227 0 : for (size_t k = 0; k < tx->vout.size(); k++) {
228 0 : const CTxOut& out = tx->vout[k];
229 :
230 0 : AddressType address_type{AddressType::UNKNOWN};
231 0 : uint160 address_bytes;
232 0 : if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
233 0 : continue;
234 : }
235 :
236 : // Record receiving activity
237 0 : addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, k, false),
238 0 : out.nValue);
239 :
240 : // Add to unspent index
241 0 : addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, txhash, k),
242 0 : CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight));
243 0 : }
244 0 : }
245 :
246 : // Also process coinbase outputs (receiving activity only)
247 0 : const CTransactionRef& coinbase = block.vtx[0];
248 0 : const uint256 coinbase_hash = coinbase->GetHash();
249 0 : for (size_t k = 0; k < coinbase->vout.size(); k++) {
250 0 : const CTxOut& out = coinbase->vout[k];
251 :
252 0 : AddressType address_type{AddressType::UNKNOWN};
253 0 : uint160 address_bytes;
254 0 : if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
255 0 : continue;
256 : }
257 :
258 : // Record receiving activity for coinbase
259 0 : addressIndex.emplace_back(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, 0, coinbase_hash, k, false),
260 0 : out.nValue);
261 :
262 : // Add coinbase outputs to unspent index
263 0 : addressUnspentIndex.emplace_back(CAddressUnspentKey(address_type, address_bytes, coinbase_hash, k),
264 0 : CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight));
265 0 : }
266 :
267 0 : return m_db->WriteBatch(addressIndex, addressUnspentIndex);
268 0 : }
269 :
270 0 : bool AddressIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
271 : {
272 0 : 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 0 : for (const CBlockIndex* pindex = current_tip; pindex != new_tip; pindex = pindex->pprev) {
277 0 : CBlock block;
278 0 : 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 0 : CBlockUndo blockundo;
284 0 : 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 0 : std::vector<CAddressIndexEntry> addressIndex;
290 0 : 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 0 : 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 0 : for (size_t i = blockundo.vtxundo.size(); i-- > 0;) {
302 0 : const CTransactionRef& tx = block.vtx[i + 1];
303 0 : const CTxUndo& txundo = blockundo.vtxundo[i];
304 0 : const uint256 txhash = tx->GetHash();
305 :
306 : // Undo outputs (remove from unspent index and transaction history)
307 0 : for (size_t k = 0; k < tx->vout.size(); k++) {
308 0 : const CTxOut& out = tx->vout[k];
309 :
310 0 : AddressType address_type{AddressType::UNKNOWN};
311 0 : uint160 address_bytes;
312 :
313 0 : if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
314 0 : continue;
315 : }
316 :
317 : // Remove receiving activity from history
318 0 : addressIndex.push_back(std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight,
319 0 : i + 1, txhash, k, false),
320 0 : out.nValue));
321 :
322 : // Remove from unspent index (mark for deletion)
323 0 : addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(address_type, address_bytes, txhash, k),
324 0 : CAddressUnspentValue() // null value signals deletion
325 : ));
326 0 : }
327 :
328 : // Undo inputs (restore to unspent index, remove spending from history)
329 0 : if (tx->vin.size() != txundo.vprevout.size()) {
330 0 : return error("%s: Undo data mismatch for tx %s", __func__, txhash.ToString());
331 : }
332 :
333 0 : for (size_t j = 0; j < tx->vin.size(); j++) {
334 0 : const CTxIn& input = tx->vin[j];
335 0 : const Coin& coin = txundo.vprevout[j];
336 0 : const CTxOut& prevout = coin.out;
337 :
338 0 : AddressType address_type{AddressType::UNKNOWN};
339 0 : uint160 address_bytes;
340 :
341 0 : if (!AddressBytesFromScript(prevout.scriptPubKey, address_type, address_bytes)) {
342 0 : continue;
343 : }
344 :
345 : // Remove spending activity from history
346 0 : addressIndex.push_back(
347 0 : std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, i + 1, txhash, j, true),
348 0 : prevout.nValue * -1));
349 :
350 : // Restore to unspent index
351 0 : addressUnspentIndex.push_back(
352 0 : std::make_pair(CAddressUnspentKey(address_type, address_bytes, input.prevout.hash, input.prevout.n),
353 0 : CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, coin.nHeight)));
354 0 : }
355 : }
356 :
357 : // Process coinbase outputs (remove from indices)
358 0 : if (!block.vtx.empty()) {
359 0 : const CTransactionRef& coinbase_tx = block.vtx[0];
360 0 : const uint256 cb_hash = coinbase_tx->GetHash();
361 :
362 0 : for (size_t k = 0; k < coinbase_tx->vout.size(); k++) {
363 0 : const CTxOut& out = coinbase_tx->vout[k];
364 :
365 0 : AddressType address_type{AddressType::UNKNOWN};
366 0 : uint160 address_bytes;
367 :
368 0 : if (!AddressBytesFromScript(out.scriptPubKey, address_type, address_bytes)) {
369 0 : continue;
370 : }
371 :
372 : // Remove coinbase receiving activity
373 0 : addressIndex.push_back(
374 0 : std::make_pair(CAddressIndexKey(address_type, address_bytes, pindex->nHeight, 0, cb_hash, k, false),
375 0 : out.nValue));
376 :
377 : // Remove from unspent index
378 0 : addressUnspentIndex.push_back(std::make_pair(CAddressUnspentKey(address_type, address_bytes, cb_hash, k),
379 0 : CAddressUnspentValue() // null value signals deletion
380 : ));
381 0 : }
382 0 : }
383 :
384 : // Apply both rewind updates in a single batch to avoid leaving the index half-rewound.
385 0 : if (!m_db->RewindBatch(addressIndex, addressUnspentIndex)) {
386 0 : return error("%s: Failed to apply address index rewind batch", __func__);
387 : }
388 0 : }
389 :
390 : // Call base class Rewind to update the best block pointer
391 0 : return BaseIndex::Rewind(current_tip, new_tip);
392 0 : }
393 :
394 0 : BaseIndex::DB& AddressIndex::GetDB() const { return *m_db; }
395 :
396 0 : 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 0 : return m_db->ReadAddressIndex(address_hash, type, entries, start, end);
400 : }
401 :
402 0 : bool AddressIndex::GetAddressUnspentIndex(const uint160& address_hash, const AddressType type,
403 : std::vector<CAddressUnspentIndexEntry>& entries, const bool height_sort) const
404 : {
405 0 : return m_db->ReadAddressUnspentIndex(address_hash, type, entries, height_sort);
406 : }
|