Line data Source code
1 : // Copyright (c) 2009-2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2021 The Bitcoin Core developers
3 : // Copyright (c) 2025 The Dash Core developers
4 : // Distributed under the MIT software license, see the accompanying
5 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 :
7 : #include <txdb.h>
8 :
9 : #include <chain.h>
10 : #include <index/addressindex_types.h>
11 : #include <index/spentindex_types.h>
12 : #include <index/timestampindex_types.h>
13 : #include <logging.h>
14 : #include <pow.h>
15 : #include <primitives/block.h>
16 : #include <primitives/transaction.h>
17 : #include <random.h>
18 : #include <shutdown.h>
19 : #include <uint256.h>
20 : #include <util/system.h>
21 : #include <util/translation.h>
22 : #include <util/vector.h>
23 :
24 : #include <cassert>
25 : #include <cstdlib>
26 : #include <iterator>
27 : #include <optional>
28 :
29 : static constexpr uint8_t DB_COIN{'C'};
30 : static constexpr uint8_t DB_BLOCK_FILES{'f'};
31 : static constexpr uint8_t DB_BLOCK_INDEX{'b'};
32 :
33 : static constexpr uint8_t DB_BEST_BLOCK{'B'};
34 : static constexpr uint8_t DB_HEAD_BLOCKS{'H'};
35 : static constexpr uint8_t DB_FLAG{'F'};
36 : static constexpr uint8_t DB_REINDEX_FLAG{'R'};
37 : static constexpr uint8_t DB_LAST_BLOCK{'l'};
38 :
39 : // Keys used in previous version that might still be found in the DB:
40 : static constexpr uint8_t DB_COINS{'c'};
41 : // CBlockTreeDB::DB_TXINDEX_BLOCK{'T'};
42 : // CBlockTreeDB::DB_TXINDEX{'t'}
43 : // CBlockTreeDB::ReadFlag("txindex")
44 :
45 : // Old synchronous index keys (deprecated):
46 : static constexpr uint8_t DB_ADDRESSINDEX{'a'};
47 : static constexpr uint8_t DB_ADDRESSUNSPENTINDEX{'u'};
48 : static constexpr uint8_t DB_SPENTINDEX{'p'};
49 : static constexpr uint8_t DB_TIMESTAMPINDEX{'s'};
50 :
51 3049 : bool CCoinsViewDB::NeedsUpgrade()
52 : {
53 3049 : std::unique_ptr<CDBIterator> cursor{m_db->NewIterator()};
54 : // DB_COINS was deprecated in v0.15.0, commit
55 : // 1088b02f0ccd7358d2b7076bb9e122d59d502d02
56 3049 : cursor->Seek(std::make_pair(DB_COINS, uint256{}));
57 3049 : return cursor->Valid();
58 3049 : }
59 :
60 : namespace {
61 :
62 : struct CoinEntry {
63 : COutPoint* outpoint;
64 : uint8_t key;
65 32679816 : explicit CoinEntry(const COutPoint* ptr) : outpoint(const_cast<COutPoint*>(ptr)), key(DB_COIN) {}
66 :
67 49014582 : SERIALIZE_METHODS(CoinEntry, obj) { READWRITE(obj.key, obj.outpoint->hash, VARINT(obj.outpoint->n)); }
68 : };
69 :
70 : } // namespace
71 :
72 6132 : CCoinsViewDB::CCoinsViewDB(fs::path ldb_path, size_t nCacheSize, bool fMemory, bool fWipe) :
73 3066 : m_db(std::make_unique<CDBWrapper>(ldb_path, nCacheSize, fMemory, fWipe, true)),
74 3064 : m_ldb_path(ldb_path),
75 9196 : m_is_memory(fMemory) { }
76 :
77 23 : void CCoinsViewDB::ResizeCache(size_t new_cache_size)
78 : {
79 : // We can't do this operation with an in-memory DB since we'll lose all the coins upon
80 : // reset.
81 23 : if (!m_is_memory) {
82 : // Have to do a reset first to get the original `m_db` state to release its
83 : // filesystem lock.
84 0 : m_db.reset();
85 0 : m_db = std::make_unique<CDBWrapper>(
86 0 : m_ldb_path, new_cache_size, m_is_memory, /*fWipe=*/false, /*obfuscate=*/true);
87 0 : }
88 23 : }
89 :
90 15361220 : bool CCoinsViewDB::GetCoin(const COutPoint &outpoint, Coin &coin) const {
91 15361220 : return m_db->Read(CoinEntry(&outpoint), coin);
92 : }
93 :
94 44 : bool CCoinsViewDB::HaveCoin(const COutPoint &outpoint) const {
95 44 : return m_db->Exists(CoinEntry(&outpoint));
96 : }
97 :
98 16521 : uint256 CCoinsViewDB::GetBestBlock() const {
99 16521 : uint256 hashBestChain;
100 16521 : if (!m_db->Read(DB_BEST_BLOCK, hashBestChain))
101 3979 : return uint256();
102 12542 : return hashBestChain;
103 16521 : }
104 :
105 3954 : std::vector<uint256> CCoinsViewDB::GetHeadBlocks() const {
106 3954 : std::vector<uint256> vhashHeadBlocks;
107 3954 : if (!m_db->Read(DB_HEAD_BLOCKS, vhashHeadBlocks)) {
108 3954 : return std::vector<uint256>();
109 : }
110 0 : return vhashHeadBlocks;
111 3954 : }
112 :
113 7676 : bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock, bool erase) {
114 7676 : CDBBatch batch(*m_db);
115 7676 : size_t count = 0;
116 7676 : size_t changed = 0;
117 7676 : size_t batch_size = (size_t)gArgs.GetIntArg("-dbbatchsize", nDefaultDbBatchSize);
118 7676 : int crash_simulate = gArgs.GetIntArg("-dbcrashratio", 0);
119 7676 : assert(!hashBlock.IsNull());
120 :
121 7676 : uint256 old_tip = GetBestBlock();
122 7676 : if (old_tip.IsNull()) {
123 : // We may be in the middle of replaying.
124 905 : std::vector<uint256> old_heads = GetHeadBlocks();
125 905 : if (old_heads.size() == 2) {
126 0 : if (old_heads[0] != hashBlock) {
127 0 : LogPrintLevel(BCLog::COINDB, BCLog::Level::Error, "The coins database detected an inconsistent state, likely due to a previous crash or shutdown. You will need to restart bitcoind with the -reindex-chainstate or -reindex configuration option.\n");
128 0 : }
129 0 : assert(old_heads[0] == hashBlock);
130 0 : old_tip = old_heads[1];
131 0 : }
132 905 : }
133 :
134 : // In the first batch, mark the database as being in the middle of a
135 : // transition from old_tip to hashBlock.
136 : // A vector is used for future extensibility, as we may want to support
137 : // interrupting after partial writes from multiple independent reorgs.
138 7676 : batch.Erase(DB_BEST_BLOCK);
139 7676 : batch.Write(DB_HEAD_BLOCKS, Vector(hashBlock, old_tip));
140 :
141 786829 : for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
142 779153 : if (it->second.flags & CCoinsCacheEntry::DIRTY) {
143 694614 : CoinEntry entry(&it->first);
144 694614 : if (it->second.coin.IsSpent())
145 39456 : batch.Erase(entry);
146 : else
147 655158 : batch.Write(entry, it->second.coin);
148 694614 : changed++;
149 694614 : }
150 779153 : count++;
151 779153 : it = erase ? mapCoins.erase(it) : std::next(it);
152 779153 : if (batch.SizeEstimate() > batch_size) {
153 0 : LogPrint(BCLog::COINDB, "Writing partial batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
154 0 : m_db->WriteBatch(batch);
155 0 : batch.Clear();
156 0 : if (crash_simulate) {
157 0 : static FastRandomContext rng;
158 0 : if (rng.randrange(crash_simulate) == 0) {
159 0 : LogPrintf("Simulating a crash. Goodbye.\n");
160 0 : _Exit(0);
161 : }
162 0 : }
163 0 : }
164 : }
165 :
166 : // In the last batch, mark the database as consistent with hashBlock again.
167 7676 : batch.Erase(DB_HEAD_BLOCKS);
168 7676 : batch.Write(DB_BEST_BLOCK, hashBlock);
169 :
170 7676 : LogPrint(BCLog::COINDB, "Writing final batch of %.2f MiB\n", batch.SizeEstimate() * (1.0 / 1048576.0));
171 7676 : bool ret = m_db->WriteBatch(batch);
172 7676 : LogPrint(BCLog::COINDB, "Committed %u changed transaction outputs (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
173 7676 : return ret;
174 7676 : }
175 :
176 111 : size_t CCoinsViewDB::EstimateSize() const
177 : {
178 111 : return m_db->EstimateSize(DB_COIN, uint8_t(DB_COIN + 1));
179 : }
180 :
181 6494 : CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(gArgs.GetDataDirNet() / "blocks" / "index", nCacheSize, fMemory, fWipe) {
182 6494 : }
183 :
184 5984 : bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {
185 5984 : return Read(std::make_pair(DB_BLOCK_FILES, nFile), info);
186 : }
187 :
188 130 : bool CBlockTreeDB::WriteReindexing(bool fReindexing) {
189 130 : if (fReindexing)
190 65 : return Write(DB_REINDEX_FLAG, uint8_t{'1'});
191 : else
192 65 : return Erase(DB_REINDEX_FLAG);
193 130 : }
194 :
195 2990 : void CBlockTreeDB::ReadReindexing(bool &fReindexing) {
196 2990 : fReindexing = Exists(DB_REINDEX_FLAG);
197 2990 : }
198 :
199 2992 : bool CBlockTreeDB::ReadLastBlockFile(int &nFile) {
200 2992 : return Read(DB_LAST_BLOCK, nFile);
201 : }
202 :
203 : /** Specialization of CCoinsViewCursor to iterate over a CCoinsViewDB */
204 : class CCoinsViewDBCursor: public CCoinsViewCursor
205 : {
206 : public:
207 : // Prefer using CCoinsViewDB::Cursor() since we want to perform some
208 : // cache warmup on instantiation.
209 5154 : CCoinsViewDBCursor(CDBIterator* pcursorIn, const uint256&hashBlockIn):
210 3436 : CCoinsViewCursor(hashBlockIn), pcursor(pcursorIn) {}
211 5154 : ~CCoinsViewDBCursor() = default;
212 :
213 : bool GetKey(COutPoint &key) const override;
214 : bool GetValue(Coin &coin) const override;
215 :
216 : bool Valid() const override;
217 : void Next() override;
218 :
219 : private:
220 : std::unique_ptr<CDBIterator> pcursor;
221 : std::pair<uint8_t, COutPoint> keyTmp;
222 :
223 : friend class CCoinsViewDB;
224 : };
225 :
226 1718 : std::unique_ptr<CCoinsViewCursor> CCoinsViewDB::Cursor() const
227 : {
228 1718 : auto i = std::make_unique<CCoinsViewDBCursor>(
229 1718 : const_cast<CDBWrapper&>(*m_db).NewIterator(), GetBestBlock());
230 : /* It seems that there are no "const iterators" for LevelDB. Since we
231 : only need read operations on it, use a const-cast to get around
232 : that restriction. */
233 1718 : i->pcursor->Seek(DB_COIN);
234 : // Cache key of first record
235 1718 : if (i->pcursor->Valid()) {
236 1714 : CoinEntry entry(&i->keyTmp.second);
237 1714 : i->pcursor->GetKey(entry);
238 1714 : i->keyTmp.first = entry.key;
239 1714 : } else {
240 4 : i->keyTmp.first = 0; // Make sure Valid() and GetKey() return false
241 : }
242 1718 : return i;
243 1718 : }
244 :
245 282316 : bool CCoinsViewDBCursor::GetKey(COutPoint &key) const
246 : {
247 : // Return cached key
248 282316 : if (keyTmp.first == DB_COIN) {
249 282316 : key = keyTmp.second;
250 282316 : return true;
251 : }
252 0 : return false;
253 282316 : }
254 :
255 282316 : bool CCoinsViewDBCursor::GetValue(Coin &coin) const
256 : {
257 282316 : return pcursor->GetValue(coin);
258 : }
259 :
260 284034 : bool CCoinsViewDBCursor::Valid() const
261 : {
262 284034 : return keyTmp.first == DB_COIN;
263 : }
264 :
265 282316 : void CCoinsViewDBCursor::Next()
266 : {
267 282316 : pcursor->Next();
268 282316 : CoinEntry entry(&keyTmp.second);
269 282316 : if (!pcursor->Valid() || !pcursor->GetKey(entry)) {
270 1714 : keyTmp.first = 0; // Invalidate cached key after last record so that Valid() and GetKey() return false
271 1714 : } else {
272 280602 : keyTmp.first = entry.key;
273 : }
274 282316 : }
275 :
276 7520 : bool CBlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*> >& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo) {
277 7520 : CDBBatch batch(*this);
278 11098 : for (std::vector<std::pair<int, const CBlockFileInfo*> >::const_iterator it=fileInfo.begin(); it != fileInfo.end(); it++) {
279 3578 : batch.Write(std::make_pair(DB_BLOCK_FILES, it->first), *it->second);
280 3578 : }
281 7520 : batch.Write(DB_LAST_BLOCK, nLastFile);
282 255323 : for (std::vector<const CBlockIndex*>::const_iterator it=blockinfo.begin(); it != blockinfo.end(); it++) {
283 247803 : batch.Write(std::make_pair(DB_BLOCK_INDEX, (*it)->GetBlockHash()), CDiskBlockIndex(*it));
284 247803 : }
285 7520 : return WriteBatch(batch, true);
286 7520 : }
287 :
288 0 : bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) {
289 0 : return Write(std::make_pair(DB_FLAG, name), fValue ? uint8_t{'1'} : uint8_t{'0'});
290 0 : }
291 :
292 3056 : bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) {
293 : uint8_t ch;
294 3056 : if (!Read(std::make_pair(DB_FLAG, name), ch))
295 3056 : return false;
296 0 : fValue = ch == uint8_t{'1'};
297 0 : return true;
298 3056 : }
299 :
300 2994 : bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex)
301 : {
302 2994 : AssertLockHeld(::cs_main);
303 2994 : std::unique_ptr<CDBIterator> pcursor(NewIterator());
304 2994 : pcursor->Seek(std::make_pair(DB_BLOCK_INDEX, uint256()));
305 :
306 : // Load m_block_index
307 327107 : while (pcursor->Valid()) {
308 326105 : if (ShutdownRequested()) return false;
309 326105 : std::pair<uint8_t, uint256> key;
310 326105 : if (pcursor->GetKey(key) && key.first == DB_BLOCK_INDEX) {
311 324113 : CDiskBlockIndex diskindex;
312 324113 : if (pcursor->GetValue(diskindex)) {
313 : // Construct block index object
314 324113 : CBlockIndex* pindexNew = insertBlockIndex(diskindex.ConstructBlockHash());
315 324113 : pindexNew->pprev = insertBlockIndex(diskindex.hashPrev);
316 324113 : pindexNew->nHeight = diskindex.nHeight;
317 324113 : pindexNew->nFile = diskindex.nFile;
318 324113 : pindexNew->nDataPos = diskindex.nDataPos;
319 324113 : pindexNew->nUndoPos = diskindex.nUndoPos;
320 324113 : pindexNew->nVersion = diskindex.nVersion;
321 324113 : pindexNew->hashMerkleRoot = diskindex.hashMerkleRoot;
322 324113 : pindexNew->nTime = diskindex.nTime;
323 324113 : pindexNew->nBits = diskindex.nBits;
324 324113 : pindexNew->nNonce = diskindex.nNonce;
325 324113 : pindexNew->nStatus = diskindex.nStatus;
326 324113 : pindexNew->nTx = diskindex.nTx;
327 :
328 324113 : if (!CheckProofOfWork(pindexNew->GetBlockHash(), pindexNew->nBits, consensusParams)) {
329 0 : return error("%s: CheckProofOfWork failed: %s", __func__, pindexNew->ToString());
330 : }
331 :
332 324113 : pcursor->Next();
333 324113 : } else {
334 0 : return error("%s: failed to read value", __func__);
335 : }
336 324113 : } else {
337 1992 : break;
338 : }
339 : }
340 :
341 2994 : return true;
342 2994 : }
343 :
344 : /**
345 : * Template helper to migrate a single index type from the old block index database
346 : * to a new async index database.
347 : *
348 : * @tparam DbKey The key prefix byte for this index type
349 : * @tparam KeyType The index key struct type
350 : * @tparam ValueType The index value type
351 : * @param source_db The old database to migrate from (CBlockTreeDB)
352 : * @param target_db The new database to migrate to, or nullptr to discard
353 : * source entries without copying them (for stale legacy data)
354 : * @param index_name Human-readable name for logging
355 : * @param batch_size Maximum batch size before flushing
356 : * @return Number of source entries processed, -1 on error,
357 : * or -2 if shutdown was requested mid-migration
358 : */
359 : template <uint8_t DbKey, typename KeyType, typename ValueType>
360 36 : static int64_t MigrateIndex(CBlockTreeDB& source_db, CDBWrapper* target_db,
361 : const char* index_name, size_t batch_size)
362 : {
363 : using KeyPair = std::pair<uint8_t, KeyType>;
364 :
365 36 : size_t count = 0;
366 36 : std::optional<CDBBatch> new_batch;
367 36 : if (target_db) new_batch.emplace(*target_db);
368 36 : CDBBatch erase_batch(source_db);
369 :
370 36 : KeyPair start = std::make_pair(DbKey, KeyType());
371 36 : KeyPair key;
372 20 : ValueType value;
373 :
374 : // Compact after erasing this much data to reclaim disk space
375 36 : const size_t compact_threshold = 256 << 20; // 256 MiB
376 36 : size_t erased_since_compact = 0;
377 :
378 36 : std::unique_ptr<CDBIterator> pcursor(source_db.NewIterator());
379 36 : pcursor->Seek(start);
380 :
381 36 : while (pcursor->Valid()) {
382 : // Allow the user to abort a long-running migration without leaving
383 : // the source/target DBs in a half-applied state.
384 4 : if (ShutdownRequested()) {
385 0 : LogPrintf("Shutdown requested during %s migration, aborting before next batch\n", index_name);
386 0 : return -2;
387 : }
388 4 : if (pcursor->GetKey(key) && key.first == DbKey) {
389 0 : if (target_db) {
390 0 : if (!pcursor->GetValue(value)) {
391 0 : LogPrintf("Failed to read %s value\n", index_name);
392 0 : return -1;
393 : }
394 0 : new_batch->Write(key, value);
395 0 : }
396 0 : erase_batch.Erase(key);
397 0 : count++;
398 0 : pcursor->Next();
399 :
400 0 : const size_t estimated = new_batch ? new_batch->SizeEstimate() : erase_batch.SizeEstimate();
401 0 : if (estimated > batch_size) {
402 0 : LogPrintf("Processing partial batch of %s entries (%.2f MiB, %d entries)...\n",
403 : index_name, estimated * (1.0 / 1048576.0), count);
404 0 : erased_since_compact += erase_batch.SizeEstimate();
405 : // Sync target writes before erasing source so a crash cannot leave
406 : // entries erased from source without durably reaching target.
407 0 : if (target_db && !target_db->WriteBatch(*new_batch, /*fSync=*/true)) {
408 0 : LogPrintf("Failed to write %s batch to new database\n", index_name);
409 0 : return -1;
410 : }
411 0 : if (!source_db.WriteBatch(erase_batch)) {
412 0 : LogPrintf("Failed to erase old %s data\n", index_name);
413 0 : return -1;
414 : }
415 0 : if (new_batch) new_batch->Clear();
416 0 : erase_batch.Clear();
417 :
418 : // Compact periodically to reclaim disk space
419 0 : if (erased_since_compact >= compact_threshold) {
420 : // Close iterator before compaction so LevelDB can delete old SST files
421 0 : pcursor.reset();
422 0 : source_db.CompactRange(start, key);
423 0 : erased_since_compact = 0;
424 :
425 : // Reopen iterator - seek to start finds next unprocessed key since we erased previous ones
426 0 : pcursor.reset(source_db.NewIterator());
427 0 : pcursor->Seek(start);
428 0 : }
429 0 : }
430 0 : } else {
431 4 : break;
432 : }
433 : }
434 :
435 : // Always write final batch with sync to ensure durability
436 36 : if (target_db && !target_db->WriteBatch(*new_batch, /*fSync=*/true)) {
437 0 : LogPrintf("Failed to write final %s batch\n", index_name);
438 0 : return -1;
439 : }
440 36 : if (!source_db.WriteBatch(erase_batch, true)) {
441 0 : LogPrintf("Failed to erase final %s batch\n", index_name);
442 0 : return -1;
443 : }
444 36 : if (new_batch) new_batch->Clear();
445 36 : erase_batch.Clear();
446 :
447 : // Close iterator before final compaction
448 36 : pcursor.reset();
449 :
450 : // Compact final range if we processed any keys
451 36 : if (count > 0) {
452 0 : source_db.CompactRange(start, key);
453 0 : }
454 :
455 36 : return static_cast<int64_t>(count);
456 36 : }
457 :
458 : /**
459 : * Write best block locator to an index database.
460 : * Note: Source database compaction is done incrementally in MigrateIndex.
461 : */
462 0 : static bool FinalizeMigration(CDBWrapper& target_db, const uint256& best_block_hash,
463 : const char* index_name)
464 : {
465 0 : if (!best_block_hash.IsNull()) {
466 0 : CBlockLocator locator;
467 0 : locator.vHave.push_back(best_block_hash);
468 0 : CDBBatch best_block_batch(target_db);
469 0 : best_block_batch.Write(DB_BEST_BLOCK, locator);
470 0 : if (!target_db.WriteBatch(best_block_batch, true)) {
471 0 : return error("%s: Failed to write best block for %s", __func__, index_name);
472 : }
473 0 : LogPrintf("Set %s best block to %s\n", index_name, best_block_hash.ToString());
474 0 : }
475 :
476 0 : return true;
477 0 : }
478 :
479 : // Returns true if the target index database has no best-block locator yet —
480 : // i.e. the new async index has never been finalized on this datadir. Used to
481 : // guard FinalizeMigration so we don't regress an already-advanced locator
482 : // (e.g. left by ThreadSync after a previous successful migration).
483 0 : static bool TargetNeedsLocator(CDBWrapper& target_db)
484 : {
485 0 : CBlockLocator existing;
486 0 : return !target_db.Read(DB_BEST_BLOCK, existing) || existing.IsNull();
487 0 : }
488 :
489 2990 : bool CBlockTreeDB::MigrateOldIndexData()
490 : {
491 : // Migrate old synchronous index data that was stored in the block index database
492 : // to new async indexes in separate databases under indexes/{timestampindex,spentindex,addressindex}/
493 : // This preserves existing index data so users don't need to rebuild.
494 : // Indexes are migrated independently since they can be enabled individually.
495 :
496 : // Only migrate indexes that are actually enabled via command-line flags
497 : // NOTE: not using DEFAULT_* constants here to avoid circular dependencies
498 2990 : const bool fTimestampIndex = gArgs.GetBoolArg("-timestampindex", false);
499 2990 : const bool fSpentIndex = gArgs.GetBoolArg("-spentindex", false);
500 2990 : const bool fAddressIndex = gArgs.GetBoolArg("-addressindex", false);
501 :
502 2990 : if (!fTimestampIndex && !fSpentIndex && !fAddressIndex) {
503 : // No indexes enabled, skip migration entirely
504 2968 : return true;
505 : }
506 :
507 22 : LogPrintf("Checking for old index data in block index database...\n");
508 :
509 : // The legacy synchronous index code persisted a flag in the block index DB whenever
510 : // the user changed -addressindex/-spentindex/-timestampindex, and refused to start
511 : // with a re-enabled index unless the user reindexed. That re-enable check is gone now,
512 : // so we use these flags to detect stale on-disk data: if the flag is missing or false,
513 : // the legacy entries don't cover the suffix of the chain that ran with the index
514 : // disabled, and copying them while stamping the chainstate tip as the locator would
515 : // mark an incomplete index as fully synced. In that case we discard the source entries
516 : // instead and let the new async index resync from genesis.
517 22 : bool legacy_flag = false;
518 22 : const bool addressindex_was_current = ReadFlag("addressindex", legacy_flag) && legacy_flag;
519 22 : const bool spentindex_was_current = ReadFlag("spentindex", legacy_flag) && legacy_flag;
520 22 : const bool timestampindex_was_current = ReadFlag("timestampindex", legacy_flag) && legacy_flag;
521 :
522 22 : size_t batch_size = (size_t)gArgs.GetIntArg("-dbbatchsize", nDefaultDbBatchSize);
523 22 : size_t total_count = 0;
524 22 : const fs::path indexes_path = gArgs.GetDataDirNet() / "indexes";
525 :
526 : // Read the best block hash from coins database to set as best block for migrated indexes
527 : // Old synchronous indexes were updated during ConnectBlock, so they're synced to the active chain tip
528 22 : uint256 best_block_hash;
529 : {
530 22 : fs::path chainstate_path = gArgs.GetDataDirNet() / "chainstate";
531 22 : CDBWrapper coins_db(chainstate_path, 0, false, false);
532 22 : if (!coins_db.Read(DB_BEST_BLOCK, best_block_hash)) {
533 : // If we can't read the best block, the indexes will resync from scratch
534 14 : LogPrintf("Warning: Could not read best block from chainstate, migrated indexes will resync\n");
535 14 : best_block_hash.SetNull();
536 14 : } else {
537 8 : LogPrintf("Migrating indexes with best block: %s\n", best_block_hash.ToString());
538 : }
539 22 : }
540 :
541 : // Returns true if migration of this index step succeeded (count >= 0); on
542 : // shutdown (-2) and error (-1) returns false, leaving the caller to bail
543 : // without finalizing — the source DB still has the un-processed remainder
544 : // so the next start can resume.
545 58 : auto handle_count = [](int64_t count, const char* index_name, const char* func_name,
546 : bool was_current, size_t& total) -> bool {
547 36 : if (count < 0) return error("%s: Failed to migrate %s", func_name, index_name);
548 36 : if (count > 0) {
549 0 : LogPrintf("%s %d %s entries\n",
550 : was_current ? "Migrated" : "Discarded stale", count, index_name);
551 0 : total += count;
552 0 : }
553 36 : return true;
554 36 : };
555 :
556 : // Migrate timestamp index (only if enabled)
557 22 : if (fTimestampIndex) {
558 6 : const fs::path db_path = indexes_path / "timestampindex";
559 6 : CDBWrapper timestamp_db(db_path, 0, false, false);
560 :
561 : // Pass nullptr to discard rather than copy if legacy data is stale.
562 6 : CDBWrapper* target = timestampindex_was_current ? ×tamp_db : nullptr;
563 6 : int64_t count = MigrateIndex<DB_TIMESTAMPINDEX, CTimestampIndexKey, bool>(
564 6 : *this, target, "timestamp index", batch_size);
565 6 : if (!handle_count(count, "timestamp index", __func__, timestampindex_was_current, total_count)) {
566 0 : return false;
567 : }
568 : // Finalize even when count==0 to recover from a previous run that crashed
569 : // after writing target data but before writing the locator.
570 6 : if (timestampindex_was_current && TargetNeedsLocator(timestamp_db)) {
571 0 : if (!FinalizeMigration(timestamp_db, best_block_hash, "timestamp index")) {
572 0 : return false;
573 : }
574 0 : }
575 6 : }
576 :
577 : // Migrate spent index (only if enabled)
578 22 : if (fSpentIndex) {
579 10 : const fs::path db_path = indexes_path / "spentindex";
580 10 : CDBWrapper spent_db(db_path, 0, false, false);
581 :
582 10 : CDBWrapper* target = spentindex_was_current ? &spent_db : nullptr;
583 10 : int64_t count = MigrateIndex<DB_SPENTINDEX, CSpentIndexKey, CSpentIndexValue>(
584 10 : *this, target, "spent index", batch_size);
585 10 : if (!handle_count(count, "spent index", __func__, spentindex_was_current, total_count)) {
586 0 : return false;
587 : }
588 10 : if (spentindex_was_current && TargetNeedsLocator(spent_db)) {
589 0 : if (!FinalizeMigration(spent_db, best_block_hash, "spent index")) {
590 0 : return false;
591 : }
592 0 : }
593 10 : }
594 :
595 : // Migrate address index (includes both address and unspent indexes) (only if enabled)
596 22 : if (fAddressIndex) {
597 10 : const fs::path db_path = indexes_path / "addressindex";
598 10 : CDBWrapper address_db(db_path, 0, false, false);
599 :
600 10 : CDBWrapper* target = addressindex_was_current ? &address_db : nullptr;
601 :
602 : // Migrate address index (transaction history)
603 10 : int64_t address_count = MigrateIndex<DB_ADDRESSINDEX, CAddressIndexKey, CAmount>(
604 10 : *this, target, "address index", batch_size);
605 10 : if (!handle_count(address_count, "address index", __func__, addressindex_was_current, total_count)) {
606 0 : return false;
607 : }
608 :
609 : // Migrate address unspent index
610 10 : int64_t unspent_count = MigrateIndex<DB_ADDRESSUNSPENTINDEX, CAddressUnspentKey, CAddressUnspentValue>(
611 10 : *this, target, "address unspent index", batch_size);
612 10 : if (!handle_count(unspent_count, "address unspent index", __func__, addressindex_was_current, total_count)) {
613 0 : return false;
614 : }
615 :
616 10 : if (addressindex_was_current && TargetNeedsLocator(address_db)) {
617 0 : if (!FinalizeMigration(address_db, best_block_hash, "address and address unspent indexes")) {
618 0 : return false;
619 : }
620 0 : }
621 10 : }
622 :
623 22 : if (total_count > 0) {
624 0 : LogPrintf("Compacting remaining block index database...\n");
625 0 : CompactFull();
626 0 : LogPrintf("Successfully processed %d legacy index entries\n", total_count);
627 0 : } else {
628 22 : LogPrintf("No old index data found\n");
629 : }
630 :
631 22 : return true;
632 2990 : }
|