LCOV - code coverage report
Current view: top level - src/index - base.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 173 234 73.9 %
Date: 2026-06-25 07:23:51 Functions: 22 25 88.0 %

          Line data    Source code
       1             : // Copyright (c) 2017-2021 The Bitcoin 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 <chainparams.h>
       6             : #include <index/base.h>
       7             : #include <node/blockstorage.h>
       8             : #include <node/interface_ui.h>
       9             : #include <shutdown.h>
      10             : #include <tinyformat.h>
      11             : #include <util/system.h>
      12             : #include <util/thread.h>
      13             : #include <util/translation.h>
      14             : #include <validation.h>
      15             : #include <warnings.h>
      16             : 
      17             : using node::PruneLockInfo;
      18             : using node::ReadBlockFromDisk;
      19             : using node::fPruneMode;
      20             : 
      21             : constexpr uint8_t DB_BEST_BLOCK{'B'};
      22             : 
      23             : constexpr auto SYNC_LOG_INTERVAL{30s};
      24             : constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s};
      25             : 
      26           0 : void BaseIndex::FatalErrorImpl(const std::string& message)
      27             : {
      28           0 :     SetMiscWarning(Untranslated(message));
      29           0 :     LogPrintf("*** %s\n", message);
      30           0 :     AbortError(_("A fatal internal error occurred, see debug.log for details"));
      31           0 :     StartShutdown();
      32           0 : }
      33             : 
      34          91 : BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) :
      35          80 :     CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate)
      36          91 : {}
      37             : 
      38          73 : bool BaseIndex::DB::ReadBestBlock(CBlockLocator& locator) const
      39             : {
      40          73 :     bool success = Read(DB_BEST_BLOCK, locator);
      41          73 :     if (!success) {
      42          72 :         locator.SetNull();
      43          72 :     }
      44          73 :     return success;
      45             : }
      46             : 
      47         183 : void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
      48             : {
      49         183 :     batch.Write(DB_BEST_BLOCK, locator);
      50         183 : }
      51             : 
      52          76 : BaseIndex::~BaseIndex()
      53          76 : {
      54          76 :     Interrupt();
      55          76 :     Stop();
      56          76 : }
      57             : 
      58          73 : bool BaseIndex::Init()
      59             : {
      60          73 :     CBlockLocator locator;
      61          73 :     if (!GetDB().ReadBestBlock(locator)) {
      62          72 :         locator.SetNull();
      63          72 :     }
      64             : 
      65          73 :     LOCK(cs_main);
      66          73 :     CChain& active_chain = m_chainstate->m_chain;
      67          73 :     if (locator.IsNull()) {
      68          72 :         SetBestBlockIndex(nullptr);
      69          72 :     } else {
      70           1 :         SetBestBlockIndex(m_chainstate->FindForkInGlobalIndex(locator));
      71             :     }
      72             : 
      73             :     // Note: this will latch to true immediately if the user starts up with an empty
      74             :     // datadir and an index enabled. If this is the case, indexation will happen solely
      75             :     // via `BlockConnected` signals until, possibly, the next restart.
      76          73 :     m_synced = m_best_block_index.load() == active_chain.Tip();
      77          73 :     if (!m_synced) {
      78          72 :         bool prune_violation = false;
      79          72 :         if (!m_best_block_index) {
      80             :             // index is not built yet
      81             :             // make sure we have all block data back to the genesis
      82          72 :             prune_violation = m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis();
      83          72 :         }
      84             :         // in case the index has a best block set and is not fully synced
      85             :         // check if we have the required blocks to continue building the index
      86             :         else {
      87           0 :             const CBlockIndex* block_to_test = m_best_block_index.load();
      88           0 :             if (!active_chain.Contains(block_to_test)) {
      89             :                 // if the bestblock is not part of the mainchain, find the fork
      90             :                 // and make sure we have all data down to the fork
      91           0 :                 block_to_test = active_chain.FindFork(block_to_test);
      92           0 :             }
      93           0 :             const CBlockIndex* block = active_chain.Tip();
      94           0 :             prune_violation = true;
      95             :             // check backwards from the tip if we have all block data until we reach the indexes bestblock
      96           0 :             while (block_to_test && block && (block->nStatus & BLOCK_HAVE_DATA)) {
      97           0 :                 if (block_to_test == block) {
      98           0 :                     prune_violation = false;
      99           0 :                     break;
     100             :                 }
     101             :                 // block->pprev must exist at this point, since block_to_test is part of the chain
     102             :                 // and thus must be encountered when going backwards from the tip
     103           0 :                 assert(block->pprev);
     104           0 :                 block = block->pprev;
     105             :             }
     106             :         }
     107          72 :         if (prune_violation) {
     108           0 :             return InitError(strprintf(Untranslated("%s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)"), GetName()));
     109             :         }
     110          72 :     }
     111          73 :     return true;
     112          73 : }
     113             : 
     114       11667 : static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
     115             : {
     116       11667 :     AssertLockHeld(cs_main);
     117             : 
     118       11667 :     if (!pindex_prev) {
     119          72 :         return chain.Genesis();
     120             :     }
     121             : 
     122       11595 :     const CBlockIndex* pindex = chain.Next(pindex_prev);
     123       11595 :     if (pindex) {
     124       11523 :         return pindex;
     125             :     }
     126             : 
     127          72 :     return chain.Next(chain.FindFork(pindex_prev));
     128       11667 : }
     129             : 
     130          73 : void BaseIndex::ThreadSync()
     131             : {
     132          73 :     const CBlockIndex* pindex = m_best_block_index.load();
     133          73 :     if (!m_synced) {
     134          72 :         auto& consensus_params = Params().GetConsensus();
     135             : 
     136          72 :         std::chrono::steady_clock::time_point last_log_time{0s};
     137          72 :         std::chrono::steady_clock::time_point last_locator_write_time{0s};
     138       11667 :         while (true) {
     139       11667 :             if (m_interrupt) {
     140           0 :                 SetBestBlockIndex(pindex);
     141             :                 // No need to handle errors in Commit. If it fails, the error will be already be
     142             :                 // logged. The best way to recover is to continue, as index cannot be corrupted by
     143             :                 // a missed commit to disk for an advanced index state.
     144           0 :                 Commit();
     145           0 :                 return;
     146             :             }
     147             : 
     148             :             {
     149       11667 :                 LOCK(cs_main);
     150       11667 :                 const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain);
     151       11667 :                 if (!pindex_next) {
     152          72 :                     SetBestBlockIndex(pindex);
     153             :                     // No need to handle errors in Commit. See rationale above.
     154          72 :                     Commit();
     155          72 :                     m_synced = true;
     156          72 :                     break;
     157             :                 }
     158       11595 :                 if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) {
     159           0 :                     FatalError("%s: Failed to rewind index %s to a previous chain tip",
     160           0 :                                __func__, GetName());
     161           0 :                     return;
     162             :                 }
     163       11595 :                 pindex = pindex_next;
     164       11667 :             }
     165             : 
     166       11595 :             CBlock block;
     167       11595 :             if (!ReadBlockFromDisk(block, pindex, consensus_params)) {
     168           0 :                 FatalError("%s: Failed to read block %s from disk",
     169           0 :                            __func__, pindex->GetBlockHash().ToString());
     170           0 :                 return;
     171             :             }
     172       11595 :             if (!WriteBlock(block, pindex)) {
     173           0 :                 FatalError("%s: Failed to write block %s to index database",
     174           0 :                            __func__, pindex->GetBlockHash().ToString());
     175           0 :                 return;
     176             :             }
     177             : 
     178       11595 :             auto current_time{std::chrono::steady_clock::now()};
     179       11595 :             if (last_log_time + SYNC_LOG_INTERVAL < current_time) {
     180          72 :                 LogPrintf("Syncing %s with block chain from height %d\n",
     181             :                           GetName(), pindex->nHeight);
     182          72 :                 last_log_time = current_time;
     183          72 :             }
     184             : 
     185       11595 :             if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
     186          72 :                 SetBestBlockIndex(pindex);
     187          72 :                 last_locator_write_time = current_time;
     188             :                 // No need to handle errors in Commit. See rationale above.
     189          72 :                 Commit();
     190          72 :             }
     191       11595 :         }
     192          72 :     }
     193             : 
     194          73 :     if (pindex) {
     195          73 :         LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight);
     196          73 :     } else {
     197           0 :         LogPrintf("%s is enabled\n", GetName());
     198             :     }
     199          73 : }
     200             : 
     201         183 : bool BaseIndex::Commit()
     202             : {
     203         183 :     CDBBatch batch(GetDB());
     204         183 :     if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) {
     205           0 :         return error("%s: Failed to commit latest %s state", __func__, GetName());
     206             :     }
     207         183 :     return true;
     208         183 : }
     209             : 
     210         183 : bool BaseIndex::CommitInternal(CDBBatch& batch)
     211             : {
     212         183 :     LOCK(cs_main);
     213             :     // Don't commit anything if we haven't indexed any block yet
     214             :     // (this could happen if init is interrupted).
     215         183 :     if (m_best_block_index == nullptr) {
     216           0 :         return false;
     217             :     }
     218         183 :     GetDB().WriteBestBlock(batch, m_chainstate->m_chain.GetLocator(m_best_block_index));
     219         183 :     return true;
     220         183 : }
     221             : 
     222          21 : bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
     223             : {
     224          21 :     assert(current_tip == m_best_block_index);
     225          21 :     assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
     226             : 
     227             :     // In the case of a reorg, ensure persisted block locator is not stale.
     228             :     // Pruning has a minimum of 288 blocks-to-keep and getting the index
     229             :     // out of sync may be possible but a users fault.
     230             :     // In case we reorg beyond the pruned depth, ReadBlockFromDisk would
     231             :     // throw and lead to a graceful shutdown
     232          21 :     SetBestBlockIndex(new_tip);
     233          21 :     if (!Commit()) {
     234             :         // If commit fails, revert the best block index to avoid corruption.
     235           0 :         SetBestBlockIndex(current_tip);
     236           0 :         return false;
     237             :     }
     238             : 
     239          21 :     return true;
     240          21 : }
     241             : 
     242       12505 : void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     243             : {
     244       12505 :     if (!m_synced) {
     245          14 :         return;
     246             :     }
     247             : 
     248       12491 :     const CBlockIndex* best_block_index = m_best_block_index.load();
     249       12491 :     if (!best_block_index) {
     250           0 :         if (pindex->nHeight != 0) {
     251           0 :             FatalError("%s: First block connected is not the genesis block (height=%d)",
     252           0 :                        __func__, pindex->nHeight);
     253           0 :             return;
     254             :         }
     255           0 :     } else {
     256             :         // Ensure block connects to an ancestor of the current best block. This should be the case
     257             :         // most of the time, but may not be immediately after the sync thread catches up and sets
     258             :         // m_synced. Consider the case where there is a reorg and the blocks on the stale branch are
     259             :         // in the ValidationInterface queue backlog even after the sync thread has caught up to the
     260             :         // new chain tip. In this unlikely event, log a warning and let the queue clear.
     261       12491 :         if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) {
     262           0 :             LogPrintf("%s: WARNING: Block %s does not connect to an ancestor of " /* Continued */
     263             :                       "known best chain (tip=%s); not updating index\n",
     264             :                       __func__, pindex->GetBlockHash().ToString(),
     265             :                       best_block_index->GetBlockHash().ToString());
     266           0 :             return;
     267             :         }
     268       12491 :         if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) {
     269           0 :             FatalError("%s: Failed to rewind index %s to a previous chain tip",
     270           0 :                        __func__, GetName());
     271           0 :             return;
     272             :         }
     273             :     }
     274             : 
     275       12491 :     if (WriteBlock(*block, pindex)) {
     276             :         // Setting the best block index is intentionally the last step of this
     277             :         // function, so BlockUntilSyncedToCurrentChain callers waiting for the
     278             :         // best block index to be updated can rely on the block being fully
     279             :         // processed, and the index object being safe to delete.
     280       12491 :         SetBestBlockIndex(pindex);
     281       12491 :     } else {
     282           0 :         FatalError("%s: Failed to write block %s to index",
     283           0 :                    __func__, pindex->GetBlockHash().ToString());
     284           0 :         return;
     285             :     }
     286       12505 : }
     287             : 
     288          20 : void BaseIndex::BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     289             : {
     290          20 :     if (!m_synced) {
     291           0 :         return;
     292             :     }
     293             : 
     294          20 :     const CBlockIndex* best_block_index = m_best_block_index.load();
     295             : 
     296             :     // Ignore stale-branch disconnect notifications that do not connect to the indexed chain.
     297             :     // We must check that pindex itself is on the indexed chain, not just that it shares
     298             :     // a parent — otherwise same-height siblings would incorrectly trigger a rewind.
     299          20 :     if (best_block_index && best_block_index->nHeight >= pindex->nHeight && pindex->pprev) {
     300          20 :         if (best_block_index->GetAncestor(pindex->nHeight) != pindex) {
     301           0 :             LogPrintf("%s: WARNING: Block %s is not on the indexed chain " /* Continued */
     302             :                       "(tip=%s); not updating index\n",
     303             :                       __func__, pindex->GetBlockHash().ToString(), best_block_index->GetBlockHash().ToString());
     304           0 :             return;
     305             :         }
     306          20 :         if (!Rewind(best_block_index, pindex->pprev)) {
     307           0 :             FatalError("%s: Failed to rewind %s to previous block after disconnect", __func__, GetName());
     308           0 :         }
     309          20 :     }
     310          20 : }
     311             : 
     312          18 : void BaseIndex::ChainStateFlushed(const CBlockLocator& locator)
     313             : {
     314          18 :     if (!m_synced) {
     315           0 :         return;
     316             :     }
     317             : 
     318          18 :     const uint256& locator_tip_hash = locator.vHave.front();
     319             :     const CBlockIndex* locator_tip_index;
     320             :     {
     321          18 :         LOCK(cs_main);
     322          18 :         locator_tip_index = m_chainstate->m_blockman.LookupBlockIndex(locator_tip_hash);
     323          18 :     }
     324             : 
     325          18 :     if (!locator_tip_index) {
     326           0 :         FatalError("%s: First block (hash=%s) in locator was not found",
     327           0 :                    __func__, locator_tip_hash.ToString());
     328           0 :         return;
     329             :     }
     330             : 
     331             :     // This checks that ChainStateFlushed callbacks are received after BlockConnected. The check may fail
     332             :     // immediately after the sync thread catches up and sets m_synced. Consider the case where
     333             :     // there is a reorg and the blocks on the stale branch are in the ValidationInterface queue
     334             :     // backlog even after the sync thread has caught up to the new chain tip. In this unlikely
     335             :     // event, log a warning and let the queue clear.
     336          18 :     const CBlockIndex* best_block_index = m_best_block_index.load();
     337          18 :     if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) {
     338           0 :         LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known best " /* Continued */
     339             :                   "chain (tip=%s); not writing index locator\n",
     340             :                   __func__, locator_tip_hash.ToString(),
     341             :                   best_block_index->GetBlockHash().ToString());
     342           0 :         return;
     343             :     }
     344             : 
     345             :     // No need to handle errors in Commit. If it fails, the error will be already be logged. The
     346             :     // best way to recover is to continue, as index cannot be corrupted by a missed commit to disk
     347             :     // for an advanced index state.
     348          18 :     Commit();
     349          18 : }
     350             : 
     351         354 : bool BaseIndex::BlockUntilSyncedToCurrentChain() const
     352             : {
     353         354 :     AssertLockNotHeld(cs_main);
     354             : 
     355         354 :     if (!m_synced) {
     356         185 :         return false;
     357             :     }
     358             : 
     359             :     {
     360             :         // Skip the queue-draining stuff if we know we're caught up with
     361             :         // m_chain.Tip().
     362         169 :         LOCK(cs_main);
     363         169 :         const CBlockIndex* chain_tip = m_chainstate->m_chain.Tip();
     364         169 :         const CBlockIndex* best_block_index = m_best_block_index.load();
     365         169 :         if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
     366         148 :             return true;
     367             :         }
     368         169 :     }
     369             : 
     370          21 :     LogPrintf("%s: %s is catching up on block notifications\n", __func__, GetName());
     371          21 :     SyncWithValidationInterfaceQueue();
     372          21 :     return true;
     373         354 : }
     374             : 
     375         145 : void BaseIndex::Interrupt()
     376             : {
     377         145 :     m_interrupt();
     378         145 : }
     379             : 
     380          73 : bool BaseIndex::Start(CChainState& active_chainstate)
     381             : {
     382          73 :     m_chainstate = &active_chainstate;
     383             :     // Need to register this ValidationInterface before running Init(), so that
     384             :     // callbacks are not missed if Init sets m_synced to true.
     385          73 :     RegisterValidationInterface(this);
     386          73 :     if (!Init()) {
     387           0 :         return false;
     388             :     }
     389             : 
     390         146 :     m_thread_sync = std::thread(&util::TraceThread, GetName(), [this] { ThreadSync(); });
     391          73 :     return true;
     392          73 : }
     393             : 
     394         149 : void BaseIndex::Stop()
     395             : {
     396         149 :     UnregisterValidationInterface(this);
     397             : 
     398         149 :     if (m_thread_sync.joinable()) {
     399          73 :         m_thread_sync.join();
     400          73 :     }
     401         149 : }
     402             : 
     403         145 : IndexSummary BaseIndex::GetSummary() const
     404             : {
     405         145 :     IndexSummary summary{};
     406             :     summary.name = GetName();
     407             :     summary.synced = m_synced;
     408             :     summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0;
     409             :     return summary;
     410             : }
     411             : 
     412       12729 : void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) {
     413       12729 :     assert(!fPruneMode || AllowPrune());
     414             : 
     415       12729 :     if (AllowPrune() && block) {
     416          23 :         PruneLockInfo prune_lock;
     417          23 :         prune_lock.height_first = block->nHeight;
     418          46 :         WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock));
     419          23 :     }
     420             : 
     421             :     // Intentionally set m_best_block_index as the last step in this function,
     422             :     // after updating prune locks above, and after making any other references
     423             :     // to *this, so the BlockUntilSyncedToCurrentChain function (which checks
     424             :     // m_best_block_index as an optimization) can be used to wait for the last
     425             :     // BlockConnected notification and safely assume that prune locks are
     426             :     // updated and that the index object is safe to delete.
     427       12729 :     m_best_block_index = block;
     428       12729 : }

Generated by: LCOV version 1.16