blocks: avoid recomputing block header hash in `ReadBlock` #32487

pull l0rinc wants to merge 2 commits into bitcoin:master from l0rinc:l0rinc/optimize-readblock-hash-check changing 3 files +23 −12
  1. l0rinc commented at 8:18 PM on May 13, 2025: contributor

    Eliminate one block header hash calculation per block-read by reusing the hash for:

    • proof‑of‑work verification;
    • (optional) integrity check against the supplied hash.

    This part of the code wasn't covered by tests either, so the first commit exercises this part first, before pushing the validation to the delegate method.

  2. DrahtBot commented at 8:18 PM on May 13, 2025: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32487.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK maflcko, jonatack, pinheadmz, achow101
    Stale ACK ryanofsky, TheCharlatan

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. in src/node/blockstorage.cpp:1032 in 8117c16fd7 outdated
    1035 |      const FlatFilePos block_pos{WITH_LOCK(cs_main, return index.GetBlockPos())};
    1036 | -
    1037 | -    if (!ReadBlock(block, block_pos)) {
    1038 | -        return false;
    1039 | -    }
    1040 | -    if (block.GetHash() != index.GetBlockHash()) {
    


    TheCharlatan commented at 9:43 AM on May 17, 2025:

    Is there really a scenario where we could be reading a block, but not have its block index entry? Maybe this should just be an Assume instead.


    l0rinc commented at 3:19 PM on May 17, 2025:

    We're not always checking this, only when the index is available. But after this change this check is for free, no need to recalculate the header's hash anymore.


    maflcko commented at 12:46 PM on May 19, 2025:

    Is there really a scenario where we could be reading a block, but not have its block index entry?

    I can't see any, but this would require some more refactors


    l0rinc commented at 4:07 PM on May 19, 2025:

    I can't see any

    Most usages have a hash available, but I'm not sure about e.g. LoadExternalBlockFile - but even if I would, I'd prefer tackling it separately, I'm not comfortable adding new exceptions to consensus code.


    ryanofsky commented at 8:37 PM on May 19, 2025:

    re: #32487 (review)

    I don't think I understand suggestion to use Assume. But if the hash is always available I guess expected_hash could be a required parameter instead of an optional one. Would seem fine to expand the PR, or just keep it focused on not calculating the hash twice.

  4. ryanofsky approved
  5. ryanofsky commented at 8:39 PM on May 19, 2025: contributor

    Code review ACK 8117c16fd7a0a5334fe63efaff94ea4e1d3cf851. Nice new test coverage and simple optimization

  6. l0rinc requested review from TheCharlatan on May 21, 2025
  7. in src/node/blockstorage.cpp:1026 in 8117c16fd7 outdated
    1021 | @@ -1019,21 +1022,18 @@ bool BlockManager::ReadBlock(CBlock& block, const FlatFilePos& pos) const
    1022 |          return false;
    1023 |      }
    1024 |  
    1025 | +    if (expected_hash && block_hash != *expected_hash) {
    1026 | +        LogError("GetHash() doesn't match index at %s while reading block", pos.ToString());
    


    jonatack commented at 6:50 PM on May 23, 2025:

    This error log change no longer provides info about the index that doesn't match.

    Examples from the unit test log:

    $ ./build/bin/test_bitcoin --run_test=blockmanager_tests -l test_suite -- DEBUG_LOG_OUT

    before

    025-05-23T18:42:55.808136Z (mocktime: 2020-08-31T15:34:12Z) [test] [node/blockstorage.cpp:1033] [ReadBlock] [error] GetHash() doesn't match index for CBlockIndex(pprev=0x6000019085e0, nHeight=100, merkle=851b8e14be513191d6dd7df5ac3c997c2b0b1f58c99143905eac61b296d01169, hashBlock=0000000000000000000000000000000000000000000000000000000000000001) at FlatFilePos(nFile=0, nPos=27015) while reading block
    
    

    after

    2025-05-23T18:44:21.481359Z (mocktime: 2020-08-31T15:34:12Z) [test] [node/blockstorage.cpp:1026] [ReadBlock] [error] GetHash() doesn't match index at FlatFilePos(nFile=0, nPos=27015) while reading block
    

    jonatack commented at 6:52 PM on May 23, 2025:

    (Thank you for adding the unit test!)


    l0rinc commented at 7:14 PM on May 23, 2025:

    Yes, there is no index available, only the desired hash, wherever it comes from. Would it help if we included the two different hashes in the error?


    jonatack commented at 8:15 PM on May 26, 2025:

    Would it help if we included the two different hashes in the error?

    Sure, why not, since we have them available.


    l0rinc commented at 8:19 PM on May 26, 2025:

    I'm planning a follow-up to this, will do it there


    l0rinc commented at 9:25 PM on May 26, 2025:

    Decided to do it here instead:

    [ReadBlock] [error] GetHash() doesn't match index at FlatFilePos(nFile=0, nPos=8) while reading block (000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f != 0000000000000000000000000000000000000000000000000000000000000001) test/blockmanager_tests.cpp:147: info: check !m_node.chainman->m_blockman.ReadBlock(dummy, *fake_index) has passed

  8. jonatack commented at 6:51 PM on May 23, 2025: member

    Concept ACK

  9. TheCharlatan approved
  10. TheCharlatan commented at 8:52 PM on May 23, 2025: contributor

    ACK 8117c16fd7a0a5334fe63efaff94ea4e1d3cf851

    I guess checking the hash could also protect against some weird file corruption, where a different block occupies the same position? Could this happen if e.g. the files are renamed?

  11. DrahtBot requested review from jonatack on May 23, 2025
  12. in src/test/blockmanager_tests.cpp:140 in c8cd9c66e6 outdated
     136 | @@ -137,6 +137,16 @@ BOOST_FIXTURE_TEST_CASE(blockmanager_block_data_availability, TestChain100Setup)
     137 |      BOOST_CHECK(!blockman.CheckBlockDataAvailability(tip, *last_pruned_block));
     138 |  }
     139 |  
     140 | +BOOST_FIXTURE_TEST_CASE(blockmanager_readblock_hash_mismatch, TestChain100Setup)
    


    maflcko commented at 7:32 AM on May 26, 2025:

    nit: Can drop the 100 blocks and just use TestingSetup to half the duration of the test.


    l0rinc commented at 8:19 PM on May 26, 2025:

    Will do it in a follow up, thanks


    l0rinc commented at 9:25 PM on May 26, 2025:

    Did it here instead - thanks

  13. maflcko commented at 7:35 AM on May 26, 2025: member

    review ACK 8117c16fd7a0a5334fe63efaff94ea4e1d3cf851 🌋

    <details><summary>Show signature</summary>

    Signature:

    untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
    RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
    trusted comment: review ACK 8117c16fd7a0a5334fe63efaff94ea4e1d3cf851 🌋
    12TE8ZKu4avgseXLh/q3bD12L6JUoKwRM4JRSVJomoSPCLwU7x9A91dE0TdOMtRbkIOg6/qKppZ6YA6uZ8jIDQ==
    

    </details>

  14. jonatack commented at 8:18 PM on May 26, 2025: member

    LGTM, perhaps take some of the review suggestions if you're willing

  15. DrahtBot requested review from jonatack on May 26, 2025
  16. test: exercise `ReadBlock` hash‑mismatch path
    Ensure `ReadBlock` rejects a block when the tip’s `phashBlock` differs from the expected hash.
    2bf173210f
  17. node: avoid recomputing block hash in `ReadBlock`
    Eliminate one SHA‑256 double‑hash computation of the header per block read by reusing the hash for:
    * proof‑of‑work verification;
    * (optional) integrity check against the supplied hash.
    09ee8b7f27
  18. l0rinc force-pushed on May 26, 2025
  19. maflcko commented at 6:11 AM on May 27, 2025: member

    lgtm ACK 09ee8b7f278627b917f0784adf23cbc76cae5fa0

  20. DrahtBot requested review from TheCharlatan on May 27, 2025
  21. DrahtBot requested review from ryanofsky on May 27, 2025
  22. in src/node/blockstorage.cpp:1026 in 09ee8b7f27
    1021 | @@ -1019,21 +1022,19 @@ bool BlockManager::ReadBlock(CBlock& block, const FlatFilePos& pos) const
    1022 |          return false;
    1023 |      }
    1024 |  
    1025 | +    if (expected_hash && block_hash != *expected_hash) {
    1026 | +        LogError("GetHash() doesn't match index at %s while reading block (%s != %s)",
    


    jonatack commented at 1:52 PM on May 27, 2025:

    If you retouch now or in another pull, would be clearer to state which blockhash is expected vs actual.

            LogError("GetHash() doesn't match index at %s while reading block (actual %s != expected %s)",
    

    l0rinc commented at 3:41 PM on May 27, 2025:

    I didn't want to make it that verbose, people can check the source code if they see a failure here :)

  23. jonatack commented at 1:55 PM on May 27, 2025: member

    ACK 09ee8b7f278627b917f0784adf23cbc76cae5fa0

  24. pinheadmz commented at 2:21 PM on May 27, 2025: member

    Is there a reason we don't just cache the hash as a member of CBlockHeader and compute-or-return that value in GetHash()?

  25. l0rinc commented at 3:41 PM on May 27, 2025: contributor

    @pinheadmz, that's the first thing I though of, but unfortunately the header is as mutable as it gets (see e.g. while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;), so it would be dangerous to cache the hash, given that all fields are accessible from the outside and there's no simple way to invalidate the pre-computed hash, unless we expose them all to getter/setters which would invalidate the hash on change (and which would slow down nNonce exploration). Or alternatively to convert to an immutable header after we're done mutating - but that's also likely a huge change. So, for now, this seemed to be both simpler and slightly more performant than before.

  26. pinheadmz approved
  27. pinheadmz commented at 5:16 PM on May 27, 2025: member

    ACK 09ee8b7f278627b917f0784adf23cbc76cae5fa0

    Built and tested on macos/arm64 and reviewed code. This is a simple optimization that deduplicates a call to Block.GetHash() when reading a block from disk by BlockManager. The hash is checked against the expected result from a CBlockIndex. Covered by a new unit test.

    <details><summary>Show Signature</summary>

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    ACK 09ee8b7f278627b917f0784adf23cbc76cae5fa0
    -----BEGIN PGP SIGNATURE-----
    
    iQIzBAEBCAAdFiEE5hdzzW4BBA4vG9eM5+KYS2KJyToFAmg18rgACgkQ5+KYS2KJ
    yTpYuA//S3kDsTQA0vhOf5RwbyLWnRBj/6raPmUNz8H/54Jy1ltT53KEVE/HHgOD
    zAlOKVxfH2lmgAhcbFF5DdcUtR+z0mBrMNzXBt+ELWVep+7gV2lCA/XKNSpMBAEh
    id84hJE7haKB+gjvYuYBsQG9mvhZ9QrgWVD/RkvjGjcQtO4zclTOMiOdL8c/5JU5
    V7yCNF25ymoEv7m+58JTzWqvM5bfoYlbLUpoLUX6mWvdFVq8UdOzpM+3Pg4/HdQR
    2jkv/oxNq+cuDypSonQlF5SWjQENzGMQVO29NZ4iE/DA34Z/5Xe3ouRZUzgWG7xp
    aK/x4d/hlHvo/l+PCWgV7iABnK+3avdsWza+GBwG08GTE3s6xVqga0DG9S3TCrNq
    0rM01t5FjzOK0Hwj5rnUr3EBPvGUrWC4DqszUrQ6EpO3pJQvhrXgqWTHusERo3B7
    uLCvvg+QaxUlfxli4KEbELq2UO4hWlxATTUSa5unbdwNaD2nW3QH7s2tin+jhPbt
    a+rCJdqggK83QDtEtesqzTQQmI++dCKP9qA9oOexrUZAB93ndxzs9omMDQlFrkPV
    Bl0J58bI/extHJgwqe/QpFiHboCaxyWkpi3QyLuW0a9GpBMRk+LREM0Hl6d/FphW
    A5gjkCAhHisOxz8w4tzdXq+oJAls6LGTSQwh6XlqvBLUCF0LIQI=
    =XxuP
    -----END PGP SIGNATURE-----
    

    pinheadmz's public key is on openpgp.org

    </details>

  28. achow101 commented at 7:45 PM on May 27, 2025: member

    ACK 09ee8b7f278627b917f0784adf23cbc76cae5fa0

  29. achow101 merged this on May 27, 2025
  30. achow101 closed this on May 27, 2025

  31. l0rinc deleted the branch on May 27, 2025
  32. l0rinc commented at 10:00 AM on May 29, 2025: contributor

    Thanks for the reviews and the suggestions - added #32638 as a follow-up which enforces most remaining usages of ReadBlock to validate their hash.

  33. TheCharlatan referenced this in commit cb3cd9b8b9 on May 29, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-28 03:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me