diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -691,29 +691,83 @@ enum class FlushStateMode { NONE, IF_NEEDED, PERIODIC, ALWAYS }; /** - * CChainState stores and provides an API to update our local knowledge of the - * current best chain and header tree. + * Maintains a tree of blocks (stored in `m_block_index`) which is consulted + * to determine where the most-work tip is. * - * It generally provides access to the current block tree, as well as functions - * to provide new data, which it will appropriately validate and incorporate in - * its state as necessary. + * This data is used mostly in `CChainState` - information about, e.g., + * candidate tips is not maintained here. + */ +class BlockManager { +public: + BlockMap m_block_index GUARDED_BY(cs_main); + + /** + * In order to efficiently track invalidity of headers, we keep the set of + * blocks which we tried to connect and found to be invalid here (ie which + * were set to BLOCK_FAILED_VALID since the last restart). We can then + * walk this set and check if a new header is a descendant of something in + * this set, preventing us from having to walk m_block_index when we try + * to connect a bad block and fail. + * + * While this is more complicated than marking everything which descends + * from an invalid block as invalid at the time we discover it to be + * invalid, doing so would require walking all of m_block_index to find all + * descendants. Since this case should be very rare, keeping track of all + * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as + * well. + * + * Because we already walk m_block_index in height-order at startup, we go + * ahead and mark descendants of invalid blocks as FAILED_CHILD at that + * time, instead of putting things in this set. + */ + std::set m_failed_blocks; + + /** + * All pairs A->B, where A (or one of its ancestors) misses transactions, + * but B has transactions. Pruned nodes may have entries where B is missing + * data. + */ + std::multimap m_blocks_unlinked; + + bool LoadBlockIndex(const Consensus::Params &consensus_params, + CBlockTreeDB &blocktree) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** Clear all data members. */ + void Unload() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + CBlockIndex *AddToBlockIndex(const CBlockHeader &block) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** Create a new block index entry for a given block hash */ + CBlockIndex *InsertBlockIndex(const BlockHash &hash) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * If a block header hasn't already been seen, call CheckBlockHeader on it, + * ensure that it doesn't descend from an invalid block, and then add it to + * mapBlockIndex. + */ + bool AcceptBlockHeader(const Config &config, const CBlockHeader &block, + BlockValidationState &state, CBlockIndex **ppindex) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); +}; + +/** + * CChainState stores and provides an API to update our local knowledge of the + * current best chain. * * Eventually, the API here is targeted at being exposed externally as a * consumable libconsensus library, so any functions added must only call * other class member functions, pure functions in other parts of the consensus * library, callbacks via the validation interface, or read/write-to-disk * functions (eventually this will also be via callbacks). + * + * Anything that is contingent on the current tip of the chain is stored here, + * whereas block information and metadata independent of the current tip is + * kept in `BlockMetadataManager`. */ class CChainState { private: - /** - * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for - * itself and all ancestors) and as good as our current tip or better. - * Entries may be failed or parked though, and pruning nodes may be missing - * the data for the block; these will get cleaned during FindMostWorkChain. - */ - std::set setBlockIndexCandidates; - /** * the ChainState CriticalSection * A lock that must be held when modifying this ChainState - held in @@ -732,27 +786,6 @@ /** chainwork for the last block that preciousblock has been applied to. */ arith_uint256 nLastPreciousChainwork = 0; - /** - * In order to efficiently track invalidity of headers, we keep the set of - * blocks which we tried to connect and found to be invalid here (ie which - * were set to BLOCK_FAILED_VALID since the last restart). We can then - * walk this set and check if a new header is a descendant of something in - * this set, preventing us from having to walk mapBlockIndex when we try - * to connect a bad block and fail. - * - * While this is more complicated than marking everything which descends - * from an invalid block as invalid at the time we discover it to be - * invalid, doing so would require walking all of mapBlockIndex to find all - * descendants. Since this case should be very rare, keeping track of all - * BLOCK_FAILED_VALID blocks in a set should be just fine and work just as - * well. - * - * Because we already walk mapBlockIndex in height-order at startup, we go - * ahead and mark descendants of invalid blocks as FAILED_CHILD at that - * time, instead of putting things in this set. - */ - std::set m_failed_blocks; - /** * Whether this chainstate is undergoing initial block download. * @@ -761,17 +794,27 @@ */ mutable std::atomic m_cached_finished_ibd{false}; + //! Reference to a BlockManager instance which itself is shared across all + //! CChainState instances. Keeping a local reference allows us to test more + //! easily as opposed to referencing a global. + BlockManager &m_blockman; + public: + explicit CChainState(BlockManager &blockman) : m_blockman(blockman) {} + + //! The current chain of blockheaders we consult and build on. + //! @see CChain, CBlockIndex. CChain m_chain; - BlockMap mapBlockIndex GUARDED_BY(cs_main); - std::multimap mapBlocksUnlinked; CBlockIndex *pindexBestInvalid = nullptr; CBlockIndex *pindexBestParked = nullptr; CBlockIndex const *pindexFinalized = nullptr; - - bool LoadBlockIndex(const Consensus::Params ¶ms, - CBlockTreeDB &blocktree) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for + * itself and all ancestors) and as good as our current tip or better. + * Entries may be failed, though, and pruning nodes may be missing the data + * for the block. + */ + std::set setBlockIndexCandidates; /** * Update the on-disk chain state. @@ -798,14 +841,6 @@ std::shared_ptr pblock = std::shared_ptr()) LOCKS_EXCLUDED(cs_main); - /** - * If a block header hasn't already been seen, call CheckBlockHeader on it, - * ensure that it doesn't descend from an invalid block, and then add it to - * mapBlockIndex. - */ - bool AcceptBlockHeader(const Config &config, const CBlockHeader &block, - BlockValidationState &state, CBlockIndex **ppindex) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); bool AcceptBlock(const Config &config, const std::shared_ptr &pblock, BlockValidationState &state, bool fRequested, @@ -871,6 +906,14 @@ */ bool IsInitialBlockDownload() const; + /** + * Make various assertions about the state of the block index. + * + * By default this only executes fully when using the Regtest chain; see: + * fCheckBlockIndex. + */ + void CheckBlockIndex(const Consensus::Params &consensusParams); + private: bool ActivateBestChainStep(const Config &config, BlockValidationState &state, @@ -884,28 +927,13 @@ ConnectTrace &connectTrace, DisconnectedBlockTransactions &disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - CBlockIndex *AddToBlockIndex(const CBlockHeader &block) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool MarkBlockAsFinal(const Config &config, BlockValidationState &state, - const CBlockIndex *pindex) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - - /** Create a new block index entry for a given block hash */ - CBlockIndex *InsertBlockIndex(const BlockHash &hash) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** - * Make various assertions about the state of the block index. - * - * By default this only executes fully when using the Regtest chain; see: - * fCheckBlockIndex. - */ - void CheckBlockIndex(const Consensus::Params &consensusParams); - void InvalidBlockFound(CBlockIndex *pindex, const BlockValidationState &state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); CBlockIndex *FindMostWorkChain() EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool MarkBlockAsFinal(const Config &config, BlockValidationState &state, + const CBlockIndex *pindex) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); void ReceivedBlockTransactions(const CBlock &block, CBlockIndex *pindexNew, const FlatFilePos &pos) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -58,7 +58,11 @@ #define MICRO 0.000001 #define MILLI 0.001 -static CChainState g_chainstate; +namespace { +BlockManager g_blockman; +} // namespace + +static CChainState g_chainstate(g_blockman); CChainState &ChainstateActive() { return g_chainstate; @@ -82,7 +86,7 @@ */ RecursiveMutex cs_main; -BlockMap &mapBlockIndex = ::ChainstateActive().mapBlockIndex; +BlockMap &mapBlockIndex = g_blockman.m_block_index; CBlockIndex *pindexBestHeader = nullptr; Mutex g_best_block_mutex; std::condition_variable g_best_block_cv; @@ -119,13 +123,6 @@ */ CBlockIndex const *&pindexFinalized = ::ChainstateActive().pindexFinalized; -/** - * All pairs A->B, where A (or one of its ancestors) misses transactions, but B - * has transactions. Pruned nodes may have entries where B is missing data. - */ -std::multimap &mapBlocksUnlinked = - ::ChainstateActive().mapBlocksUnlinked; - RecursiveMutex cs_LastBlockFile; std::vector vinfoBlockFile; int nLastBlockFile = 0; @@ -956,7 +953,7 @@ const BlockValidationState &state) { if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus = pindex->nStatus.withFailed(); - m_failed_blocks.insert(pindex); + m_blockman.m_failed_blocks.insert(pindex); setDirtyBlockIndex.insert(pindex); InvalidChainFound(pindex); } @@ -1560,8 +1557,9 @@ // defaults can be easily reviewed. This setting doesn't force the // selection of any particular chain but makes validating some faster by // effectively caching the result of part of the verification. - BlockMap::const_iterator it = mapBlockIndex.find(hashAssumeValid); - if (it != mapBlockIndex.end()) { + BlockMap::const_iterator it = + m_blockman.m_block_index.find(hashAssumeValid); + if (it != m_blockman.m_block_index.end()) { if (it->second->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->GetAncestor(pindex->nHeight) == pindex && pindexBestHeader->nChainWork >= nMinimumChainWork) { @@ -2585,10 +2583,10 @@ .withParkedParent(fParkedChain); } else if (fMissingData) { // If we're missing data, then add back to - // mapBlocksUnlinked, so that if the block arrives in the + // m_blocks_unlinked, so that if the block arrives in the // future we can try adding to setBlockIndexCandidates // again. - mapBlocksUnlinked.insert( + m_blockman.m_blocks_unlinked.insert( std::make_pair(pindexFailed->pprev, pindexFailed)); } setBlockIndexCandidates.erase(pindexFailed); @@ -2993,7 +2991,7 @@ { LOCK(cs_main); - for (const auto &entry : mapBlockIndex) { + for (const auto &entry : m_blockman.m_block_index) { CBlockIndex *candidate = entry.second; // We don't need to put anything in our active chain into the // multimap, because those candidates will be found and considered @@ -3114,7 +3112,7 @@ : to_mark_failed_or_parked->nStatus.withParked(); setDirtyBlockIndex.insert(to_mark_failed_or_parked); if (invalidate) { - m_failed_blocks.insert(to_mark_failed_or_parked); + m_blockman.m_failed_blocks.insert(to_mark_failed_or_parked); } // If any new blocks somehow arrived while we were disconnecting @@ -3125,7 +3123,7 @@ // Loop back over all block index entries and add any missing entries // to setBlockIndexCandidates. for (const std::pair &it : - mapBlockIndex) { + m_blockman.m_block_index) { CBlockIndex *i = it.second; if (i->IsValid(BlockValidity::TRANSACTIONS) && i->HaveTxsDownloaded() && @@ -3219,7 +3217,7 @@ pindex->nStatus = newStatus; setDirtyBlockIndex.insert(pindex); if (newStatus.isValid()) { - m_failed_blocks.erase(pindex); + m_blockman.m_failed_blocks.erase(pindex); } if (pindex->IsValid(BlockValidity::TRANSACTIONS) && @@ -3256,8 +3254,8 @@ } // Update all blocks under modified blocks. - BlockMap::iterator it = mapBlockIndex.begin(); - while (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_blockman.m_block_index.begin(); + while (it != m_blockman.m_block_index.end()) { UpdateFlagsForBlock(pindex, it->second, fChild); UpdateFlagsForBlock(pindexDeepestChanged, it->second, fAncestorWasChanged); @@ -3327,13 +3325,13 @@ pindexFinalized->GetAncestor(pindex->nHeight) == pindex; } -CBlockIndex *CChainState::AddToBlockIndex(const CBlockHeader &block) { +CBlockIndex *BlockManager::AddToBlockIndex(const CBlockHeader &block) { AssertLockHeld(cs_main); // Check for duplicate BlockHash hash = block.GetHash(); - BlockMap::iterator it = mapBlockIndex.find(hash); - if (it != mapBlockIndex.end()) { + BlockMap::iterator it = m_block_index.find(hash); + if (it != m_block_index.end()) { return it->second; } @@ -3344,10 +3342,10 @@ // competitive advantage. pindexNew->nSequenceId = 0; BlockMap::iterator mi = - mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); - BlockMap::iterator miPrev = mapBlockIndex.find(block.hashPrevBlock); - if (miPrev != mapBlockIndex.end()) { + BlockMap::iterator miPrev = m_block_index.find(block.hashPrevBlock); + if (miPrev != m_block_index.end()) { pindexNew->pprev = (*miPrev).second; pindexNew->nHeight = pindexNew->pprev->nHeight + 1; pindexNew->BuildSkip(); @@ -3413,18 +3411,19 @@ std::pair::iterator, std::multimap::iterator> - range = mapBlocksUnlinked.equal_range(pindex); + range = m_blockman.m_blocks_unlinked.equal_range(pindex); while (range.first != range.second) { std::multimap::iterator it = range.first; queue.push_back(it->second); range.first++; - mapBlocksUnlinked.erase(it); + m_blockman.m_blocks_unlinked.erase(it); } } } else if (pindexNew->pprev && pindexNew->pprev->IsValid(BlockValidity::TREE)) { - mapBlocksUnlinked.insert(std::make_pair(pindexNew->pprev, pindexNew)); + m_blockman.m_blocks_unlinked.insert( + std::make_pair(pindexNew->pprev, pindexNew)); } } @@ -3839,19 +3838,19 @@ * * Returns true if the block is successfully added to the block index. */ -bool CChainState::AcceptBlockHeader(const Config &config, - const CBlockHeader &block, - BlockValidationState &state, - CBlockIndex **ppindex) { +bool BlockManager::AcceptBlockHeader(const Config &config, + const CBlockHeader &block, + BlockValidationState &state, + CBlockIndex **ppindex) { AssertLockHeld(cs_main); const CChainParams &chainparams = config.GetChainParams(); // Check for duplicate BlockHash hash = block.GetHash(); - BlockMap::iterator miSelf = mapBlockIndex.find(hash); + BlockMap::iterator miSelf = m_block_index.find(hash); CBlockIndex *pindex = nullptr; if (hash != chainparams.GetConsensus().hashGenesisBlock) { - if (miSelf != mapBlockIndex.end()) { + if (miSelf != m_block_index.end()) { // Block header is already known. pindex = miSelf->second; if (ppindex) { @@ -3876,8 +3875,8 @@ } // Get prev block index - BlockMap::iterator mi = mapBlockIndex.find(block.hashPrevBlock); - if (mi == mapBlockIndex.end()) { + BlockMap::iterator mi = m_block_index.find(block.hashPrevBlock); + if (mi == m_block_index.end()) { LogPrintf("ERROR: %s: prev block not found\n", __func__); return state.Invalid(BlockValidationResult::BLOCK_MISSING_PREV, 0, "prev-blk-not-found"); @@ -3949,7 +3948,6 @@ *ppindex = pindex; } - CheckBlockIndex(chainparams.GetConsensus()); return true; } @@ -3963,8 +3961,12 @@ for (const CBlockHeader &header : headers) { // Use a temp pindex instead of ppindex to avoid a const_cast CBlockIndex *pindex = nullptr; - if (!::ChainstateActive().AcceptBlockHeader(config, header, state, - &pindex)) { + bool accepted = + g_blockman.AcceptBlockHeader(config, header, state, &pindex); + ::ChainstateActive().CheckBlockIndex( + config.GetChainParams().GetConsensus()); + + if (!accepted) { return false; } @@ -4027,7 +4029,12 @@ } CBlockIndex *pindex = nullptr; - if (!AcceptBlockHeader(config, block, state, &pindex)) { + + bool accepted_header = + m_blockman.AcceptBlockHeader(config, block, state, &pindex); + CheckBlockIndex(config.GetChainParams().GetConsensus()); + + if (!accepted_header) { return false; } @@ -4279,7 +4286,7 @@ void PruneOneBlockFile(const int fileNumber) { LOCK(cs_LastBlockFile); - for (const auto &entry : mapBlockIndex) { + for (const auto &entry : g_blockman.m_block_index) { CBlockIndex *pindex = entry.second; if (pindex->nFile == fileNumber) { pindex->nStatus = pindex->nStatus.withData(false).withUndo(false); @@ -4288,19 +4295,18 @@ pindex->nUndoPos = 0; setDirtyBlockIndex.insert(pindex); - // Prune from mapBlocksUnlinked -- any block we prune would have + // Prune from m_blocks_unlinked -- any block we prune would have // to be downloaded again in order to consider its chain, at which // point it would be considered as a candidate for - // mapBlocksUnlinked or setBlockIndexCandidates. - std::pair::iterator, - std::multimap::iterator> - range = mapBlocksUnlinked.equal_range(pindex->pprev); + // m_blocks_unlinked or setBlockIndexCandidates. + auto range = + g_blockman.m_blocks_unlinked.equal_range(pindex->pprev); while (range.first != range.second) { std::multimap::iterator _it = range.first; range.first++; if (_it->second == pindex) { - mapBlocksUnlinked.erase(_it); + g_blockman.m_blocks_unlinked.erase(_it); } } } @@ -4470,7 +4476,7 @@ return BlockFileSeq().FileName(pos); } -CBlockIndex *CChainState::InsertBlockIndex(const BlockHash &hash) { +CBlockIndex *BlockManager::InsertBlockIndex(const BlockHash &hash) { AssertLockHeld(cs_main); if (hash.IsNull()) { @@ -4478,21 +4484,21 @@ } // Return existing - BlockMap::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) { + BlockMap::iterator mi = m_block_index.find(hash); + if (mi != m_block_index.end()) { return (*mi).second; } // Create new CBlockIndex *pindexNew = new CBlockIndex(); - mi = mapBlockIndex.insert(std::make_pair(hash, pindexNew)).first; + mi = m_block_index.insert(std::make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); return pindexNew; } -bool CChainState::LoadBlockIndex(const Consensus::Params ¶ms, - CBlockTreeDB &blocktree) { +bool BlockManager::LoadBlockIndex(const Consensus::Params ¶ms, + CBlockTreeDB &blocktree) { AssertLockHeld(cs_main); if (!blocktree.LoadBlockIndexGuts( params, [this](const BlockHash &hash) EXCLUSIVE_LOCKS_REQUIRED( @@ -4524,7 +4530,7 @@ // at some point. Pruned nodes may have deleted the block. if (pindex->nTx > 0) { if (!pindex->UpdateChainStats() && pindex->pprev) { - mapBlocksUnlinked.insert(std::make_pair(pindex->pprev, pindex)); + m_blocks_unlinked.insert(std::make_pair(pindex->pprev, pindex)); } } @@ -4535,7 +4541,7 @@ } if (pindex->IsValid(BlockValidity::TRANSACTIONS) && (pindex->HaveTxsDownloaded() || pindex->pprev == nullptr)) { - setBlockIndexCandidates.insert(pindex); + ::ChainstateActive().setBlockIndexCandidates.insert(pindex); } if (pindex->nStatus.isInvalid() && @@ -4564,9 +4570,20 @@ return true; } +void BlockManager::Unload() { + m_failed_blocks.clear(); + m_blocks_unlinked.clear(); + + for (const BlockMap::value_type &entry : m_block_index) { + delete entry.second; + } + + m_block_index.clear(); +} + static bool LoadBlockIndexDB(const Consensus::Params ¶ms) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - if (!::ChainstateActive().LoadBlockIndex(params, *pblocktree)) { + if (!g_blockman.LoadBlockIndex(params, *pblocktree)) { return false; } @@ -4965,7 +4982,6 @@ // logic assumes a consistent block index state void CChainState::UnloadBlockIndex() { nBlockSequenceId = 1; - m_failed_blocks.clear(); setBlockIndexCandidates.clear(); } @@ -4975,6 +4991,7 @@ void UnloadBlockIndex() { LOCK(cs_main); ::ChainActive().SetTip(nullptr); + g_blockman.Unload(); pindexFinalized = nullptr; pindexBestInvalid = nullptr; pindexBestParked = nullptr; @@ -4983,17 +5000,10 @@ pindexBestForkBase = nullptr; ResetASERTAnchorBlockCache(); g_mempool.clear(); - mapBlocksUnlinked.clear(); vinfoBlockFile.clear(); nLastBlockFile = 0; setDirtyBlockIndex.clear(); setDirtyFileInfo.clear(); - - for (const BlockMap::value_type &entry : mapBlockIndex) { - delete entry.second; - } - - mapBlockIndex.clear(); fHavePruned = false; ::ChainstateActive().UnloadBlockIndex(); @@ -5040,7 +5050,7 @@ if (blockPos.IsNull()) { return error("%s: writing genesis block to disk failed", __func__); } - CBlockIndex *pindex = AddToBlockIndex(block); + CBlockIndex *pindex = m_blockman.AddToBlockIndex(block); ReceivedBlockTransactions(block, pindex, blockPos); } catch (const std::runtime_error &e) { return error("%s: failed to write genesis block: %s", __func__, @@ -5399,7 +5409,7 @@ // If some parent is missing, then it could be that this block // was in setBlockIndexCandidates but had to be removed because // of the missing data. In this case it must be in - // mapBlocksUnlinked -- see test below. + // m_blocks_unlinked -- see test below. } } else { // If this block sorts worse than the current tip or some ancestor's @@ -5407,10 +5417,11 @@ // setBlockIndexCandidates. assert(setBlockIndexCandidates.count(pindex) == 0); } - // Check whether this block is in mapBlocksUnlinked. + // Check whether this block is in m_blocks_unlinked. std::pair::iterator, std::multimap::iterator> - rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev); + rangeUnlinked = + m_blockman.m_blocks_unlinked.equal_range(pindex->pprev); bool foundInUnlinked = false; while (rangeUnlinked.first != rangeUnlinked.second) { assert(rangeUnlinked.first->first == pindex->pprev); @@ -5425,16 +5436,16 @@ pindexFirstInvalid == nullptr) { // If this block has block data available, some parent was never // received, and has no invalid parents, it must be in - // mapBlocksUnlinked. + // m_blocks_unlinked. assert(foundInUnlinked); } if (!pindex->nStatus.hasData()) { - // Can't be in mapBlocksUnlinked if we don't HAVE_DATA + // Can't be in m_blocks_unlinked if we don't HAVE_DATA assert(!foundInUnlinked); } if (pindexFirstMissing == nullptr) { // We aren't missing data for any parent -- cannot be in - // mapBlocksUnlinked. + // m_blocks_unlinked. assert(!foundInUnlinked); } if (pindex->pprev && pindex->nStatus.hasData() && @@ -5444,7 +5455,7 @@ // at some point, but we're currently missing data for some parent. // We must have pruned. assert(fHavePruned); - // This block may have entered mapBlocksUnlinked if: + // This block may have entered m_blocks_unlinked if: // - it has a descendant that at some point had more work than the // tip, and // - we tried switching to that descendant but were missing @@ -5452,7 +5463,7 @@ // tip. // So if this block is itself better than m_chain.Tip() and it // wasn't in - // setBlockIndexCandidates, then it must be in mapBlocksUnlinked. + // setBlockIndexCandidates, then it must be in m_blocks_unlinked. if (!CBlockIndexWorkComparator()(pindex, m_chain.Tip()) && setBlockIndexCandidates.count(pindex) == 0) { if (pindexFirstInvalid == nullptr) {