p2p: Add witness mutation check inside FillBlock #32646

pull instagibbs wants to merge 2 commits into bitcoin:master from instagibbs:2025-05-fillblock-mutated changing 5 files +38 −71
  1. instagibbs commented at 1:46 pm on May 30, 2025: member

    Since #29412, we have not allowed mutated blocks to continue being processed immediately the block is received, but this is only done for the legacy BLOCK message.

    Extend these checks as belt-and-suspenders to not allow similar mutation strategies to affect relay by honest peers by applying the check inside PartiallyDownloadedBlock::FillBlock, immediately before returning READ_STATUS_OK.

  2. DrahtBot commented at 1:46 pm on May 30, 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/32646.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK Crypt-iQ
    Concept ACK dergoegge

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

  3. DrahtBot added the label P2P on May 30, 2025
  4. instagibbs commented at 1:46 pm on May 30, 2025: member
  5. instagibbs force-pushed on May 30, 2025
  6. instagibbs force-pushed on May 30, 2025
  7. DrahtBot added the label CI failed on May 30, 2025
  8. DrahtBot commented at 1:54 pm on May 30, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/runs/43189542399 LLM reason (✨ experimental): Lint errors caused the CI failure.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  9. instagibbs force-pushed on May 30, 2025
  10. DrahtBot removed the label CI failed on May 30, 2025
  11. Crypt-iQ commented at 6:12 pm on May 30, 2025: contributor
    Concept ACK
  12. in src/blockencodings.cpp:222 in 2b15ec74fe outdated
    217@@ -218,6 +218,13 @@ ReadStatus PartiallyDownloadedBlock::FillBlock(CBlock& block, const std::vector<
    218         return READ_STATUS_CHECKBLOCK_FAILED;
    219     }
    220 
    221+    // Belt-and-suspenders check for other mutations now that we have a seemingly good block
    222+    if (IsBlockMutated(/*block=*/block,
    


    mzumsande commented at 7:47 pm on May 30, 2025:
    Could this be an opportunity to resolve the TODO comment above after check_block by not calling this function anymore? If I understand the comment correctly it only calls CheckBlock() to allow the caching of the check result and avoid repeated Merkle checks, which IsBlockMutated() also would do today?

    instagibbs commented at 1:55 pm on June 2, 2025:
    Would we be able to distinguish shorttxid collision from an intentionally mutated block without the check?

    mzumsande commented at 2:32 pm on June 2, 2025:
    For the sake of disconnecting the peer? I think we could use the flags set or not set in the block to learn which mutation check (CheckMerkleRoot() or CheckWitnessMalleation()) has failed.

    instagibbs commented at 5:41 pm on June 2, 2025:
    @mzumsande pushed as a follow-up commit. lmk what you think.

    instagibbs commented at 2:42 pm on June 3, 2025:
    Actually, this strategy is unneeded. Both merkle checks can fail due to collisions, and either may need full blocks fetched if they’re from the first peer. Pushed a change that is simpler and cleaned up READ_STATUS_CHECKBLOCK_FAILED which is no longer necessary.

    Crypt-iQ commented at 4:40 pm on June 9, 2025:

    Both merkle checks can fail due to collisions

    Interesting, I did not know this. Since the header merkle check is first… if the witness merkle check fails due to collision, does that mean that the transaction that collided had the same txid but a different wtxid? Or is there another way for short ID collisions to affect the witness merkle check?


    dergoegge commented at 9:55 am on June 10, 2025:

    if the witness merkle check fails due to collision, does that mean that the transaction that collided had the same txid but a different wtxid?

    That would be my understanding as well, i.e. the txs are only different in their witness. Pretty sure that is the only scenario, as otherwise the prior merkle root check would fail.

  13. in src/net_processing.cpp:3365 in 2b15ec74fe outdated
    3359@@ -3360,7 +3360,11 @@ void PeerManagerImpl::ProcessCompactBlockTxns(CNode& pfrom, Peer& peer, const Bl
    3360         }
    3361 
    3362         PartiallyDownloadedBlock& partialBlock = *range_flight.first->second.second->partialBlock;
    3363-        ReadStatus status = partialBlock.FillBlock(*pblock, block_transactions.txn);
    3364+
    3365+        const CBlockIndex* prev_block{m_chainman.m_blockman.LookupBlockIndex(partialBlock.header.hashPrevBlock)};
    3366+        Assume(prev_block); // we should not have gotten this far in compact block processing unless it's attached to a known header
    


    dergoegge commented at 12:14 pm on June 2, 2025:

    nit

    0        const CBlockIndex* prev_block{Assume(m_chainman.m_blockman.LookupBlockIndex(partialBlock.header.hashPrevBlock))};
    

    instagibbs commented at 2:38 pm on June 3, 2025:
    done
  14. dergoegge commented at 12:28 pm on June 2, 2025: member
    Concept ACK
  15. instagibbs force-pushed on Jun 3, 2025
  16. instagibbs renamed this:
    p2p: Add mutation check inside compact block's FillBlock
    p2p: Add witness mutation check inside FillBlock
    on Jun 3, 2025
  17. in src/blockencodings.cpp:213 in a7cbaca0af outdated
    218-        return READ_STATUS_CHECKBLOCK_FAILED;
    219+    // Check for possible mutations early now that we have a seemingly good block
    220+    IsBlockMutatedFn check_mutated{m_check_block_mutated_mock ? m_check_block_mutated_mock : IsBlockMutated};
    221+    if (check_mutated(/*block=*/block,
    222+                       /*check_witness_root=*/segwit_active)) {
    223+        return READ_STATUS_FAILED; // Possible Short ID collision
    


    dergoegge commented at 9:05 am on June 6, 2025:
    nit: I think this means we can get rid of READ_STATUS_CHECKBLOCK_FAILED? It’s unused now.

    instagibbs commented at 11:38 am on June 6, 2025:
    did that in a7cbaca0af38c183f009a7c8e2cfb54c17a1dc5a
  18. in src/net_processing.cpp:4521 in a7cbaca0af outdated
    4516@@ -4529,7 +4517,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
    4517                     return;
    4518                 }
    4519                 std::vector<CTransactionRef> dummy;
    4520-                status = tempBlock.FillBlock(*pblock, dummy);
    4521+                const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(cmpctblock.header.hashPrevBlock);
    4522+                Assume(prev_block); // checked prior
    


    dergoegge commented at 9:07 am on June 6, 2025:
    nit: Same as above, the Assume can be inline

    instagibbs commented at 11:39 am on June 6, 2025:
    will do the next time I touch it

    instagibbs commented at 2:08 pm on June 10, 2025:
    almost forgot, done
  19. in src/test/fuzz/partially_downloaded_block.cpp:118 in 043f07726a outdated
    130-            std::nullopt);
    131+    bool segwit_active{fuzzed_data_provider.ConsumeBool()};
    132+
    133+    // Mock IsBlockMutated
    134+    bool fail_block_mutated{fuzzed_data_provider.ConsumeBool()};
    135+    // True if txid merkle commitment will be found valid, false if both will not be marked valid
    


    Crypt-iQ commented at 4:14 pm on June 9, 2025:
    If fail_block_mutated is true, doesn’t this mean the txid merkle commitment won’t be found valid? I might be misreading the comment.

    instagibbs commented at 2:00 pm on June 10, 2025:
    stale comment, I just removed it as it’s not particularly helpful
  20. instagibbs force-pushed on Jun 10, 2025
  21. p2p: Add witness mutation check inside FillBlock
    Since #29412, we have not allowed mutated blocks to continue
    being processed immediately the block is received, but this
    is only done for the legacy BLOCK message.
    
    Extend these checks as belt-and-suspenders to not allow
    similar mutation strategies to affect relay by honest peers
    by applying the check inside
    PartiallyDownloadedBlock::FillBlock, immediately before
    returning READ_STATUS_OK.
    
    This also removes the extraneous CheckBlock call.
    bac9ee4830
  22. p2p: remove vestigial READ_STATUS_CHECKBLOCK_FAILED 28299ce776
  23. instagibbs force-pushed on Jun 10, 2025
  24. Crypt-iQ commented at 4:30 pm on June 10, 2025: contributor
    ACK 28299ce77636d7563ec545d043cf1b61bd2f01c1
  25. DrahtBot requested review from dergoegge on Jun 10, 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-06-15 06:13 UTC

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