diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -646,7 +646,7 @@ /** Park a block. */ bool ParkBlock(const Config &config, CValidationState &state, - CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex *pindex); /** Remove invalidity status from a block and its descendants. */ void ResetBlockFailureFlags(CBlockIndex *pindex) diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -169,8 +169,7 @@ bool PreciousBlock(const Config &config, CValidationState &state, CBlockIndex *pindex) LOCKS_EXCLUDED(cs_main); bool UnwindBlock(const Config &config, CValidationState &state, - CBlockIndex *pindex, bool invalidate) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex *pindex, bool invalidate); void ResetBlockFailureFlags(CBlockIndex *pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); template @@ -3068,69 +3067,88 @@ bool CChainState::UnwindBlock(const Config &config, CValidationState &state, CBlockIndex *pindex, bool invalidate) { - AssertLockHeld(cs_main); - - // We first disconnect backwards and then mark the blocks as invalid. - // This prevents a case where pruned nodes may fail to invalidateblock - // and be left unable to start as they have no tip candidates (as there - // are no blocks that meet the "have data and are not invalid per - // nStatus" criteria for inclusion in setBlockIndexCandidates). + CBlockIndex *to_mark_failed = pindex; bool pindex_was_in_chain = false; - CBlockIndex *invalid_walk_tip = chainActive.Tip(); - DisconnectedBlockTransactions disconnectpool; - while (chainActive.Contains(pindex)) { + // Disconnect (descendants of) pindex, and mark them invalid. + while (true) { + if (ShutdownRequested()) break; + + LOCK(cs_main); + + if (!chainActive.Contains(pindex)) break; pindex_was_in_chain = true; + CBlockIndex *invalid_walk_tip = chainActive.Tip(); + // ActivateBestChain considers blocks already in chainActive // unconditionally valid already, so force disconnect away from it. - if (!DisconnectTip(config, state, &disconnectpool)) { - // It's probably hopeless to try to make the mempool consistent - // here if DisconnectTip failed, but we can try. - disconnectpool.updateMempoolForReorg(config, false); - return false; - } - } - // Now mark the blocks we just disconnected as descendants invalid - // (note this may not be all descendants). - while (pindex_was_in_chain && invalid_walk_tip != pindex) { + DisconnectedBlockTransactions disconnectpool; + bool ret = DisconnectTip(config, state, &disconnectpool); + // DisconnectTip will add transactions to disconnectpool. + // Adjust the mempool to be consistent with the new tip, adding + // transactions back to the mempool if disconnecting was successful. + disconnectpool.updateMempoolForReorg(config, /* fAddToMempool = */ ret); + if (!ret) return false; + assert(invalid_walk_tip->pprev == chainActive.Tip()); + + // We immediately mark the disconnected blocks as invalid. + // This prevents a case where pruned nodes may fail to invalidateblock + // and be left unable to start as they have no tip candidates (as there + // are no blocks that meet the "have data and are not invalid per + // nStatus" criteria for inclusion in setBlockIndexCandidates). invalid_walk_tip->nStatus = invalidate ? invalid_walk_tip->nStatus.withFailedParent() : invalid_walk_tip->nStatus.withParkedParent(); + setBlockIndexCandidates.erase(invalid_walk_tip); setDirtyBlockIndex.insert(invalid_walk_tip); - invalid_walk_tip = invalid_walk_tip->pprev; - } + setBlockIndexCandidates.insert(invalid_walk_tip->pprev); - // Mark the block as either invalid or parked. - pindex->nStatus = invalidate ? pindex->nStatus.withFailed() - : pindex->nStatus.withParked(); - setDirtyBlockIndex.insert(pindex); - if (invalidate) { - m_failed_blocks.insert(pindex); + // If we abort invalidation after this iteration, make sure + // the last disconnected block gets marked failed (rather than + // just child of failed) + to_mark_failed = invalid_walk_tip; } - // DisconnectTip will add transactions to disconnectpool; try to add these - // back to the mempool. - disconnectpool.updateMempoolForReorg(config, true); + { + // Mark pindex (or the last disconnected block) as invalid, regardless + // of whether it was in the main chain or not. + LOCK(cs_main); + if (chainActive.Contains(to_mark_failed)) { + // If the to-be-marked invalid block is in the active chain, + // something is interfering and we can't proceed. + return false; + } + to_mark_failed->nStatus = invalidate ? pindex->nStatus.withFailed() + : pindex->nStatus.withParked(); + setDirtyBlockIndex.insert(to_mark_failed); + setBlockIndexCandidates.erase(to_mark_failed); + if (invalidate) { + m_failed_blocks.insert(to_mark_failed); + } - // The resulting new best tip may not be in setBlockIndexCandidates anymore, - // so add it again. - for (const std::pair &it : mapBlockIndex) { - CBlockIndex *i = it.second; - if (i->IsValid(BlockValidity::TRANSACTIONS) && i->HaveTxsDownloaded() && - !setBlockIndexCandidates.value_comp()(i, chainActive.Tip())) { - setBlockIndexCandidates.insert(i); + // The resulting new best tip may not be in setBlockIndexCandidates + // anymore, so add it again. + for (const std::pair &it : + mapBlockIndex) { + CBlockIndex *i = it.second; + if (i->IsValid(BlockValidity::TRANSACTIONS) && + i->HaveTxsDownloaded() && + !setBlockIndexCandidates.value_comp()(i, chainActive.Tip())) { + setBlockIndexCandidates.insert(i); + } } - } - if (invalidate) { - InvalidChainFound(pindex); + if (invalidate) { + InvalidChainFound(to_mark_failed); + } } // Only notify about a new block tip if the active chain was modified. if (pindex_was_in_chain) { - uiInterface.NotifyBlockTip(IsInitialBlockDownload(), pindex->pprev); + uiInterface.NotifyBlockTip(IsInitialBlockDownload(), + to_mark_failed->pprev); } return true; } @@ -3161,7 +3179,6 @@ bool InvalidateBlock(const Config &config, CValidationState &state, CBlockIndex *pindex) { - LOCK(cs_main); return g_chainstate.UnwindBlock(config, state, pindex, true); }