Skip to content

Commit 7fcd215

Browse files
committed
blockstorage: segment normal/assumedvalid blockfiles
When using an assumedvalid (snapshot) chainstate along with a background chainstate, we are syncing two very different regions of the chain simultaneously. If we use the same blockfile space for both of these syncs, wildly different height blocks will be stored alongside one another, making pruning ineffective. This change implements a separate blockfile cursor for the assumedvalid chainstate when one is in use.
1 parent 4c3b8ca commit 7fcd215

File tree

3 files changed

+179
-47
lines changed

3 files changed

+179
-47
lines changed

src/node/blockstorage.cpp

Lines changed: 107 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <dbwrapper.h>
1111
#include <flatfile.h>
1212
#include <hash.h>
13+
#include <kernel/chain.h>
1314
#include <kernel/chainparams.h>
1415
#include <kernel/messagestartchars.h>
1516
#include <logging.h>
@@ -273,7 +274,7 @@ void BlockManager::FindFilesToPruneManual(
273274
const auto [min_block_to_prune, last_block_can_prune] = chainman.GetPruneRange(chain, nManualPruneHeight);
274275

275276
int count = 0;
276-
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
277+
for (int fileNumber = 0; fileNumber < this->MaxBlockfileNum(); fileNumber++) {
277278
const auto& fileinfo = m_blockfile_info[fileNumber];
278279
if (fileinfo.nSize == 0 || fileinfo.nHeightLast > (unsigned)last_block_can_prune || fileinfo.nHeightFirst < (unsigned)min_block_to_prune) {
279280
continue;
@@ -325,7 +326,7 @@ void BlockManager::FindFilesToPrune(
325326
nBuffer += target / 10;
326327
}
327328

328-
for (int fileNumber = 0; fileNumber < m_last_blockfile; fileNumber++) {
329+
for (int fileNumber = 0; fileNumber < this->MaxBlockfileNum(); fileNumber++) {
329330
const auto& fileinfo = m_blockfile_info[fileNumber];
330331
nBytesToPrune = fileinfo.nSize + fileinfo.nUndoSize;
331332

@@ -385,19 +386,25 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
385386
return false;
386387
}
387388

388-
int snapshot_height = -1;
389389
if (snapshot_blockhash) {
390390
const AssumeutxoData au_data = *Assert(GetParams().AssumeutxoForBlockhash(*snapshot_blockhash));
391-
snapshot_height = au_data.height;
391+
m_snapshot_height = au_data.height;
392392
CBlockIndex* base{LookupBlockIndex(*snapshot_blockhash)};
393393

394-
// Since nChainTx (responsible for estiamted progress) isn't persisted
394+
// Since nChainTx (responsible for estimated progress) isn't persisted
395395
// to disk, we must bootstrap the value for assumedvalid chainstates
396396
// from the hardcoded assumeutxo chainparams.
397397
base->nChainTx = au_data.nChainTx;
398398
LogPrintf("[snapshot] set nChainTx=%d for %s\n", au_data.nChainTx, snapshot_blockhash->ToString());
399+
} else {
400+
// If this isn't called with a snapshot blockhash, make sure the cached snapshot height
401+
// is null. This is relevant during snapshot completion, when the blockman may be loaded
402+
// with a height that then needs to be cleared after the snapshot is fully validated.
403+
m_snapshot_height.reset();
399404
}
400405

406+
Assert(m_snapshot_height.has_value() == snapshot_blockhash.has_value());
407+
401408
// Calculate nChainWork
402409
std::vector<CBlockIndex*> vSortedByHeight{GetAllBlockIndices()};
403410
std::sort(vSortedByHeight.begin(), vSortedByHeight.end(),
@@ -414,7 +421,7 @@ bool BlockManager::LoadBlockIndex(const std::optional<uint256>& snapshot_blockha
414421
// Pruned nodes may have deleted the block.
415422
if (pindex->nTx > 0) {
416423
if (pindex->pprev) {
417-
if (snapshot_blockhash && pindex->nHeight == snapshot_height &&
424+
if (m_snapshot_height && pindex->nHeight == *m_snapshot_height &&
418425
pindex->GetBlockHash() == *snapshot_blockhash) {
419426
// Should have been set above; don't disturb it with code below.
420427
Assert(pindex->nChainTx > 0);
@@ -455,7 +462,8 @@ bool BlockManager::WriteBlockIndexDB()
455462
vBlocks.push_back(*it);
456463
m_dirty_blockindex.erase(it++);
457464
}
458-
if (!m_block_tree_db->WriteBatchSync(vFiles, m_last_blockfile, vBlocks)) {
465+
int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
466+
if (!m_block_tree_db->WriteBatchSync(vFiles, max_blockfile, vBlocks)) {
459467
return false;
460468
}
461469
return true;
@@ -466,16 +474,17 @@ bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_block
466474
if (!LoadBlockIndex(snapshot_blockhash)) {
467475
return false;
468476
}
477+
int max_blockfile_num{0};
469478

470479
// Load block file info
471-
m_block_tree_db->ReadLastBlockFile(m_last_blockfile);
472-
m_blockfile_info.resize(m_last_blockfile + 1);
473-
LogPrintf("%s: last block file = %i\n", __func__, m_last_blockfile);
474-
for (int nFile = 0; nFile <= m_last_blockfile; nFile++) {
480+
m_block_tree_db->ReadLastBlockFile(max_blockfile_num);
481+
m_blockfile_info.resize(max_blockfile_num + 1);
482+
LogPrintf("%s: last block file = %i\n", __func__, max_blockfile_num);
483+
for (int nFile = 0; nFile <= max_blockfile_num; nFile++) {
475484
m_block_tree_db->ReadBlockFileInfo(nFile, m_blockfile_info[nFile]);
476485
}
477-
LogPrintf("%s: last block file info: %s\n", __func__, m_blockfile_info[m_last_blockfile].ToString());
478-
for (int nFile = m_last_blockfile + 1; true; nFile++) {
486+
LogPrintf("%s: last block file info: %s\n", __func__, m_blockfile_info[max_blockfile_num].ToString());
487+
for (int nFile = max_blockfile_num + 1; true; nFile++) {
479488
CBlockFileInfo info;
480489
if (m_block_tree_db->ReadBlockFileInfo(nFile, info)) {
481490
m_blockfile_info.push_back(info);
@@ -499,6 +508,15 @@ bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_block
499508
}
500509
}
501510

511+
{
512+
// Initialize the blockfile cursors.
513+
LOCK(cs_LastBlockFile);
514+
for (size_t i = 0; i < m_blockfile_info.size(); ++i) {
515+
const auto last_height_in_file = m_blockfile_info[i].nHeightLast;
516+
m_blockfile_cursors[BlockfileTypeForHeight(last_height_in_file)] = {static_cast<int>(i), 0};
517+
}
518+
}
519+
502520
// Check whether we have ever pruned block & undo files
503521
m_block_tree_db->ReadFlag("prunedblockfiles", m_have_pruned);
504522
if (m_have_pruned) {
@@ -516,12 +534,13 @@ bool BlockManager::LoadBlockIndexDB(const std::optional<uint256>& snapshot_block
516534
void BlockManager::ScanAndUnlinkAlreadyPrunedFiles()
517535
{
518536
AssertLockHeld(::cs_main);
537+
int max_blockfile = WITH_LOCK(cs_LastBlockFile, return this->MaxBlockfileNum());
519538
if (!m_have_pruned) {
520539
return;
521540
}
522541

523542
std::set<int> block_files_to_prune;
524-
for (int file_number = 0; file_number < m_last_blockfile; file_number++) {
543+
for (int file_number = 0; file_number < max_blockfile; file_number++) {
525544
if (m_blockfile_info[file_number].nSize == 0) {
526545
block_files_to_prune.insert(file_number);
527546
}
@@ -696,7 +715,7 @@ bool BlockManager::FlushUndoFile(int block_file, bool finalize)
696715
return true;
697716
}
698717

699-
bool BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
718+
bool BlockManager::FlushBlockFile(int blockfile_num, bool fFinalize, bool finalize_undo)
700719
{
701720
bool success = true;
702721
LOCK(cs_LastBlockFile);
@@ -708,23 +727,43 @@ bool BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
708727
// have populated `m_blockfile_info` via LoadBlockIndexDB().
709728
return true;
710729
}
711-
assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile);
730+
assert(static_cast<int>(m_blockfile_info.size()) > blockfile_num);
712731

713-
FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize);
732+
FlatFilePos block_pos_old(blockfile_num, m_blockfile_info[blockfile_num].nSize);
714733
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
715734
m_opts.notifications.flushError("Flushing block file to disk failed. This is likely the result of an I/O error.");
716735
success = false;
717736
}
718737
// we do not always flush the undo file, as the chain tip may be lagging behind the incoming blocks,
719738
// e.g. during IBD or a sync after a node going offline
720739
if (!fFinalize || finalize_undo) {
721-
if (!FlushUndoFile(m_last_blockfile, finalize_undo)) {
740+
if (!FlushUndoFile(blockfile_num, finalize_undo)) {
722741
success = false;
723742
}
724743
}
725744
return success;
726745
}
727746

747+
BlockfileType BlockManager::BlockfileTypeForHeight(int height)
748+
{
749+
if (!m_snapshot_height) {
750+
return BlockfileType::NORMAL;
751+
}
752+
return (height >= *m_snapshot_height) ? BlockfileType::ASSUMED : BlockfileType::NORMAL;
753+
}
754+
755+
bool BlockManager::FlushChainstateBlockFile(int tip_height)
756+
{
757+
LOCK(cs_LastBlockFile);
758+
auto& cursor = m_blockfile_cursors[BlockfileTypeForHeight(tip_height)];
759+
if (cursor) {
760+
// The cursor may not exist after a snapshot has been loaded but before any
761+
// blocks have been downloaded.
762+
return FlushBlockFile(cursor->file_num, /*fFinalize=*/false, /*finalize_undo=*/false);
763+
}
764+
return false;
765+
}
766+
728767
uint64_t BlockManager::CalculateCurrentUsage()
729768
{
730769
LOCK(cs_LastBlockFile);
@@ -779,8 +818,19 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
779818
{
780819
LOCK(cs_LastBlockFile);
781820

782-
unsigned int nFile = fKnown ? pos.nFile : m_last_blockfile;
783-
if (m_blockfile_info.size() <= nFile) {
821+
const BlockfileType chain_type = BlockfileTypeForHeight(nHeight);
822+
823+
if (!m_blockfile_cursors[chain_type]) {
824+
// If a snapshot is loaded during runtime, we may not have initialized this cursor yet.
825+
assert(chain_type == BlockfileType::ASSUMED);
826+
const auto new_cursor = BlockfileCursor{this->MaxBlockfileNum() + 1};
827+
m_blockfile_cursors[chain_type] = new_cursor;
828+
LogPrint(BCLog::BLOCKSTORAGE, "[%s] initializing blockfile cursor to %s\n", chain_type, new_cursor);
829+
}
830+
const int last_blockfile = m_blockfile_cursors[chain_type]->file_num;
831+
832+
int nFile = fKnown ? pos.nFile : last_blockfile;
833+
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
784834
m_blockfile_info.resize(nFile + 1);
785835
}
786836

@@ -797,23 +847,31 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
797847
}
798848
}
799849
assert(nAddSize < max_blockfile_size);
850+
800851
while (m_blockfile_info[nFile].nSize + nAddSize >= max_blockfile_size) {
801852
// when the undo file is keeping up with the block file, we want to flush it explicitly
802853
// when it is lagging behind (more blocks arrive than are being connected), we let the
803854
// undo block write case handle it
804-
finalize_undo = (m_blockfile_info[nFile].nHeightLast == m_undo_height_in_last_blockfile);
805-
nFile++;
806-
if (m_blockfile_info.size() <= nFile) {
855+
finalize_undo = (static_cast<int>(m_blockfile_info[nFile].nHeightLast) ==
856+
Assert(m_blockfile_cursors[chain_type])->undo_height);
857+
858+
// Try the next unclaimed blockfile number
859+
nFile = this->MaxBlockfileNum() + 1;
860+
// Set to increment MaxBlockfileNum() for next iteration
861+
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
862+
863+
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
807864
m_blockfile_info.resize(nFile + 1);
808865
}
809866
}
810867
pos.nFile = nFile;
811868
pos.nPos = m_blockfile_info[nFile].nSize;
812869
}
813870

814-
if ((int)nFile != m_last_blockfile) {
871+
if (nFile != last_blockfile) {
815872
if (!fKnown) {
816-
LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s\n", m_last_blockfile, m_blockfile_info[m_last_blockfile].ToString());
873+
LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s (onto %i) (height %i)\n",
874+
last_blockfile, m_blockfile_info[last_blockfile].ToString(), nFile, nHeight);
817875
}
818876

819877
// Do not propagate the return code. The flush concerns a previous block
@@ -823,13 +881,13 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
823881
// data may be inconsistent after a crash if the flush is called during
824882
// a reindex. A flush error might also leave some of the data files
825883
// untrimmed.
826-
if (!FlushBlockFile(!fKnown, finalize_undo)) {
884+
if (!FlushBlockFile(last_blockfile, !fKnown, finalize_undo)) {
827885
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning,
828886
"Failed to flush previous block file %05i (finalize=%i, finalize_undo=%i) before opening new block file %05i\n",
829-
m_last_blockfile, !fKnown, finalize_undo, nFile);
887+
last_blockfile, !fKnown, finalize_undo, nFile);
830888
}
831-
m_last_blockfile = nFile;
832-
m_undo_height_in_last_blockfile = 0; // No undo data yet in the new file, so reset our undo-height tracking.
889+
// No undo data yet in the new file, so reset our undo-height tracking.
890+
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
833891
}
834892

835893
m_blockfile_info[nFile].AddBlock(nHeight, nTime);
@@ -903,6 +961,9 @@ bool BlockManager::WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const
903961
bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
904962
{
905963
AssertLockHeld(::cs_main);
964+
const BlockfileType type = BlockfileTypeForHeight(block.nHeight);
965+
auto& cursor = *Assert(WITH_LOCK(cs_LastBlockFile, return m_blockfile_cursors[type]));
966+
906967
// Write undo information to disk
907968
if (block.GetUndoPos().IsNull()) {
908969
FlatFilePos _pos;
@@ -917,7 +978,7 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
917978
// in the block file info as below; note that this does not catch the case where the undo writes are keeping up
918979
// with the block writes (usually when a synced up node is getting newly mined blocks) -- this case is caught in
919980
// the FindBlockPos function
920-
if (_pos.nFile < m_last_blockfile && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
981+
if (_pos.nFile < cursor.file_num && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
921982
// Do not propagate the return code, a failed flush here should not
922983
// be an indication for a failed write. If it were propagated here,
923984
// the caller would assume the undo data not to be written, when in
@@ -926,8 +987,8 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
926987
if (!FlushUndoFile(_pos.nFile, true)) {
927988
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning, "Failed to flush undo file %05i\n", _pos.nFile);
928989
}
929-
} else if (_pos.nFile == m_last_blockfile && static_cast<uint32_t>(block.nHeight) > m_undo_height_in_last_blockfile) {
930-
m_undo_height_in_last_blockfile = block.nHeight;
990+
} else if (_pos.nFile == cursor.file_num && block.nHeight > cursor.undo_height) {
991+
cursor.undo_height = block.nHeight;
931992
}
932993
// update nUndoPos in block index
933994
block.nUndoPos = _pos.nPos;
@@ -1126,4 +1187,18 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
11261187
}
11271188
} // End scope of ImportingNow
11281189
}
1190+
1191+
std::ostream& operator<<(std::ostream& os, const BlockfileType& type) {
1192+
switch(type) {
1193+
case BlockfileType::NORMAL: os << "normal"; break;
1194+
case BlockfileType::ASSUMED: os << "assumed"; break;
1195+
default: os.setstate(std::ios_base::failbit);
1196+
}
1197+
return os;
1198+
}
1199+
1200+
std::ostream& operator<<(std::ostream& os, const BlockfileCursor& cursor) {
1201+
os << strprintf("BlockfileCursor(file_num=%d, undo_height=%d)", cursor.file_num, cursor.undo_height);
1202+
return os;
1203+
}
11291204
} // namespace node

0 commit comments

Comments
 (0)