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