Skip to content

Commit 1019c39

Browse files
committed
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`.
1 parent 373cf91 commit 1019c39

File tree

5 files changed

+102
-30
lines changed

5 files changed

+102
-30
lines changed

doc/release-notes-27596.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Pruning
2+
-------
3+
4+
When using assumeutxo with `-prune`, the prune budget may be exceeded if it is set
5+
lower than 1100MB (i.e. `MIN_DISK_SPACE_FOR_BLOCK_FILES * 2`). Prune budget is normally
6+
split evenly across each chainstate, unless the resulting prune budget per chainstate
7+
is beneath `MIN_DISK_SPACE_FOR_BLOCK_FILES` in which case that value will be used.

src/node/blockstorage.cpp

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -257,40 +257,56 @@ void BlockManager::PruneOneBlockFile(const int fileNumber)
257257
m_dirty_fileinfo.insert(fileNumber);
258258
}
259259

260-
void BlockManager::FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight, int chain_tip_height)
260+
void BlockManager::FindFilesToPruneManual(
261+
std::set<int>& setFilesToPrune,
262+
int nManualPruneHeight,
263+
const Chainstate& chain,
264+
ChainstateManager& chainman)
261265
{
262266
assert(IsPruneMode() && nManualPruneHeight > 0);
263267

264268
LOCK2(cs_main, cs_LastBlockFile);
265-
if (chain_tip_height < 0) {
269+
if (chain.m_chain.Height() < 0) {
266270
return;
267271
}
268272

269-
// last block to prune is the lesser of (user-specified height, MIN_BLOCKS_TO_KEEP from the tip)
270-
unsigned int nLastBlockWeCanPrune = std::min((unsigned)nManualPruneHeight, chain_tip_height - MIN_BLOCKS_TO_KEEP);
273+
const auto [min_block_to_prune, last_block_can_prune] = chainman.GetPruneRange(chain, nManualPruneHeight);
274+
271275
int count = 0;
272276
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
273-
if (m_blockfile_info[fileNumber].nSize == 0 || m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
277+
const auto& fileinfo = m_blockfile_info[fileNumber];
278+
if (fileinfo.nSize == 0 || fileinfo.nHeightLast > (unsigned)last_block_can_prune || fileinfo.nHeightFirst < (unsigned)min_block_to_prune) {
274279
continue;
275280
}
281+
276282
PruneOneBlockFile(fileNumber);
277283
setFilesToPrune.insert(fileNumber);
278284
count++;
279285
}
280-
LogPrintf("Prune (Manual): prune_height=%d removed %d blk/rev pairs\n", nLastBlockWeCanPrune, count);
286+
LogPrintf("[%s] Prune (Manual): prune_height=%d removed %d blk/rev pairs\n",
287+
chain.GetRole(), last_block_can_prune, count);
281288
}
282289

283-
void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd)
290+
void BlockManager::FindFilesToPrune(
291+
std::set<int>& setFilesToPrune,
292+
int last_prune,
293+
const Chainstate& chain,
294+
ChainstateManager& chainman)
284295
{
285296
LOCK2(cs_main, cs_LastBlockFile);
286-
if (chain_tip_height < 0 || GetPruneTarget() == 0) {
297+
// Distribute our -prune budget over all chainstates.
298+
const auto target = std::max(
299+
MIN_DISK_SPACE_FOR_BLOCK_FILES, GetPruneTarget() / chainman.GetAll().size());
300+
301+
if (chain.m_chain.Height() < 0 || target == 0) {
287302
return;
288303
}
289-
if ((uint64_t)chain_tip_height <= nPruneAfterHeight) {
304+
if (static_cast<uint64_t>(chain.m_chain.Height()) <= chainman.GetParams().PruneAfterHeight()) {
290305
return;
291306
}
292307

293-
unsigned int nLastBlockWeCanPrune{(unsigned)std::min(prune_height, chain_tip_height - static_cast<int>(MIN_BLOCKS_TO_KEEP))};
308+
const auto [min_block_to_prune, last_block_can_prune] = chainman.GetPruneRange(chain, last_prune);
309+
294310
uint64_t nCurrentUsage = CalculateCurrentUsage();
295311
// We don't check to prune until after we've allocated new space for files
296312
// 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
299315
uint64_t nBytesToPrune;
300316
int count = 0;
301317

302-
if (nCurrentUsage + nBuffer >= GetPruneTarget()) {
318+
if (nCurrentUsage + nBuffer >= target) {
303319
// On a prune event, the chainstate DB is flushed.
304320
// To avoid excessive prune events negating the benefit of high dbcache
305321
// values, we should not prune too rapidly.
306322
// So when pruning in IBD, increase the buffer a bit to avoid a re-prune too soon.
307-
if (is_ibd) {
323+
if (chainman.IsInitialBlockDownload()) {
308324
// Since this is only relevant during IBD, we use a fixed 10%
309-
nBuffer += GetPruneTarget() / 10;
325+
nBuffer += target / 10;
310326
}
311327

312328
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
313-
nBytesToPrune = m_blockfile_info[fileNumber].nSize + m_blockfile_info[fileNumber].nUndoSize;
329+
const auto& fileinfo = m_blockfile_info[fileNumber];
330+
nBytesToPrune = fileinfo.nSize + fileinfo.nUndoSize;
314331

315-
if (m_blockfile_info[fileNumber].nSize == 0) {
332+
if (fileinfo.nSize == 0) {
316333
continue;
317334
}
318335

319-
if (nCurrentUsage + nBuffer < GetPruneTarget()) { // are we below our target?
336+
if (nCurrentUsage + nBuffer < target) { // are we below our target?
320337
break;
321338
}
322339

323-
// don't prune files that could have a block within MIN_BLOCKS_TO_KEEP of the main chain's tip but keep scanning
324-
if (m_blockfile_info[fileNumber].nHeightLast > nLastBlockWeCanPrune) {
340+
// don't prune files that could have a block that's not within the allowable
341+
// prune range for the chain being pruned.
342+
if (fileinfo.nHeightLast > (unsigned)last_block_can_prune || fileinfo.nHeightFirst < (unsigned)min_block_to_prune) {
325343
continue;
326344
}
327345

@@ -333,10 +351,10 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
333351
}
334352
}
335353

336-
LogPrint(BCLog::PRUNE, "target=%dMiB actual=%dMiB diff=%dMiB max_prune_height=%d removed %d blk/rev pairs\n",
337-
GetPruneTarget() / 1024 / 1024, nCurrentUsage / 1024 / 1024,
338-
(int64_t(GetPruneTarget()) - int64_t(nCurrentUsage)) / 1024 / 1024,
339-
nLastBlockWeCanPrune, count);
354+
LogPrint(BCLog::PRUNE, "[%s] target=%dMiB actual=%dMiB diff=%dMiB min_height=%d max_prune_height=%d removed %d blk/rev pairs\n",
355+
chain.GetRole(), target / 1024 / 1024, nCurrentUsage / 1024 / 1024,
356+
(int64_t(target) - int64_t(nCurrentUsage)) / 1024 / 1024,
357+
min_block_to_prune, last_block_can_prune, count);
340358
}
341359

342360
void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {

src/node/blockstorage.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class CBlockUndo;
3636
class CChainParams;
3737
class Chainstate;
3838
class ChainstateManager;
39+
enum class ChainstateRole;
3940
struct CCheckpointData;
4041
struct FlatFilePos;
4142
namespace Consensus {
@@ -138,7 +139,11 @@ class BlockManager
138139
bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock) const;
139140

140141
/* Calculate the block/rev files to delete based on height specified by user with RPC command pruneblockchain */
141-
void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight, int chain_tip_height);
142+
void FindFilesToPruneManual(
143+
std::set<int>& setFilesToPrune,
144+
int nManualPruneHeight,
145+
const Chainstate& chain,
146+
ChainstateManager& chainman);
142147

143148
/**
144149
* 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 @@ class BlockManager
154159
* A db flag records the fact that at least some block files have been pruned.
155160
*
156161
* @param[out] setFilesToPrune The set of file indices that can be unlinked will be returned
162+
* @param last_prune The last height we're able to prune, according to the prune locks
157163
*/
158-
void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight, int chain_tip_height, int prune_height, bool is_ibd);
164+
void FindFilesToPrune(
165+
std::set<int>& setFilesToPrune,
166+
int last_prune,
167+
const Chainstate& chain,
168+
ChainstateManager& chainman);
159169

160170
RecursiveMutex cs_LastBlockFile;
161171
std::vector<CBlockFileInfo> m_blockfile_info;

src/validation.cpp

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#include <optional>
7070
#include <string>
7171
#include <utility>
72+
#include <tuple>
7273

7374
using kernel::CCoinsStats;
7475
using kernel::CoinStatsHashType;
@@ -2552,11 +2553,14 @@ bool Chainstate::FlushStateToDisk(
25522553
if (nManualPruneHeight > 0) {
25532554
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
25542555

2555-
m_blockman.FindFilesToPruneManual(setFilesToPrune, std::min(last_prune, nManualPruneHeight), m_chain.Height());
2556+
m_blockman.FindFilesToPruneManual(
2557+
setFilesToPrune,
2558+
std::min(last_prune, nManualPruneHeight),
2559+
*this, m_chainman);
25562560
} else {
25572561
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune", BCLog::BENCH);
25582562

2559-
m_blockman.FindFilesToPrune(setFilesToPrune, m_chainman.GetParams().PruneAfterHeight(), m_chain.Height(), last_prune, m_chainman.IsInitialBlockDownload());
2563+
m_blockman.FindFilesToPrune(setFilesToPrune, last_prune, *this, m_chainman);
25602564
m_blockman.m_check_for_pruning = false;
25612565
}
25622566
if (!setFilesToPrune.empty()) {
@@ -5939,3 +5943,30 @@ Chainstate& ChainstateManager::GetChainstateForIndexing()
59395943
// indexed.
59405944
return (this->GetAll().size() > 1) ? *m_ibd_chainstate : *m_active_chainstate;
59415945
}
5946+
5947+
std::pair<int, int> ChainstateManager::GetPruneRange(const Chainstate& chainstate, int last_height_can_prune)
5948+
{
5949+
if (chainstate.m_chain.Height() <= 0) {
5950+
return {0, 0};
5951+
}
5952+
int prune_start{0};
5953+
5954+
if (this->GetAll().size() > 1 && m_snapshot_chainstate.get() == &chainstate) {
5955+
// Leave the blocks in the background IBD chain alone if we're pruning
5956+
// the snapshot chain.
5957+
prune_start = *Assert(GetSnapshotBaseHeight()) + 1;
5958+
}
5959+
5960+
int max_prune = std::max<int>(
5961+
0, chainstate.m_chain.Height() - static_cast<int>(MIN_BLOCKS_TO_KEEP));
5962+
5963+
// last block to prune is the lesser of (caller-specified height, MIN_BLOCKS_TO_KEEP from the tip)
5964+
//
5965+
// While you might be tempted to prune the background chainstate more
5966+
// aggressively (i.e. fewer MIN_BLOCKS_TO_KEEP), this won't work with index
5967+
// building - specifically blockfilterindex requires undo data, and if
5968+
// we don't maintain this trailing window, we hit indexing failures.
5969+
int prune_end = std::min(last_height_can_prune, max_prune);
5970+
5971+
return {prune_start, prune_end};
5972+
}

src/validation.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -885,10 +885,6 @@ class ChainstateManager
885885
/** Most recent headers presync progress update, for rate-limiting. */
886886
std::chrono::time_point<std::chrono::steady_clock> m_last_presync_update GUARDED_BY(::cs_main) {};
887887

888-
//! Return the height of the base block of the snapshot in use, if one exists, else
889-
//! nullopt.
890-
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
891-
892888
std::array<ThresholdConditionCache, VERSIONBITS_NUM_BITS> m_warningcache GUARDED_BY(::cs_main);
893889

894890
//! Return true if a chainstate is considered usable.
@@ -1241,6 +1237,16 @@ class ChainstateManager
12411237
//! fully validated chain.
12421238
Chainstate& GetChainstateForIndexing() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
12431239

1240+
//! Return the [start, end] (inclusive) of block heights we can prune.
1241+
//!
1242+
//! start > end is possible, meaning no blocks can be pruned.
1243+
std::pair<int, int> GetPruneRange(
1244+
const Chainstate& chainstate, int last_height_can_prune) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1245+
1246+
//! Return the height of the base block of the snapshot in use, if one exists, else
1247+
//! nullopt.
1248+
std::optional<int> GetSnapshotBaseHeight() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1249+
12441250
~ChainstateManager();
12451251
};
12461252

0 commit comments

Comments
 (0)