diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3025,69 +3025,101 @@ bool CChainState::UnwindBlock(const Config &config, CValidationState &state, CBlockIndex *pindex, bool invalidate) { - LOCK(cs_main); + CBlockIndex *to_mark_failed_or_parked = pindex; + bool pindex_was_in_chain = false; - // 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). + // Disconnect (descendants of) pindex, and mark them invalid. + while (true) { + if (ShutdownRequested()) { + break; + } - bool pindex_was_in_chain = false; - CBlockIndex *invalid_walk_tip = chainActive.Tip(); + LOCK(cs_main); + + if (!chainActive.Contains(pindex)) { + break; + } - DisconnectedBlockTransactions disconnectpool; - while (chainActive.Contains(pindex)) { 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); + + 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; } - } - // 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) { + 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(); + 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_or_parked = 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 or parked, + // regardless of whether it was in the main chain or not. + LOCK(cs_main); + if (chainActive.Contains(to_mark_failed_or_parked)) { + // If the to-be-marked invalid block is in the active chain, + // something is interfering and we can't proceed. + return false; + } - // 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); + to_mark_failed_or_parked->nStatus = + invalidate ? to_mark_failed_or_parked->nStatus.withFailed() + : 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); } - } - if (invalidate) { - InvalidChainFound(pindex); + // 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(to_mark_failed_or_parked); + } } // 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_or_parked->pprev); } return true; }