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