mining: check witness commitment in submitBlock #33745

pull Sjors wants to merge 3 commits into bitcoin:master from Sjors:2025/10/submit-solution-doc changing 4 files +57 −7
  1. Sjors commented at 12:02 pm on October 30, 2025: member

    When an IPC client requests a new block template via the Mining interface, we hold on to its CBlock. That way when they call submitSolution() we can modify it in place, rather than having to reconstruct the full block like the submitblock RPC does.

    Before this commit however we forgot to invalidate m_checked_witness_commitment, which we should since the client brings a new coinbase.

    This would cause us to accept an invalid chaintip.

    Fix this and add a test to confirm that we now reject such a block. As a sanity check, we add a second node to the test and confirm that will accept our mined block.

    As first noticed in #33374 the IPC code takes the coinbase as provided, unlike the submitblock RPC which calls UpdateUncommittedBlockStructures() and adds witness commitment to the coinbase if it was missing.

    Although that could have been an alternative fix, we instead document that IPC clients are expected to provide the full coinbase including witness commitment.

    Patch to produce the original issue:

     0diff --git a/src/node/miner.cpp b/src/node/miner.cpp
     1index b988e28a3f..28e9048a4d 100644
     2--- a/src/node/miner.cpp
     3+++ b/src/node/miner.cpp
     4@@ -450,15 +450,10 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
     5     }
     6     block.nVersion = version;
     7     block.nTime = timestamp;
     8     block.nNonce = nonce;
     9     block.hashMerkleRoot = BlockMerkleRoot(block);
    10-
    11-    // Reset cached checks
    12-    block.m_checked_witness_commitment = false;
    13-    block.m_checked_merkle_root = false;
    14-    block.fChecked = false;
    15 }
    16
    17 std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
    18                                                       KernelNotifications& kernel_notifications,
    19                                                       CTxMemPool* mempool,
    20diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
    21index cce56e3294..bf1b7048ab 100755
    22--- a/test/functional/interface_ipc.py
    23+++ b/test/functional/interface_ipc.py
    24@@ -216,22 +216,22 @@ class IPCInterfaceTest(BitcoinTestFramework):
    25             assert_equal(res.result, True)
    26
    27             # The remote template block will be mutated, capture the original:
    28             remote_block_before = await self.parse_and_deserialize_block(template, ctx)
    29
    30-            self.log.debug("Submitted coinbase must include witness")
    31+            self.log.debug("Submitted coinbase with missing witness is accepted")
    32             assert_not_equal(coinbase.serialize_without_witness().hex(), coinbase.serialize().hex())
    33             res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize_without_witness())
    34-            assert_equal(res.result, False)
    35+            assert_equal(res.result, True)
    36
    37             self.log.debug("Even a rejected submitBlock() mutates the template's block")
    38             # Can be used by clients to download and inspect the (rejected)
    39             # reconstructed block.
    40             remote_block_after = await self.parse_and_deserialize_block(template, ctx)
    41             assert_not_equal(remote_block_before.serialize().hex(), remote_block_after.serialize().hex())
    42
    43-            self.log.debug("Submit again, with the witness")
    44+            self.log.debug("Submit again, with the witness - does not replace the invalid block")
    45             res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
    46             assert_equal(res.result, True)
    47
    48             self.log.debug("Block should propagate")
    49             assert_equal(self.nodes[1].getchaintips()[0]["height"], current_block_height + 1)
    
  2. DrahtBot added the label Mining on Oct 30, 2025
  3. DrahtBot commented at 12:03 pm on October 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/33745.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky
    Concept ACK ismaelsadeeq
    Stale ACK TheCharlatan

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

  4. in src/node/interfaces.cpp:921 in 68fc907de9
    917+        AddMerkleRootAndCoinbase(block, std::move(coinbase), version, timestamp, nonce);
    918+
    919+        // We modified the coinbase which should invalidate the check we did
    920+        // when the block template was constructed. Additionally, unlike the
    921+        // submitblock RPC we don't adjust the provided coinbase.
    922+        block.m_checked_witness_commitment = false;
    


    TheCharlatan commented at 12:15 pm on October 30, 2025:
    What about the other checked flags? Should we just reset all of them in AddMerkleRootAndCoinbase?

    ryanofsky commented at 12:46 pm on October 30, 2025:

    re: #33745 (review)

    What about the other checked flags? Should we just reset all of them in AddMerkleRootAndCoinbase?

    This seems like a really good idea. Having AddMerkleRootAndCoinbase update relevant flags seems like a more robust solution that would have prevented this problem.


    Sjors commented at 1:53 pm on October 30, 2025:
    Done
  5. in src/node/interfaces.cpp:916 in 68fc907de9 outdated
    920+        // when the block template was constructed. Additionally, unlike the
    921+        // submitblock RPC we don't adjust the provided coinbase.
    922+        block.m_checked_witness_commitment = false;
    923+        Assume(!block.fChecked);
    924+
    925         return chainman().ProcessNewBlock(std::make_shared<const CBlock>(m_block_template->block), /*force_processing=*/true, /*min_pow_checked=*/true, /*new_block=*/nullptr);
    


    ryanofsky commented at 12:43 pm on October 30, 2025:

    In commit “mining: check witness commitment in submitBlock” (68fc907de93f422fd5566f9066bf16b1239e3a1b)

    If keeping changes in this function instead of making the fix in AddMerkleRootAndCoinbase, would be good to s/m_block_template->block/block on this line to be consistent


    Sjors commented at 1:44 pm on October 30, 2025:
    I moved the change away from here.
  6. in src/interfaces/mining.h:57 in 68fc907de9 outdated
    53@@ -54,7 +54,15 @@ class BlockTemplate
    54     virtual std::vector<uint256> getCoinbaseMerklePath() = 0;
    55 
    56     /**
    57-     * Construct and broadcast the block.
    58+     * Construct and broadcast the block. Modifies the template in place.
    


    ryanofsky commented at 12:51 pm on October 30, 2025:

    In commit “mining: check witness commitment in submitBlock” (68fc907de93f422fd5566f9066bf16b1239e3a1b)

    It’s good to mention the block is modified now but it would seem helpful to provide a little more detail here about what is expected and what is modified here especially since IPC behavior is different from RPC.

    You also mentioned that RPC “This is safe for submitted blocks” documentation isn’t really true and it would be nice if that documentation was fixed as well.

    Finally if we need to add some more accessors (getWitnessCommitment? getCoinbaseWitnessReservedValue?) here so miners do not need to make unsafe assumptions about the node, could we maybe do that in this PR? I understand SV2 protocol coudln’t currently use this information but it’d be nice to not have to revisit this issue and make more changes here if sv2 will need this in the future.


    Sjors commented at 2:25 pm on October 30, 2025:

    Thinking about adding the following to this comment:

    0    /** Update uncommitted block structures (currently: only the witness reserved
    1     * value). This is safe for submitted blocks as long as they honor
    2     * default_witness_commitment from the template. */
    3    void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev) const;
    

    Sjors commented at 2:57 pm on October 30, 2025:
    Done. I also clarified which fields are modified.
  7. in src/interfaces/mining.h:68 in 68fc907de9 outdated
    63+     * @param[in] coinbase complete coinbase transaction (including witness)
    64+     *
    65+     * @note unlike the submitblock RPC, this method does NOT add the
    66+     *       coinbase witness automatically.
    67      *
    68      * @returns if the block was processed, independent of block validity
    


    ryanofsky commented at 1:04 pm on October 30, 2025:

    In commit “mining: check witness commitment in submitBlock” (68fc907de93f422fd5566f9066bf16b1239e3a1b)

    It seems this comment is not actually true if this function is returning false now, or the comment could at least be clarified.

    It also seems like just returning false here on failure without error messages could make it difficult for clients to debug issues. Will more specific errors be logged here? Could they be easily returned?


    Sjors commented at 1:51 pm on October 30, 2025:

    It behaves the same way as ProcessNewBlock, which has the following confusing documentation:

    This only returns after the best known valid block is made active. Note that it does not, however, guarantee that the specific block passed to it has been checked for validity!


    Sjors commented at 1:53 pm on October 30, 2025:
    I do think it would be nice to return clear errors somehow, but that seems a separate issue.

    davidgumberg commented at 6:27 pm on October 30, 2025:

    Maybe the comment here could be changed to something like:

    0     * [@returns](/bitcoin-bitcoin/contributor/returns/) if the block was processed, does not necessarily indicate validity.
    

    Sjors commented at 8:51 am on October 31, 2025:
    Taken
  8. fanquake commented at 1:38 pm on October 30, 2025: member

    https://github.com/bitcoin/bitcoin/actions/runs/18939943748/job/54075967306?pr=33745#step:9:2351:

    0
    1test/miner_tests.cpp(70): Entering test suite "miner_tests"
    2test/miner_tests.cpp(686): Entering test case "CreateNewBlock_validity"
    3<snip>
    4 2025-10-30T12:08:21.221330Z [test] [validationinterface.cpp:218] [void ValidationSignals::BlockConnected(ChainstateRole, const std::shared_ptr<const CBlock> &, const CBlockIndex *)] [validation] Enqueuing BlockConnected: block hash=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f block height=0
    52025-10-30T12:08:21.221451Z [scheduler] [validationinterface.cpp:218] [auto ValidationSignals::BlockConnected(ChainstateRole, const std::shared_ptr<const CBlock> &, const CBlockIndex *)::(anonymous class)::operator()() const] [validation] BlockConnected: block hash=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60node/miner.cpp:187 CreateNewBlock: Assertion `pblock->m_checked_witness_commitment' failed.
    
  9. Sjors force-pushed on Oct 30, 2025
  10. Sjors commented at 1:45 pm on October 30, 2025: member

    @TheCharlatan wrote:

    What about the other checked flags? Should we just reset all of them in AddMerkleRootAndCoinbase?

    Done and pushed, but still checking the CI failure.

  11. Sjors force-pushed on Oct 30, 2025
  12. Sjors commented at 1:48 pm on October 30, 2025: member
    I dropped Assume(pblock->m_checked_witness_commitment); from miner.cpp, since the assumptions seems to be wrong.
  13. Sjors force-pushed on Oct 30, 2025
  14. in test/functional/interface_ipc.py:223 in 504acfcd5d outdated
    216             res = await mining.result.checkBlock(block.serialize(), check_opts)
    217             assert_equal(res.result, True)
    218+
    219+            self.log.debug("Submitted coinbase must include witness")
    220+            assert_not_equal(coinbase.serialize_without_witness().hex(), coinbase.serialize().hex())
    221+            res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize_without_witness())
    


    TheCharlatan commented at 9:33 pm on October 30, 2025:
    In commit 504acfcd5d1ed4d77f7fd4f5dc57e66d6718d3d4: If I apply this test independently before the miner.cpp code changes, it still passes, so not sure what we are testing here. Wouldn’t this only be relevant if we’d run checkBlock on the template’s own block? Afaict we only do checkBlock on blocks we re-create here.

    davidgumberg commented at 9:40 pm on October 30, 2025:

    Strange, for me this test fails as expected with the following diff applied to this branch:

     0diff --git a/src/node/miner.cpp b/src/node/miner.cpp
     1index b988e28a3f..485984e437 100644
     2--- a/src/node/miner.cpp
     3+++ b/src/node/miner.cpp
     4@@ -454,9 +454,9 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
     5     block.hashMerkleRoot = BlockMerkleRoot(block);
     6
     7     // Reset cached checks
     8-    block.m_checked_witness_commitment = false;
     9-    block.m_checked_merkle_root = false;
    10-    block.fChecked = false;
    11+    // block.m_checked_witness_commitment = false;
    12+    // block.m_checked_merkle_root = false;
    13+    // block.fChecked = false;
    14 }
    

    TheCharlatan commented at 10:00 pm on October 30, 2025:
    You’re right, I messed something up. EDIT: Please resolve.
  15. in src/node/miner.cpp:458 in 7d57c4e444 outdated
    451@@ -452,6 +452,11 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
    452     block.nTime = timestamp;
    453     block.nNonce = nonce;
    454     block.hashMerkleRoot = BlockMerkleRoot(block);
    455+
    456+    // Reset cached checks
    457+    block.m_checked_witness_commitment = false;
    458+    block.m_checked_merkle_root = false;
    


    TheCharlatan commented at 10:06 pm on October 30, 2025:
    I previously hinted at resetting this flag too, but does that really make sense, if we’ve just re-calculated the merkle root?

    ryanofsky commented at 10:38 pm on October 30, 2025:

    re: #33745 (review)

    I previously hinted at resetting this flag too, but does that really make sense, if we’ve just re-calculated the merkle root?

    To me, it would seem safest to go other direction and reset all the cached flags including fChecked. Or if any flags are intentionally not reset it would be nice to a comment making it clear that it was intentional and should be safe.

    In the case of CheckMerkleRoot, it is not just checking the block.hashMerkleRoot value but also checking for malleability. I guess malleablilty case would not be triggered because coinbase probably should not duplicate another transaction, but not sure if we should to make assumptions about how AddMerkleRootAndCoinbase may be used in the future and what inputs might be passed.

    Would seem best to repeat checks by default after mutating the block, or at least comment if it’s intentional not to repeat some of them.


    Sjors commented at 9:13 am on October 31, 2025:
    I kept all three, but as I explained in #33745 (comment) two of them are already false.
  16. in test/functional/interface_ipc.py:231 in 504acfcd5d outdated
    227-            assert_equal(self.nodes[0].getchaintips()[0]["height"], current_block_height + 1)
    228+
    229+            self.log.debug("Submit mutated the block in the template")
    230+            remote_block_after = await self.parse_and_deserialize_block(template, ctx)
    231+            assert_not_equal(remote_block_before.serialize().hex(), remote_block_after.serialize().hex())
    232+            assert_equal(self.nodes[1].getchaintips()[0]["height"], current_block_height + 1)
    


    ryanofsky commented at 10:08 pm on October 30, 2025:

    In commit “mining: ensure witness commitment check in submitBlock” (504acfcd5d1ed4d77f7fd4f5dc57e66d6718d3d4)

    Is there a submitSolution call missing here? I don’t understand what this is testing


    davidgumberg commented at 11:23 pm on October 30, 2025:

    (Aside from the last line checking that the second node’s chaintip has advanced), I don’t think this is testing anything changed by this PR, this test succeeds on master. As I understand it, this is checking that after the submitSolution calls above, that m_block_template->block has been modified, but I’m not sure why that would be an interesting test, AFAICT it’s really just checking that submitSolution calls AddMerkleRootAndCoinbase(), since that modifies the block passed to it.

    https://github.com/bitcoin/bitcoin/blob/7d57c4e44451ce1167ba76a73c9cc6f6fd6cfca4/src/node/miner.cpp#L446-L454


    Sjors commented at 8:51 am on October 31, 2025:
    remote_block_before is set before the two submitBlock calls above. I improved the wording a bit and I moved the getchaintips()[0]["height"] down to the next test, because that was probably confusing.
  17. ryanofsky approved
  18. ryanofsky commented at 10:42 pm on October 30, 2025: contributor
    Code review ACK 7d57c4e44451ce1167ba76a73c9cc6f6fd6cfca4. Code, test, documentation updates all seem like improvements and it is good to make submitSolution provide better feedback. I do think more improvements could be made here: documentation could be updated to say what requirements on submitSolution inputs are, and I think BlockTemplate accessors could be added so miners do not need to make hardcoded assumptions about the coinbase transaction? (https://github.com/bitcoin/bitcoin/pull/33745#discussion_r2477980297). It would also be nice to return error messages and not just the false return value.
  19. in test/functional/interface_ipc.py:234 in 7d57c4e444 outdated
    221+            res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize_without_witness())
    222+            assert_equal(res.result, False)
    223+
    224+            self.log.debug("Submit again, with the witness")
    225             res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
    226             assert_equal(res.result, True)
    


    davidgumberg commented at 11:31 pm on October 30, 2025:

    I want to suggest adding a check here:

    0            assert_equal(res.result, True)
    1            # self.nodes[1]'s chaintip did not advance.
    2            assert_equal(self.nodes[1].getchaintips()[0]["height"], current_block_height)
    

    But I’m a bit confused, because testing this without the changes to src/node/miner.cpp, self.nodes[1]’s chaintip advances after submitSolution is called with the witnessless coinbase, and this check I’m suggesting fails.

    To reproduce, I’m using the following diff on this branch:

     0diff --git a/src/node/miner.cpp b/src/node/miner.cpp
     1index b988e28a3f..485984e437 100644
     2--- a/src/node/miner.cpp
     3+++ b/src/node/miner.cpp
     4@@ -454,9 +454,9 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
     5     block.hashMerkleRoot = BlockMerkleRoot(block);
     6
     7     // Reset cached checks
     8-    block.m_checked_witness_commitment = false;
     9-    block.m_checked_merkle_root = false;
    10-    block.fChecked = false;
    11+    // block.m_checked_witness_commitment = false;
    12+    // block.m_checked_merkle_root = false;
    13+    // block.fChecked = false;
    14 }
    15
    16 std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
    17diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
    18index a6010c2fb3..0d99a6b2c3 100755
    19--- a/test/functional/interface_ipc.py
    20+++ b/test/functional/interface_ipc.py
    21@@ -219,7 +219,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
    22             self.log.debug("Submitted coinbase must include witness")
    23             assert_not_equal(coinbase.serialize_without_witness().hex(), coinbase.serialize().hex())
    24             res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize_without_witness())
    25-            assert_equal(res.result, False)
    26+            #assert_equal(res.result, False)
    27+            # self.nodes[1]'s chaintip did not advance.
    28+            assert_equal(self.nodes[1].getchaintips()[0]["height"], current_block_height)
    29
    30             self.log.debug("Submit again, with the witness")
    31             res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
    

    And the check fails, because self.nodes[1]’s chaintip was advanced!


    Sjors commented at 8:17 am on October 31, 2025:

    What your patch demonstrates is that without the fix in this PR (clearing the cache), the invalid block is accepted by the node.

    If you then also drop the assert_equal ... current_block_height) line, you’ll see that the fails at “Block should propagate”.

    The “Submit again, with the witness” step doesn’t replace the invalid block, and ProcessNewBlock returns true for duplicate blocks and submitSolution passes it on.

    I think is good, because in Stratum v2 (depending on configuration) the same solution may be submitted via the pool and the individual miner, causing a race over the p2p network. I’ll add a note to clarify this.


    Sjors commented at 9:01 am on October 31, 2025:
    Added a patch to the PR description which reproduces the issue.
  20. TheCharlatan approved
  21. TheCharlatan commented at 8:24 am on October 31, 2025: contributor
    ACK 7d57c4e44451ce1167ba76a73c9cc6f6fd6cfca4
  22. Sjors force-pushed on Oct 31, 2025
  23. Sjors commented at 8:55 am on October 31, 2025: member

    Pushed 7d57c4e to 2fc95ca: compare. No behavior change.

    I added a patch the to PR description that reproduces the original issue.

    Expanded the submitSolution documentation to indicate that a duplicate block is not considered a failure, and why.

    I kept all three reset caches for simplicity.

    Note that m_checked_merkle_root will be false anyway, because BlockAssembler::CreateNewBlock skips the merle root check. If we really wanted to skip that check, since we created the merle root ourselves, we would have to modify ProcessNewBlock to forward check_merkle_root to its CheckBlock call. That seems out of scope.

    Similarly fChecked will already be false because the only place that sets it to true is guarded by if (fCheckPOW && fCheckMerkleRoot).

  24. Sjors commented at 9:11 am on October 31, 2025: member

    @ryanofsky wrote:

    I do think more improvements could be made here:

    These could be followups, some thoughts:

    • documentation could be updated to say what requirements on submitSolution inputs are

    Not sure what to write there, basically whatever consensus requires. Previously (external) mining software would generate the whole coinbase from scratch and we never really documented that.

    The Stratum v2 spec does specify what their SubmitSolution message should look like: https://stratumprotocol.org/specification/07-Template-Distribution-Protocol/#77-submitsolution-client-server

    That’s presumably based on their understanding of our code, so we shouldn’t blindly copy-paste it.

    and I think BlockTemplate accessors could be added so miners do not need to make hardcoded assumptions about the coinbase transaction? (https://github.com/bitcoin/bitcoin/pull/33745#discussion_r2477980297).

    The Template Provider I implemented takes all sorts of stuff from the coinbase in order to produce its NewTemplate message:

    This could be refactored to use more accessors. Though I’d be worried about having lots of back and forth IPC messages.

    It would also be nice to return error messages and not just the false return value.

    Indeed, though this requires a refactor of ProcessNewBlock.

  25. in test/functional/interface_ipc.py:240 in 2fc95ca731 outdated
    233+
    234+            self.log.debug("Block should propagate")
    235+            assert_equal(self.nodes[1].getchaintips()[0]["height"], current_block_height + 1)
    236+            # Stalls if a regression causes submitBlock() to accept an invalid block:
    237+            self.sync_all()
    238+            assert_equal(self.nodes[0].getchaintips()[0], self.nodes[1].getchaintips()[0])
    


    Sjors commented at 9:19 am on October 31, 2025:
    If you apply the patch in the description and also drop the sync_all call, you’ll see that the second node sees the block as headers-only.
  26. Sjors commented at 9:23 am on October 31, 2025: member
  27. doc: clarify UpdateUncommittedBlockStructures 00d1b6ef4b
  28. mining: ensure witness commitment check in submitBlock
    When an IPC client requests a new block template via the Mining interface,
    we hold on to its CBlock. That way when they call submitSolution() we can
    modify it in place, rather than having to reconstruct the full block like
    the submitblock RPC does.
    
    Before this commit however we forgot to invalidate
    m_checked_witness_commitment, which we should since the client brings a
    new coinbase.
    
    This would cause us to accept an invalid chaintip.
    
    Fix this and add a test to confirm that we now reject such a block.
    As a sanity check, we add a second node to the test and confirm that will
    accept our mined block.
    
    Note that the IPC code takes the coinbase as provided, unlike the
    submitblock RPC which calls UpdateUncommittedBlockStructures() and adds
    witness commitment to the coinbase if it was missing.
    
    Although that could have been an alternative fix, we instead document that
    IPC clients are expected to provide the full coinbase including witness
    commitment.
    862bd43283
  29. Sjors force-pushed on Oct 31, 2025
  30. Sjors commented at 10:59 am on October 31, 2025: member
    I split the template block mutation test into a third commit and documented its history in the commit message.
  31. Sjors force-pushed on Oct 31, 2025
  32. test: clarify submitBlock() mutates the template
    PR #33374 proposed a new Mining IPC method applySolution() which
    could be used by clients to obtain the reconstructed block for
    inspection, especially in the case of a rejected block.
    
    However it was pointed out during review that submitBlock() modified
    the template CBlock in place, so the client can just call getBlock()
    and no new method is needed.
    
    This commit adds a test to document that (now intentional) behavior.
    6eaa00fe20
  33. Sjors force-pushed on Oct 31, 2025
  34. Sjors commented at 4:10 pm on October 31, 2025: member

    I wrote:

    The Template Provider I implemented takes all sorts of stuff from the coinbase in order to produce its NewTemplate message

    This is a continuing source of confusion, see e.g. https://github.com/Sjors/sv2-tp/pull/55#pullrequestreview-3404172536

    Orthogonal to this PR, but I plan to add some getters to the interface later.

  35. ryanofsky commented at 6:18 pm on October 31, 2025: contributor

    Code review ACK 6eaa00fe20206baedc0d8ade5bb8d066ea615704. Just documentation updates and test clarifications since last review, also splitting up a commit.


    re: #33745 (comment)

    and I think BlockTemplate accessors could be added so miners do not need to make hardcoded assumptions about the coinbase transaction? (#33745 (comment)).

    The Template Provider I implemented takes all sorts of stuff from the coinbase […]

    Thanks, those links to your implementation were really helpful. I didn’t realize all of the required information for a possible protocol upgrade is actually available from getCoinbaseTx() and getWitnessCommitmentIndex() and not that hard to access. It’s really the SV2 protocol that needs to change to be more future proof, not the mining interface.

    This could be refactored to use more accessors. Though I’d be worried about having lots of back and forth IPC messages.

    Yeah I wouldn’t suggest adding lots of accessors just for convenience. If there are maybe a handful of fields that would be useful to return, could have a single getCoinbaseInfo() or similar method. But seems not very important.

    It would also be nice to return error messages and not just the false return value.

    Indeed, though this requires a refactor of ProcessNewBlock.

    Yes, and definitely tangential to this PR, but it does seem like this change wouldn’t be a big refactor since there is already a BlockValidationState variable that can be returned.

    • documentation could be updated to say what requirements on submitSolution inputs are

    Not sure what to write there, basically whatever consensus requires. Previously (external) mining software would generate the whole coinbase from scratch and we never really documented that.

    I guess the following comment would be overkill, but honestly I find this issue pretty confusing and think if we are going to be in an awkward state where external software and protocols are making hardcoded assumptions about the node it could be useful to have a description of what’s required and why.

    I asked chatgpt to write a comment describing requirements fully and maybe this is just useful for me personally, but to me it covers the information that seems useful to know:

     0/**
     1 * Construct a block from this template using the supplied header fields and
     2 * coinbase transaction, then submit it for processing/broadcast.
     3 *
     4 * SegWit / coinbase requirements:
     5 *   - When a witness commitment is expected for this template
     6 *     (i.e. getWitnessCommitmentIndex() != -1), the caller MUST supply a
     7 *     coinbase that is consistent with the template’s commitment semantics:
     8 *
     9 *       • Coinbase OUTPUT at the commitment index with script
    10 *         OP_RETURN 0xaa21a9ed <32-byte commitment>.
    11 *
    12 *       • Coinbase INPUT witness stack that contains a single 32-byte
    13 *         “witness-reserved value” R at vin[0].scriptWitness.stack[0].
    14 *
    15 *       • The 32-byte commitment MUST equal:
    16 *            SHA256d( WitnessMerkleRoot(block) || R )
    17 *
    18 *     IMPORTANT (future-proofing): The value R is consensus-opaque. Its
    19 *     contents and required construction MAY change via soft-fork in the
    20 *     future. Clients MUST treat R as a node-provided value and MUST NOT
    21 *     invent or substitute their own, unless the template explicitly
    22 *     authorizes client-chosen R.
    23 *
    24 * Provided helpers on BlockTemplate:
    25 *   - getCoinbaseTx()               : Base coinbase transaction for this template
    26 *                                     satisfying the requirements.
    27 *   - getWitnessCommitmentIndex()   : vout index of the witness-commitment OP_RETURN
    28 *                                     or -1 if no commitment is expected.
    29 *   - getCoinbaseCommitment()       : Serialized OP_RETURN script (0xaa21a9ed || 32B payload)
    30 *                                     to place at the commitment index.
    31 *
    32 * No auto-fixups:
    33 *   - This IPC method DOES NOT call UpdateUncommittedBlockStructures() and
    34 *     DOES NOT modify/mint R or the commitment on your behalf. If the
    35 *     coinbase is missing/malformed/inconsistent or R does not match the
    36 *     template’s requirements, the call SHOULD fail with a descriptive error.
    37 *
    38 * Mutation:
    39 *   - This call mutates the underlying template’s block (coinbase, merkle
    40 *     root, header fields). Subsequent getBlockHeader()/getBlock()/
    41 *     getCoinbaseTx() reflect the updated state.
    42 *
    43 * Errors (recommended set):
    44 *   - MissingWitnessCommitmentOutput
    45 *   - MissingCoinbaseWitnessReservedValue
    46 *   - InvalidWitnessReservedValueSize
    47 *   - WitnessCommitmentMismatch
    48 *   - ReservedValueNotAuthorized   // client supplied R not matching template
    49 *   - TemplateStale
    50 *
    51 * Parameters:
    52 * [@param](/bitcoin-bitcoin/contributor/param/) version   Block header nVersion.
    53 * [@param](/bitcoin-bitcoin/contributor/param/) timestamp Block header nTime (UNIX epoch).
    54 * [@param](/bitcoin-bitcoin/contributor/param/) nonce     Block header nNonce (32-bit).
    55 * [@param](/bitcoin-bitcoin/contributor/param/) coinbase  COMPLETE coinbase tx (including witness if required).
    56 *
    57 * [@returns](/bitcoin-bitcoin/contributor/returns/) true if enqueued for processing (independent of final validity).
    58 */
    59virtual bool submitSolution(uint32_t version,
    60                            uint32_t timestamp,
    61                            uint32_t nonce,
    62                            CTransactionRef coinbase) = 0;
    
  36. DrahtBot requested review from TheCharlatan on Oct 31, 2025
  37. ismaelsadeeq commented at 6:23 pm on October 31, 2025: member
    Concept ACK, will review.

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-11-02 18:12 UTC

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