Page MenuHomePhabricator

D17896.id53375.diff
No OneTemporary

D17896.id53375.diff

diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h
--- a/src/node/blockstorage.h
+++ b/src/node/blockstorage.h
@@ -13,6 +13,7 @@
#include <chain.h>
#include <chainparams.h>
#include <kernel/blockmanager_opts.h>
+#include <kernel/chain.h>
#include <kernel/cs_main.h>
#include <protocol.h>
#include <sync.h>
@@ -28,7 +29,6 @@
class CTxUndo;
class Chainstate;
class ChainstateManager;
-enum class ChainstateRole;
struct CCheckpointData;
class Config;
struct FlatFilePos;
@@ -65,6 +65,34 @@
int height_first{std::numeric_limits<int>::max()};
};
+enum BlockfileType {
+ // Values used as array indexes - do not change carelessly.
+ NORMAL = 0,
+ ASSUMED = 1,
+ NUM_TYPES = 2,
+};
+
+std::ostream &operator<<(std::ostream &os, const BlockfileType &type);
+
+struct BlockfileCursor {
+ // The latest blockfile number.
+ int file_num{0};
+
+ // Track the height of the highest block in file_num whose undo
+ // data has been written. Block data is written to block files in download
+ // order, but is written to undo files in validation order, which is
+ // usually in order by height. To avoid wasting disk space, undo files will
+ // be trimmed whenever the corresponding block file is finalized and
+ // the height of the highest block written to the block file equals the
+ // height of the highest block written to the undo file. This is a
+ // heuristic and can sometimes preemptively trim undo files that will write
+ // more data later, and sometimes fail to trim undo files that can't have
+ // more data written later.
+ int undo_height{0};
+};
+
+std::ostream &operator<<(std::ostream &os, const BlockfileCursor &cursor);
+
/**
* Maintains a tree of blocks (stored in `m_block_index`) which is consulted
* to determine where the most-work tip is.
@@ -90,8 +118,8 @@
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Return false if block file or undo file flushing fails. */
- [[nodiscard]] bool FlushBlockFile(bool fFinalize = false,
- bool finalize_undo = false);
+ [[nodiscard]] bool FlushBlockFile(int blockfile_num, bool fFinalize,
+ bool finalize_undo);
/** Return false if undo file flushing fails. */
[[nodiscard]] bool FlushUndoFile(int block_file, bool finalize = false);
@@ -99,6 +127,7 @@
[[nodiscard]] bool FindBlockPos(FlatFilePos &pos, unsigned int nAddSize,
unsigned int nHeight, uint64_t nTime,
bool fKnown);
+ [[nodiscard]] bool FlushChainstateBlockFile(int tip_height);
bool FindUndoPos(BlockValidationState &state, int nFile, FlatFilePos &pos,
unsigned int nAddSize);
@@ -151,19 +180,31 @@
RecursiveMutex cs_LastBlockFile;
std::vector<CBlockFileInfo> m_blockfile_info;
- int m_last_blockfile = 0;
- // Track the height of the highest block in m_last_blockfile whose undo
- // data has been written. Block data is written to block files in download
- // order, but is written to undo files in validation order, which is
- // usually in order by height. To avoid wasting disk space, undo files will
- // be trimmed whenever the corresponding block file is finalized and
- // the height of the highest block written to the block file equals the
- // height of the highest block written to the undo file. This is a
- // heuristic and can sometimes preemptively trim undo files that will write
- // more data later, and sometimes fail to trim undo files that can't have
- // more data written later.
- unsigned int m_undo_height_in_last_blockfile = 0;
+ //! Since assumedvalid chainstates may be syncing a range of the chain that
+ //! is very far away from the normal/background validation process, we
+ //! should segment blockfiles for assumed chainstates. Otherwise, we might
+ //! have wildly different height ranges mixed into the same block files,
+ //! which would impair our ability to prune effectively.
+ //!
+ //! This data structure maintains separate blockfile number cursors for each
+ //! BlockfileType. The ASSUMED state is initialized, when necessary, in
+ //! FindBlockPos().
+ //!
+ //! The first element is the NORMAL cursor, second is ASSUMED.
+ std::array<std::optional<BlockfileCursor>, BlockfileType::NUM_TYPES>
+ m_blockfile_cursors GUARDED_BY(cs_LastBlockFile) = {{
+ BlockfileCursor{},
+ std::nullopt,
+ }};
+ int MaxBlockfileNum() const EXCLUSIVE_LOCKS_REQUIRED(cs_LastBlockFile) {
+ static const BlockfileCursor empty_cursor;
+ const auto &normal =
+ m_blockfile_cursors[BlockfileType::NORMAL].value_or(empty_cursor);
+ const auto &assumed =
+ m_blockfile_cursors[BlockfileType::ASSUMED].value_or(empty_cursor);
+ return std::max(normal.file_num, assumed.file_num);
+ }
/**
* Global flag to indicate we should check to see if there are
@@ -190,6 +231,8 @@
std::unordered_map<std::string, PruneLockInfo>
m_prune_locks GUARDED_BY(::cs_main);
+ BlockfileType BlockfileTypeForHeight(int height);
+
const kernel::BlockManagerOpts m_opts;
public:
@@ -202,6 +245,20 @@
BlockMap m_block_index GUARDED_BY(cs_main);
+ /**
+ * The height of the base block of an assumeutxo snapshot, if one is in use.
+ *
+ * This controls how blockfiles are segmented by chainstate type to avoid
+ * comingling different height regions of the chain when an assumedvalid
+ * chainstate is in use. If heights are drastically different in the same
+ * blockfile, pruning suffers.
+ *
+ * This is set during ActivateSnapshot() or upon LoadBlockIndex() if a
+ * snapshot had been previously loaded. After the snapshot is validated,
+ * this is unset to restore normal LoadBlockIndex behavior.
+ */
+ std::optional<int> m_snapshot_height;
+
std::vector<CBlockIndex *> GetAllBlockIndices()
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp
--- a/src/node/blockstorage.cpp
+++ b/src/node/blockstorage.cpp
@@ -13,6 +13,7 @@
#include <consensus/validation.h>
#include <flatfile.h>
#include <hash.h>
+#include <kernel/chain.h>
#include <kernel/chainparams.h>
#include <logging.h>
#include <pow/pow.h>
@@ -142,7 +143,8 @@
const auto [min_block_to_prune, last_block_can_prune] =
chainman.GetPruneRange(chain, nManualPruneHeight);
int count = 0;
- for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
+ for (int fileNumber = 0; fileNumber < this->MaxBlockfileNum();
+ fileNumber++) {
const auto &fileinfo = m_blockfile_info[fileNumber];
if (fileinfo.nSize == 0 ||
fileinfo.nHeightLast > (unsigned)last_block_can_prune ||
@@ -196,7 +198,8 @@
nBuffer += target / 10;
}
- for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
+ for (int fileNumber = 0; fileNumber < this->MaxBlockfileNum();
+ fileNumber++) {
const auto &fileinfo = m_blockfile_info[fileNumber];
nBytesToPrune = fileinfo.nSize + fileinfo.nUndoSize;
@@ -263,11 +266,10 @@
return false;
}
- int snapshot_height = -1;
if (snapshot_blockhash) {
const AssumeutxoData au_data =
*Assert(GetParams().AssumeutxoForBlockhash(*snapshot_blockhash));
- snapshot_height = au_data.height;
+ m_snapshot_height = au_data.height;
CBlockIndex *base{LookupBlockIndex(*snapshot_blockhash)};
// Since nChainTx (responsible for estimated progress) isn't persisted
@@ -276,8 +278,16 @@
base->nChainTx = au_data.nChainTx;
LogPrintf("[snapshot] set nChainTx=%d for %s\n", au_data.nChainTx,
snapshot_blockhash->ToString());
+ } else {
+ // If this isn't called with a snapshot blockhash, make sure the cached
+ // snapshot height is null. This is relevant during snapshot
+ // completion, when the blockman may be loaded with a height that then
+ // needs to be cleared after the snapshot is fully validated.
+ m_snapshot_height.reset();
}
+ Assert(m_snapshot_height.has_value() == snapshot_blockhash.has_value());
+
// Calculate nChainWork
std::vector<CBlockIndex *> vSortedByHeight{GetAllBlockIndices()};
std::sort(vSortedByHeight.begin(), vSortedByHeight.end(),
@@ -298,7 +308,7 @@
// basis of snapshot load (see PopulateAndValidateSnapshot()).
// Pruned nodes may have deleted the block.
if (pindex->nTx > 0) {
- if (snapshot_blockhash && pindex->nHeight == snapshot_height &&
+ if (m_snapshot_height && pindex->nHeight == *m_snapshot_height &&
pindex->GetBlockHash() == *snapshot_blockhash) {
Assert(pindex->pprev);
// Should have been set above; don't disturb it with code below.
@@ -340,7 +350,9 @@
m_dirty_blockindex.clear();
- if (!m_block_tree_db->WriteBatchSync(vFiles, m_last_blockfile, vBlocks)) {
+ int max_blockfile =
+ WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
+ if (!m_block_tree_db->WriteBatchSync(vFiles, max_blockfile, vBlocks)) {
return false;
}
return true;
@@ -351,17 +363,18 @@
if (!LoadBlockIndex(snapshot_blockhash)) {
return false;
}
+ int max_blockfile_num{0};
// Load block file info
- m_block_tree_db->ReadLastBlockFile(m_last_blockfile);
- m_blockfile_info.resize(m_last_blockfile + 1);
- LogPrintf("%s: last block file = %i\n", __func__, m_last_blockfile);
- for (int nFile = 0; nFile <= m_last_blockfile; nFile++) {
+ m_block_tree_db->ReadLastBlockFile(max_blockfile_num);
+ m_blockfile_info.resize(max_blockfile_num + 1);
+ LogPrintf("%s: last block file = %i\n", __func__, max_blockfile_num);
+ for (int nFile = 0; nFile <= max_blockfile_num; nFile++) {
m_block_tree_db->ReadBlockFileInfo(nFile, m_blockfile_info[nFile]);
}
LogPrintf("%s: last block file info: %s\n", __func__,
- m_blockfile_info[m_last_blockfile].ToString());
- for (int nFile = m_last_blockfile + 1; true; nFile++) {
+ m_blockfile_info[max_blockfile_num].ToString());
+ for (int nFile = max_blockfile_num + 1; true; nFile++) {
CBlockFileInfo info;
if (m_block_tree_db->ReadBlockFileInfo(nFile, info)) {
m_blockfile_info.push_back(info);
@@ -387,6 +400,16 @@
}
}
+ {
+ // Initialize the blockfile cursors.
+ LOCK(cs_LastBlockFile);
+ for (size_t i = 0; i < m_blockfile_info.size(); ++i) {
+ const auto last_height_in_file = m_blockfile_info[i].nHeightLast;
+ m_blockfile_cursors[BlockfileTypeForHeight(last_height_in_file)] = {
+ static_cast<int>(i), 0};
+ }
+ }
+
// Check whether we have ever pruned block & undo files
m_block_tree_db->ReadFlag("prunedblockfiles", m_have_pruned);
if (m_have_pruned) {
@@ -404,12 +427,14 @@
void BlockManager::ScanAndUnlinkAlreadyPrunedFiles() {
AssertLockHeld(::cs_main);
+ int max_blockfile =
+ WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
if (!m_have_pruned) {
return;
}
std::set<int> block_files_to_prune;
- for (int file_number = 0; file_number < m_last_blockfile; file_number++) {
+ for (int file_number = 0; file_number < max_blockfile; file_number++) {
if (m_blockfile_info[file_number].nSize == 0) {
block_files_to_prune.insert(file_number);
}
@@ -592,7 +617,8 @@
return true;
}
-bool BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo) {
+bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize,
+ bool finalize_undo) {
bool success = true;
LOCK(cs_LastBlockFile);
@@ -604,10 +630,10 @@
// have populated `m_blockfile_info` via LoadBlockIndexDB().
return true;
}
- assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile);
+ assert(static_cast<int>(m_blockfile_info.size()) > blockfile_num);
- FlatFilePos block_pos_old(m_last_blockfile,
- m_blockfile_info[m_last_blockfile].nSize);
+ FlatFilePos block_pos_old(blockfile_num,
+ m_blockfile_info[blockfile_num].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the "
"result of an I/O error.");
@@ -617,13 +643,35 @@
// behind the incoming blocks,
// e.g. during IBD or a sync after a node going offline
if (!fFinalize || finalize_undo) {
- if (!FlushUndoFile(m_last_blockfile, finalize_undo)) {
+ if (!FlushUndoFile(blockfile_num, finalize_undo)) {
success = false;
}
}
return success;
}
+BlockfileType BlockManager::BlockfileTypeForHeight(int height) {
+ if (!m_snapshot_height) {
+ return BlockfileType::NORMAL;
+ }
+ return (height >= *m_snapshot_height) ? BlockfileType::ASSUMED
+ : BlockfileType::NORMAL;
+}
+
+bool BlockManager::FlushChainstateBlockFile(int tip_height) {
+ LOCK(cs_LastBlockFile);
+ auto &cursor = m_blockfile_cursors[BlockfileTypeForHeight(tip_height)];
+ // If the cursor does not exist, it means an assumeutxo snapshot is loaded,
+ // but no blocks past the snapshot height have been written yet, so there
+ // is no data associated with the chainstate, and it is safe not to flush.
+ if (cursor) {
+ return FlushBlockFile(cursor->file_num, /*fFinalize=*/false,
+ /*finalize_undo=*/false);
+ }
+ // No need to log warnings in this case.
+ return true;
+}
+
uint64_t BlockManager::CalculateCurrentUsage() {
LOCK(cs_LastBlockFile);
@@ -680,8 +728,22 @@
bool fKnown) {
LOCK(cs_LastBlockFile);
- unsigned int nFile = fKnown ? pos.nFile : m_last_blockfile;
- if (m_blockfile_info.size() <= nFile) {
+ const BlockfileType chain_type = BlockfileTypeForHeight(nHeight);
+
+ if (!m_blockfile_cursors[chain_type]) {
+ // If a snapshot is loaded during runtime, we may not have initialized
+ // this cursor yet.
+ assert(chain_type == BlockfileType::ASSUMED);
+ const auto new_cursor = BlockfileCursor{this->MaxBlockfileNum() + 1};
+ m_blockfile_cursors[chain_type] = new_cursor;
+ LogPrint(BCLog::BLOCKSTORE,
+ "[%s] initializing blockfile cursor to %s\n", chain_type,
+ new_cursor);
+ }
+ const int last_blockfile = m_blockfile_cursors[chain_type]->file_num;
+
+ int nFile = fKnown ? pos.nFile : last_blockfile;
+ if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
m_blockfile_info.resize(nFile + 1);
}
@@ -702,15 +764,22 @@
// or raise MAX_BLOCKFILE_SIZE when we reach block sizes larger than
// 128 MiB
assert(nAddSize < max_blockfile_size);
+
while (m_blockfile_info[nFile].nSize + nAddSize >= max_blockfile_size) {
// when the undo file is keeping up with the block file, we want to
// flush it explicitly when it is lagging behind (more blocks arrive
// than are being connected), we let the undo block write case
// handle it
- finalize_undo = (m_blockfile_info[nFile].nHeightLast ==
- m_undo_height_in_last_blockfile);
- nFile++;
- if (m_blockfile_info.size() <= nFile) {
+ finalize_undo =
+ (static_cast<int>(m_blockfile_info[nFile].nHeightLast) ==
+ Assert(m_blockfile_cursors[chain_type])->undo_height);
+
+ // Try the next unclaimed blockfile number
+ nFile = this->MaxBlockfileNum() + 1;
+ // Set to increment MaxBlockfileNum() for next iteration
+ m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
+
+ if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
m_blockfile_info.resize(nFile + 1);
}
}
@@ -718,11 +787,13 @@
pos.nPos = m_blockfile_info[nFile].nSize;
}
- if ((int)nFile != m_last_blockfile) {
+ if (nFile != last_blockfile) {
if (!fKnown) {
- LogPrint(BCLog::BLOCKSTORE, "Leaving block file %i: %s\n",
- m_last_blockfile,
- m_blockfile_info[m_last_blockfile].ToString());
+ LogPrint(BCLog::BLOCKSTORE,
+ "Leaving block file %i: %s (onto %i) (height %i)\n",
+ last_blockfile,
+ m_blockfile_info[last_blockfile].ToString(), nFile,
+ nHeight);
}
// Do not propagate the return code. The flush concerns a previous block
@@ -732,16 +803,15 @@
// data may be inconsistent after a crash if the flush is called during
// a reindex. A flush error might also leave some of the data files
// untrimmed.
- if (!FlushBlockFile(!fKnown, finalize_undo)) {
+ if (!FlushBlockFile(last_blockfile, !fKnown, finalize_undo)) {
LogPrintLevel(
BCLog::BLOCKSTORE, BCLog::Level::Warning,
"Failed to flush previous block file %05i (finalize=%i, "
"finalize_undo=%i) before opening new block file %05i\n",
- m_last_blockfile, !fKnown, finalize_undo, nFile);
+ last_blockfile, !fKnown, finalize_undo, nFile);
}
- m_last_blockfile = nFile;
// No undo data yet in the new file, so reset our undo-height tracking.
- m_undo_height_in_last_blockfile = 0;
+ m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
}
m_blockfile_info[nFile].AddBlock(nHeight, nTime);
@@ -822,6 +892,10 @@
BlockValidationState &state,
CBlockIndex &block) {
AssertLockHeld(::cs_main);
+ const BlockfileType type = BlockfileTypeForHeight(block.nHeight);
+ auto &cursor =
+ *Assert(WITH_LOCK(cs_LastBlockFile, return m_blockfile_cursors[type]));
+
// Write undo information to disk
if (block.GetUndoPos().IsNull()) {
FlatFilePos _pos;
@@ -840,7 +914,7 @@
// does not catch the case where the undo writes are keeping up with the
// block writes (usually when a synced up node is getting newly mined
// blocks) -- this case is caught in the FindBlockPos function
- if (_pos.nFile < m_last_blockfile &&
+ if (_pos.nFile < cursor.file_num &&
static_cast<uint32_t>(block.nHeight) ==
m_blockfile_info[_pos.nFile].nHeightLast) {
// Do not propagate the return code, a failed flush here should not
@@ -852,10 +926,9 @@
LogPrintLevel(BCLog::BLOCKSTORE, BCLog::Level::Warning,
"Failed to flush undo file %05i\n", _pos.nFile);
}
- } else if (_pos.nFile == m_last_blockfile &&
- static_cast<uint32_t>(block.nHeight) >
- m_undo_height_in_last_blockfile) {
- m_undo_height_in_last_blockfile = block.nHeight;
+ } else if (_pos.nFile == cursor.file_num &&
+ block.nHeight > cursor.undo_height) {
+ cursor.undo_height = block.nHeight;
}
// update nUndoPos in block index
block.nUndoPos = _pos.nPos;
@@ -1110,4 +1183,24 @@
}
} // End scope of ImportingNow
}
+
+std::ostream &operator<<(std::ostream &os, const BlockfileType &type) {
+ switch (type) {
+ case BlockfileType::NORMAL:
+ os << "normal";
+ break;
+ case BlockfileType::ASSUMED:
+ os << "assumed";
+ break;
+ default:
+ os.setstate(std::ios_base::failbit);
+ }
+ return os;
+}
+
+std::ostream &operator<<(std::ostream &os, const BlockfileCursor &cursor) {
+ os << strprintf("BlockfileCursor(file_num=%d, undo_height=%d)",
+ cursor.file_num, cursor.undo_height);
+ return os;
+}
} // namespace node
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2651,7 +2651,8 @@
// disk.
// TODO: Handle return error, or add detailed comment why
// it is safe to not return an error upon failure.
- if (!m_blockman.FlushBlockFile()) {
+ if (!m_blockman.FlushChainstateBlockFile(
+ m_chain.Height())) {
LogPrintLevel(BCLog::VALIDATION, BCLog::Level::Warning,
"%s: Failed to flush block file.\n",
__func__);
@@ -6556,6 +6557,7 @@
assert(chaintip_loaded);
m_active_chainstate = m_snapshot_chainstate.get();
+ m_blockman.m_snapshot_height = this->GetSnapshotBaseHeight();
LogPrintf("[snapshot] successfully activated snapshot %s\n",
base_blockhash.ToString());

File Metadata

Mime Type
text/plain
Expires
Tue, May 20, 20:57 (14 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865926
Default Alt Text
D17896.id53375.diff (21 KB)

Event Timeline