diff --git a/src/index/base.cpp b/src/index/base.cpp index 055f388db..450f0855b 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -1,331 +1,331 @@ // Copyright (c) 2017-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include constexpr char DB_BEST_BLOCK = 'B'; constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30; // seconds template static void FatalError(const char *fmt, const Args &... args) { std::string strMessage = tfm::format(fmt, args...); SetMiscWarning(strMessage); LogPrintf("*** %s\n", strMessage); uiInterface.ThreadSafeMessageBox( Untranslated("Error: A fatal internal error occurred, see debug.log " "for details"), "", CClientUIInterface::MSG_ERROR); StartShutdown(); } BaseIndex::DB::DB(const fs::path &path, size_t n_cache_size, bool f_memory, bool f_wipe, bool f_obfuscate) : CDBWrapper(path, n_cache_size, f_memory, f_wipe, f_obfuscate) {} bool BaseIndex::DB::ReadBestBlock(CBlockLocator &locator) const { bool success = Read(DB_BEST_BLOCK, locator); if (!success) { locator.SetNull(); } return success; } void BaseIndex::DB::WriteBestBlock(CDBBatch &batch, const CBlockLocator &locator) { batch.Write(DB_BEST_BLOCK, locator); } BaseIndex::~BaseIndex() { Interrupt(); Stop(); } bool BaseIndex::Init() { CBlockLocator locator; if (!GetDB().ReadBestBlock(locator)) { locator.SetNull(); } LOCK(cs_main); if (locator.IsNull()) { m_best_block_index = nullptr; } else { m_best_block_index = FindForkInGlobalIndex(::ChainActive(), locator); } m_synced = m_best_block_index.load() == ::ChainActive().Tip(); return true; } static const CBlockIndex *NextSyncBlock(const CBlockIndex *pindex_prev) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { AssertLockHeld(cs_main); if (!pindex_prev) { return ::ChainActive().Genesis(); } const CBlockIndex *pindex = ::ChainActive().Next(pindex_prev); if (pindex) { return pindex; } return ::ChainActive().Next(::ChainActive().FindFork(pindex_prev)); } void BaseIndex::ThreadSync() { const CBlockIndex *pindex = m_best_block_index.load(); if (!m_synced) { auto &consensus_params = GetConfig().GetChainParams().GetConsensus(); int64_t last_log_time = 0; int64_t last_locator_write_time = 0; while (true) { if (m_interrupt) { m_best_block_index = pindex; // No need to handle errors in Commit. If it fails, the error // will be already be logged. The best way to recover is to // continue, as index cannot be corrupted by a missed commit to // disk for an advanced index state. Commit(); return; } { LOCK(cs_main); const CBlockIndex *pindex_next = NextSyncBlock(pindex); if (!pindex_next) { m_best_block_index = pindex; m_synced = true; // No need to handle errors in Commit. See rationale above. Commit(); break; } if (pindex_next->pprev != pindex && !Rewind(pindex, pindex_next->pprev)) { FatalError( "%s: Failed to rewind index %s to a previous chain tip", __func__, GetName()); return; } pindex = pindex_next; } int64_t current_time = GetTime(); if (last_log_time + SYNC_LOG_INTERVAL < current_time) { LogPrintf("Syncing %s with block chain from height %d\n", GetName(), pindex->nHeight); last_log_time = current_time; } if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) { m_best_block_index = pindex; last_locator_write_time = current_time; // No need to handle errors in Commit. See rationale above. Commit(); } CBlock block; if (!ReadBlockFromDisk(block, pindex, consensus_params)) { FatalError("%s: Failed to read block %s from disk", __func__, pindex->GetBlockHash().ToString()); return; } if (!WriteBlock(block, pindex)) { FatalError("%s: Failed to write block %s to index database", __func__, pindex->GetBlockHash().ToString()); return; } } } if (pindex) { LogPrintf("%s is enabled at height %d\n", GetName(), pindex->nHeight); } else { LogPrintf("%s is enabled\n", GetName()); } } bool BaseIndex::Commit() { CDBBatch batch(GetDB()); if (!CommitInternal(batch) || !GetDB().WriteBatch(batch)) { return error("%s: Failed to commit latest %s state", __func__, GetName()); } return true; } bool BaseIndex::CommitInternal(CDBBatch &batch) { LOCK(cs_main); GetDB().WriteBestBlock(batch, ::ChainActive().GetLocator(m_best_block_index)); return true; } bool BaseIndex::Rewind(const CBlockIndex *current_tip, const CBlockIndex *new_tip) { assert(current_tip == m_best_block_index); assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); // In the case of a reorg, ensure persisted block locator is not stale. m_best_block_index = new_tip; if (!Commit()) { // If commit fails, revert the best block index to avoid corruption. m_best_block_index = current_tip; return false; } return true; } void BaseIndex::BlockConnected( const std::shared_ptr &block, const CBlockIndex *pindex, const std::vector &txn_conflicted) { if (!m_synced) { return; } const CBlockIndex *best_block_index = m_best_block_index.load(); if (!best_block_index) { if (pindex->nHeight != 0) { FatalError("%s: First block connected is not the genesis block " "(height=%d)", __func__, pindex->nHeight); return; } } else { // Ensure block connects to an ancestor of the current best block. This // should be the case most of the time, but may not be immediately after // the the sync thread catches up and sets m_synced. Consider the case // where there is a reorg and the blocks on the stale branch are in the // ValidationInterface queue backlog even after the sync thread has // caught up to the new chain tip. In this unlikely event, log a warning // and let the queue clear. if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) { LogPrintf("%s: WARNING: Block %s does not connect to an ancestor " "of known best chain (tip=%s); not updating index\n", __func__, pindex->GetBlockHash().ToString(), best_block_index->GetBlockHash().ToString()); return; } if (best_block_index != pindex->pprev && !Rewind(best_block_index, pindex->pprev)) { FatalError("%s: Failed to rewind index %s to a previous chain tip", __func__, GetName()); return; } } if (WriteBlock(*block, pindex)) { m_best_block_index = pindex; } else { FatalError("%s: Failed to write block %s to index", __func__, pindex->GetBlockHash().ToString()); return; } } void BaseIndex::ChainStateFlushed(const CBlockLocator &locator) { if (!m_synced) { return; } const BlockHash &locator_tip_hash = locator.vHave.front(); const CBlockIndex *locator_tip_index; { LOCK(cs_main); locator_tip_index = LookupBlockIndex(locator_tip_hash); } if (!locator_tip_index) { FatalError("%s: First block (hash=%s) in locator was not found", __func__, locator_tip_hash.ToString()); return; } // This checks that ChainStateFlushed callbacks are received after // BlockConnected. The check may fail immediately after the the sync thread // catches up and sets m_synced. Consider the case where there is a reorg // and the blocks on the stale branch are in the ValidationInterface queue // backlog even after the sync thread has caught up to the new chain tip. In // this unlikely event, log a warning and let the queue clear. const CBlockIndex *best_block_index = m_best_block_index.load(); if (best_block_index->GetAncestor(locator_tip_index->nHeight) != locator_tip_index) { LogPrintf("%s: WARNING: Locator contains block (hash=%s) not on known " "best chain (tip=%s); not writing index locator\n", __func__, locator_tip_hash.ToString(), best_block_index->GetBlockHash().ToString()); return; } // No need to handle errors in Commit. If it fails, the error will be // already be logged. The best way to recover is to continue, as index // cannot be corrupted by a missed commit to disk for an advanced index // state. Commit(); } -bool BaseIndex::BlockUntilSyncedToCurrentChain() { +bool BaseIndex::BlockUntilSyncedToCurrentChain() const { AssertLockNotHeld(cs_main); if (!m_synced) { return false; } { // Skip the queue-draining stuff if we know we're caught up with // ::ChainActive().Tip(). LOCK(cs_main); const CBlockIndex *chain_tip = ::ChainActive().Tip(); const CBlockIndex *best_block_index = m_best_block_index.load(); if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) { return true; } } LogPrintf("%s: %s is catching up on block notifications\n", __func__, GetName()); SyncWithValidationInterfaceQueue(); return true; } void BaseIndex::Interrupt() { m_interrupt(); } void BaseIndex::Start() { // Need to register this ValidationInterface before running Init(), so that // callbacks are not missed if Init sets m_synced to true. RegisterValidationInterface(this); if (!Init()) { FatalError("%s: %s failed to initialize", __func__, GetName()); return; } m_thread_sync = std::thread(&TraceThread>, GetName(), std::bind(&BaseIndex::ThreadSync, this)); } void BaseIndex::Stop() { UnregisterValidationInterface(this); if (m_thread_sync.joinable()) { m_thread_sync.join(); } } diff --git a/src/index/base.h b/src/index/base.h index 05936c157..248ab15e5 100644 --- a/src/index/base.h +++ b/src/index/base.h @@ -1,117 +1,117 @@ // Copyright (c) 2017-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INDEX_BASE_H #define BITCOIN_INDEX_BASE_H #include #include #include #include #include class CBlockIndex; /** * Base class for indices of blockchain data. This implements * CValidationInterface and ensures blocks are indexed sequentially according * to their position in the active chain. */ class BaseIndex : public CValidationInterface { protected: class DB : public CDBWrapper { public: DB(const fs::path &path, size_t n_cache_size, bool f_memory = false, bool f_wipe = false, bool f_obfuscate = false); /// Read block locator of the chain that the txindex is in sync with. bool ReadBestBlock(CBlockLocator &locator) const; /// Write block locator of the chain that the txindex is in sync with. void WriteBestBlock(CDBBatch &batch, const CBlockLocator &locator); }; private: /// Whether the index is in sync with the main chain. The flag is flipped /// from false to true once, after which point this starts processing /// ValidationInterface notifications to stay in sync. std::atomic m_synced{false}; /// The last block in the chain that the index is in sync with. std::atomic m_best_block_index{nullptr}; std::thread m_thread_sync; CThreadInterrupt m_interrupt; /// Sync the index with the block index starting from the current best /// block. Intended to be run in its own thread, m_thread_sync, and can be /// interrupted with m_interrupt. Once the index gets in sync, the m_synced /// flag is set and the BlockConnected ValidationInterface callback takes /// over and the sync thread exits. void ThreadSync(); /// Write the current index state (eg. chain block locator and /// subclass-specific items) to disk. /// /// Recommendations for error handling: /// If called on a successor of the previous committed best block in the /// index, the index can continue processing without risk of corruption, /// though the index state will need to catch up from further behind on /// reboot. If the new state is not a successor of the previous state (due /// to a chain reorganization), the index must halt until Commit succeeds or /// else it could end up getting corrupted. bool Commit(); protected: void BlockConnected(const std::shared_ptr &block, const CBlockIndex *pindex, const std::vector &txn_conflicted) override; void ChainStateFlushed(const CBlockLocator &locator) override; /// Initialize internal state from the database and block index. virtual bool Init(); /// Write update index entries for a newly connected block. virtual bool WriteBlock(const CBlock &block, const CBlockIndex *pindex) { return true; } /// Virtual method called internally by Commit that can be overridden to /// atomically commit more index state. virtual bool CommitInternal(CDBBatch &batch); /// Rewind index to an earlier chain tip during a chain reorg. The tip must /// be an ancestor of the current best block. virtual bool Rewind(const CBlockIndex *current_tip, const CBlockIndex *new_tip); virtual DB &GetDB() const = 0; /// Get the name of the index for display in logs. virtual const char *GetName() const = 0; public: /// Destructor interrupts sync thread if running and blocks until it exits. virtual ~BaseIndex(); /// Blocks the current thread until the index is caught up to the current /// state of the block chain. This only blocks if the index has gotten in /// sync once and only needs to process blocks in the ValidationInterface /// queue. If the index is catching up from far behind, this method does /// not block and immediately returns false. - bool BlockUntilSyncedToCurrentChain(); + bool BlockUntilSyncedToCurrentChain() const; void Interrupt(); /// Start initializes the sync state and registers the instance as a /// ValidationInterface so that it stays in sync with blockchain updates. void Start(); /// Stops the instance from staying in sync with blockchain updates. void Stop(); }; #endif // BITCOIN_INDEX_BASE_H diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index d6e6e6d10..3f763c7be 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -1,506 +1,508 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include