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

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

    Code Coverage & Benchmarks

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

    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.

  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

    0025-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

    02025-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 🌋

    Signature:

    0untrusted 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}"
    1RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
    2trusted comment: review ACK 8117c16fd7a0a5334fe63efaff94ea4e1d3cf851 🌋
    312TE8ZKu4avgseXLh/q3bD12L6JUoKwRM4JRSVJomoSPCLwU7x9A91dE0TdOMtRbkIOg6/qKppZ6YA6uZ8jIDQ==
    
  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.

    0        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.

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

    pinheadmz’s public key is on openpgp.org

  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: 2025-07-06 06:13 UTC

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