Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FreshEyes] Prune locks #12

Open
wants to merge 10 commits into
base: bitcoin-fresheyes-staging-master-19463
Choose a base branch
from
5 changes: 5 additions & 0 deletions src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct CBlockLocator;
struct FeeCalculation;
namespace node {
struct NodeContext;
struct PruneLockInfo;
} // namespace node

namespace interfaces {
Expand Down Expand Up @@ -136,6 +137,10 @@ class Chain
//! pruned), and contains transactions.
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.
virtual CBlockLocator getTipLocator() = 0;

Expand Down
101 changes: 97 additions & 4 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <undo.h>
#include <util/batchpriority.h>
#include <util/fs.h>
#include <util/overflow.h>
#include <util/signalinterrupt.h>
#include <util/strencodings.h>
#include <util/translation.h>
Expand All @@ -36,6 +37,7 @@ static constexpr uint8_t DB_BLOCK_INDEX{'b'};
static constexpr uint8_t DB_FLAG{'F'};
static constexpr uint8_t DB_REINDEX_FLAG{'R'};
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:
// BlockTreeDB::DB_TXINDEX_BLOCK{'T'};
// BlockTreeDB::DB_TXINDEX{'t'}
Expand Down Expand Up @@ -65,7 +67,7 @@ bool BlockTreeDB::ReadLastBlockFile(int& 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);
for (const auto& [file, info] : fileInfo) {
Expand All @@ -75,9 +77,40 @@ bool BlockTreeDB::WriteBatchSync(const std::vector<std::pair<int, const CBlockFi
for (const CBlockIndex* bi : blockinfo) {
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);
}

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)) {
return error("%s: failed to %s prune lock '%s'", __func__, "read", key.second);
}
lock_info.temporary = false;
}

return true;
}

bool BlockTreeDB::WriteFlag(const std::string& name, bool fValue)
{
return Write(std::make_pair(DB_FLAG, name), fValue ? uint8_t{'1'} : uint8_t{'0'});
Expand Down Expand Up @@ -165,6 +198,13 @@ bool CBlockIndexHeightOnlyComparator::operator()(const CBlockIndex* pa, const CB
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()
{
AssertLockHeld(cs_main);
Expand Down Expand Up @@ -258,6 +298,24 @@ void BlockManager::PruneOneBlockFile(const int 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

LogPrint(BCLog::PRUNE, "%s limited pruning to height %d\n", prune_lock.first, lock_height);
return true;
}
return false;
}

void BlockManager::FindFilesToPruneManual(
std::set<int>& setFilesToPrune,
int nManualPruneHeight,
Expand All @@ -280,6 +338,8 @@ void BlockManager::FindFilesToPruneManual(
continue;
}

if (DoPruneLocksForbidPruning(m_blockfile_info[fileNumber])) continue;

PruneOneBlockFile(fileNumber);
setFilesToPrune.insert(fileNumber);
count++;
Expand Down Expand Up @@ -344,6 +404,8 @@ void BlockManager::FindFilesToPrune(
continue;
}

if (DoPruneLocksForbidPruning(m_blockfile_info[fileNumber])) continue;

PruneOneBlockFile(fileNumber);
// Queue up the files for removal
setFilesToPrune.insert(fileNumber);
Expand All @@ -358,9 +420,38 @@ void BlockManager::FindFilesToPrune(
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);
if (sync) {
if (!m_block_tree_db->WritePruneLock(name, lock_info)) {
return error("%s: failed to %s prune lock '%s'", __func__, "write", name);
}
}
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)) {
return error("%s: failed to %s prune lock '%s'", __func__, "erase", name);
}
}
stored_lock_info = lock_info;
return true;
}

bool BlockManager::DeletePruneLock(const std::string& name)
{
AssertLockHeld(::cs_main);
m_prune_locks[name] = lock_info;
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)) {
return error("%s: failed to %s prune lock '%s'", __func__, "erase", name);
}
return true;
}

CBlockIndex* BlockManager::InsertBlockIndex(const uint256& hash)
Expand All @@ -386,6 +477,8 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
return false;
}

if (!m_block_tree_db->LoadPruneLocks(m_prune_locks, m_interrupt)) return false;

if (snapshot_blockhash) {
const AssumeutxoData au_data = *Assert(GetParams().AssumeutxoForBlockhash(*snapshot_blockhash));
m_snapshot_height = au_data.height;
Expand Down Expand Up @@ -468,7 +561,7 @@ bool BlockManager::WriteBlockIndexDB()
m_dirty_blockindex.erase(it++);
}
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 true;
Expand Down
33 changes: 28 additions & 5 deletions src/node/blockstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ struct FlatFilePos;
namespace Consensus {
struct Params;
}
namespace node {
struct PruneLockInfo;
};
namespace util {
class SignalInterrupt;
} // namespace util
Expand All @@ -51,15 +54,18 @@ class BlockTreeDB : public CDBWrapper
{
public:
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 ReadLastBlockFile(int& nFile);
bool WriteReindexing(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 ReadFlag(const std::string& name, bool& fValue);
bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex, const util::SignalInterrupt& interrupt)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
bool LoadPruneLocks(std::unordered_map<std::string, node::PruneLockInfo>& prune_locks, const util::SignalInterrupt& interrupt);
};
} // namespace kernel

Expand Down Expand Up @@ -94,7 +100,17 @@ struct CBlockIndexHeightOnlyComparator {
};

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 {
Expand Down Expand Up @@ -167,6 +183,8 @@ class BlockManager
bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const;
bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock) 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 */
void FindFilesToPruneManual(
std::set<int>& setFilesToPrune,
Expand Down Expand Up @@ -236,14 +254,17 @@ class BlockManager
/** Dirty block file entries. */
std::set<int> m_dirty_fileinfo;

public:
/**
* 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.
* @note Internally, only blocks before height (height_first - PRUNE_LOCK_BUFFER - 1) and
* 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);

private:
BlockfileType BlockfileTypeForHeight(int height);

const kernel::BlockManagerOpts m_opts;
Expand Down Expand Up @@ -346,8 +367,10 @@ class BlockManager
//! Check whether the block associated with this index entry is pruned or not.
bool IsBlockPruned(const CBlockIndex* pblockindex) 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
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) */
CAutoFile OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false) const;
Expand Down
18 changes: 18 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,24 @@ class ChainImpl : public Chain
const CBlockIndex* block{chainman().ActiveChain()[height]};
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
{
LOCK(::cs_main);
Expand Down
Loading
Loading