Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864611
D17896.id53375.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Subscribers
None
D17896.id53375.diff
View Options
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
Details
Attached
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)
Attached To
D17896: blockstorage: segment normal/assumedvalid blockfiles
Event Timeline
Log In to Comment