Skip to content

Commit 2f53f22

Browse files
committed
Merge bitcoin#29975: blockstorage: Separate reindexing from saving new blocks
e41667b blockstorage: Don't move cursor backwards in UpdateBlockInfo (Ryan Ofsky) 1710363 blockstorage: Rename FindBlockPos and have it return a FlatFilePos (Martin Zumsande) d9e477c validation, blockstorage: Separate code paths for reindex and saving new blocks (Martin Zumsande) 064859b blockstorage: split up FindBlockPos function (Martin Zumsande) fdae638 doc: Improve doc for functions involved in saving blocks to disk (Martin Zumsande) 0d114e3 blockstorage: Add Assume for fKnown / snapshot chainstate (Martin Zumsande) Pull request description: `SaveBlockToDisk` / `FindBlockPos` are used for two purposes, depending on whether they are called during reindexing (`dbp` set,  `fKnown = true`) or in the "normal" case when adding new blocks (`dbp == nullptr`,  `fKnown = false`). The actual tasks are quite different - In normal mode, preparations for saving a new block are made, which is then saved: find the correct position on disk (maybe skipping to a new blk file), check for available disk space, update the blockfile info db, save the block. - during reindex, most of this is not necessary (the block is already on disk after all), only the blockfile info needs to rebuilt because reindex wiped the leveldb it's saved in. Using one function with many conditional statements for this leads to code that is hard to read / understand and bug-prone: - many code paths in `FindBlockPos` are conditional on `fKnown` or `!fKnown` - It's not really clear what actually needs to be done during reindex (we don't need to "save a block to disk" or "find a block pos" as the function names suggest) - logic that should be applied to only one of the two modes is sometimes applied to both (see first commit, or bitcoin#27039) bitcoin#24858 and bitcoin#27039 were recent bugs directly related to the differences between reindexing and normal mode, and in both cases the simple fix took a long time to be reviewed and merged. This PR proposes to clean this code up by splitting out the reindex logic into a separate function (`UpdateBlockInfo`) which will be called directly from validation. As a result, `SaveBlockToDisk` and `FindBlockPos` only need to cover the non-reindex logic. ACKs for top commit: paplorinc: ACK e41667b TheCharlatan: Re-ACK e41667b ryanofsky: Code review ACK e41667b. Just improvements to comments since last review. Tree-SHA512: a14ff9a0facf6b1e3c1cd724a2d19a79a25d4b48de64398fdd172671532a472bc10a20cbb64ac3a3e55814dcc877d0597a3e1699cabc4f9d9a86b439b6eaba20
2 parents 75118a6 + e41667b commit 2f53f22

File tree

5 files changed

+143
-105
lines changed

5 files changed

+143
-105
lines changed

src/bench/readblock.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ static FlatFilePos WriteBlockToDisk(ChainstateManager& chainman)
1818
CBlock block;
1919
stream >> TX_WITH_WITNESS(block);
2020

21-
return chainman.m_blockman.SaveBlockToDisk(block, 0, nullptr);
21+
return chainman.m_blockman.SaveBlockToDisk(block, 0);
2222
}
2323

2424
static void ReadBlockFromDiskTest(benchmark::Bench& bench)

src/node/blockstorage.cpp

+86-81
Original file line numberDiff line numberDiff line change
@@ -848,7 +848,7 @@ fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
848848
return BlockFileSeq().FileName(pos);
849849
}
850850

851-
bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown)
851+
FlatFilePos BlockManager::FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime)
852852
{
853853
LOCK(cs_LastBlockFile);
854854

@@ -863,88 +863,101 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
863863
}
864864
const int last_blockfile = m_blockfile_cursors[chain_type]->file_num;
865865

866-
int nFile = fKnown ? pos.nFile : last_blockfile;
866+
int nFile = last_blockfile;
867867
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
868868
m_blockfile_info.resize(nFile + 1);
869869
}
870870

871871
bool finalize_undo = false;
872-
if (!fKnown) {
873-
unsigned int max_blockfile_size{MAX_BLOCKFILE_SIZE};
874-
// Use smaller blockfiles in test-only -fastprune mode - but avoid
875-
// the possibility of having a block not fit into the block file.
876-
if (m_opts.fast_prune) {
877-
max_blockfile_size = 0x10000; // 64kiB
878-
if (nAddSize >= max_blockfile_size) {
879-
// dynamically adjust the blockfile size to be larger than the added size
880-
max_blockfile_size = nAddSize + 1;
881-
}
872+
unsigned int max_blockfile_size{MAX_BLOCKFILE_SIZE};
873+
// Use smaller blockfiles in test-only -fastprune mode - but avoid
874+
// the possibility of having a block not fit into the block file.
875+
if (m_opts.fast_prune) {
876+
max_blockfile_size = 0x10000; // 64kiB
877+
if (nAddSize >= max_blockfile_size) {
878+
// dynamically adjust the blockfile size to be larger than the added size
879+
max_blockfile_size = nAddSize + 1;
882880
}
883-
assert(nAddSize < max_blockfile_size);
884-
885-
while (m_blockfile_info[nFile].nSize + nAddSize >= max_blockfile_size) {
886-
// when the undo file is keeping up with the block file, we want to flush it explicitly
887-
// when it is lagging behind (more blocks arrive than are being connected), we let the
888-
// undo block write case handle it
889-
finalize_undo = (static_cast<int>(m_blockfile_info[nFile].nHeightLast) ==
890-
Assert(m_blockfile_cursors[chain_type])->undo_height);
891-
892-
// Try the next unclaimed blockfile number
893-
nFile = this->MaxBlockfileNum() + 1;
894-
// Set to increment MaxBlockfileNum() for next iteration
895-
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
896-
897-
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
898-
m_blockfile_info.resize(nFile + 1);
899-
}
881+
}
882+
assert(nAddSize < max_blockfile_size);
883+
884+
while (m_blockfile_info[nFile].nSize + nAddSize >= max_blockfile_size) {
885+
// when the undo file is keeping up with the block file, we want to flush it explicitly
886+
// when it is lagging behind (more blocks arrive than are being connected), we let the
887+
// undo block write case handle it
888+
finalize_undo = (static_cast<int>(m_blockfile_info[nFile].nHeightLast) ==
889+
Assert(m_blockfile_cursors[chain_type])->undo_height);
890+
891+
// Try the next unclaimed blockfile number
892+
nFile = this->MaxBlockfileNum() + 1;
893+
// Set to increment MaxBlockfileNum() for next iteration
894+
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
895+
896+
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
897+
m_blockfile_info.resize(nFile + 1);
900898
}
901-
pos.nFile = nFile;
902-
pos.nPos = m_blockfile_info[nFile].nSize;
903899
}
900+
FlatFilePos pos;
901+
pos.nFile = nFile;
902+
pos.nPos = m_blockfile_info[nFile].nSize;
904903

905904
if (nFile != last_blockfile) {
906-
if (!fKnown) {
907-
LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s (onto %i) (height %i)\n",
908-
last_blockfile, m_blockfile_info[last_blockfile].ToString(), nFile, nHeight);
909-
910-
// Do not propagate the return code. The flush concerns a previous block
911-
// and undo file that has already been written to. If a flush fails
912-
// here, and we crash, there is no expected additional block data
913-
// inconsistency arising from the flush failure here. However, the undo
914-
// data may be inconsistent after a crash if the flush is called during
915-
// a reindex. A flush error might also leave some of the data files
916-
// untrimmed.
917-
if (!FlushBlockFile(last_blockfile, !fKnown, finalize_undo)) {
918-
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning,
919-
"Failed to flush previous block file %05i (finalize=%i, finalize_undo=%i) before opening new block file %05i\n",
920-
last_blockfile, !fKnown, finalize_undo, nFile);
921-
}
905+
LogPrint(BCLog::BLOCKSTORAGE, "Leaving block file %i: %s (onto %i) (height %i)\n",
906+
last_blockfile, m_blockfile_info[last_blockfile].ToString(), nFile, nHeight);
907+
908+
// Do not propagate the return code. The flush concerns a previous block
909+
// and undo file that has already been written to. If a flush fails
910+
// here, and we crash, there is no expected additional block data
911+
// inconsistency arising from the flush failure here. However, the undo
912+
// data may be inconsistent after a crash if the flush is called during
913+
// a reindex. A flush error might also leave some of the data files
914+
// untrimmed.
915+
if (!FlushBlockFile(last_blockfile, /*fFinalize=*/true, finalize_undo)) {
916+
LogPrintLevel(BCLog::BLOCKSTORAGE, BCLog::Level::Warning,
917+
"Failed to flush previous block file %05i (finalize=1, finalize_undo=%i) before opening new block file %05i\n",
918+
last_blockfile, finalize_undo, nFile);
922919
}
923920
// No undo data yet in the new file, so reset our undo-height tracking.
924921
m_blockfile_cursors[chain_type] = BlockfileCursor{nFile};
925922
}
926923

927924
m_blockfile_info[nFile].AddBlock(nHeight, nTime);
928-
if (fKnown) {
929-
m_blockfile_info[nFile].nSize = std::max(pos.nPos + nAddSize, m_blockfile_info[nFile].nSize);
930-
} else {
931-
m_blockfile_info[nFile].nSize += nAddSize;
925+
m_blockfile_info[nFile].nSize += nAddSize;
926+
927+
bool out_of_space;
928+
size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
929+
if (out_of_space) {
930+
m_opts.notifications.fatalError(_("Disk space is too low!"));
931+
return {};
932+
}
933+
if (bytes_allocated != 0 && IsPruneMode()) {
934+
m_check_for_pruning = true;
932935
}
933936

934-
if (!fKnown) {
935-
bool out_of_space;
936-
size_t bytes_allocated = BlockFileSeq().Allocate(pos, nAddSize, out_of_space);
937-
if (out_of_space) {
938-
m_opts.notifications.fatalError(_("Disk space is too low!"));
939-
return false;
940-
}
941-
if (bytes_allocated != 0 && IsPruneMode()) {
942-
m_check_for_pruning = true;
943-
}
937+
m_dirty_fileinfo.insert(nFile);
938+
return pos;
939+
}
940+
941+
void BlockManager::UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos)
942+
{
943+
LOCK(cs_LastBlockFile);
944+
945+
// Update the cursor so it points to the last file.
946+
const BlockfileType chain_type{BlockfileTypeForHeight(nHeight)};
947+
auto& cursor{m_blockfile_cursors[chain_type]};
948+
if (!cursor || cursor->file_num < pos.nFile) {
949+
m_blockfile_cursors[chain_type] = BlockfileCursor{pos.nFile};
944950
}
945951

952+
// Update the file information with the current block.
953+
const unsigned int added_size = ::GetSerializeSize(TX_WITH_WITNESS(block));
954+
const int nFile = pos.nFile;
955+
if (static_cast<int>(m_blockfile_info.size()) <= nFile) {
956+
m_blockfile_info.resize(nFile + 1);
957+
}
958+
m_blockfile_info[nFile].AddBlock(nHeight, block.GetBlockTime());
959+
m_blockfile_info[nFile].nSize = std::max(pos.nPos + added_size, m_blockfile_info[nFile].nSize);
946960
m_dirty_fileinfo.insert(nFile);
947-
return true;
948961
}
949962

950963
bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize)
@@ -1014,7 +1027,7 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
10141027
// we want to flush the rev (undo) file once we've written the last block, which is indicated by the last height
10151028
// in the block file info as below; note that this does not catch the case where the undo writes are keeping up
10161029
// with the block writes (usually when a synced up node is getting newly mined blocks) -- this case is caught in
1017-
// the FindBlockPos function
1030+
// the FindNextBlockPos function
10181031
if (_pos.nFile < cursor.file_num && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
10191032
// Do not propagate the return code, a failed flush here should not
10201033
// be an indication for a failed write. If it were propagated here,
@@ -1130,28 +1143,20 @@ bool BlockManager::ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatF
11301143
return true;
11311144
}
11321145

1133-
FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, const FlatFilePos* dbp)
1146+
FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight)
11341147
{
11351148
unsigned int nBlockSize = ::GetSerializeSize(TX_WITH_WITNESS(block));
1136-
FlatFilePos blockPos;
1137-
const auto position_known {dbp != nullptr};
1138-
if (position_known) {
1139-
blockPos = *dbp;
1140-
} else {
1141-
// when known, blockPos.nPos points at the offset of the block data in the blk file. that already accounts for
1142-
// the serialization header present in the file (the 4 magic message start bytes + the 4 length bytes = 8 bytes = BLOCK_SERIALIZATION_HEADER_SIZE).
1143-
// we add BLOCK_SERIALIZATION_HEADER_SIZE only for new blocks since they will have the serialization header added when written to disk.
1144-
nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE);
1145-
}
1146-
if (!FindBlockPos(blockPos, nBlockSize, nHeight, block.GetBlockTime(), position_known)) {
1147-
LogError("%s: FindBlockPos failed\n", __func__);
1149+
// Account for the 4 magic message start bytes + the 4 length bytes (8 bytes total,
1150+
// defined as BLOCK_SERIALIZATION_HEADER_SIZE)
1151+
nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE);
1152+
FlatFilePos blockPos{FindNextBlockPos(nBlockSize, nHeight, block.GetBlockTime())};
1153+
if (blockPos.IsNull()) {
1154+
LogError("%s: FindNextBlockPos failed\n", __func__);
11481155
return FlatFilePos();
11491156
}
1150-
if (!position_known) {
1151-
if (!WriteBlockToDisk(block, blockPos)) {
1152-
m_opts.notifications.fatalError(_("Failed to write block."));
1153-
return FlatFilePos();
1154-
}
1157+
if (!WriteBlockToDisk(block, blockPos)) {
1158+
m_opts.notifications.fatalError(_("Failed to write block."));
1159+
return FlatFilePos();
11551160
}
11561161
return blockPos;
11571162
}

src/node/blockstorage.h

+35-4
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,16 @@ class BlockManager
155155
/** Return false if undo file flushing fails. */
156156
[[nodiscard]] bool FlushUndoFile(int block_file, bool finalize = false);
157157

158-
[[nodiscard]] bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown);
158+
/**
159+
* Helper function performing various preparations before a block can be saved to disk:
160+
* Returns the correct position for the block to be saved, which may be in the current or a new
161+
* block file depending on nAddSize. May flush the previous blockfile to disk if full, updates
162+
* blockfile info, and checks if there is enough disk space to save the block.
163+
*
164+
* The nAddSize argument passed to this function should include not just the size of the serialized CBlock, but also the size of
165+
* separator fields which are written before it by WriteBlockToDisk (BLOCK_SERIALIZATION_HEADER_SIZE).
166+
*/
167+
[[nodiscard]] FlatFilePos FindNextBlockPos(unsigned int nAddSize, unsigned int nHeight, uint64_t nTime);
159168
[[nodiscard]] bool FlushChainstateBlockFile(int tip_height);
160169
bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);
161170

@@ -164,6 +173,12 @@ class BlockManager
164173

165174
AutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const;
166175

176+
/**
177+
* Write a block to disk. The pos argument passed to this function is modified by this call. Before this call, it should
178+
* point to an unused file location where separator fields will be written, followed by the serialized CBlock data.
179+
* After this call, it will point to the beginning of the serialized CBlock data, after the separator fields
180+
* (BLOCK_SERIALIZATION_HEADER_SIZE)
181+
*/
167182
bool WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const;
168183
bool UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos, const uint256& hashBlock) const;
169184

@@ -206,7 +221,7 @@ class BlockManager
206221
//! effectively.
207222
//!
208223
//! This data structure maintains separate blockfile number cursors for each
209-
//! BlockfileType. The ASSUMED state is initialized, when necessary, in FindBlockPos().
224+
//! BlockfileType. The ASSUMED state is initialized, when necessary, in FindNextBlockPos().
210225
//!
211226
//! The first element is the NORMAL cursor, second is ASSUMED.
212227
std::array<std::optional<BlockfileCursor>, BlockfileType::NUM_TYPES>
@@ -312,8 +327,24 @@ class BlockManager
312327
bool WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValidationState& state, CBlockIndex& block)
313328
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
314329

315-
/** Store block on disk. If dbp is not nullptr, then it provides the known position of the block within a block file on disk. */
316-
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, const FlatFilePos* dbp);
330+
/** Store block on disk and update block file statistics.
331+
*
332+
* @param[in] block the block to be stored
333+
* @param[in] nHeight the height of the block
334+
*
335+
* @returns in case of success, the position to which the block was written to
336+
* in case of an error, an empty FlatFilePos
337+
*/
338+
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight);
339+
340+
/** Update blockfile info while processing a block during reindex. The block must be available on disk.
341+
*
342+
* @param[in] block the block being processed
343+
* @param[in] nHeight the height of the block
344+
* @param[in] pos the position of the serialized CBlock on disk. This is the position returned
345+
* by WriteBlockToDisk pointing at the CBlock, not the separator fields before it
346+
*/
347+
void UpdateBlockInfo(const CBlock& block, unsigned int nHeight, const FlatFilePos& pos);
317348

318349
/** Whether running in -prune mode. */
319350
[[nodiscard]] bool IsPruneMode() const { return m_prune_mode; }

src/test/blockmanager_tests.cpp

+10-14
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
3535
};
3636
BlockManager blockman{*Assert(m_node.shutdown), blockman_opts};
3737
// simulate adding a genesis block normally
38-
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
38+
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
3939
// simulate what happens during reindex
4040
// simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file
4141
// the block is found at offset 8 because there is an 8 byte serialization header
4242
// consisting of 4 magic bytes + 4 length bytes before each block in a well-formed blk file.
43-
FlatFilePos pos{0, BLOCK_SERIALIZATION_HEADER_SIZE};
44-
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, &pos).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
43+
const FlatFilePos pos{0, BLOCK_SERIALIZATION_HEADER_SIZE};
44+
blockman.UpdateBlockInfo(params->GenesisBlock(), 0, pos);
4545
// now simulate what happens after reindex for the first new block processed
4646
// the actual block contents don't matter, just that it's a block.
4747
// verify that the write position is at offset 0x12d.
4848
// this is a check to make sure that https://github.com/bitcoin/bitcoin/issues/21379 does not recur
4949
// 8 bytes (for serialization header) + 285 (for serialized genesis block) = 293
5050
// add another 8 bytes for the second block's serialization header and we get 293 + 8 = 301
51-
FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1, nullptr)};
51+
FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1)};
5252
BOOST_CHECK_EQUAL(actual.nPos, BLOCK_SERIALIZATION_HEADER_SIZE + ::GetSerializeSize(TX_WITH_WITNESS(params->GenesisBlock())) + BLOCK_SERIALIZATION_HEADER_SIZE);
5353
}
5454

@@ -156,12 +156,11 @@ BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
156156
// Blockstore is empty
157157
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), 0);
158158

159-
// Write the first block; dbp=nullptr means this block doesn't already have a disk
160-
// location, so allocate a free location and write it there.
161-
FlatFilePos pos1{blockman.SaveBlockToDisk(block1, /*nHeight=*/1, /*dbp=*/nullptr)};
159+
// Write the first block to a new location.
160+
FlatFilePos pos1{blockman.SaveBlockToDisk(block1, /*nHeight=*/1)};
162161

163162
// Write second block
164-
FlatFilePos pos2{blockman.SaveBlockToDisk(block2, /*nHeight=*/2, /*dbp=*/nullptr)};
163+
FlatFilePos pos2{blockman.SaveBlockToDisk(block2, /*nHeight=*/2)};
165164

166165
// Two blocks in the file
167166
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + BLOCK_SERIALIZATION_HEADER_SIZE) * 2);
@@ -181,22 +180,19 @@ BOOST_AUTO_TEST_CASE(blockmanager_flush_block_file)
181180
BOOST_CHECK_EQUAL(read_block.nVersion, 2);
182181
}
183182

184-
// When FlatFilePos* dbp is given, SaveBlockToDisk() will not write or
185-
// overwrite anything to the flat file block storage. It will, however,
186-
// update the blockfile metadata. This is to facilitate reindexing
187-
// when the user has the blocks on disk but the metadata is being rebuilt.
183+
// During reindex, the flat file block storage will not be written to.
184+
// UpdateBlockInfo will, however, update the blockfile metadata.
188185
// Verify this behavior by attempting (and failing) to write block 3 data
189186
// to block 2 location.
190187
CBlockFileInfo* block_data = blockman.GetBlockFileInfo(0);
191188
BOOST_CHECK_EQUAL(block_data->nBlocks, 2);
192-
BOOST_CHECK(blockman.SaveBlockToDisk(block3, /*nHeight=*/3, /*dbp=*/&pos2) == pos2);
189+
blockman.UpdateBlockInfo(block3, /*nHeight=*/3, /*pos=*/pos2);
193190
// Metadata is updated...
194191
BOOST_CHECK_EQUAL(block_data->nBlocks, 3);
195192
// ...but there are still only two blocks in the file
196193
BOOST_CHECK_EQUAL(blockman.CalculateCurrentUsage(), (TEST_BLOCK_SIZE + BLOCK_SERIALIZATION_HEADER_SIZE) * 2);
197194

198195
// Block 2 was not overwritten:
199-
// SaveBlockToDisk() did not call WriteBlockToDisk() because `FlatFilePos* dbp` was non-null
200196
blockman.ReadBlockFromDisk(read_block, pos2);
201197
BOOST_CHECK_EQUAL(read_block.nVersion, 2);
202198
}

0 commit comments

Comments
 (0)