Skip to content

Commit 351f0f8

Browse files
Merge #7272: perf: pass pre-computed block hash through reindex path to avoid redundant X11 computations
6e138f0 review: document known_hash safety and fix test to exercise reindex path (UdjinM6) 01af278 refactor: return std::optional<uint256> from ReadBlockFromDisk(FlatFilePos) (UdjinM6) 89d8c45 assert: use ASSERT_IF_DEBUG for known_hash invariants (UdjinM6) 01e72cb test: exercise CheckBlock and AcceptBlock with pre-computed known_hash (UdjinM6) ec87196 assert: add debug-only invariant to verify known_hash matches block hash (UdjinM6) f7c43a2 perf: pass pre-computed block hash through reindex path to avoid redundant X11 (UdjinM6) Pull request description: ## Issue being fixed or feature implemented 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. See #6610 for more info. ## What was done? Thread the already-computed hash through `AcceptBlock`, `AcceptBlockHeader`, `CheckBlock`, and `ReadBlockFromDisk` to eliminate redundant X11 computations (from 3-5 per block down to 1). ## How Has This Been Tested? Reindexed on testnet with `--stopatheight=1` to confirm performance is on par with #6610. Then competed reindex on testnet with no issues. Tests are green too. ## Breaking Changes n/a ## Checklist: - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ Top commit has no ACKs. Tree-SHA512: 473fb7bf177c721f8d0f50e04394015554e779b26b1f004e52efb7803d267a13692a0f6a31426f084f1fbc25d2db44a56b0621fe7ed1b65c9c49ed322d98f1e9
2 parents 56bceb4 + 6e138f0 commit 351f0f8

5 files changed

Lines changed: 76 additions & 26 deletions

File tree

src/node/blockstorage.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -756,39 +756,44 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
756756
return true;
757757
}
758758

759-
bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams)
759+
std::optional<uint256> ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams)
760760
{
761761
block.SetNull();
762762

763763
// Open history file to read
764764
CAutoFile filein(OpenBlockFile(pos, true), SER_DISK, CLIENT_VERSION);
765765
if (filein.IsNull()) {
766-
return error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString());
766+
error("ReadBlockFromDisk: OpenBlockFile failed for %s", pos.ToString());
767+
return std::nullopt;
767768
}
768769

769770
// Read block
770771
try {
771772
filein >> block;
772773
} catch (const std::exception& e) {
773-
return error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString());
774+
error("%s: Deserialize or I/O error - %s at %s", __func__, e.what(), pos.ToString());
775+
return std::nullopt;
774776
}
775777

776778
// Check the header
777-
if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) {
778-
return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
779+
const uint256 hash{block.GetHash()};
780+
if (!CheckProofOfWork(hash, block.nBits, consensusParams)) {
781+
error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString());
782+
return std::nullopt;
779783
}
780784

781-
return true;
785+
return hash;
782786
}
783787

784788
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams)
785789
{
786790
const FlatFilePos block_pos{WITH_LOCK(cs_main, return pindex->GetBlockPos())};
787791

788-
if (!ReadBlockFromDisk(block, block_pos, consensusParams)) {
792+
const auto hash{ReadBlockFromDisk(block, block_pos, consensusParams)};
793+
if (!hash) {
789794
return false;
790795
}
791-
if (block.GetHash() != pindex->GetBlockHash()) {
796+
if (*hash != pindex->GetBlockHash()) {
792797
return error("ReadBlockFromDisk(CBlock&, CBlockIndex*): GetHash() doesn't match index for %s at %s",
793798
pindex->ToString(), block_pos.ToString());
794799
}

src/node/blockstorage.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <txdb.h>
1616

1717
#include <cstdint>
18+
#include <optional>
1819
#include <unordered_map>
1920
#include <vector>
2021

@@ -231,7 +232,7 @@ fs::path GetBlockPosFilename(const FlatFilePos& pos);
231232
void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
232233

233234
/** Functions for disk access for blocks */
234-
bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams);
235+
std::optional<uint256> ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::Params& consensusParams);
235236
bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams);
236237

237238
bool UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex* pindex);

src/test/validation_block_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,39 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
202202
BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
203203
}
204204

205+
/**
206+
* Test that AcceptBlock works correctly when a pre-computed known_hash is
207+
* supplied, exercising the reindex optimization path through AcceptBlockHeader
208+
* and CheckBlock.
209+
*/
210+
BOOST_AUTO_TEST_CASE(checkblock_accept_known_hash)
211+
{
212+
bool ignored;
213+
BOOST_REQUIRE(Assert(m_node.chainman)->ProcessNewBlock(
214+
std::make_shared<CBlock>(Params().GenesisBlock()), true, &ignored));
215+
216+
auto good = GoodBlock(Params().GenesisBlock().GetHash());
217+
const uint256 hash{good->GetHash()};
218+
219+
// AcceptBlock with correct known_hash should succeed.
220+
// Do not call CheckBlock beforehand: that would set fChecked=true,
221+
// causing AcceptBlock's internal CheckBlock to short-circuit and skip
222+
// the known_hash path. Keeping fChecked=false mirrors the reindex flow.
223+
{
224+
LOCK(::cs_main);
225+
BlockValidationState state;
226+
CBlockIndex* pindex = nullptr;
227+
bool newblock = false;
228+
BOOST_REQUIRE(m_node.chainman->ActiveChainstate().AcceptBlock(
229+
good, state, &pindex, /*fRequested=*/true,
230+
/*dbp=*/nullptr, &newblock, &hash));
231+
BOOST_REQUIRE(state.IsValid());
232+
BOOST_REQUIRE(newblock);
233+
BOOST_REQUIRE(pindex != nullptr);
234+
BOOST_CHECK_EQUAL(pindex->GetBlockHash(), hash);
235+
}
236+
}
237+
205238
/**
206239
* Test that mempool updates happen atomically with reorgs.
207240
*

src/validation.cpp

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3981,18 +3981,21 @@ static bool CheckBlockHeader(const CBlockHeader& block, const uint256& hash, Blo
39813981
return true;
39823982
}
39833983

3984-
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot)
3984+
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot, const uint256* known_hash)
39853985
{
39863986
// These are checks that are independent of context.
39873987

39883988
auto start = Now<SteadyMicroseconds>();
39893989

3990+
ASSERT_IF_DEBUG(!known_hash || *known_hash == block.GetHash());
3991+
39903992
if (block.fChecked)
39913993
return true;
39923994

39933995
// Check that the header is valid (particularly PoW). This is mostly
39943996
// redundant with the call in AcceptBlockHeader.
3995-
if (!CheckBlockHeader(block, block.GetHash(), state, consensusParams, fCheckPOW))
3997+
const uint256 hash{known_hash ? *known_hash : block.GetHash()};
3998+
if (!CheckBlockHeader(block, hash, state, consensusParams, fCheckPOW))
39963999
return false;
39974000

39984001
// Check the merkle root.
@@ -4192,11 +4195,11 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat
41924195
return true;
41934196
}
41944197

4195-
bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex)
4198+
bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationState& state, CBlockIndex** ppindex, const uint256& hash)
41964199
{
41974200
AssertLockHeld(cs_main);
4198-
// Check for duplicate
4199-
uint256 hash = block.GetHash();
4201+
4202+
ASSERT_IF_DEBUG(hash == block.GetHash());
42004203

42014204
// TODO : ENABLE BLOCK CACHE IN SPECIFIC CASES
42024205
BlockMap::iterator miSelf{m_blockman.m_block_index.find(hash)};
@@ -4330,7 +4333,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
43304333
LOCK(cs_main);
43314334
for (const CBlockHeader& header : headers) {
43324335
CBlockIndex *pindex = nullptr; // Use a temp pindex instead of ppindex to avoid a const_cast
4333-
bool accepted{AcceptBlockHeader(header, state, &pindex)};
4336+
bool accepted{AcceptBlockHeader(header, state, &pindex, header.GetHash())};
43344337
ActiveChainstate().CheckBlockIndex();
43354338

43364339
if (!accepted) {
@@ -4353,7 +4356,7 @@ bool ChainstateManager::ProcessNewBlockHeaders(const std::vector<CBlockHeader>&
43534356
}
43544357

43554358
/** Store block on disk. If dbp is non-nullptr, the file is known to already reside on disk */
4356-
bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock)
4359+
bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, const uint256* known_hash)
43574360
{
43584361
auto start = Now<SteadyMicroseconds>();
43594362

@@ -4365,7 +4368,13 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
43654368
CBlockIndex *pindexDummy = nullptr;
43664369
CBlockIndex *&pindex = ppindex ? *ppindex : pindexDummy;
43674370

4368-
bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex)};
4371+
// If the caller supplies a pre-computed hash, verify it in debug builds.
4372+
// In release builds a wrong hash is still caught: AcceptBlockHeader calls
4373+
// CheckBlockHeader which runs CheckProofOfWork against the header's nBits.
4374+
ASSERT_IF_DEBUG(!known_hash || *known_hash == block.GetHash());
4375+
4376+
const uint256 hash{known_hash ? *known_hash : block.GetHash()};
4377+
bool accepted_header{m_chainman.AcceptBlockHeader(block, state, &pindex, hash)};
43694378
CheckBlockIndex();
43704379

43714380
if (!accepted_header)
@@ -4404,7 +4413,7 @@ bool CChainState::AcceptBlock(const std::shared_ptr<const CBlock>& pblock, Block
44044413
if (pindex->nChainWork < nMinimumChainWork) return true;
44054414
}
44064415

4407-
if (!CheckBlock(block, state, m_params.GetConsensus()) ||
4416+
if (!CheckBlock(block, state, m_params.GetConsensus(), true, true, &hash) ||
44084417
!ContextualCheckBlock(block, state, m_chainman, pindex->pprev)) {
44094418
if (state.IsInvalid() && state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
44104419
pindex->nStatus |= BLOCK_FAILED_VALID;
@@ -5159,7 +5168,7 @@ void CChainState::LoadExternalBlockFile(
51595168
nRewind = blkdat.GetPos();
51605169

51615170
BlockValidationState state;
5162-
if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr)) {
5171+
if (AcceptBlock(pblock, state, nullptr, true, dbp, nullptr, &hash)) {
51635172
nLoaded++;
51645173
}
51655174
if (state.IsError()) {
@@ -5207,14 +5216,15 @@ void CChainState::LoadExternalBlockFile(
52075216
while (range.first != range.second) {
52085217
std::multimap<uint256, FlatFilePos>::iterator it = range.first;
52095218
std::shared_ptr<CBlock> pblockrecursive = std::make_shared<CBlock>();
5210-
if (ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())) {
5211-
LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, pblockrecursive->GetHash().ToString(),
5219+
if (auto opt_hash{ReadBlockFromDisk(*pblockrecursive, it->second, m_params.GetConsensus())}) {
5220+
const uint256& blockhash = *opt_hash;
5221+
LogPrint(BCLog::REINDEX, "%s: Processing out of order child %s of %s\n", __func__, blockhash.ToString(),
52125222
head.ToString());
52135223
LOCK(cs_main);
52145224
BlockValidationState dummy;
5215-
if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr)) {
5225+
if (AcceptBlock(pblockrecursive, dummy, nullptr, true, &it->second, nullptr, &blockhash)) {
52165226
nLoaded++;
5217-
queue.push_back(pblockrecursive->GetHash());
5227+
queue.push_back(blockhash);
52185228
}
52195229
}
52205230
range.first++;

src/validation.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ void InitScriptExecutionCache();
371371
/** Functions for validating blocks and updating the block tree */
372372

373373
/** Context-independent validity checks */
374-
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true);
374+
bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true, bool fCheckMerkleRoot = true, const uint256* known_hash = nullptr);
375375

376376
/** Check a block is completely valid from start to finish (only works on top of our current best block) */
377377
bool TestBlockValidity(BlockValidationState& state,
@@ -705,7 +705,7 @@ class CChainState
705705
EXCLUSIVE_LOCKS_REQUIRED(!m_chainstate_mutex)
706706
LOCKS_EXCLUDED(::cs_main);
707707

708-
bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
708+
bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, BlockValidationState& state, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock, const uint256* known_hash = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
709709

710710
// Block (dis)connection on a given view:
711711
DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -915,7 +915,8 @@ class ChainstateManager
915915
bool AcceptBlockHeader(
916916
const CBlockHeader& block,
917917
BlockValidationState& state,
918-
CBlockIndex** ppindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
918+
CBlockIndex** ppindex,
919+
const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
919920
friend CChainState;
920921

921922
public:

0 commit comments

Comments
 (0)