aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/validation.cpp46
-rwxr-xr-xtest/functional/feature_reindex.py50
2 files changed, 78 insertions, 18 deletions
diff --git a/src/validation.cpp b/src/validation.cpp
index 37e68cfe4a..b9105b9090 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -4393,6 +4393,8 @@ void Chainstate::LoadExternalBlockFile(
try {
// This takes over fileIn and calls fclose() on it in the CBufferedFile destructor
CBufferedFile blkdat(fileIn, 2*MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE+8, SER_DISK, CLIENT_VERSION);
+ // nRewind indicates where to resume scanning in case something goes wrong,
+ // such as a block fails to deserialize.
uint64_t nRewind = blkdat.GetPos();
while (!blkdat.eof()) {
if (ShutdownRequested()) return;
@@ -4416,28 +4418,30 @@ void Chainstate::LoadExternalBlockFile(
continue;
} catch (const std::exception&) {
// no valid block header found; don't complain
+ // (this happens at the end of every blk.dat file)
break;
}
try {
- // read block
- uint64_t nBlockPos = blkdat.GetPos();
+ // read block header
+ const uint64_t nBlockPos{blkdat.GetPos()};
if (dbp)
dbp->nPos = nBlockPos;
blkdat.SetLimit(nBlockPos + nSize);
- std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
- CBlock& block = *pblock;
- blkdat >> block;
- nRewind = blkdat.GetPos();
-
- uint256 hash = block.GetHash();
+ CBlockHeader header;
+ blkdat >> header;
+ const uint256 hash{header.GetHash()};
+ // Skip the rest of this block (this may read from disk into memory); position to the marker before the
+ // next block, but it's still possible to rewind to the start of the current block (without a disk read).
+ nRewind = nBlockPos + nSize;
+ blkdat.SkipTo(nRewind);
{
LOCK(cs_main);
// detect out of order blocks, and store them for later
- if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(block.hashPrevBlock)) {
+ if (hash != params.GetConsensus().hashGenesisBlock && !m_blockman.LookupBlockIndex(header.hashPrevBlock)) {
LogPrint(BCLog::REINDEX, "%s: Out of order block %s, parent %s not known\n", __func__, hash.ToString(),
- block.hashPrevBlock.ToString());
+ header.hashPrevBlock.ToString());
if (dbp && blocks_with_unknown_parent) {
- blocks_with_unknown_parent->emplace(block.hashPrevBlock, *dbp);
+ blocks_with_unknown_parent->emplace(header.hashPrevBlock, *dbp);
}
continue;
}
@@ -4445,13 +4449,19 @@ void Chainstate::LoadExternalBlockFile(
// process in case the block isn't known yet
const CBlockIndex* pindex = m_blockman.LookupBlockIndex(hash);
if (!pindex || (pindex->nStatus & BLOCK_HAVE_DATA) == 0) {
- BlockValidationState state;
- if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) {
- nLoaded++;
- }
- if (state.IsError()) {
- break;
- }
+ // This block can be processed immediately; rewind to its start, read and deserialize it.
+ blkdat.SetPos(nBlockPos);
+ std::shared_ptr<CBlock> pblock{std::make_shared<CBlock>()};
+ blkdat >> *pblock;
+ nRewind = blkdat.GetPos();
+
+ BlockValidationState state;
+ if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, true)) {
+ nLoaded++;
+ }
+ if (state.IsError()) {
+ break;
+ }
} else if (hash != params.GetConsensus().hashGenesisBlock && pindex->nHeight % 1000 == 0) {
LogPrint(BCLog::REINDEX, "Block Import: already had block %s at height %d\n", hash.ToString(), pindex->nHeight);
}
diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py
index 44040f426f..0f6a8fd0d2 100755
--- a/test/functional/feature_reindex.py
+++ b/test/functional/feature_reindex.py
@@ -7,9 +7,12 @@
- Start a single node and generate 3 blocks.
- Stop the node and restart it with -reindex. Verify that the node has reindexed up to block 3.
- Stop the node and restart it with -reindex-chainstate. Verify that the node has reindexed up to block 3.
+- Verify that out-of-order blocks are correctly processed, see LoadExternalBlockFile()
"""
+import os
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.p2p import MAGIC_BYTES
from test_framework.util import assert_equal
@@ -27,11 +30,58 @@ class ReindexTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getblockcount(), blockcount) # start_node is blocking on reindex
self.log.info("Success")
+ # Check that blocks can be processed out of order
+ def out_of_order(self):
+ # The previous test created 12 blocks
+ assert_equal(self.nodes[0].getblockcount(), 12)
+ self.stop_nodes()
+
+ # In this test environment, blocks will always be in order (since
+ # we're generating them rather than getting them from peers), so to
+ # test out-of-order handling, swap blocks 1 and 2 on disk.
+ blk0 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'blk00000.dat')
+ with open(blk0, 'r+b') as bf:
+ # Read at least the first few blocks (including genesis)
+ b = bf.read(2000)
+
+ # Find the offsets of blocks 2, 3, and 4 (the first 3 blocks beyond genesis)
+ # by searching for the regtest marker bytes (see pchMessageStart).
+ def find_block(b, start):
+ return b.find(MAGIC_BYTES["regtest"], start)+4
+
+ genesis_start = find_block(b, 0)
+ assert_equal(genesis_start, 4)
+ b2_start = find_block(b, genesis_start)
+ b3_start = find_block(b, b2_start)
+ b4_start = find_block(b, b3_start)
+
+ # Blocks 2 and 3 should be the same size.
+ assert_equal(b3_start-b2_start, b4_start-b3_start)
+
+ # Swap the second and third blocks (don't disturb the genesis block).
+ bf.seek(b2_start)
+ bf.write(b[b3_start:b4_start])
+ bf.write(b[b2_start:b3_start])
+
+ # The reindexing code should detect and accommodate out of order blocks.
+ with self.nodes[0].assert_debug_log([
+ 'LoadExternalBlockFile: Out of order block',
+ 'LoadExternalBlockFile: Processing out of order child',
+ ]):
+ extra_args = [["-reindex"]]
+ self.start_nodes(extra_args)
+
+ # All blocks should be accepted and processed.
+ assert_equal(self.nodes[0].getblockcount(), 12)
+
def run_test(self):
self.reindex(False)
self.reindex(True)
self.reindex(False)
self.reindex(True)
+ self.out_of_order()
+
+
if __name__ == '__main__':
ReindexTest().main()