mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge 162f0dba2f
into c5e44a0435
This commit is contained in:
commit
16bd4ed830
11 changed files with 339 additions and 38 deletions
|
@ -34,6 +34,7 @@ struct CBlockLocator;
|
||||||
struct FeeCalculation;
|
struct FeeCalculation;
|
||||||
namespace node {
|
namespace node {
|
||||||
struct NodeContext;
|
struct NodeContext;
|
||||||
|
struct PruneLockInfo;
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
namespace interfaces {
|
namespace interfaces {
|
||||||
|
@ -142,6 +143,10 @@ public:
|
||||||
//! pruned), and contains transactions.
|
//! pruned), and contains transactions.
|
||||||
virtual bool haveBlockOnDisk(int height) = 0;
|
virtual bool haveBlockOnDisk(int height) = 0;
|
||||||
|
|
||||||
|
virtual bool pruneLockExists(const std::string& name) const = 0;
|
||||||
|
virtual bool updatePruneLock(const std::string& name, const node::PruneLockInfo& lock_info, bool sync=false) = 0;
|
||||||
|
virtual bool deletePruneLock(const std::string& name) = 0;
|
||||||
|
|
||||||
//! Get locator for the current chain tip.
|
//! Get locator for the current chain tip.
|
||||||
virtual CBlockLocator getTipLocator() = 0;
|
virtual CBlockLocator getTipLocator() = 0;
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <util/batchpriority.h>
|
#include <util/batchpriority.h>
|
||||||
#include <util/check.h>
|
#include <util/check.h>
|
||||||
#include <util/fs.h>
|
#include <util/fs.h>
|
||||||
|
#include <util/overflow.h>
|
||||||
#include <util/signalinterrupt.h>
|
#include <util/signalinterrupt.h>
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
|
@ -46,6 +47,7 @@ static constexpr uint8_t DB_BLOCK_INDEX{'b'};
|
||||||
static constexpr uint8_t DB_FLAG{'F'};
|
static constexpr uint8_t DB_FLAG{'F'};
|
||||||
static constexpr uint8_t DB_REINDEX_FLAG{'R'};
|
static constexpr uint8_t DB_REINDEX_FLAG{'R'};
|
||||||
static constexpr uint8_t DB_LAST_BLOCK{'l'};
|
static constexpr uint8_t DB_LAST_BLOCK{'l'};
|
||||||
|
static constexpr uint8_t DB_PRUNE_LOCK{'L'};
|
||||||
// Keys used in previous version that might still be found in the DB:
|
// Keys used in previous version that might still be found in the DB:
|
||||||
// BlockTreeDB::DB_TXINDEX_BLOCK{'T'};
|
// BlockTreeDB::DB_TXINDEX_BLOCK{'T'};
|
||||||
// BlockTreeDB::DB_TXINDEX{'t'}
|
// BlockTreeDB::DB_TXINDEX{'t'}
|
||||||
|
@ -75,7 +77,7 @@ bool BlockTreeDB::ReadLastBlockFile(int& nFile)
|
||||||
return Read(DB_LAST_BLOCK, nFile);
|
return Read(DB_LAST_BLOCK, nFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*>>& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo)
|
bool BlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*>>& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo, const std::unordered_map<std::string, node::PruneLockInfo>& prune_locks)
|
||||||
{
|
{
|
||||||
CDBBatch batch(*this);
|
CDBBatch batch(*this);
|
||||||
for (const auto& [file, info] : fileInfo) {
|
for (const auto& [file, info] : fileInfo) {
|
||||||
|
@ -85,9 +87,41 @@ bool BlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFi
|
||||||
for (const CBlockIndex* bi : blockinfo) {
|
for (const CBlockIndex* bi : blockinfo) {
|
||||||
batch.Write(std::make_pair(DB_BLOCK_INDEX, bi->GetBlockHash()), CDiskBlockIndex{bi});
|
batch.Write(std::make_pair(DB_BLOCK_INDEX, bi->GetBlockHash()), CDiskBlockIndex{bi});
|
||||||
}
|
}
|
||||||
|
for (const auto& prune_lock : prune_locks) {
|
||||||
|
if (prune_lock.second.temporary) continue;
|
||||||
|
batch.Write(std::make_pair(DB_PRUNE_LOCK, prune_lock.first), prune_lock.second);
|
||||||
|
}
|
||||||
return WriteBatch(batch, true);
|
return WriteBatch(batch, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BlockTreeDB::WritePruneLock(const std::string& name, const node::PruneLockInfo& lock_info) {
|
||||||
|
if (lock_info.temporary) return true;
|
||||||
|
return Write(std::make_pair(DB_PRUNE_LOCK, name), lock_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlockTreeDB::DeletePruneLock(const std::string& name) {
|
||||||
|
return Erase(std::make_pair(DB_PRUNE_LOCK, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlockTreeDB::LoadPruneLocks(std::unordered_map<std::string, node::PruneLockInfo>& prune_locks, const util::SignalInterrupt& interrupt) {
|
||||||
|
std::unique_ptr<CDBIterator> pcursor(NewIterator());
|
||||||
|
for (pcursor->Seek(DB_PRUNE_LOCK); pcursor->Valid(); pcursor->Next()) {
|
||||||
|
if (interrupt) return false;
|
||||||
|
|
||||||
|
std::pair<uint8_t, std::string> key;
|
||||||
|
if ((!pcursor->GetKey(key)) || key.first != DB_PRUNE_LOCK) break;
|
||||||
|
|
||||||
|
node::PruneLockInfo& lock_info = prune_locks[key.second];
|
||||||
|
if (!pcursor->GetValue(lock_info)) {
|
||||||
|
LogError("%s: failed to %s prune lock '%s'\n", __func__, "read", key.second);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lock_info.temporary = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool BlockTreeDB::WriteFlag(const std::string& name, bool fValue)
|
bool BlockTreeDB::WriteFlag(const std::string& name, bool fValue)
|
||||||
{
|
{
|
||||||
return Write(std::make_pair(DB_FLAG, name), fValue ? uint8_t{'1'} : uint8_t{'0'});
|
return Write(std::make_pair(DB_FLAG, name), fValue ? uint8_t{'1'} : uint8_t{'0'});
|
||||||
|
@ -176,6 +210,13 @@ bool CBlockIndexHeightOnlyComparator::operator()(const CBlockIndex* pa, const CB
|
||||||
return pa->nHeight < pb->nHeight;
|
return pa->nHeight < pb->nHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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};
|
||||||
|
|
||||||
std::vector<CBlockIndex*> BlockManager::GetAllBlockIndices()
|
std::vector<CBlockIndex*> BlockManager::GetAllBlockIndices()
|
||||||
{
|
{
|
||||||
AssertLockHeld(cs_main);
|
AssertLockHeld(cs_main);
|
||||||
|
@ -269,6 +310,24 @@ void BlockManager::PruneOneBlockFile(const int fileNumber)
|
||||||
m_dirty_fileinfo.insert(fileNumber);
|
m_dirty_fileinfo.insert(fileNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BlockManager::DoPruneLocksForbidPruning(const CBlockFileInfo& block_file_info)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_main);
|
||||||
|
for (const auto& prune_lock : m_prune_locks) {
|
||||||
|
if (prune_lock.second.height_first == std::numeric_limits<uint64_t>::max()) continue;
|
||||||
|
// Remove the buffer and one additional block here to get actual height that is outside of the buffer
|
||||||
|
const uint64_t lock_height{(prune_lock.second.height_first <= PRUNE_LOCK_BUFFER + 1) ? 1 : (prune_lock.second.height_first - PRUNE_LOCK_BUFFER - 1)};
|
||||||
|
const uint64_t lock_height_last{SaturatingAdd(prune_lock.second.height_last, (uint64_t)PRUNE_LOCK_BUFFER)};
|
||||||
|
if (block_file_info.nHeightFirst > lock_height_last) continue;
|
||||||
|
if (block_file_info.nHeightLast <= lock_height) continue;
|
||||||
|
// TODO: Check each block within the file against the prune_lock range
|
||||||
|
|
||||||
|
LogDebug(BCLog::PRUNE, "%s limited pruning to height %d\n", prune_lock.first, lock_height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void BlockManager::FindFilesToPruneManual(
|
void BlockManager::FindFilesToPruneManual(
|
||||||
std::set<int>& setFilesToPrune,
|
std::set<int>& setFilesToPrune,
|
||||||
int nManualPruneHeight,
|
int nManualPruneHeight,
|
||||||
|
@ -291,6 +350,8 @@ void BlockManager::FindFilesToPruneManual(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DoPruneLocksForbidPruning(m_blockfile_info[fileNumber])) continue;
|
||||||
|
|
||||||
PruneOneBlockFile(fileNumber);
|
PruneOneBlockFile(fileNumber);
|
||||||
setFilesToPrune.insert(fileNumber);
|
setFilesToPrune.insert(fileNumber);
|
||||||
count++;
|
count++;
|
||||||
|
@ -359,6 +420,8 @@ void BlockManager::FindFilesToPrune(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DoPruneLocksForbidPruning(m_blockfile_info[fileNumber])) continue;
|
||||||
|
|
||||||
PruneOneBlockFile(fileNumber);
|
PruneOneBlockFile(fileNumber);
|
||||||
// Queue up the files for removal
|
// Queue up the files for removal
|
||||||
setFilesToPrune.insert(fileNumber);
|
setFilesToPrune.insert(fileNumber);
|
||||||
|
@ -373,9 +436,41 @@ void BlockManager::FindFilesToPrune(
|
||||||
min_block_to_prune, last_block_can_prune, count);
|
min_block_to_prune, last_block_can_prune, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) {
|
bool BlockManager::PruneLockExists(const std::string& name) const {
|
||||||
|
return m_prune_locks.count(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlockManager::UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info, const bool sync) {
|
||||||
AssertLockHeld(::cs_main);
|
AssertLockHeld(::cs_main);
|
||||||
m_prune_locks[name] = lock_info;
|
if (sync) {
|
||||||
|
if (!m_block_tree_db->WritePruneLock(name, lock_info)) {
|
||||||
|
LogError("%s: failed to %s prune lock '%s'\n", __func__, "write", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PruneLockInfo& stored_lock_info = m_prune_locks[name];
|
||||||
|
if (lock_info.temporary && !stored_lock_info.temporary) {
|
||||||
|
// Erase non-temporary lock from disk
|
||||||
|
if (!m_block_tree_db->DeletePruneLock(name)) {
|
||||||
|
LogError("%s: failed to %s prune lock '%s'\n", __func__, "erase", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stored_lock_info = lock_info;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlockManager::DeletePruneLock(const std::string& name)
|
||||||
|
{
|
||||||
|
AssertLockHeld(::cs_main);
|
||||||
|
m_prune_locks.erase(name);
|
||||||
|
|
||||||
|
// Since there is no reasonable expectation for any follow-up to this prune lock, actually ensure it gets committed to disk immediately
|
||||||
|
if (!m_block_tree_db->DeletePruneLock(name)) {
|
||||||
|
LogError("%s: failed to %s prune lock '%s'\n", __func__, "erase", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
|
CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
|
||||||
|
@ -401,6 +496,8 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_block_tree_db->LoadPruneLocks(m_prune_locks, m_interrupt)) return false;
|
||||||
|
|
||||||
if (snapshot_blockhash) {
|
if (snapshot_blockhash) {
|
||||||
const std::optional<AssumeutxoData> maybe_au_data = GetParams().AssumeutxoForBlockhash(*snapshot_blockhash);
|
const std::optional<AssumeutxoData> maybe_au_data = GetParams().AssumeutxoForBlockhash(*snapshot_blockhash);
|
||||||
if (!maybe_au_data) {
|
if (!maybe_au_data) {
|
||||||
|
@ -489,7 +586,7 @@ bool BlockManager::WriteBlockIndexDB()
|
||||||
m_dirty_blockindex.erase(it++);
|
m_dirty_blockindex.erase(it++);
|
||||||
}
|
}
|
||||||
int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
|
int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
|
||||||
if (!m_block_tree_db->WriteBatchSync(vFiles, max_blockfile, vBlocks)) {
|
if (!m_block_tree_db->WriteBatchSync(vFiles, max_blockfile, vBlocks, m_prune_locks)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -42,6 +42,9 @@ class ChainstateManager;
|
||||||
namespace Consensus {
|
namespace Consensus {
|
||||||
struct Params;
|
struct Params;
|
||||||
}
|
}
|
||||||
|
namespace node {
|
||||||
|
struct PruneLockInfo;
|
||||||
|
};
|
||||||
namespace util {
|
namespace util {
|
||||||
class SignalInterrupt;
|
class SignalInterrupt;
|
||||||
} // namespace util
|
} // namespace util
|
||||||
|
@ -52,15 +55,18 @@ class BlockTreeDB : public CDBWrapper
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using CDBWrapper::CDBWrapper;
|
using CDBWrapper::CDBWrapper;
|
||||||
bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*>>& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo);
|
bool WriteBatchSync(const std::vector<std::pair<int, const CBlockFileInfo*>>& fileInfo, int nLastFile, const std::vector<const CBlockIndex*>& blockinfo, const std::unordered_map<std::string, node::PruneLockInfo>& prune_locks);
|
||||||
bool ReadBlockFileInfo(int nFile, CBlockFileInfo& info);
|
bool ReadBlockFileInfo(int nFile, CBlockFileInfo& info);
|
||||||
bool ReadLastBlockFile(int& nFile);
|
bool ReadLastBlockFile(int& nFile);
|
||||||
bool WriteReindexing(bool fReindexing);
|
bool WriteReindexing(bool fReindexing);
|
||||||
void ReadReindexing(bool& fReindexing);
|
void ReadReindexing(bool& fReindexing);
|
||||||
|
bool WritePruneLock(const std::string& name, const node::PruneLockInfo&);
|
||||||
|
bool DeletePruneLock(const std::string& name);
|
||||||
bool WriteFlag(const std::string& name, bool fValue);
|
bool WriteFlag(const std::string& name, bool fValue);
|
||||||
bool ReadFlag(const std::string& name, bool& fValue);
|
bool ReadFlag(const std::string& name, bool& fValue);
|
||||||
bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt)
|
bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
bool LoadPruneLocks(std::unordered_map<std::string, node::PruneLockInfo>& prune_locks, const util::SignalInterrupt& interrupt);
|
||||||
};
|
};
|
||||||
} // namespace kernel
|
} // namespace kernel
|
||||||
|
|
||||||
|
@ -96,7 +102,17 @@ struct CBlockIndexHeightOnlyComparator {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PruneLockInfo {
|
struct PruneLockInfo {
|
||||||
int height_first{std::numeric_limits<int>::max()}; //! Height of earliest block that should be kept and not pruned
|
std::string desc; //! Arbitrary human-readable description of the lock purpose
|
||||||
|
uint64_t height_first{std::numeric_limits<uint64_t>::max()}; //! Height of earliest block that should be kept and not pruned
|
||||||
|
uint64_t height_last{std::numeric_limits<uint64_t>::max()}; //! Height of latest block that should be kept and not pruned
|
||||||
|
bool temporary{true};
|
||||||
|
|
||||||
|
SERIALIZE_METHODS(PruneLockInfo, obj)
|
||||||
|
{
|
||||||
|
READWRITE(obj.desc);
|
||||||
|
READWRITE(VARINT(obj.height_first));
|
||||||
|
READWRITE(VARINT(obj.height_last));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum BlockfileType {
|
enum BlockfileType {
|
||||||
|
@ -172,6 +188,8 @@ private:
|
||||||
|
|
||||||
AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
|
AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
|
||||||
|
|
||||||
|
bool DoPruneLocksForbidPruning(const CBlockFileInfo& block_file_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
/* 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(
|
void FindFilesToPruneManual(
|
||||||
std::set<int>& setFilesToPrune,
|
std::set<int>& setFilesToPrune,
|
||||||
|
@ -243,14 +261,17 @@ private:
|
||||||
/** Dirty block file entries. */
|
/** Dirty block file entries. */
|
||||||
std::set<int> m_dirty_fileinfo;
|
std::set<int> m_dirty_fileinfo;
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* Map from external index name to oldest block that must not be pruned.
|
* 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
|
* @note Internally, only blocks before height (height_first - PRUNE_LOCK_BUFFER - 1) and
|
||||||
* below will be pruned, but callers should avoid assuming any particular buffer size.
|
* after height (height_last + PRUNE_LOCK_BUFFER) 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);
|
std::unordered_map<std::string, PruneLockInfo> m_prune_locks GUARDED_BY(::cs_main);
|
||||||
|
|
||||||
|
private:
|
||||||
BlockfileType BlockfileTypeForHeight(int height);
|
BlockfileType BlockfileTypeForHeight(int height);
|
||||||
|
|
||||||
const kernel::BlockManagerOpts m_opts;
|
const kernel::BlockManagerOpts m_opts;
|
||||||
|
@ -396,8 +417,10 @@ public:
|
||||||
//! Check whether the block associated with this index entry is pruned or not.
|
//! Check whether the block associated with this index entry is pruned or not.
|
||||||
bool IsBlockPruned(const CBlockIndex& block) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
bool IsBlockPruned(const CBlockIndex& block) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
|
bool PruneLockExists(const std::string& name) const SHARED_LOCKS_REQUIRED(::cs_main);
|
||||||
//! Create or update a prune lock identified by its name
|
//! 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);
|
bool UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info, bool sync=false) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
bool DeletePruneLock(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
/** Open a block file (blk?????.dat) */
|
/** Open a block file (blk?????.dat) */
|
||||||
AutoFile OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const;
|
AutoFile OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const;
|
||||||
|
|
|
@ -561,6 +561,24 @@ public:
|
||||||
const CBlockIndex* block{chainman().ActiveChain()[height]};
|
const CBlockIndex* block{chainman().ActiveChain()[height]};
|
||||||
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
|
return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0;
|
||||||
}
|
}
|
||||||
|
bool pruneLockExists(const std::string& name) const override
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
auto& blockman = m_node.chainman->m_blockman;
|
||||||
|
return blockman.PruneLockExists(name);
|
||||||
|
}
|
||||||
|
bool updatePruneLock(const std::string& name, const node::PruneLockInfo& lock_info, bool sync) override
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
auto& blockman = m_node.chainman->m_blockman;
|
||||||
|
return blockman.UpdatePruneLock(name, lock_info, sync);
|
||||||
|
}
|
||||||
|
bool deletePruneLock(const std::string& name) override
|
||||||
|
{
|
||||||
|
LOCK(cs_main);
|
||||||
|
auto& blockman = m_node.chainman->m_blockman;
|
||||||
|
return blockman.DeletePruneLock(name);
|
||||||
|
}
|
||||||
CBlockLocator getTipLocator() override
|
CBlockLocator getTipLocator() override
|
||||||
{
|
{
|
||||||
LOCK(::cs_main);
|
LOCK(::cs_main);
|
||||||
|
|
|
@ -852,6 +852,167 @@ std::optional<int> GetPruneHeight(const BlockManager& blockman, const CChain& ch
|
||||||
return CHECK_NONFATAL(first_unpruned.pprev)->nHeight;
|
return CHECK_NONFATAL(first_unpruned.pprev)->nHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan listprunelocks()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"listprunelocks",
|
||||||
|
"\nReturns a list of pruning locks.\n",
|
||||||
|
{},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::OBJ, "", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::ARR, "prune_locks", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::OBJ, "", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::STR, "id", "A unique identifier for the lock"},
|
||||||
|
{RPCResult::Type::STR, "desc", "A description of the lock's purpose"},
|
||||||
|
{RPCResult::Type::ARR_FIXED, "height", "Range of blocks prevented from being pruned",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::NUM, "height_first", "Height of first block that may not be pruned"},
|
||||||
|
{RPCResult::Type::NUM, "height_last", "Height of last block that may not be pruned (omitted if unbounded)"},
|
||||||
|
}},
|
||||||
|
{RPCResult::Type::BOOL, "temporary", "Indicates the lock will not remain after a restart of the node"},
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("listprunelocks", "")
|
||||||
|
+ HelpExampleRpc("listprunelocks", "")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
ChainstateManager& chainman = EnsureChainman(node);
|
||||||
|
Chainstate& active_chainstate = chainman.ActiveChainstate();
|
||||||
|
|
||||||
|
UniValue locks_uv(UniValue::VARR);
|
||||||
|
{
|
||||||
|
LOCK(::cs_main);
|
||||||
|
BlockManager * const blockman = &active_chainstate.m_blockman;
|
||||||
|
for (const auto& prune_lock : blockman->m_prune_locks) {
|
||||||
|
UniValue prune_lock_uv(UniValue::VOBJ);
|
||||||
|
const auto& lock_info = prune_lock.second;
|
||||||
|
prune_lock_uv.pushKV("id", prune_lock.first);
|
||||||
|
prune_lock_uv.pushKV("desc", lock_info.desc);
|
||||||
|
UniValue heights_uv(UniValue::VARR);
|
||||||
|
heights_uv.push_back(lock_info.height_first);
|
||||||
|
if (lock_info.height_last < std::numeric_limits<uint64_t>::max()) {
|
||||||
|
heights_uv.push_back(lock_info.height_last);
|
||||||
|
}
|
||||||
|
prune_lock_uv.pushKV("height", heights_uv);
|
||||||
|
prune_lock_uv.pushKV("temporary", lock_info.temporary);
|
||||||
|
locks_uv.push_back(prune_lock_uv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue result(UniValue::VOBJ);
|
||||||
|
result.pushKV("prune_locks", locks_uv);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static RPCHelpMan setprunelock()
|
||||||
|
{
|
||||||
|
return RPCHelpMan{"setprunelock",
|
||||||
|
"\nManipulate pruning locks.\n",
|
||||||
|
{
|
||||||
|
{"id", RPCArg::Type::STR, RPCArg::Optional::NO, "The unique id of the manipulated prune lock (or \"*\" if deleting all)"},
|
||||||
|
{"lock_info", RPCArg::Type::OBJ, RPCArg::Optional::NO, "An object describing the desired lock",
|
||||||
|
{
|
||||||
|
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Description of the lock"},
|
||||||
|
{"height", RPCArg::Type::RANGE, RPCArg::DefaultHint("deletes the lock"), "The range of block heights to prevent pruning"},
|
||||||
|
{"sync", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, success indicates the lock change was stored to disk (if non-temporary). If false, it is possible for a subsequent node crash to lose the lock."},
|
||||||
|
{"temporary", RPCArg::Type::BOOL, RPCArg::Default(false), "If true, the lock will not persist across node restart."},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RPCResult{
|
||||||
|
RPCResult::Type::OBJ, "", "",
|
||||||
|
{
|
||||||
|
{RPCResult::Type::BOOL, "success", "Whether the change was successful"},
|
||||||
|
}},
|
||||||
|
RPCExamples{
|
||||||
|
HelpExampleCli("setprunelock", "\"test\" \"{\\\"desc\\\": \\\"Just a test\\\", \\\"height\\\": [0,100]}\"")
|
||||||
|
+ HelpExampleCli("setprunelock", "\"test-2\" \"{\\\"desc\\\": \\\"Second RPC-created prunelock test\\\", \\\"height\\\": [100]}\"")
|
||||||
|
+ HelpExampleRpc("setprunelock", "\"test\", {\"desc\": \"Just a test\", \"height\": [0,100]}")
|
||||||
|
},
|
||||||
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
|
{
|
||||||
|
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
ChainstateManager& chainman = EnsureChainman(node);
|
||||||
|
Chainstate& active_chainstate = chainman.ActiveChainstate();
|
||||||
|
|
||||||
|
const auto& lock_info_json = request.params[1];
|
||||||
|
RPCTypeCheckObj(lock_info_json,
|
||||||
|
{
|
||||||
|
{"desc", UniValueType(UniValue::VSTR)},
|
||||||
|
{"height", UniValueType()}, // will be checked below
|
||||||
|
{"sync", UniValueType(UniValue::VBOOL)},
|
||||||
|
{"temporary", UniValueType(UniValue::VBOOL)},
|
||||||
|
},
|
||||||
|
/*fAllowNull=*/ true, /*fStrict=*/ true);
|
||||||
|
|
||||||
|
const auto& lockid = request.params[0].get_str();
|
||||||
|
|
||||||
|
node::PruneLockInfo lock_info;
|
||||||
|
|
||||||
|
auto height_param = lock_info_json["height"];
|
||||||
|
if (!height_param.isArray()) {
|
||||||
|
UniValue new_height_param(UniValue::VARR);
|
||||||
|
new_height_param.push_back(std::move(height_param));
|
||||||
|
height_param = std::move(new_height_param);
|
||||||
|
}
|
||||||
|
bool success;
|
||||||
|
if (height_param[0].isNull() && height_param[1].isNull()) {
|
||||||
|
// Delete
|
||||||
|
LOCK(::cs_main);
|
||||||
|
BlockManager * const blockman = &active_chainstate.m_blockman;
|
||||||
|
if (lockid == "*") {
|
||||||
|
// Delete all
|
||||||
|
success = true;
|
||||||
|
std::vector<std::string> all_ids;
|
||||||
|
all_ids.reserve(blockman->m_prune_locks.size());
|
||||||
|
for (const auto& prune_lock : blockman->m_prune_locks) {
|
||||||
|
all_ids.push_back(prune_lock.first);
|
||||||
|
}
|
||||||
|
for (auto& lockid : all_ids) {
|
||||||
|
success |= blockman->DeletePruneLock(lockid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success = blockman->PruneLockExists(lockid) && blockman->DeletePruneLock(lockid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (lockid == "*") throw JSONRPCError(RPC_INVALID_PARAMETER, "id \"*\" only makes sense when deleting");
|
||||||
|
if (!height_param[0].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid start height");
|
||||||
|
lock_info.height_first = height_param[0].getInt<uint64_t>();
|
||||||
|
if (!height_param[1].isNull()) {
|
||||||
|
if (!height_param[1].isNum()) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid end height");
|
||||||
|
lock_info.height_last = height_param[1].getInt<uint64_t>();
|
||||||
|
}
|
||||||
|
lock_info.desc = lock_info_json["desc"].get_str();
|
||||||
|
if (lock_info_json["temporary"].isNull()) {
|
||||||
|
lock_info.temporary = false;
|
||||||
|
} else {
|
||||||
|
lock_info.temporary = lock_info_json["temporary"].get_bool();
|
||||||
|
}
|
||||||
|
bool sync = false;
|
||||||
|
if (!lock_info_json["sync"].isNull()) {
|
||||||
|
sync = lock_info_json["sync"].get_bool();
|
||||||
|
}
|
||||||
|
LOCK(::cs_main);
|
||||||
|
BlockManager * const blockman = &active_chainstate.m_blockman;
|
||||||
|
success = blockman->UpdatePruneLock(lockid, lock_info, sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
UniValue result(UniValue::VOBJ);
|
||||||
|
result.pushKV("success", success);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
static RPCHelpMan pruneblockchain()
|
static RPCHelpMan pruneblockchain()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"pruneblockchain", "",
|
return RPCHelpMan{"pruneblockchain", "",
|
||||||
|
@ -3412,6 +3573,8 @@ void RegisterBlockchainRPCCommands(CRPCTable& t)
|
||||||
{"blockchain", &getdeploymentinfo},
|
{"blockchain", &getdeploymentinfo},
|
||||||
{"blockchain", &gettxout},
|
{"blockchain", &gettxout},
|
||||||
{"blockchain", &gettxoutsetinfo},
|
{"blockchain", &gettxoutsetinfo},
|
||||||
|
{"blockchain", &listprunelocks},
|
||||||
|
{"blockchain", &setprunelock},
|
||||||
{"blockchain", &pruneblockchain},
|
{"blockchain", &pruneblockchain},
|
||||||
{"blockchain", &verifychain},
|
{"blockchain", &verifychain},
|
||||||
{"blockchain", &preciousblock},
|
{"blockchain", &preciousblock},
|
||||||
|
|
|
@ -253,6 +253,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "verifychain", 1, "nblocks" },
|
{ "verifychain", 1, "nblocks" },
|
||||||
{ "getblockstats", 0, "hash_or_height" },
|
{ "getblockstats", 0, "hash_or_height" },
|
||||||
{ "getblockstats", 1, "stats" },
|
{ "getblockstats", 1, "stats" },
|
||||||
|
{ "setprunelock", 1, "lock_info" },
|
||||||
{ "pruneblockchain", 0, "height" },
|
{ "pruneblockchain", 0, "height" },
|
||||||
{ "keypoolrefill", 0, "newsize" },
|
{ "keypoolrefill", 0, "newsize" },
|
||||||
{ "getrawmempool", 0, "verbose" },
|
{ "getrawmempool", 0, "verbose" },
|
||||||
|
|
|
@ -89,7 +89,8 @@ FUZZ_TARGET(block_index, .init = init_block_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store these files and blocks in the block index. It should not fail.
|
// Store these files and blocks in the block index. It should not fail.
|
||||||
assert(block_index.WriteBatchSync(files_info, files_count - 1, blocks_info));
|
const std::unordered_map<std::string, node::PruneLockInfo> prune_locks;
|
||||||
|
assert(block_index.WriteBatchSync(files_info, files_count - 1, blocks_info, prune_locks));
|
||||||
|
|
||||||
// We should be able to read every block file info we stored. Its value should correspond to
|
// We should be able to read every block file info we stored. Its value should correspond to
|
||||||
// what we stored above.
|
// what we stored above.
|
||||||
|
|
|
@ -158,6 +158,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
||||||
"invalidateblock",
|
"invalidateblock",
|
||||||
"joinpsbts",
|
"joinpsbts",
|
||||||
"listbanned",
|
"listbanned",
|
||||||
|
"listprunelocks",
|
||||||
"logging",
|
"logging",
|
||||||
"mockscheduler",
|
"mockscheduler",
|
||||||
"ping",
|
"ping",
|
||||||
|
@ -171,6 +172,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
|
||||||
"sendrawtransaction",
|
"sendrawtransaction",
|
||||||
"setmocktime",
|
"setmocktime",
|
||||||
"setnetworkactive",
|
"setnetworkactive",
|
||||||
|
"setprunelock",
|
||||||
"signmessagewithprivkey",
|
"signmessagewithprivkey",
|
||||||
"signrawtransactionwithkey",
|
"signrawtransactionwithkey",
|
||||||
"submitblock",
|
"submitblock",
|
||||||
|
|
|
@ -104,12 +104,6 @@ const std::vector<std::string> CHECKLEVEL_DOC {
|
||||||
"level 4 tries to reconnect the blocks",
|
"level 4 tries to reconnect the blocks",
|
||||||
"each level includes the checks of the previous levels",
|
"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};
|
|
||||||
|
|
||||||
TRACEPOINT_SEMAPHORE(validation, block_connected);
|
TRACEPOINT_SEMAPHORE(validation, block_connected);
|
||||||
TRACEPOINT_SEMAPHORE(utxocache, flush);
|
TRACEPOINT_SEMAPHORE(utxocache, flush);
|
||||||
|
@ -2841,21 +2835,6 @@ bool Chainstate::FlushStateToDisk(
|
||||||
// make sure we don't prune above any of the prune locks bestblocks
|
// make sure we don't prune above any of the prune locks bestblocks
|
||||||
// pruning is height-based
|
// 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
|
|
||||||
|
|
||||||
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) {
|
|
||||||
LogDebug(BCLog::PRUNE, "%s limited pruning to height %d\n", limiting_lock.value(), last_prune);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -3088,13 +3067,14 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra
|
||||||
Ticks<MillisecondsDouble>(SteadyClock::now() - time_start));
|
Ticks<MillisecondsDouble>(SteadyClock::now() - time_start));
|
||||||
|
|
||||||
{
|
{
|
||||||
// Prune locks that began at or after the tip should be moved backward so they get a chance to reorg
|
// Prune locks that began around the tip should be moved backward so they get a chance to reorg
|
||||||
const int max_height_first{pindexDelete->nHeight - 1};
|
const uint64_t max_height_first{static_cast<uint64_t>(pindexDelete->nHeight - 1)};
|
||||||
for (auto& prune_lock : m_blockman.m_prune_locks) {
|
for (auto& prune_lock : m_blockman.m_prune_locks) {
|
||||||
if (prune_lock.second.height_first <= max_height_first) continue;
|
if (prune_lock.second.height_first < max_height_first) continue;
|
||||||
|
|
||||||
prune_lock.second.height_first = max_height_first;
|
--prune_lock.second.height_first;
|
||||||
LogDebug(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, max_height_first);
|
LogDebug(BCLog::PRUNE, "%s prune lock moved back to %d\n", prune_lock.first, prune_lock.second.height_first);
|
||||||
|
// NOTE: Don't need to write to db here, since it will get synced with the rest of the chainstate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
|
||||||
|
|
||||||
self.log.info("prune some blocks")
|
self.log.info("prune some blocks")
|
||||||
for node in self.nodes[:2]:
|
for node in self.nodes[:2]:
|
||||||
with node.assert_debug_log(['limited pruning to height 689']):
|
with node.assert_debug_log(['Prune: UnlinkPrunedFiles deleted blk/rev (00000)']):
|
||||||
pruneheight_new = node.pruneblockchain(400)
|
pruneheight_new = node.pruneblockchain(400)
|
||||||
# the prune heights used here and below are magic numbers that are determined by the
|
# the prune heights used here and below are magic numbers that are determined by the
|
||||||
# thresholds at which block files wrap, so they depend on disk serialization and default block file size.
|
# thresholds at which block files wrap, so they depend on disk serialization and default block file size.
|
||||||
|
@ -145,7 +145,7 @@ class FeatureIndexPruneTest(BitcoinTestFramework):
|
||||||
self.sync_index(height=2500)
|
self.sync_index(height=2500)
|
||||||
|
|
||||||
for node in self.nodes[:2]:
|
for node in self.nodes[:2]:
|
||||||
with node.assert_debug_log(['limited pruning to height 2489']):
|
with node.assert_debug_log(['Prune: UnlinkPrunedFiles deleted blk/rev (00007)']):
|
||||||
pruneheight_new = node.pruneblockchain(2500)
|
pruneheight_new = node.pruneblockchain(2500)
|
||||||
assert_equal(pruneheight_new, 2005)
|
assert_equal(pruneheight_new, 2005)
|
||||||
|
|
||||||
|
|
|
@ -323,6 +323,17 @@ class PruneTest(BitcoinTestFramework):
|
||||||
node.pruneblockchain(height(0))
|
node.pruneblockchain(height(0))
|
||||||
assert has_block(0), "blk00000.dat is missing when should still be there"
|
assert has_block(0), "blk00000.dat is missing when should still be there"
|
||||||
|
|
||||||
|
# height=500 shouldn't prune first file if there's a prune lock
|
||||||
|
node.setprunelock("test", {
|
||||||
|
"desc": "Testing",
|
||||||
|
"height": [2, 2],
|
||||||
|
})
|
||||||
|
assert_equal(node.listprunelocks(), {'prune_locks': [{'id': 'test', 'desc': 'Testing', 'height': [2, 2], 'temporary': False}]})
|
||||||
|
prune(500)
|
||||||
|
assert has_block(0), "blk00000.dat is missing when should still be there"
|
||||||
|
node.setprunelock("test", {}) # delete prune lock
|
||||||
|
assert_equal(node.listprunelocks(), {'prune_locks': []})
|
||||||
|
|
||||||
# height=500 should prune first file
|
# height=500 should prune first file
|
||||||
prune(500)
|
prune(500)
|
||||||
assert not has_block(0), "blk00000.dat is still there, should be pruned by now"
|
assert not has_block(0), "blk00000.dat is still there, should be pruned by now"
|
||||||
|
|
Loading…
Add table
Reference in a new issue