From f7c43a29f84ad1dbc6ede8b925f9d20fe6ae3f68 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 6 Apr 2026 15:00:29 +0300 Subject: [PATCH 1/6] perf: pass pre-computed block hash through reindex path to avoid redundant X11 During reindex, the X11 block header hash was computed multiple times for the same block: once in LoadExternalBlockFile, again in AcceptBlockHeader, again in CheckBlock, and more for out-of-order blocks. Thread the already-computed hash through AcceptBlock, AcceptBlockHeader, CheckBlock, and ReadBlockFromDisk to eliminate redundant X11 computations (from 3-5 per block down to 1). Co-Authored-By: Claude Opus 4.6 --- src/node/blockstorage.cpp | 11 +++++++---- src/node/blockstorage.h | 2 +- src/validation.cpp | 28 ++++++++++++++-------------- src/validation.h | 7 ++++--- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 76fd09843ac7..6a7b55b61a5d 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -745,7 +745,7 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid return true; } -bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams) +bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams, uint256* hash_out) { block.SetNull(); @@ -763,9 +763,11 @@ bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::P } // Check the header - if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) { + const uint256 hash{block.GetHash()}; + if (!CheckProofOfWork(hash, block.nBits, consensusParams)) { return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); } + if (hash_out) *hash_out = hash; return true; } @@ -774,10 +776,11 @@ bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus { const FlatFilePos block_pos{WITH_LOCK(cs_main, return pindex->GetBlockPos())}; - if (!ReadBlockFromDisk(block, block_pos, consensusParams)) { + uint256 hash; + if (!ReadBlockFromDisk(block, block_pos, consensusParams, &hash)) { return false; } - if (block.GetHash() != pindex->GetBlockHash()) { + if (hash != pindex->GetBlockHash()) { return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s", pindex->ToString(), block_pos.ToString()); } diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 2cfae856c2cc..a7bb10d4c49c 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -221,7 +221,7 @@ fs::path GetBlockPosFilename(const FlatFilePos& pos); void UnlinkPrunedFiles(const std::set& setFilesToPrune); /** Functions for disk access for blocks */ -bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams); +bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams, uint256* hash_out = nullptr); bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams); bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); diff --git a/src/validation.cpp b/src/validation.cpp index d10eac6f8eac..17de3f9d83ee 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3971,7 +3971,7 @@ static bool CheckBlockHeader(const CBlockHeader& block, const uint256& hash, Blo return true; } -bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot) +bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot, const uint256* known_hash) { // These are checks that are independent of context. @@ -3982,7 +3982,7 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. - if (!CheckBlockHeader(block, block.GetHash(), state, consensusParams, fCheckPOW)) + if (!CheckBlockHeader(block, known_hash ? *known_hash : block.GetHash(), state, consensusParams, fCheckPOW)) return false; // Check the merkle root. @@ -4182,11 +4182,9 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat return true; } -bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex) +bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex, const uint256& hash) { AssertLockHeld(cs_main); - // Check for duplicate - uint256 hash = block.GetHash(); // TODO : ENABLE BLOCK CACHE IN SPECIFIC CASES BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; @@ -4320,7 +4318,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& LOCK(cs_main); for (const CBlockHeader& header : headers) { CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast - bool accepted{AcceptBlockHeader(header, state, &pindex)}; + bool accepted{AcceptBlockHeader(header, state, &pindex, header.GetHash())}; ActiveChainstate().CheckBlockIndex(); if (!accepted) { @@ -4343,7 +4341,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector& } /** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */ -bool CChainState::AcceptBlock(const std::shared_ptr& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) +bool CChainState::AcceptBlock(const std::shared_ptr& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, const uint256* known_hash) { auto start = Now(); @@ -4355,7 +4353,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)}; + const uint256 hash{known_hash ? *known_hash : block.GetHash()}; + bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, hash)}; CheckBlockIndex(); if (!accepted_header) @@ -4394,7 +4393,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block if (pindex->nChainWork < nMinimumChainWork) return true; } - if (!CheckBlock(block, state, m_params.GetConsensus()) || + if (!CheckBlock(block, state, m_params.GetConsensus(), true, true, &hash) || !ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) { if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) { pindex->nStatus |= BLOCK_FAILED_VALID; @@ -5146,7 +5145,7 @@ void CChainState::LoadExternalBlockFile( nRewind = blkdat.GetPos(); BlockValidationState state; - if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) { + if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, &hash)) { nLoaded++; } if (state.IsError()) { @@ -5179,14 +5178,15 @@ void CChainState::LoadExternalBlockFile( while (range.first != range.second) { std::multimap::iterator it = range.first; std::shared_ptr pblockrecursive = std::make_shared(); - if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) { - LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(), + uint256 blockhash; + if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus(), &blockhash)) { + LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, blockhash.ToString(), head.ToString()); LOCK(cs_main); BlockValidationState dummy; - if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) { + if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr, &blockhash)) { nLoaded++; - queue.push_back(pblockrecursive->GetHash()); + queue.push_back(blockhash); } } range.first++; diff --git a/src/validation.h b/src/validation.h index 56bedba032ab..5a9badab22d6 100644 --- a/src/validation.h +++ b/src/validation.h @@ -371,7 +371,7 @@ void InitScriptExecutionCache(); /** Functions for validating blocks and updating the block tree */ /** Context-independent validity checks */ -bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true); +bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true, const uint256* known_hash = nullptr); /** Check a block is completely valid from start to finish (only works on top of our current best block) */ bool TestBlockValidity(BlockValidationState& state, @@ -705,7 +705,7 @@ class CChainState EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex) LOCKS_EXCLUDED(::cs_main); - bool AcceptBlock(const std::shared_ptr& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + bool AcceptBlock(const std::shared_ptr& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, const uint256* known_hash = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Block (dis)connection on a given view: DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) EXCLUSIVE_LOCKS_REQUIRED(cs_main); @@ -915,7 +915,8 @@ class ChainstateManager bool AcceptBlockHeader( const CBlockHeader& block, BlockValidationState& state, - CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + CBlockIndex** ppindex, + const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main); friend CChainState; public: From ec87196c21e41bcf9ddec26f8a12396e80ca729b Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 7 Apr 2026 13:39:25 +0300 Subject: [PATCH 2/6] assert: add debug-only invariant to verify known_hash matches block hash Add assert() checks in CheckBlock, AcceptBlockHeader, and AcceptBlock to ensure caller-supplied pre-computed hashes match block.GetHash(). Catches misuse in debug builds; compiles out in release builds. Co-Authored-By: Claude Opus 4.6 --- src/validation.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/validation.cpp b/src/validation.cpp index 17de3f9d83ee..7ded05eea54f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3977,12 +3977,15 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu auto start = Now(); + assert(!known_hash || *known_hash == block.GetHash()); + if (block.fChecked) return true; // Check that the header is valid (particularly PoW). This is mostly // redundant with the call in AcceptBlockHeader. - if (!CheckBlockHeader(block, known_hash ? *known_hash : block.GetHash(), state, consensusParams, fCheckPOW)) + const uint256 hash{known_hash ? *known_hash : block.GetHash()}; + if (!CheckBlockHeader(block, hash, state, consensusParams, fCheckPOW)) return false; // Check the merkle root. @@ -4186,6 +4189,8 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida { AssertLockHeld(cs_main); + assert(hash == block.GetHash()); + // TODO : ENABLE BLOCK CACHE IN SPECIFIC CASES BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; if (hash != GetConsensus().hashGenesisBlock) { @@ -4353,6 +4358,8 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; + assert(!known_hash || *known_hash == block.GetHash()); + const uint256 hash{known_hash ? *known_hash : block.GetHash()}; bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, hash)}; CheckBlockIndex(); From 01e72cb9871419107c69a97455216aab37193b61 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 7 Apr 2026 13:39:41 +0300 Subject: [PATCH 3/6] test: exercise CheckBlock and AcceptBlock with pre-computed known_hash Add checkblock_accept_known_hash test to cover the reindex optimization path where a caller supplies a pre-computed block hash. Co-Authored-By: Claude Opus 4.6 --- src/test/validation_block_tests.cpp | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 84daeb098541..b4cda43a82e5 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -202,6 +202,44 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash()); } +/** + * Test that CheckBlock and AcceptBlock work correctly when a pre-computed + * known_hash is supplied (the reindex optimization path). + */ +BOOST_AUTO_TEST_CASE(checkblock_accept_known_hash) +{ + bool ignored; + BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock( + std::make_shared(Params().GenesisBlock()), true, &ignored)); + + auto good = GoodBlock(Params().GenesisBlock().GetHash()); + const uint256 hash{good->GetHash()}; + const CChainParams& chainparams = Params(); + + // CheckBlock with correct known_hash should succeed + { + BlockValidationState state; + BOOST_CHECK(CheckBlock(*good, state, chainparams.GetConsensus(), + /*fCheckPOW=*/true, /*fCheckMerkleRoot=*/true, &hash)); + BOOST_CHECK(state.IsValid()); + } + + // AcceptBlock with correct known_hash should succeed + { + LOCK(::cs_main); + BlockValidationState state; + CBlockIndex* pindex = nullptr; + bool newblock = false; + BOOST_REQUIRE(m_node.chainman->ActiveChainstate().AcceptBlock( + good, state, &pindex, /*fRequested=*/true, + /*dbp=*/nullptr, &newblock, &hash)); + BOOST_REQUIRE(state.IsValid()); + BOOST_REQUIRE(newblock); + BOOST_REQUIRE(pindex != nullptr); + BOOST_CHECK_EQUAL(pindex->GetBlockHash(), hash); + } +} + /** * Test that mempool updates happen atomically with reorgs. * From 89d8c458b0119d77b7f2eda88a4f387ab50734d2 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 10 Apr 2026 12:44:52 +0300 Subject: [PATCH 4/6] assert: use ASSERT_IF_DEBUG for known_hash invariants Switch the known_hash debug invariants from assert() to ASSERT_IF_DEBUG() so the extra X11 hashing only occurs in debug builds. Dash always compiles with assertions enabled, so plain assert() would add unnecessary overhead in release builds. Co-Authored-By: Claude Opus 4.6 --- src/validation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/validation.cpp b/src/validation.cpp index 7ded05eea54f..c2d176fba279 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3977,7 +3977,7 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu auto start = Now(); - assert(!known_hash || *known_hash == block.GetHash()); + ASSERT_IF_DEBUG(!known_hash || *known_hash == block.GetHash()); if (block.fChecked) return true; @@ -4189,7 +4189,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida { AssertLockHeld(cs_main); - assert(hash == block.GetHash()); + ASSERT_IF_DEBUG(hash == block.GetHash()); // TODO : ENABLE BLOCK CACHE IN SPECIFIC CASES BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)}; @@ -4358,7 +4358,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; - assert(!known_hash || *known_hash == block.GetHash()); + ASSERT_IF_DEBUG(!known_hash || *known_hash == block.GetHash()); const uint256 hash{known_hash ? *known_hash : block.GetHash()}; bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, hash)}; From 01af278356983c1f97649a0b5dd50cbb359412e6 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 10 Apr 2026 12:45:02 +0300 Subject: [PATCH 5/6] refactor: return std::optional from ReadBlockFromDisk(FlatFilePos) Replace the uint256* hash_out output parameter with a std::optional return value. The optional is empty on failure, contains the block hash on success. This makes the hash lifetime explicit and eliminates the risk of dangling pointers. Co-Authored-By: Claude Opus 4.6 --- src/node/blockstorage.cpp | 20 +++++++++++--------- src/node/blockstorage.h | 3 ++- src/validation.cpp | 4 ++-- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 6a7b55b61a5d..be6e45e4f477 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -745,42 +745,44 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid return true; } -bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams, uint256* hash_out) +std::optional ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams) { block.SetNull(); // Open history file to read CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION); if (filein.IsNull()) { - return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString()); + error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString()); + return std::nullopt; } // Read block try { filein >> block; } catch (const std::exception& e) { - return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); + error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString()); + return std::nullopt; } // Check the header const uint256 hash{block.GetHash()}; if (!CheckProofOfWork(hash, block.nBits, consensusParams)) { - return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); + error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); + return std::nullopt; } - if (hash_out) *hash_out = hash; - return true; + return hash; } bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams) { const FlatFilePos block_pos{WITH_LOCK(cs_main, return pindex->GetBlockPos())}; - uint256 hash; - if (!ReadBlockFromDisk(block, block_pos, consensusParams, &hash)) { + const auto hash{ReadBlockFromDisk(block, block_pos, consensusParams)}; + if (!hash) { return false; } - if (hash != pindex->GetBlockHash()) { + if (*hash != pindex->GetBlockHash()) { return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s", pindex->ToString(), block_pos.ToString()); } diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index a7bb10d4c49c..8b741b0b2ff8 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -221,7 +222,7 @@ fs::path GetBlockPosFilename(const FlatFilePos& pos); void UnlinkPrunedFiles(const std::set& setFilesToPrune); /** Functions for disk access for blocks */ -bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams, uint256* hash_out = nullptr); +std::optional ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams); bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams); bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex); diff --git a/src/validation.cpp b/src/validation.cpp index c2d176fba279..206d164340ba 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -5185,8 +5185,8 @@ void CChainState::LoadExternalBlockFile( while (range.first != range.second) { std::multimap::iterator it = range.first; std::shared_ptr pblockrecursive = std::make_shared(); - uint256 blockhash; - if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus(), &blockhash)) { + if (auto opt_hash{ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())}) { + const uint256& blockhash = *opt_hash; LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, blockhash.ToString(), head.ToString()); LOCK(cs_main); From 6e138f04dddfe1a4987180e74d74db39e7cea65e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 10 Apr 2026 17:56:32 +0300 Subject: [PATCH 6/6] review: document known_hash safety and fix test to exercise reindex path Add comment explaining that CheckProofOfWork serves as the runtime consistency check for known_hash in release builds. Restructure test to skip standalone CheckBlock before AcceptBlock so that fChecked remains false and AcceptBlock's internal CheckBlock actually exercises the known_hash code path, mirroring the reindex flow. Co-Authored-By: Claude Opus 4.6 --- src/test/validation_block_tests.cpp | 19 +++++++------------ src/validation.cpp | 3 +++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index b4cda43a82e5..88867be2e75d 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -203,8 +203,9 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) } /** - * Test that CheckBlock and AcceptBlock work correctly when a pre-computed - * known_hash is supplied (the reindex optimization path). + * Test that AcceptBlock works correctly when a pre-computed known_hash is + * supplied, exercising the reindex optimization path through AcceptBlockHeader + * and CheckBlock. */ BOOST_AUTO_TEST_CASE(checkblock_accept_known_hash) { @@ -214,17 +215,11 @@ BOOST_AUTO_TEST_CASE(checkblock_accept_known_hash) auto good = GoodBlock(Params().GenesisBlock().GetHash()); const uint256 hash{good->GetHash()}; - const CChainParams& chainparams = Params(); - // CheckBlock with correct known_hash should succeed - { - BlockValidationState state; - BOOST_CHECK(CheckBlock(*good, state, chainparams.GetConsensus(), - /*fCheckPOW=*/true, /*fCheckMerkleRoot=*/true, &hash)); - BOOST_CHECK(state.IsValid()); - } - - // AcceptBlock with correct known_hash should succeed + // AcceptBlock with correct known_hash should succeed. + // Do not call CheckBlock beforehand: that would set fChecked=true, + // causing AcceptBlock's internal CheckBlock to short-circuit and skip + // the known_hash path. Keeping fChecked=false mirrors the reindex flow. { LOCK(::cs_main); BlockValidationState state; diff --git a/src/validation.cpp b/src/validation.cpp index 206d164340ba..a82a27269197 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -4358,6 +4358,9 @@ bool CChainState::AcceptBlock(const std::shared_ptr& pblock, Block CBlockIndex *pindexDummy = nullptr; CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy; + // If the caller supplies a pre-computed hash, verify it in debug builds. + // In release builds a wrong hash is still caught: AcceptBlockHeader calls + // CheckBlockHeader which runs CheckProofOfWork against the header's nBits. ASSERT_IF_DEBUG(!known_hash || *known_hash == block.GetHash()); const uint256 hash{known_hash ? *known_hash : block.GetHash()};