aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Jahr <fjahr@protonmail.com>2021-05-13 19:13:08 +0200
committerFabian Jahr <fjahr@protonmail.com>2022-04-25 23:21:58 +0200
commit2561823531c25e1510c107eb41de944b00444ce0 (patch)
tree715633d4adf89c6c78b9733ed1f2d6cd776843bf
parent231fc7b035481f748159968353c1cab81354e843 (diff)
downloadbitcoin-2561823531c25e1510c107eb41de944b00444ce0.tar.xz
blockstorage: Add prune locks to BlockManager
This change also introduces an aditional buffer of 10 blocks (PRUNE_LOCK_BUFFER) that will not be pruned before the best block. Co-authored-by: Luke Dashjr <luke-jr+git@utopios.org>
-rw-r--r--src/node/blockstorage.cpp7
-rw-r--r--src/node/blockstorage.h16
-rw-r--r--src/validation.cpp40
3 files changed, 60 insertions, 3 deletions
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
index c3f42fde2d..8ed22bbbce 100644
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -21,6 +21,8 @@
#include <util/system.h>
#include <validation.h>
+#include <unordered_map>
+
namespace node {
std::atomic_bool fImporting(false);
std::atomic_bool fReindex(false);
@@ -230,6 +232,11 @@ void BlockManager::FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPr
nLastBlockWeCanPrune, count);
}
+void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
+ AssertLockHeld(::cs_main);
+ m_prune_locks[name] = lock_info;
+}
+
CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
{
AssertLockHeld(cs_main);
diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
index 229c099f1f..b33941dfa6 100644
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -13,6 +13,7 @@
#include <atomic>
#include <cstdint>
+#include <unordered_map>
#include <vector>
extern RecursiveMutex cs_main;
@@ -65,6 +66,10 @@ struct CBlockIndexHeightOnlyComparator {
bool operator()(const CBlockIndex* pa, const CBlockIndex* pb) const;
};
+struct PruneLockInfo {
+ int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned
+};
+
/**
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
* to determine where the most-work tip is.
@@ -118,6 +123,14 @@ private:
/** Dirty block file entries. */
std::set<int> m_dirty_fileinfo;
+ /**
+ * Map from external index name to oldest block that must not be pruned.
+ *
+ * @note Internally, only blocks at height (height_first - PRUNE_LOCK_BUFFER - 1) and
+ * below will be pruned, but callers should avoid assuming any particular buffer size.
+ */
+ std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main);
+
public:
BlockMap m_block_index GUARDED_BY(cs_main);
@@ -175,6 +188,9 @@ public:
//! Check whether the block associated with this index entry is pruned or not.
bool IsBlockPruned(const CBlockIndex* pblockindex) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+ //! Create or update a prune lock identified by its name
+ void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
+
~BlockManager()
{
Unload();
diff --git a/src/validation.cpp b/src/validation.cpp
index 58686632f9..c0c541a396 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -106,6 +106,12 @@ const std::vector<std::string> CHECKLEVEL_DOC {
"level 4 tries to reconnect the blocks",
"each level includes the checks of the previous levels",
};
+/** The number of blocks to keep below the deepest prune lock.
+ * There is nothing special about this number. It is higher than what we
+ * expect to see in regular mainnet reorgs, but not so high that it would
+ * noticeably interfere with the pruning mechanism.
+ * */
+static constexpr int PRUNE_LOCK_BUFFER{10};
/**
* Mutex to guard access to validation specific variables, such as reading
@@ -2338,13 +2344,29 @@ bool CChainState::FlushStateToDisk(
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
LOCK(m_blockman.cs_LastBlockFile);
if (fPruneMode && (m_blockman.m_check_for_pruning || nManualPruneHeight > 0) && !fReindex) {
- // make sure we don't prune above the blockfilterindexes bestblocks
+ // make sure we don't prune above any of the prune locks bestblocks
// pruning is height-based
- int last_prune = m_chain.Height(); // last height we can prune
+ int last_prune{m_chain.Height()}; // last height we can prune
+ std::optional<std::string> limiting_lock; // prune lock that actually was the limiting factor, only used for logging
+
ForEachBlockFilterIndex([&](BlockFilterIndex& index) {
- last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height));
+ last_prune = std::max(1, std::min(last_prune, index.GetSummary().best_block_height));
});
+ for (const auto& prune_lock : m_blockman.m_prune_locks) {
+ if (prune_lock.second.height_first == std::numeric_limits<int>::max()) continue;
+ // Remove the buffer and one additional block here to get actual height that is outside of the buffer
+ const int lock_height{prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1};
+ last_prune = std::max(1, std::min(last_prune, lock_height));
+ if (last_prune == lock_height) {
+ limiting_lock = prune_lock.first;
+ }
+ }
+
+ if (limiting_lock) {
+ LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune);
+ }
+
if (nManualPruneHeight > 0) {
LOG_TIME_MILLIS_WITH_CATEGORY("find files to prune (manual)", BCLog::BENCH);
@@ -2581,6 +2603,18 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
assert(flushed);
}
LogPrint(BCLog::BENCH, "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * MILLI);
+
+ {
+ // Prune locks that began at or after the tip should be moved backward so they get a chance to reorg
+ const int max_height_first{pindexDelete->nHeight - 1};
+ for (auto& prune_lock : m_blockman.m_prune_locks) {
+ if (prune_lock.second.height_first <= max_height_first) continue;
+
+ prune_lock.second.height_first = max_height_first;
+ LogPrint(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first);
+ }
+ }
+
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FlushStateMode::IF_NEEDED)) {
return false;