Skip to content

Commit 01e944c

Browse files
committed
Merge 34ae04d into merged_master (Bitcoin PR bitcoin/bitcoin#21726)
This changed the node pruning logic and moved test/functional/feature_blockfilterindex_prune.py to test/functional/feature_index_prune.py. Please verify that: 1. I migrated the test correctly 2. The magic numbers in the test look fine With respect to delta1#2: I believe the magic numbers are wrong. I previously had to tweak them heavily in commit 1278b31. I don't think I did it correctly then, and so I don't believe them to be correct now. To summarize what this tweaking was: I changed the magic numbers in the test to work properly, but I suspect that in changing them, I may have nullified what the test was testing. It's very possible that the reason the test was failing was because of an underlying bug with the pruning in elements which we have to fix, rather than just being an issue with the test itself.
2 parents 249473e + 34ae04d commit 01e944c

15 files changed

+265
-115
lines changed

src/index/base.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,17 @@ bool BaseIndex::Init()
6565
LOCK(cs_main);
6666
CChain& active_chain = m_chainstate->m_chain;
6767
if (locator.IsNull()) {
68-
m_best_block_index = nullptr;
68+
SetBestBlockIndex(nullptr);
6969
} else {
70-
m_best_block_index = m_chainstate->FindForkInGlobalIndex(locator);
70+
SetBestBlockIndex(m_chainstate->FindForkInGlobalIndex(locator));
7171
}
7272
m_synced = m_best_block_index.load() == active_chain.Tip();
7373
if (!m_synced) {
7474
bool prune_violation = false;
7575
if (!m_best_block_index) {
7676
// index is not built yet
7777
// make sure we have all block data back to the genesis
78-
const CBlockIndex* block = active_chain.Tip();
79-
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
80-
block = block->pprev;
81-
}
82-
prune_violation = block != active_chain.Genesis();
78+
prune_violation = node::GetFirstStoredBlock(active_chain.Tip()) != active_chain.Genesis();
8379
}
8480
// in case the index has a best block set and is not fully synced
8581
// check if we have the required blocks to continue building the index
@@ -138,7 +134,7 @@ void BaseIndex::ThreadSync()
138134
int64_t last_locator_write_time = 0;
139135
while (true) {
140136
if (m_interrupt) {
141-
m_best_block_index = pindex;
137+
SetBestBlockIndex(pindex);
142138
// No need to handle errors in Commit. If it fails, the error will be already be
143139
// logged. The best way to recover is to continue, as index cannot be corrupted by
144140
// a missed commit to disk for an advanced index state.
@@ -150,7 +146,7 @@ void BaseIndex::ThreadSync()
150146
LOCK(cs_main);
151147
const CBlockIndex* pindex_next = NextSyncBlock(pindex, m_chainstate->m_chain);
152148
if (!pindex_next) {
153-
m_best_block_index = pindex;
149+
SetBestBlockIndex(pindex);
154150
m_synced = true;
155151
// No need to handle errors in Commit. See rationale above.
156152
Commit();
@@ -172,7 +168,7 @@ void BaseIndex::ThreadSync()
172168
}
173169

174170
if (last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time) {
175-
m_best_block_index = pindex;
171+
SetBestBlockIndex(pindex);
176172
last_locator_write_time = current_time;
177173
// No need to handle errors in Commit. See rationale above.
178174
Commit();
@@ -230,10 +226,10 @@ bool BaseIndex::Rewind(const CBlockIndex* current_tip, const CBlockIndex* new_ti
230226
// out of sync may be possible but a users fault.
231227
// In case we reorg beyond the pruned depth, ReadBlockFromDisk would
232228
// throw and lead to a graceful shutdown
233-
m_best_block_index = new_tip;
229+
SetBestBlockIndex(new_tip);
234230
if (!Commit()) {
235231
// If commit fails, revert the best block index to avoid corruption.
236-
m_best_block_index = current_tip;
232+
SetBestBlockIndex(current_tip);
237233
return false;
238234
}
239235

@@ -274,7 +270,7 @@ void BaseIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const
274270
}
275271

276272
if (WriteBlock(*block, pindex)) {
277-
m_best_block_index = pindex;
273+
SetBestBlockIndex(pindex);
278274
} else {
279275
FatalError("%s: Failed to write block %s to index",
280276
__func__, pindex->GetBlockHash().ToString());
@@ -381,3 +377,14 @@ IndexSummary BaseIndex::GetSummary() const
381377
summary.best_block_height = m_best_block_index ? m_best_block_index.load()->nHeight : 0;
382378
return summary;
383379
}
380+
381+
void BaseIndex::SetBestBlockIndex(const CBlockIndex* block) {
382+
assert(!node::fPruneMode || AllowPrune());
383+
384+
m_best_block_index = block;
385+
if (AllowPrune() && block) {
386+
node::PruneLockInfo prune_lock;
387+
prune_lock.height_first = block->nHeight;
388+
WITH_LOCK(::cs_main, m_chainstate->m_blockman.UpdatePruneLock(GetName(), prune_lock));
389+
}
390+
}

src/index/base.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ class BaseIndex : public CValidationInterface
7575
/// to a chain reorganization), the index must halt until Commit succeeds or else it could end up
7676
/// getting corrupted.
7777
bool Commit();
78+
79+
virtual bool AllowPrune() const = 0;
80+
7881
protected:
7982
CChainState* m_chainstate{nullptr};
8083

@@ -103,6 +106,9 @@ class BaseIndex : public CValidationInterface
103106
/// Get the name of the index for display in logs.
104107
virtual const char* GetName() const = 0;
105108

109+
/// Update the internal best block index as well as the prune lock.
110+
void SetBestBlockIndex(const CBlockIndex* block);
111+
106112
public:
107113
/// Destructor interrupts sync thread if running and blocks until it exits.
108114
virtual ~BaseIndex();

src/index/blockfilterindex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class BlockFilterIndex final : public BaseIndex
3838
/** cache of block hash to filter header, to avoid disk access when responding to getcfcheckpt. */
3939
std::unordered_map<uint256, uint256, FilterHeaderHasher> m_headers_cache GUARDED_BY(m_cs_headers_cache);
4040

41+
bool AllowPrune() const override { return true; }
42+
4143
protected:
4244
bool Init() override;
4345

src/index/coinstatsindex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class CoinStatsIndex final : public BaseIndex
3636

3737
bool ReverseBlock(const CBlock& block, const CBlockIndex* pindex);
3838

39+
bool AllowPrune() const override { return true; }
40+
3941
protected:
4042
bool Init() override;
4143

src/index/txindex.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class TxIndex final : public BaseIndex
2020
private:
2121
const std::unique_ptr<DB> m_db;
2222

23+
bool AllowPrune() const override { return false; }
24+
2325
protected:
2426
bool WriteBlock(const CBlock& block, const CBlockIndex* pindex) override;
2527

src/init.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ void SetupServerArgs(ArgsManager& argsman)
433433
-GetNumCores(), MAX_SCRIPTCHECK_THREADS, DEFAULT_SCRIPTCHECK_THREADS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
434434
argsman.AddArg("-persistmempool", strprintf("Whether to save the mempool on shutdown and load on restart (default: %u)", DEFAULT_PERSIST_MEMPOOL), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
435435
argsman.AddArg("-pid=<file>", strprintf("Specify pid file. Relative paths will be prefixed by a net-specific datadir location. (default: %s)", BITCOIN_PID_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
436-
argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -coinstatsindex. "
436+
argsman.AddArg("-prune=<n>", strprintf("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex. "
437437
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
438438
"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB)", MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
439439
argsman.AddArg("-reindex", "Rebuild chain state and block index from the blk*.dat files on disk. This will also rebuild active optional indexes.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
@@ -904,8 +904,6 @@ bool AppInitParameterInteraction(const ArgsManager& args, bool use_syscall_sandb
904904
if (args.GetIntArg("-prune", 0)) {
905905
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX))
906906
return InitError(_("Prune mode is incompatible with -txindex."));
907-
if (args.GetBoolArg("-coinstatsindex", DEFAULT_COINSTATSINDEX))
908-
return InitError(_("Prune mode is incompatible with -coinstatsindex."));
909907
if (args.GetBoolArg("-reindex-chainstate", false)) {
910908
return InitError(_("Prune mode is incompatible with -reindex-chainstate. Use full -reindex instead."));
911909
}

src/node/blockstorage.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
#include <util/system.h>
2323
#include <validation.h>
2424

25+
#include <unordered_map>
26+
2527
namespace node {
2628
std::atomic_bool fImporting(false);
2729
std::atomic_bool fReindex(false);
@@ -234,6 +236,11 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
234236
nLastBlockWeCanPrune, count);
235237
}
236238

239+
void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
240+
AssertLockHeld(::cs_main);
241+
m_prune_locks[name] = lock_info;
242+
}
243+
237244
CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
238245
{
239246
AssertLockHeld(cs_main);
@@ -413,6 +420,16 @@ bool BlockManager::IsBlockPruned(const CBlockIndex* pblockindex)
413420
return (m_have_pruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0);
414421
}
415422

423+
const CBlockIndex* GetFirstStoredBlock(const CBlockIndex* start_block) {
424+
AssertLockHeld(::cs_main);
425+
assert(start_block);
426+
const CBlockIndex* last_block = start_block;
427+
while (last_block->pprev && (last_block->pprev->nStatus & BLOCK_HAVE_DATA)) {
428+
last_block = last_block->pprev;
429+
}
430+
return last_block;
431+
}
432+
416433
// If we're using -prune with -reindex, then delete block files that will be ignored by the
417434
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
418435
// is missing, do the same here to delete any later block files after a gap. Also delete all

src/node/blockstorage.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <atomic>
1515
#include <cstdint>
16+
#include <unordered_map>
1617
#include <vector>
1718

1819
extern RecursiveMutex cs_main;
@@ -72,6 +73,10 @@ struct CBlockIndexHeightOnlyComparator {
7273
bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
7374
};
7475

76+
struct PruneLockInfo {
77+
int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned
78+
};
79+
7580
/**
7681
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
7782
* to determine where the most-work tip is.
@@ -125,6 +130,14 @@ class BlockManager
125130
/** Dirty block file entries. */
126131
std::set<int> m_dirty_fileinfo;
127132

133+
/**
134+
* Map from external index name to oldest block that must not be pruned.
135+
*
136+
* @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and
137+
* below will be pruned, but callers should avoid assuming any particular buffer size.
138+
*/
139+
std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main);
140+
128141
public:
129142
BlockMap m_block_index GUARDED_BY(cs_main);
130143

@@ -182,12 +195,18 @@ class BlockManager
182195
//! Check whether the block associated with this index entry is pruned or not.
183196
bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
184197

198+
//! Create or update a prune lock identified by its name
199+
void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
200+
185201
~BlockManager()
186202
{
187203
Unload();
188204
}
189205
};
190206

207+
//! Find the first block that is not pruned
208+
const CBlockIndex* GetFirstStoredBlock(const CBlockIndex* start_block) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
209+
191210
void CleanupBlockRevFiles();
192211

193212
/** Open a block file (blk?????.dat) */

src/rpc/blockchain.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -911,10 +911,9 @@ static RPCHelpMan pruneblockchain()
911911

912912
PruneBlockFilesManual(active_chainstate, height);
913913
const CBlockIndex* block = CHECK_NONFATAL(active_chain.Tip());
914-
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
915-
block = block->pprev;
916-
}
917-
return uint64_t(block->nHeight);
914+
const CBlockIndex* last_block = node::GetFirstStoredBlock(block);
915+
916+
return static_cast<uint64_t>(last_block->nHeight);
918917
},
919918
};
920919
}
@@ -1411,11 +1410,7 @@ RPCHelpMan getblockchaininfo()
14111410

14121411
if (node::fPruneMode) {
14131412
const CBlockIndex* block = CHECK_NONFATAL(tip);
1414-
while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) {
1415-
block = block->pprev;
1416-
}
1417-
1418-
obj.pushKV("pruneheight", block->nHeight);
1413+
obj.pushKV("pruneheight", node::GetFirstStoredBlock(block)->nHeight);
14191414

14201415
// if 0, execution bypasses the whole if block.
14211416
bool automatic_pruning{args.GetIntArg("-prune", 0) != 1};

src/validation.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include <deploymentstatus.h>
2020
#include <flatfile.h>
2121
#include <hash.h>
22-
#include <index/blockfilterindex.h>
2322
#include <logging.h>
2423
#include <logging/timer.h>
2524
#include <mainchainrpc.h>
@@ -112,6 +111,12 @@ const std::vector<std::string> CHECKLEVEL_DOC {
112111
"level 4 tries to reconnect the blocks",
113112
"each level includes the checks of the previous levels",
114113
};
114+
/** The number of blocks to keep below the deepest prune lock.
115+
* There is nothing special about this number. It is higher than what we
116+
* expect to see in regular mainnet reorgs, but not so high that it would
117+
* noticeably interfere with the pruning mechanism.
118+
* */
119+
static constexpr int PRUNE_LOCK_BUFFER{10};
115120

116121
/**
117122
* Mutex to guard access to validation specific variables, such as reading
@@ -2564,12 +2569,24 @@ bool CChainState::FlushStateToDisk(
25642569
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
25652570
LOCK(m_blockman.cs_LastBlockFile);
25662571
if (fPruneMode && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && !fReindex) {
2567-
// make sure we don't prune above the blockfilterindexes bestblocks
2572+
// make sure we don't prune above any of the prune locks bestblocks
25682573
// pruning is height-based
2569-
int last_prune = m_chain.Height(); // last height we can prune
2570-
ForEachBlockFilterIndex([&](BlockFilterIndex& index) {
2571-
last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height));
2572-
});
2574+
int last_prune{m_chain.Height()}; // last height we can prune
2575+
std::optional<std::string> limiting_lock; // prune lock that actually was the limiting factor, only used for logging
2576+
2577+
for (const auto& prune_lock : m_blockman.m_prune_locks) {
2578+
if (prune_lock.second.height_first == std::numeric_limits<int>::max()) continue;
2579+
// Remove the buffer and one additional block here to get actual height that is outside of the buffer
2580+
const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1};
2581+
last_prune = std::max(1, std::min(last_prune, lock_height));
2582+
if (last_prune == lock_height) {
2583+
limiting_lock = prune_lock.first;
2584+
}
2585+
}
2586+
2587+
if (limiting_lock) {
2588+
LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune);
2589+
}
25732590

25742591
if (nManualPruneHeight > 0) {
25752592
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
@@ -2846,6 +2863,18 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
28462863
assert(flushed);
28472864
}
28482865
LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI);
2866+
2867+
{
2868+
// Prune locks that began at or after the tip should be moved backward so they get a chance to reorg
2869+
const int max_height_first{pindexDelete->nHeight - 1};
2870+
for (auto& prune_lock : m_blockman.m_prune_locks) {
2871+
if (prune_lock.second.height_first <= max_height_first) continue;
2872+
2873+
prune_lock.second.height_first = max_height_first;
2874+
LogPrint(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first);
2875+
}
2876+
}
2877+
28492878
// Write the chain state to disk, if necessary.
28502879
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
28512880
return false;

0 commit comments

Comments
 (0)