LCOV - code coverage report
Current view: top level - src/index - base.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 190 234 81.2 %
Date: 2026-06-25 07:23:43 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        5266 : BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) :
      35        4084 :     CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate)
      36        5266 : {}
      37             : 
      38        3626 : bool BaseIndex::DB::ReadBestBlock(CBlockLocator& locator) const
      39             : {
      40        3626 :     bool success = Read(DB_BEST_BLOCK, locator);
      41        3626 :     if (!success) {
      42        1700 :         locator.SetNull();
      43        1700 :     }
      44        3626 :     return success;
      45             : }
      46             : 
      47       23336 : void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
      48             : {
      49       23336 :     batch.Write(DB_BEST_BLOCK, locator);
      50       23336 : }
      51             : 
      52        3629 : BaseIndex::~BaseIndex()
      53        3629 : {
      54        3629 :     Interrupt();
      55        3629 :     Stop();
      56        3629 : }
      57             : 
      58        3626 : bool BaseIndex::Init()
      59             : {
      60        3626 :     CBlockLocator locator;
      61        3626 :     if (!GetDB().ReadBestBlock(locator)) {
      62        1700 :         locator.SetNull();
      63        1700 :     }
      64             : 
      65        3626 :     LOCK(cs_main);
      66        3626 :     CChain& active_chain = m_chainstate->m_chain;
      67        3626 :     if (locator.IsNull()) {
      68        1700 :         SetBestBlockIndex(nullptr);
      69        1700 :     } else {
      70        1926 :         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        3626 :     m_synced = m_best_block_index.load() == active_chain.Tip();
      77        3626 :     if (!m_synced) {
      78         773 :         bool prune_violation = false;
      79         773 :         if (!m_best_block_index) {
      80             :             // index is not built yet
      81             :             // make sure we have all block data back to the genesis
      82         739 :             prune_violation = m_chainstate->m_blockman.GetFirstStoredBlock(*active_chain.Tip()) != active_chain.Genesis();
      83         739 :         }
      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          34 :             const CBlockIndex* block_to_test = m_best_block_index.load();
      88          34 :             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          34 :             const CBlockIndex* block = active_chain.Tip();
      94          34 :             prune_violation = true;
      95             :             // check backwards from the tip if we have all block data until we reach the indexes bestblock
      96        4915 :             while (block_to_test && block && (block->nStatus & BLOCK_HAVE_DATA)) {
      97        4915 :                 if (block_to_test == block) {
      98          34 :                     prune_violation = false;
      99          34 :                     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        4881 :                 assert(block->pprev);
     104        4881 :                 block = block->pprev;
     105             :             }
     106             :         }
     107         773 :         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         773 :     }
     111        3626 :     return true;
     112        3626 : }
     113             : 
     114      102799 : static const CBlockIndex* NextSyncBlock(const CBlockIndex* pindex_prev, CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
     115             : {
     116      102799 :     AssertLockHeld(cs_main);
     117             : 
     118      102799 :     if (!pindex_prev) {
     119         739 :         return chain.Genesis();
     120             :     }
     121             : 
     122      102060 :     const CBlockIndex* pindex = chain.Next(pindex_prev);
     123      102060 :     if (pindex) {
     124      101321 :         return pindex;
     125             :     }
     126             : 
     127         739 :     return chain.Next(chain.FindFork(pindex_prev));
     128      102799 : }
     129             : 
     130        3626 : void BaseIndex::ThreadSync()
     131             : {
     132        3626 :     const CBlockIndex* pindex = m_best_block_index.load();
     133        3626 :     if (!m_synced) {
     134         773 :         auto& consensus_params = Params().GetConsensus();
     135             : 
     136         773 :         std::chrono::steady_clock::time_point last_log_time{0s};
     137         773 :         std::chrono::steady_clock::time_point last_locator_write_time{0s};
     138      102833 :         while (true) {
     139      102833 :             if (m_interrupt) {
     140          34 :                 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          34 :                 Commit();
     145          34 :                 return;
     146             :             }
     147             : 
     148             :             {
     149      102799 :                 LOCK(cs_main);
     150      102799 :                 const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain);
     151      102799 :                 if (!pindex_next) {
     152         739 :                     SetBestBlockIndex(pindex);
     153             :                     // No need to handle errors in Commit. See rationale above.
     154         739 :                     Commit();
     155         739 :                     m_synced = true;
     156         739 :                     break;
     157             :                 }
     158      102060 :                 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      102060 :                 pindex = pindex_next;
     164      102799 :             }
     165             : 
     166      102070 :             CBlock block;
     167      102070 :             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      102058 :             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      102058 :             auto current_time{std::chrono::steady_clock::now()};
     179      102058 :             if (last_log_time + SYNC_LOG_INTERVAL < current_time) {
     180         773 :                 LogPrintf("Syncing %s with block chain from height %d\n",
     181             :                           GetName(), pindex->nHeight);
     182         773 :                 last_log_time = current_time;
     183         773 :             }
     184             : 
     185      102049 :             if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
     186         773 :                 SetBestBlockIndex(pindex);
     187         773 :                 last_locator_write_time = current_time;
     188             :                 // No need to handle errors in Commit. See rationale above.
     189         773 :                 Commit();
     190         773 :             }
     191      102052 :         }
     192         739 :     }
     193             : 
     194        3592 :     if (pindex) {
     195        2616 :         LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight);
     196        2616 :     } else {
     197         976 :         LogPrintf("%s is enabled\n", GetName());
     198             :     }
     199        3668 : }
     200             : 
     201       23336 : bool BaseIndex::Commit()
     202             : {
     203       23336 :     CDBBatch batch(GetDB());
     204       23336 :     if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) {
     205           0 :         return error("%s: Failed to commit latest %s state", __func__, GetName());
     206             :     }
     207       23336 :     return true;
     208       23336 : }
     209             : 
     210       23336 : bool BaseIndex::CommitInternal(CDBBatch& batch)
     211             : {
     212       23336 :     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       23336 :     if (m_best_block_index == nullptr) {
     216           0 :         return false;
     217             :     }
     218       23336 :     GetDB().WriteBestBlock(batch, m_chainstate->m_chain.GetLocator(m_best_block_index));
     219       23336 :     return true;
     220       23336 : }
     221             : 
     222       16460 : bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
     223             : {
     224       16460 :     assert(current_tip == m_best_block_index);
     225       16460 :     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       16460 :     SetBestBlockIndex(new_tip);
     233       16460 :     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       16460 :     return true;
     240       16460 : }
     241             : 
     242      336041 : void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     243             : {
     244      336041 :     if (!m_synced) {
     245          14 :         return;
     246             :     }
     247             : 
     248      336027 :     const CBlockIndex* best_block_index = m_best_block_index.load();
     249      336027 :     if (!best_block_index) {
     250         976 :         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         976 :     } 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      335051 :         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      335051 :         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      336027 :     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      336027 :         SetBestBlockIndex(pindex);
     281      336027 :     } else {
     282           0 :         FatalError("%s: Failed to write block %s to index",
     283           0 :                    __func__, pindex->GetBlockHash().ToString());
     284           0 :         return;
     285             :     }
     286      336041 : }
     287             : 
     288       16459 : void BaseIndex::BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
     289             : {
     290       16459 :     if (!m_synced) {
     291           0 :         return;
     292             :     }
     293             : 
     294       16459 :     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       16459 :     if (best_block_index && best_block_index->nHeight >= pindex->nHeight && pindex->pprev) {
     300       16459 :         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       16459 :         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       16459 :     }
     310       16459 : }
     311             : 
     312        5364 : void BaseIndex::ChainStateFlushed(const CBlockLocator& locator)
     313             : {
     314        5364 :     if (!m_synced) {
     315          34 :         return;
     316             :     }
     317             : 
     318        5330 :     const uint256& locator_tip_hash = locator.vHave.front();
     319             :     const CBlockIndex* locator_tip_index;
     320             :     {
     321        5330 :         LOCK(cs_main);
     322        5330 :         locator_tip_index = m_chainstate->m_blockman.LookupBlockIndex(locator_tip_hash);
     323        5330 :     }
     324             : 
     325        5330 :     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        5330 :     const CBlockIndex* best_block_index = m_best_block_index.load();
     337        5330 :     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        5330 :     Commit();
     349        5364 : }
     350             : 
     351       43866 : bool BaseIndex::BlockUntilSyncedToCurrentChain() const
     352             : {
     353       43866 :     AssertLockNotHeld(cs_main);
     354             : 
     355       43866 :     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       43681 :         LOCK(cs_main);
     363       43681 :         const CBlockIndex* chain_tip = m_chainstate->m_chain.Tip();
     364       43681 :         const CBlockIndex* best_block_index = m_best_block_index.load();
     365       43681 :         if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
     366       42312 :             return true;
     367             :         }
     368       43681 :     }
     369             : 
     370        1369 :     LogPrintf("%s: %s is catching up on block notifications\n", __func__, GetName());
     371        1369 :     SyncWithValidationInterfaceQueue();
     372        1369 :     return true;
     373       43866 : }
     374             : 
     375        7251 : void BaseIndex::Interrupt()
     376             : {
     377        7251 :     m_interrupt();
     378        7251 : }
     379             : 
     380        3626 : bool BaseIndex::Start(CChainState& active_chainstate)
     381             : {
     382        3626 :     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        3626 :     RegisterValidationInterface(this);
     386        3626 :     if (!Init()) {
     387           0 :         return false;
     388             :     }
     389             : 
     390        7252 :     m_thread_sync = std::thread(&util::TraceThread, GetName(), [this] { ThreadSync(); });
     391        3626 :     return true;
     392        3626 : }
     393             : 
     394        7255 : void BaseIndex::Stop()
     395             : {
     396        7255 :     UnregisterValidationInterface(this);
     397             : 
     398        7255 :     if (m_thread_sync.joinable()) {
     399        3626 :         m_thread_sync.join();
     400        3626 :     }
     401        7255 : }
     402             : 
     403         855 : IndexSummary BaseIndex::GetSummary() const
     404             : {
     405         855 :     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      357659 : void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) {
     413      357659 :     assert(!fPruneMode || AllowPrune());
     414             : 
     415      357659 :     if (AllowPrune() && block) {
     416       99399 :         PruneLockInfo prune_lock;
     417       99399 :         prune_lock.height_first = block->nHeight;
     418      198798 :         WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock));
     419       99399 :     }
     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      357659 :     m_best_block_index = block;
     428      357659 : }

Generated by: LCOV version 1.16