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