diff --git a/src/index/base.cpp b/src/index/base.cpp --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -66,6 +66,48 @@ m_best_block_index = FindForkInGlobalIndex(::ChainActive(), locator); } m_synced = m_best_block_index.load() == ::ChainActive().Tip(); + if (!m_synced) { + bool prune_violation = false; + if (!m_best_block_index) { + // index is not built yet + // make sure we have all block data back to the genesis + const CBlockIndex *block = ::ChainActive().Tip(); + while (block->pprev && block->pprev->nStatus.hasData()) { + block = block->pprev; + } + prune_violation = block != ::ChainActive().Genesis(); + } + // in case the index has a best block set and is not fully synced + // check if we have the required blocks to continue building the index + else { + const CBlockIndex *block_to_test = m_best_block_index.load(); + if (!ChainActive().Contains(block_to_test)) { + // if the bestblock is not part of the mainchain, find the fork + // and make sure we have all data down to the fork + block_to_test = ::ChainActive().FindFork(block_to_test); + } + const CBlockIndex *block = ::ChainActive().Tip(); + prune_violation = true; + // check backwards from the tip if we have all block data until we + // reach the indexes bestblock + while (block_to_test && block && block->nStatus.hasData()) { + if (block_to_test == block) { + prune_violation = false; + break; + } + assert(block->pprev); + block = block->pprev; + } + } + if (prune_violation) { + // throw error and graceful shutdown if we can't build the index + FatalError("%s: %s best block of the index goes beyond pruned " + "data. Please disable the index or reindex (which will " + "download the whole blockchain again)", + __func__, GetName()); + return false; + } + } return true; } @@ -181,6 +223,10 @@ assert(current_tip->GetAncestor(new_tip->nHeight) == new_tip); // In the case of a reorg, ensure persisted block locator is not stale. + // Pruning has a minimum of 288 blocks-to-keep and getting the index + // out of sync may be possible but a users fault. + // In case we reorg beyond the pruned depth, ReadBlockFromDisk would + // throw and lead to a graceful shutdown m_best_block_index = new_tip; if (!Commit()) { // If commit fails, revert the best block index to avoid corruption. @@ -332,6 +378,7 @@ IndexSummary summary{}; summary.name = GetName(); summary.synced = m_synced; - summary.best_block_height = m_best_block_index.load()->nHeight; + summary.best_block_height = + m_best_block_index ? m_best_block_index.load()->nHeight : 0; return summary; } diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -1900,10 +1900,6 @@ if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { return InitError(_("Prune mode is incompatible with -txindex.")); } - if (!g_enabled_filter_types.empty()) { - return InitError( - _("Prune mode is incompatible with -blockfilterindex.")); - } } // -bind and -whitebind can't be set when not listening diff --git a/src/validation.h b/src/validation.h --- a/src/validation.h +++ b/src/validation.h @@ -615,7 +615,7 @@ */ void FindFilesToPrune(std::set &setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, - bool is_ibd); + int prune_height, bool is_ibd); public: BlockMap m_block_index GUARDED_BY(cs_main); diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -2116,22 +2117,36 @@ { bool fFlushForPrune = false; bool fDoFullFlush = false; + CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(&m_mempool); LOCK(cs_LastBlockFile); if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) && !fReindex) { + // make sure we don't prune above the blockfilterindexes + // bestblocks + // pruning is height-based + // last height we can prune + int last_prune = m_chain.Height(); + ForEachBlockFilterIndex([&](BlockFilterIndex &index) { + last_prune = std::max( + 1, std::min(last_prune, + index.GetSummary().best_block_height)); + }); + if (nManualPruneHeight > 0) { LOG_TIME_MILLIS_WITH_CATEGORY( "find files to prune (manual)", BCLog::BENCH); m_blockman.FindFilesToPruneManual( - setFilesToPrune, nManualPruneHeight, m_chain.Height()); + setFilesToPrune, + std::min(last_prune, nManualPruneHeight), + m_chain.Height()); } else { LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH); m_blockman.FindFilesToPrune( setFilesToPrune, chainparams.PruneAfterHeight(), - m_chain.Height(), IsInitialBlockDownload()); + m_chain.Height(), last_prune, IsInitialBlockDownload()); fCheckForPruning = false; } if (!setFilesToPrune.empty()) { @@ -4585,7 +4600,8 @@ void BlockManager::FindFilesToPrune(std::set &setFilesToPrune, uint64_t nPruneAfterHeight, - int chain_tip_height, bool is_ibd) { + int chain_tip_height, int prune_height, + bool is_ibd) { LOCK2(cs_main, cs_LastBlockFile); if (chain_tip_height < 0 || nPruneTarget == 0) { return; @@ -4594,7 +4610,8 @@ return; } - unsigned int nLastBlockWeCanPrune = chain_tip_height - MIN_BLOCKS_TO_KEEP; + unsigned int nLastBlockWeCanPrune = std::min( + prune_height, chain_tip_height - static_cast(MIN_BLOCKS_TO_KEEP)); uint64_t nCurrentUsage = CalculateCurrentUsage(); // We don't check to prune until after we've allocated new space for files, // so we should leave a buffer under our target to account for another