mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-06-14 02:41:34 +02:00
validation: pruning for multiple chainstates
Introduces ChainstateManager::GetPruneRange(). The prune budget is split evenly between the number of chainstates, however the prune budget may be exceeded if the resulting shares are beneath `MIN_DISK_SPACE_FOR_BLOCK_FILES`.
This commit is contained in:
parent
373cf91531
commit
1019c39982
7
doc/release-notes-27596.md
Normal file
7
doc/release-notes-27596.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Pruning
|
||||||
|
-------
|
||||||
|
|
||||||
|
When using assumeutxo with `-prune`, the prune budget may be exceeded if it is set
|
||||||
|
lower than 1100MB (i.e. `MIN_DISK_SPACE_FOR_BLOCK_FILES * 2`). Prune budget is normally
|
||||||
|
split evenly across each chainstate, unless the resulting prune budget per chainstate
|
||||||
|
is beneath `MIN_DISK_SPACE_FOR_BLOCK_FILES` in which case that value will be used.
|
@ -257,40 +257,56 @@ void BlockManager::PruneOneBlockFile(const int fileNumber)
|
|||||||
m_dirty_fileinfo.insert(fileNumber);
|
m_dirty_fileinfo.insert(fileNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight, int chain_tip_height)
|
void BlockManager::FindFilesToPruneManual(
|
||||||
|
std::set<int>& setFilesToPrune,
|
||||||
|
int nManualPruneHeight,
|
||||||
|
const Chainstate& chain,
|
||||||
|
ChainstateManager& chainman)
|
||||||
{
|
{
|
||||||
assert(IsPruneMode() && nManualPruneHeight > 0);
|
assert(IsPruneMode() && nManualPruneHeight > 0);
|
||||||
|
|
||||||
LOCK2(cs_main, cs_LastBlockFile);
|
LOCK2(cs_main, cs_LastBlockFile);
|
||||||
if (chain_tip_height < 0) {
|
if (chain.m_chain.Height() < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// last block to prune is the lesser of (user-specified height, MIN_BLOCKS_TO_KEEP from the tip)
|
const auto [min_block_to_prune, last_block_can_prune] = chainman.GetPruneRange(chain, nManualPruneHeight);
|
||||||
unsigned int nLastBlockWeCanPrune = std::min((unsigned)nManualPruneHeight, chain_tip_height - MIN_BLOCKS_TO_KEEP);
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
|
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
|
||||||
if (m_blockfile_info[fileNumber].nSize == 0 || m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
|
const auto& fileinfo = m_blockfile_info[fileNumber];
|
||||||
|
if (fileinfo.nSize == 0 || fileinfo.nHeightLast > (unsigned)last_block_can_prune || fileinfo.nHeightFirst < (unsigned)min_block_to_prune) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
PruneOneBlockFile(fileNumber);
|
PruneOneBlockFile(fileNumber);
|
||||||
setFilesToPrune.insert(fileNumber);
|
setFilesToPrune.insert(fileNumber);
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
LogPrintf("Prune (Manual): prune_height=%d removed %d blk/rev pairs\n", nLastBlockWeCanPrune, count);
|
LogPrintf("[%s] Prune (Manual): prune_height=%d removed %d blk/rev pairs\n",
|
||||||
|
chain.GetRole(), last_block_can_prune, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd)
|
void BlockManager::FindFilesToPrune(
|
||||||
|
std::set<int>& setFilesToPrune,
|
||||||
|
int last_prune,
|
||||||
|
const Chainstate& chain,
|
||||||
|
ChainstateManager& chainman)
|
||||||
{
|
{
|
||||||
LOCK2(cs_main, cs_LastBlockFile);
|
LOCK2(cs_main, cs_LastBlockFile);
|
||||||
if (chain_tip_height < 0 || GetPruneTarget() == 0) {
|
// Distribute our -prune budget over all chainstates.
|
||||||
|
const auto target = std::max(
|
||||||
|
MIN_DISK_SPACE_FOR_BLOCK_FILES, GetPruneTarget() / chainman.GetAll().size());
|
||||||
|
|
||||||
|
if (chain.m_chain.Height() < 0 || target == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((uint64_t)chain_tip_height <= nPruneAfterHeight) {
|
if (static_cast<uint64_t>(chain.m_chain.Height()) <= chainman.GetParams().PruneAfterHeight()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int nLastBlockWeCanPrune{(unsigned)std::min(prune_height, chain_tip_height - static_cast<int>(MIN_BLOCKS_TO_KEEP))};
|
const auto [min_block_to_prune, last_block_can_prune] = chainman.GetPruneRange(chain, last_prune);
|
||||||
|
|
||||||
uint64_t nCurrentUsage = CalculateCurrentUsage();
|
uint64_t nCurrentUsage = CalculateCurrentUsage();
|
||||||
// We don't check to prune until after we've allocated new space for files
|
// 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 allocation
|
// So we should leave a buffer under our target to account for another allocation
|
||||||
@ -299,29 +315,31 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
|
|||||||
uint64_t nBytesToPrune;
|
uint64_t nBytesToPrune;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
if (nCurrentUsage + nBuffer >= GetPruneTarget()) {
|
if (nCurrentUsage + nBuffer >= target) {
|
||||||
// On a prune event, the chainstate DB is flushed.
|
// On a prune event, the chainstate DB is flushed.
|
||||||
// To avoid excessive prune events negating the benefit of high dbcache
|
// To avoid excessive prune events negating the benefit of high dbcache
|
||||||
// values, we should not prune too rapidly.
|
// values, we should not prune too rapidly.
|
||||||
// So when pruning in IBD, increase the buffer a bit to avoid a re-prune too soon.
|
// So when pruning in IBD, increase the buffer a bit to avoid a re-prune too soon.
|
||||||
if (is_ibd) {
|
if (chainman.IsInitialBlockDownload()) {
|
||||||
// Since this is only relevant during IBD, we use a fixed 10%
|
// Since this is only relevant during IBD, we use a fixed 10%
|
||||||
nBuffer += GetPruneTarget() / 10;
|
nBuffer += target / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
|
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
|
||||||
nBytesToPrune = m_blockfile_info[fileNumber].nSize + m_blockfile_info[fileNumber].nUndoSize;
|
const auto& fileinfo = m_blockfile_info[fileNumber];
|
||||||
|
nBytesToPrune = fileinfo.nSize + fileinfo.nUndoSize;
|
||||||
|
|
||||||
if (m_blockfile_info[fileNumber].nSize == 0) {
|
if (fileinfo.nSize == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nCurrentUsage + nBuffer < GetPruneTarget()) { // are we below our target?
|
if (nCurrentUsage + nBuffer < target) { // are we below our target?
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't prune files that could have a block within MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning
|
// don't prune files that could have a block that's not within the allowable
|
||||||
if (m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
|
// prune range for the chain being pruned.
|
||||||
|
if (fileinfo.nHeightLast > (unsigned)last_block_can_prune || fileinfo.nHeightFirst < (unsigned)min_block_to_prune) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,10 +351,10 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LogPrint(BCLog::PRUNE, "target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n",
|
LogPrint(BCLog::PRUNE, "[%s] target=%dMiB actual=%dMiB diff=%dMiB min_height=%d max_prune_height=%d removed %d blk/rev pairs\n",
|
||||||
GetPruneTarget() / 1024 / 1024, nCurrentUsage / 1024 / 1024,
|
chain.GetRole(), target / 1024 / 1024, nCurrentUsage / 1024 / 1024,
|
||||||
(int64_t(GetPruneTarget()) - int64_t(nCurrentUsage)) / 1024 / 1024,
|
(int64_t(target) - int64_t(nCurrentUsage)) / 1024 / 1024,
|
||||||
nLastBlockWeCanPrune, count);
|
min_block_to_prune, last_block_can_prune, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
|
void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
|
||||||
|
@ -36,6 +36,7 @@ class CBlockUndo;
|
|||||||
class CChainParams;
|
class CChainParams;
|
||||||
class Chainstate;
|
class Chainstate;
|
||||||
class ChainstateManager;
|
class ChainstateManager;
|
||||||
|
enum class ChainstateRole;
|
||||||
struct CCheckpointData;
|
struct CCheckpointData;
|
||||||
struct FlatFilePos;
|
struct FlatFilePos;
|
||||||
namespace Consensus {
|
namespace Consensus {
|
||||||
@ -138,7 +139,11 @@ private:
|
|||||||
bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock) const;
|
bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock) const;
|
||||||
|
|
||||||
/* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
|
/* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
|
||||||
void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight, int chain_tip_height);
|
void FindFilesToPruneManual(
|
||||||
|
std::set<int>& setFilesToPrune,
|
||||||
|
int nManualPruneHeight,
|
||||||
|
const Chainstate& chain,
|
||||||
|
ChainstateManager& chainman);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prune block and undo files (blk???.dat and rev???.dat) so that the disk space used is less than a user-defined target.
|
* Prune block and undo files (blk???.dat and rev???.dat) so that the disk space used is less than a user-defined target.
|
||||||
@ -154,8 +159,13 @@ private:
|
|||||||
* A db flag records the fact that at least some block files have been pruned.
|
* A db flag records the fact that at least some block files have been pruned.
|
||||||
*
|
*
|
||||||
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
|
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
|
||||||
|
* @param last_prune The last height we're able to prune, according to the prune locks
|
||||||
*/
|
*/
|
||||||
void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd);
|
void FindFilesToPrune(
|
||||||
|
std::set<int>& setFilesToPrune,
|
||||||
|
int last_prune,
|
||||||
|
const Chainstate& chain,
|
||||||
|
ChainstateManager& chainman);
|
||||||
|
|
||||||
RecursiveMutex cs_LastBlockFile;
|
RecursiveMutex cs_LastBlockFile;
|
||||||
std::vector<CBlockFileInfo> m_blockfile_info;
|
std::vector<CBlockFileInfo> m_blockfile_info;
|
||||||
|
@ -69,6 +69,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
using kernel::CCoinsStats;
|
using kernel::CCoinsStats;
|
||||||
using kernel::CoinStatsHashType;
|
using kernel::CoinStatsHashType;
|
||||||
@ -2552,11 +2553,14 @@ bool Chainstate::FlushStateToDisk(
|
|||||||
if (nManualPruneHeight > 0) {
|
if (nManualPruneHeight > 0) {
|
||||||
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
|
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
|
||||||
|
|
||||||
m_blockman.FindFilesToPruneManual(setFilesToPrune, std::min(last_prune, nManualPruneHeight), m_chain.Height());
|
m_blockman.FindFilesToPruneManual(
|
||||||
|
setFilesToPrune,
|
||||||
|
std::min(last_prune, nManualPruneHeight),
|
||||||
|
*this, m_chainman);
|
||||||
} else {
|
} else {
|
||||||
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
|
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
|
||||||
|
|
||||||
m_blockman.FindFilesToPrune(setFilesToPrune, m_chainman.GetParams().PruneAfterHeight(), m_chain.Height(), last_prune, m_chainman.IsInitialBlockDownload());
|
m_blockman.FindFilesToPrune(setFilesToPrune, last_prune, *this, m_chainman);
|
||||||
m_blockman.m_check_for_pruning = false;
|
m_blockman.m_check_for_pruning = false;
|
||||||
}
|
}
|
||||||
if (!setFilesToPrune.empty()) {
|
if (!setFilesToPrune.empty()) {
|
||||||
@ -5939,3 +5943,30 @@ Chainstate& ChainstateManager::GetChainstateForIndexing()
|
|||||||
// indexed.
|
// indexed.
|
||||||
return (this->GetAll().size() > 1) ? *m_ibd_chainstate : *m_active_chainstate;
|
return (this->GetAll().size() > 1) ? *m_ibd_chainstate : *m_active_chainstate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> ChainstateManager::GetPruneRange(const Chainstate& chainstate, int last_height_can_prune)
|
||||||
|
{
|
||||||
|
if (chainstate.m_chain.Height() <= 0) {
|
||||||
|
return {0, 0};
|
||||||
|
}
|
||||||
|
int prune_start{0};
|
||||||
|
|
||||||
|
if (this->GetAll().size() > 1 && m_snapshot_chainstate.get() == &chainstate) {
|
||||||
|
// Leave the blocks in the background IBD chain alone if we're pruning
|
||||||
|
// the snapshot chain.
|
||||||
|
prune_start = *Assert(GetSnapshotBaseHeight()) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_prune = std::max<int>(
|
||||||
|
0, chainstate.m_chain.Height() - static_cast<int>(MIN_BLOCKS_TO_KEEP));
|
||||||
|
|
||||||
|
// last block to prune is the lesser of (caller-specified height, MIN_BLOCKS_TO_KEEP from the tip)
|
||||||
|
//
|
||||||
|
// While you might be tempted to prune the background chainstate more
|
||||||
|
// aggressively (i.e. fewer MIN_BLOCKS_TO_KEEP), this won't work with index
|
||||||
|
// building - specifically blockfilterindex requires undo data, and if
|
||||||
|
// we don't maintain this trailing window, we hit indexing failures.
|
||||||
|
int prune_end = std::min(last_height_can_prune, max_prune);
|
||||||
|
|
||||||
|
return {prune_start, prune_end};
|
||||||
|
}
|
||||||
|
@ -885,10 +885,6 @@ private:
|
|||||||
/** Most recent headers presync progress update, for rate-limiting. */
|
/** Most recent headers presync progress update, for rate-limiting. */
|
||||||
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
|
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
|
||||||
|
|
||||||
//! Return the height of the base block of the snapshot in use, if one exists, else
|
|
||||||
//! nullopt.
|
|
||||||
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
|
||||||
|
|
||||||
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
|
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
|
||||||
|
|
||||||
//! Return true if a chainstate is considered usable.
|
//! Return true if a chainstate is considered usable.
|
||||||
@ -1241,6 +1237,16 @@ public:
|
|||||||
//! fully validated chain.
|
//! fully validated chain.
|
||||||
Chainstate& GetChainstateForIndexing() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
Chainstate& GetChainstateForIndexing() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
|
//! Return the [start, end] (inclusive) of block heights we can prune.
|
||||||
|
//!
|
||||||
|
//! start > end is possible, meaning no blocks can be pruned.
|
||||||
|
std::pair<int, int> GetPruneRange(
|
||||||
|
const Chainstate& chainstate, int last_height_can_prune) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
|
//! Return the height of the base block of the snapshot in use, if one exists, else
|
||||||
|
//! nullopt.
|
||||||
|
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
~ChainstateManager();
|
~ChainstateManager();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user