LCOV - code coverage report
Current view: top level - src - txdb.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 240 343 70.0 %
Date: 2026-06-25 07:23:43 Functions: 43 46 93.5 %

          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 ? &timestamp_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 : }

Generated by: LCOV version 1.16