base.cpp raw

   1  // Copyright (c) 2017-present 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 <index/base.h>
   6  
   7  #include <chain.h>
   8  #include <common/args.h>
   9  #include <dbwrapper.h>
  10  #include <interfaces/chain.h>
  11  #include <interfaces/types.h>
  12  #include <kernel/types.h>
  13  #include <node/abort.h>
  14  #include <node/blockstorage.h>
  15  #include <node/context.h>
  16  #include <node/database_args.h>
  17  #include <node/interface_ui.h>
  18  #include <primitives/block.h>
  19  #include <sync.h>
  20  #include <tinyformat.h>
  21  #include <uint256.h>
  22  #include <undo.h>
  23  #include <util/check.h>
  24  #include <util/fs.h>
  25  #include <util/log.h>
  26  #include <util/string.h>
  27  #include <util/thread.h>
  28  #include <util/threadinterrupt.h>
  29  #include <util/time.h>
  30  #include <util/translation.h>
  31  #include <validation.h>
  32  #include <validationinterface.h>
  33  
  34  #include <compare>
  35  #include <cstdint>
  36  #include <functional>
  37  #include <memory>
  38  #include <optional>
  39  #include <stdexcept>
  40  #include <string>
  41  #include <thread>
  42  #include <utility>
  43  #include <vector>
  44  
  45  using kernel::ChainstateRole;
  46  
  47  constexpr uint8_t DB_BEST_BLOCK{'B'};
  48  
  49  constexpr auto SYNC_LOG_INTERVAL{30s};
  50  constexpr auto SYNC_LOCATOR_WRITE_INTERVAL{30s};
  51  
  52  template <typename... Args>
  53  void BaseIndex::FatalErrorf(util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
  54  {
  55      auto message = tfm::format(fmt, args...);
  56      node::AbortNode(m_chain->context()->shutdown_request, m_chain->context()->exit_status, Untranslated(message), m_chain->context()->warnings.get());
  57  }
  58  
  59  CBlockLocator GetLocator(interfaces::Chain& chain, const uint256& block_hash)
  60  {
  61      CBlockLocator locator;
  62      bool found = chain.findBlock(block_hash, interfaces::FoundBlock().locator(locator));
  63      assert(found);
  64      assert(!locator.IsNull());
  65      return locator;
  66  }
  67  
  68  BaseIndex::DB::DB(const fs::path& path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate, bool f_bloom) :
  69      CDBWrapper{DBParams{
  70          .path = path,
  71          .cache_bytes = n_cache_size,
  72          .memory_only = f_memory,
  73          .wipe_data = f_wipe,
  74          .obfuscate = f_obfuscate,
  75          .bloom_filter = f_bloom,
  76          .options = [] { DBOptions options; node::ReadDatabaseArgs(gArgs, options); return options; }()}}
  77  {}
  78  
  79  CBlockLocator BaseIndex::DB::ReadBestBlock() const
  80  {
  81      CBlockLocator locator;
  82  
  83      bool success = Read(DB_BEST_BLOCK, locator);
  84      if (!success) {
  85          locator.SetNull();
  86      }
  87  
  88      return locator;
  89  }
  90  
  91  void BaseIndex::DB::WriteBestBlock(CDBBatch& batch, const CBlockLocator& locator)
  92  {
  93      batch.Write(DB_BEST_BLOCK, locator);
  94  }
  95  
  96  BaseIndex::BaseIndex(std::unique_ptr<interfaces::Chain> chain, std::string name, std::string thread_name)
  97      : m_chain{std::move(chain)}, m_name{std::move(name)}, m_thread_name{std::move(thread_name)} {}
  98  
  99  BaseIndex::~BaseIndex()
 100  {
 101      Interrupt();
 102      Stop();
 103  }
 104  
 105  bool BaseIndex::Init()
 106  {
 107      AssertLockNotHeld(cs_main);
 108  
 109      // May need reset if index is being restarted.
 110      m_interrupt.reset();
 111  
 112      // m_chainstate member gives indexing code access to node internals. It is
 113      // removed in followup https://github.com/bitcoin/bitcoin/pull/24230
 114      m_chainstate = WITH_LOCK(::cs_main,
 115                               return &m_chain->context()->chainman->ValidatedChainstate());
 116      // Register to validation interface before setting the 'm_synced' flag, so that
 117      // callbacks are not missed once m_synced is true.
 118      m_chain->context()->validation_signals->RegisterValidationInterface(this);
 119  
 120      const auto locator{GetDB().ReadBestBlock()};
 121  
 122      LOCK(cs_main);
 123      CChain& index_chain = m_chainstate->m_chain;
 124  
 125      if (locator.IsNull()) {
 126          SetBestBlockIndex(nullptr);
 127      } else {
 128          // Setting the best block to the locator's top block. If it is not part of the
 129          // best chain, we will rewind to the fork point during index sync
 130          const CBlockIndex* locator_index{m_chainstate->m_blockman.LookupBlockIndex(locator.vHave.at(0))};
 131          if (!locator_index) {
 132              return InitError(Untranslated(strprintf("best block of %s not found. Please rebuild the index.", GetName())));
 133          }
 134          SetBestBlockIndex(locator_index);
 135      }
 136  
 137      // Child init
 138      const CBlockIndex* start_block = m_best_block_index.load();
 139      if (!CustomInit(start_block ? std::make_optional(interfaces::BlockRef{start_block->GetBlockHash(), start_block->nHeight}) : std::nullopt)) {
 140          return false;
 141      }
 142  
 143      // Note: this will latch to true immediately if the user starts up with an empty
 144      // datadir and an index enabled. If this is the case, indexation will happen solely
 145      // via `BlockConnected` signals until, possibly, the next restart.
 146      m_synced = start_block == index_chain.Tip();
 147      m_init = true;
 148      return true;
 149  }
 150  
 151  static const CBlockIndex* NextSyncBlock(const CBlockIndex* const pindex_prev, CChain& chain) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
 152  {
 153      AssertLockHeld(cs_main);
 154  
 155      if (!pindex_prev) {
 156          return chain.Genesis();
 157      }
 158  
 159      if (const auto* pindex{chain.Next(*pindex_prev)}) {
 160          return pindex;
 161      }
 162  
 163      // If there is no next block, we might be synced
 164      if (pindex_prev == chain.Tip()) {
 165          return nullptr;
 166      }
 167  
 168      // Since block is not in the chain, return the next block in the chain AFTER the last common ancestor.
 169      // Caller will be responsible for rewinding back to the common ancestor.
 170      const auto* fork{chain.FindFork(*pindex_prev)};
 171      // Common ancestor must exist (genesis).
 172      return chain.Next(*Assert(fork));
 173  }
 174  
 175  bool BaseIndex::ProcessBlock(const CBlockIndex* pindex, const CBlock* block_data)
 176  {
 177      interfaces::BlockInfo block_info = kernel::MakeBlockInfo(pindex, block_data);
 178  
 179      CBlock block;
 180      if (!block_data) { // disk lookup if block data wasn't provided
 181          if (!m_chainstate->m_blockman.ReadBlock(block, *pindex)) {
 182              FatalErrorf("Failed to read block %s from disk",
 183                          pindex->GetBlockHash().ToString());
 184              return false;
 185          }
 186          block_info.data = &block;
 187      }
 188  
 189      CBlockUndo block_undo;
 190      if (CustomOptions().connect_undo_data) {
 191          if (pindex->nHeight > 0 && !m_chainstate->m_blockman.ReadBlockUndo(block_undo, *pindex)) {
 192              FatalErrorf("Failed to read undo block data %s from disk",
 193                          pindex->GetBlockHash().ToString());
 194              return false;
 195          }
 196          block_info.undo_data = &block_undo;
 197      }
 198  
 199      if (!CustomAppend(block_info)) {
 200          FatalErrorf("Failed to write block %s to index database",
 201                      pindex->GetBlockHash().ToString());
 202          return false;
 203      }
 204  
 205      return true;
 206  }
 207  
 208  void BaseIndex::Sync()
 209  {
 210      const CBlockIndex* pindex = m_best_block_index.load();
 211      if (!m_synced) {
 212          auto last_log_time{NodeClock::now()};
 213          auto last_locator_write_time{last_log_time};
 214          while (true) {
 215              if (m_interrupt) {
 216                  LogInfo("%s: m_interrupt set; exiting ThreadSync", GetName());
 217  
 218                  SetBestBlockIndex(pindex);
 219                  // No need to handle errors in Commit. If it fails, the error will be already be
 220                  // logged. The best way to recover is to continue, as index cannot be corrupted by
 221                  // a missed commit to disk for an advanced index state.
 222                  Commit();
 223                  return;
 224              }
 225  
 226              const CBlockIndex* pindex_next = WITH_LOCK(cs_main, return NextSyncBlock(pindex, m_chainstate->m_chain));
 227              // If pindex_next is null, it means pindex is the chain tip, so
 228              // commit data indexed so far.
 229              if (!pindex_next) {
 230                  SetBestBlockIndex(pindex);
 231                  // No need to handle errors in Commit. See rationale above.
 232                  Commit();
 233  
 234                  // If pindex is still the chain tip after committing, exit the
 235                  // sync loop. It is important for cs_main to be locked while
 236                  // setting m_synced = true, otherwise a new block could be
 237                  // attached while m_synced is still false, and it would not be
 238                  // indexed.
 239                  LOCK(::cs_main);
 240                  pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain);
 241                  if (!pindex_next) {
 242                      m_synced = true;
 243                      break;
 244                  }
 245              }
 246              if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) {
 247                  FatalErrorf("Failed to rewind %s to a previous chain tip", GetName());
 248                  return;
 249              }
 250              pindex = pindex_next;
 251  
 252  
 253              if (!ProcessBlock(pindex)) return; // error logged internally
 254  
 255              auto current_time{NodeClock::now()};
 256              if (current_time - last_log_time >= SYNC_LOG_INTERVAL) {
 257                  LogInfo("Syncing %s with block chain from height %d", GetName(), pindex->nHeight);
 258                  last_log_time = current_time;
 259              }
 260  
 261              if (current_time - last_locator_write_time >= SYNC_LOCATOR_WRITE_INTERVAL) {
 262                  SetBestBlockIndex(pindex);
 263                  last_locator_write_time = current_time;
 264                  // No need to handle errors in Commit. See rationale above.
 265                  Commit();
 266              }
 267          }
 268      }
 269  
 270      if (pindex) {
 271          LogInfo("%s is enabled at height %d", GetName(), pindex->nHeight);
 272      } else {
 273          LogInfo("%s is enabled", GetName());
 274      }
 275  }
 276  
 277  void BaseIndex::Commit()
 278  {
 279      // Don't commit anything if we haven't indexed any block yet
 280      // (this could happen if init is interrupted).
 281      bool ok = m_best_block_index != nullptr;
 282      if (ok) {
 283          // Don't commit if the index best block is not an ancestor of the chainstate's last flushed
 284          // block. Otherwise, after an unclean shutdown, the index could be
 285          // persisted ahead of a chainstate it can no longer roll back to, which
 286          // would corrupt indexes with state (e.g. coinstatsindex).
 287          const CBlockIndex* index_tip = m_best_block_index.load();
 288          const CBlockIndex* last_flushed = WITH_LOCK(::cs_main, return m_chainstate->GetLastFlushedBlock());
 289          if (!last_flushed || last_flushed->GetAncestor(index_tip->nHeight) != index_tip) {
 290              LogDebug(BCLog::COINDB, "Skipping commit, index is ahead of flushed chainstate (index height %d, last flush at height %d)",
 291                      index_tip->nHeight, last_flushed ? last_flushed->nHeight : -1);
 292              return;
 293          }
 294          CDBBatch batch(GetDB());
 295          ok = CustomCommit(batch);
 296          if (ok) {
 297              GetDB().WriteBestBlock(batch, GetLocator(*m_chain, m_best_block_index.load()->GetBlockHash()));
 298              GetDB().WriteBatch(batch);
 299          }
 300      }
 301      if (!ok) {
 302          LogError("Failed to commit latest %s state", GetName());
 303      }
 304  }
 305  
 306  bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_tip)
 307  {
 308      assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip);
 309  
 310      CBlock block;
 311      CBlockUndo block_undo;
 312  
 313      for (const CBlockIndex* iter_tip = current_tip; iter_tip != new_tip; iter_tip = iter_tip->pprev) {
 314          interfaces::BlockInfo block_info = kernel::MakeBlockInfo(iter_tip);
 315          if (CustomOptions().disconnect_data) {
 316              if (!m_chainstate->m_blockman.ReadBlock(block, *iter_tip)) {
 317                  LogError("Failed to read block %s from disk",
 318                           iter_tip->GetBlockHash().ToString());
 319                  return false;
 320              }
 321              block_info.data = &block;
 322          }
 323          if (CustomOptions().disconnect_undo_data && iter_tip->nHeight > 0) {
 324              if (!m_chainstate->m_blockman.ReadBlockUndo(block_undo, *iter_tip)) {
 325                  return false;
 326              }
 327              block_info.undo_data = &block_undo;
 328          }
 329          if (!CustomRemove(block_info)) {
 330              return false;
 331          }
 332      }
 333  
 334      // Don't commit here - the committed index state must never be ahead of the
 335      // flushed chainstate, otherwise unclean restarts would lead to index corruption.
 336      // Pruning has a minimum of 288 blocks-to-keep and getting the index
 337      // out of sync may be possible but a users fault.
 338      // In case we reorg beyond the pruned depth, ReadBlock would
 339      // throw and lead to a graceful shutdown
 340      SetBestBlockIndex(new_tip);
 341      return true;
 342  }
 343  
 344  void BaseIndex::BlockConnected(const ChainstateRole& role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex)
 345  {
 346      // Ignore events from not fully validated chains to avoid out-of-order indexing.
 347      //
 348      // TODO at some point we could parameterize whether a particular index can be
 349      // built out of order, but for now just do the conservative simple thing.
 350      if (!role.validated) {
 351          return;
 352      }
 353  
 354      // Ignore BlockConnected signals until we have fully indexed the chain.
 355      if (!m_synced) {
 356          return;
 357      }
 358  
 359      const CBlockIndex* best_block_index = m_best_block_index.load();
 360      if (!best_block_index) {
 361          if (pindex->nHeight != 0) {
 362              FatalErrorf("First block connected is not the genesis block (height=%d)",
 363                         pindex->nHeight);
 364              return;
 365          }
 366      } else {
 367          // Ensure block connects to an ancestor of the current best block. This should be the case
 368          // most of the time, but may not be immediately after the sync thread catches up and sets
 369          // m_synced. Consider the case where there is a reorg and the blocks on the stale branch are
 370          // in the ValidationInterface queue backlog even after the sync thread has caught up to the
 371          // new chain tip. In this unlikely event, log a warning and let the queue clear.
 372          if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) {
 373              LogWarning("Block %s does not connect to an ancestor of "
 374                        "known best chain (tip=%s); not updating index",
 375                        pindex->GetBlockHash().ToString(),
 376                        best_block_index->GetBlockHash().ToString());
 377              return;
 378          }
 379          if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) {
 380              FatalErrorf("Failed to rewind %s to a previous chain tip",
 381                         GetName());
 382              return;
 383          }
 384      }
 385  
 386      // Dispatch block to child class; errors are logged internally and abort the node.
 387      if (ProcessBlock(pindex, block.get())) {
 388          // Setting the best block index is intentionally the last step of this
 389          // function, so BlockUntilSyncedToCurrentChain callers waiting for the
 390          // best block index to be updated can rely on the block being fully
 391          // processed, and the index object being safe to delete.
 392          SetBestBlockIndex(pindex);
 393      }
 394  }
 395  
 396  void BaseIndex::ChainStateFlushed(const ChainstateRole& role, const CBlockLocator& locator)
 397  {
 398      // Ignore events from not fully validated chains to avoid out-of-order indexing.
 399      if (!role.validated) {
 400          return;
 401      }
 402  
 403      if (!m_synced) {
 404          return;
 405      }
 406  
 407      const uint256& locator_tip_hash = locator.vHave.front();
 408      const CBlockIndex* locator_tip_index;
 409      {
 410          LOCK(cs_main);
 411          locator_tip_index = m_chainstate->m_blockman.LookupBlockIndex(locator_tip_hash);
 412      }
 413  
 414      if (!locator_tip_index) {
 415          FatalErrorf("First block (hash=%s) in locator was not found",
 416                     locator_tip_hash.ToString());
 417          return;
 418      }
 419  
 420      // This checks that ChainStateFlushed callbacks are received after BlockConnected. The check may fail
 421      // immediately after the sync thread catches up and sets m_synced. Consider the case where
 422      // there is a reorg and the blocks on the stale branch are in the ValidationInterface queue
 423      // backlog even after the sync thread has caught up to the new chain tip. In this unlikely
 424      // event, log a warning and let the queue clear.
 425      const CBlockIndex* best_block_index = m_best_block_index.load();
 426      if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) {
 427          LogWarning("Locator contains block (hash=%s) not on known best "
 428                    "chain (tip=%s); not writing index locator",
 429                    locator_tip_hash.ToString(),
 430                    best_block_index->GetBlockHash().ToString());
 431          return;
 432      }
 433  
 434      // No need to handle errors in Commit. If it fails, the error will be already be logged. The
 435      // best way to recover is to continue, as index cannot be corrupted by a missed commit to disk
 436      // for an advanced index state.
 437      Commit();
 438  }
 439  
 440  bool BaseIndex::BlockUntilSyncedToCurrentChain() const
 441  {
 442      AssertLockNotHeld(cs_main);
 443  
 444      if (!m_synced) {
 445          return false;
 446      }
 447  
 448      {
 449          // Skip the queue-draining stuff if we know we're caught up with
 450          // m_chain.Tip().
 451          LOCK(cs_main);
 452          const CBlockIndex* chain_tip = m_chainstate->m_chain.Tip();
 453          const CBlockIndex* best_block_index = m_best_block_index.load();
 454          if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
 455              return true;
 456          }
 457      }
 458  
 459      LogInfo("%s is catching up on block notifications", GetName());
 460      m_chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
 461      return true;
 462  }
 463  
 464  void BaseIndex::Interrupt()
 465  {
 466      m_interrupt();
 467  }
 468  
 469  bool BaseIndex::StartBackgroundSync()
 470  {
 471      if (!m_init) throw std::logic_error("Error: Cannot start a non-initialized index");
 472  
 473      m_thread_sync = std::thread(&util::TraceThread, m_thread_name, [this] { Sync(); });
 474      return true;
 475  }
 476  
 477  void BaseIndex::Stop()
 478  {
 479      if (m_chain->context()->validation_signals) {
 480          m_chain->context()->validation_signals->UnregisterValidationInterface(this);
 481      }
 482  
 483      if (m_thread_sync.joinable()) {
 484          m_thread_sync.join();
 485      }
 486  }
 487  
 488  IndexSummary BaseIndex::GetSummary() const
 489  {
 490      IndexSummary summary{};
 491      summary.name = GetName();
 492      summary.synced = m_synced;
 493      if (const auto& pindex = m_best_block_index.load()) {
 494          summary.best_block_height = pindex->nHeight;
 495          summary.best_block_hash = pindex->GetBlockHash();
 496      } else {
 497          summary.best_block_height = 0;
 498          summary.best_block_hash = m_chain->getBlockHash(0);
 499      }
 500      return summary;
 501  }
 502  
 503  void BaseIndex::SetBestBlockIndex(const CBlockIndex* block)
 504  {
 505      assert(!m_chainstate->m_blockman.IsPruneMode() || AllowPrune());
 506  
 507      if (AllowPrune() && block) {
 508          node::PruneLockInfo prune_lock;
 509          prune_lock.height_first = block->nHeight;
 510          WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock));
 511      }
 512  
 513      // Intentionally set m_best_block_index as the last step in this function,
 514      // after updating prune locks above, and after making any other references
 515      // to *this, so the BlockUntilSyncedToCurrentChain function (which checks
 516      // m_best_block_index as an optimization) can be used to wait for the last
 517      // BlockConnected notification and safely assume that prune locks are
 518      // updated and that the index object is safe to delete.
 519      m_best_block_index = block;
 520  }
 521