mining: getCoinbase() returns struct instead of raw tx #33819

pull Sjors wants to merge 2 commits into bitcoin:master from Sjors:2025/11/coinbase-template changing 9 files +237 −11
  1. Sjors commented at 2:30 pm on November 7, 2025: member

    The first commit renames getCoinbaseTx() to getCoinbaseRawTx() to reflect that it returns a serialised transaction. This does not impact IPC clients, because they use the function name.

    The second commit then introduces a replacement getCoinbase() that provides a struct with everything clients need to construct a coinbase. This avoids clients having to parse and manipulate our dummy transaction.

    Deprecate but don’t remove getCoinbaseRawTx(), getCoinbaseCommitment() and getWitnessCommitmentIndex().

    After this change we can drop these deprecated methods, which in turn would allow us to clear the dummy transaction from the getBlock() result. But that is left for a followup to keep this PR focussed. See https://github.com/Sjors/bitcoin/pull/106 for an approach.

    Expand the interface_ipc.py functional test to document its usage.

    Can be tested using:

  2. DrahtBot added the label Mining on Nov 7, 2025
  3. DrahtBot commented at 2:30 pm on November 7, 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/33819.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky
    Concept ACK optout21
    Approach ACK ajtowns

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34003 (test: interface_ipc.py minor fixes and cleanup by ryanofsky)
    • #33966 (refactor: disentangle miner startup defaults from runtime options by Sjors)
    • #33965 (mining: fix -blockreservedweight shadows IPC option by Sjors)
    • #33922 (mining: add getMemoryLoad() and track template non-mempool memory footprint by Sjors)
    • #33795 (test: Ignore error message give from python because of PYTHON_GIL by kevkevinpal)
    • #32420 (miner: drop dummy extraNonce in coinbase scriptSig for templates requested via IPC by Sjors)
    • #29641 (scripted-diff: Use LogInfo over LogPrintf by maflcko)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. Sjors commented at 2:31 pm on November 7, 2025: member
    Working on a matching sv2-tp pull request. It will first try getCoinbase() and if that fails it falls back to getCoinbaseTx().
  5. in src/interfaces/mining.h:45 in 79e73b0947 outdated
    41@@ -42,8 +42,26 @@ class BlockTemplate
    42     // Sigop cost per transaction, not including coinbase transaction.
    43     virtual std::vector<int64_t> getTxSigops() = 0;
    44 
    45+    /** Return fields needed to construct a coinbase transaction */
    


    ismaelsadeeq commented at 2:58 pm on November 7, 2025:

    Given sv2-tp and a few others are the users of this interface and they are still in development.

    I’d rather have any un-useful methods pruned. When we release a stable interface then we can start deprecating things to avoid breaking clients (As you mentioned in the tracking issue it is okay to make breaking changes so all these methods should be pruned and clients should update).


    Sjors commented at 3:16 pm on November 7, 2025:

    There’s a note in the tracking issue #33777 that we can drop these deprecated methods as soon as there’s an important breaking change.

    Even for testing it’s nice that current clients work against both v30 and master.


    ismaelsadeeq commented at 3:34 pm on November 7, 2025:

    Hmm, do you plan on deprecating it first and then removing it in a later release? On the contrary, I think it should be dropped as soon as we deem it unnecessary. Otherwise, we’ll end up with interface methods that conflict with each other. It would also be easier to review the changes together, rather than keeping them all here with deprecation comments and later deleting them it will save us review cycle.

    Even for testing it’s nice that current clients works against both v30 and master.

    I think this is not big of deal, since you have a release that is against v30, your master should be in sync with master which can easily update when breaking change occur.


    Sjors commented at 3:52 pm on November 7, 2025:

    do you plan on deprecating it first and then removing it in a later release?

    No, because the interface is marked experimental I think it’s fine to drop it in any release we want. That might be as soon as v31, but we can also do it later.

    your master should be in sync with master

    sv2-tp is used by people who only run v30, so it has to support both master and v30.

    It can already handle new methods by just trying them and falling back to old methods when they’re missing.

    Recent versions of sv2-tp all work against v30, so I haven’t had to push people to switch to a newer version either. But we start making breaking changes, then I have to start instructing users of Bitcoin Core master to use specific newer versions of sv2-tp.


    ismaelsadeeq commented at 4:09 pm on November 7, 2025:
    Thanks for the context. I still think it’s fine to do it together. You can have a major release of sv2-tp that works with v30 and minor releases that includes additional improvements to that the major releases that is compatible with v30. sv2-tp master should just be in sync with bitcoin/bitcoin master people that want to run that should build master from source.

    Sjors commented at 4:50 pm on November 7, 2025:
    In the longer run it probably makes sense for sv2-tp to have one major release of sv2-tp for every major Bitcoin Core release, but current it doesn’t have backports. Each release is a combination of bug fixes and new features.

    ismaelsadeeq commented at 5:14 pm on November 7, 2025:

    In the longer run it probably makes sense for sv2-tp to have one major release of sv2-tp for every major Bitcoin Core release, but current it doesn’t have backports. Each release is a combination of bug fixes and new features.

    Hmm, fwiw I am approach NACK changes that introduce conflicting interface method that deprecate and don’t prune on experimental interface that did not offer interface stability for the reason discussed above. However I am open minded to convincing otherwise and curious to know what other contributors think.


    Sjors commented at 5:21 pm on November 7, 2025:

    The old methods are redundant, but they’re not conflicting. Clients can use either and both have test coverage.

    From this projects perspective there’s hardly any additional review burden when we prune deprecated methods in a later PR, that should be trivial.


    Sjors commented at 6:09 pm on November 7, 2025:

    To sum up what needs to happen if we delete instead of deprecate these old methods:

    1. sv2-tp needs a new release that includes https://github.com/stratum-mining/sv2-tp/pull/59 (no problem, will happen anyway)
    2. the SRI TP (draft) https://github.com/stratum-mining/sv2-apps/pull/59 needs to be updated to support both the old and new method (like sv2-tp does), sometime before v31 comes out (support for the old method can be dropped after v31 comes out, at the cost of dropping support for v30). a) https://github.com/2140-dev/bitcoin-capnp-types needs to track 30.x and master
    3. the instructions at https://stratumprotocol.org/developers#run-template-provider need to be updated to specify a minimum template provider version (since it supports “Bitcoin Core v30 or newer”)

    Things that should be unaffected:

    1. CI for https://github.com/stratum-mining/sv2-apps pins Bitcoin Core to a release (e.g. v30) so is not impacted by changes to master. As long as they bump the template provider version when bumping Bitcoin Core to v31 nothing should break.
    2. Miners who patch Bitcoin Core (e.g. for merge mining) and stick to the v30.x branch
    3. Any sv2 tutorial or Docker automation that pins Bitcoin Core to v30 (rather than master)

    None of these things are hard, but I prefer to avoid the coordination overhead and reserve that for a more important change.


    Sjors commented at 1:51 pm on November 11, 2025:

    Let’s also wait and see what comes out of #33421 (review) because if I have to modify the signature of submitSolution() (to return a CBlock), that’s the kind of breaking change I think is worth making.

    Update: no need to change submitSolution()


    ryanofsky commented at 5:39 pm on November 12, 2025:

    re: #33819 (review)

    I tend to agree with Sjors that keeping deprecated methods around for a release should not add much burden, since any followup removing the methods should be easy to review. And the alternative just deleting the methods would seem to impose real costs on developers of mining clients and on users: Users might be forced to update two pieces of software at the same time instead of one, and mining client developers would be under more time pressure to make changes and might have to resort to additional complexity like maintaining multiple branches of their software to be compatible with different bitcoin core versions, or needing to customize the mining.capnp file.

    I do want to a suggest a stronger way deprecating the methods though: They could be renamed to break source compatibility but keep binary compatibility. This would force mining interface clients to update their source code, but still allow them to call the deprecated methods, and still keep binary compatibility so existing client software would continue to work with newer bitcoin core releases.

    This would require a change in the capnp file like:

     0--- a/src/ipc/capnp/mining.capnp
     1+++ b/src/ipc/capnp/mining.capnp
     2@@ -28,13 +28,15 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
     3     getTxFees [@3](/bitcoin-bitcoin/contributor/3/) (context: Proxy.Context) -> (result: List(Int64));
     4     getTxSigops [@4](/bitcoin-bitcoin/contributor/4/) (context: Proxy.Context) -> (result: List(Int64));
     5     getCoinbase [@12](/bitcoin-bitcoin/contributor/12/) (context: Proxy.Context) -> (result: CoinbaseTemplate);
     6-    getCoinbaseTx [@5](/bitcoin-bitcoin/contributor/5/) (context: Proxy.Context) -> (result: Data);
     7-    getCoinbaseCommitment [@6](/bitcoin-bitcoin/contributor/6/) (context: Proxy.Context) -> (result: Data);
     8-    getWitnessCommitmentIndex [@7](/bitcoin-bitcoin/contributor/7/) (context: Proxy.Context) -> (result: Int32);
     9     getCoinbaseMerklePath [@8](/bitcoin-bitcoin/contributor/8/) (context: Proxy.Context) -> (result: List(Data));
    10     submitSolution [@9](/bitcoin-bitcoin/contributor/9/) (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
    11     waitNext [@10](/bitcoin-bitcoin/contributor/10/) (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
    12     interruptWait [@11](/bitcoin-bitcoin/contributor/11/)() -> ();
    13+
    14+    # Deprecated methods, use getCoinBase() instead!
    15+    deprecatedGetCoinbaseTx [@5](/bitcoin-bitcoin/contributor/5/) (context: Proxy.Context) -> (result: Data);
    16+    deprecatedGetCoinbaseCommitment [@6](/bitcoin-bitcoin/contributor/6/) (context: Proxy.Context) -> (result: Data);
    17+    deprecatedGetWitnessCommitmentIndex [@7](/bitcoin-bitcoin/contributor/7/) (context: Proxy.Context) -> (result: Int32);
    18 }
    19 
    20 struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
    

    And the corresponding c++ methods (and python calls) would need to be renamed to match.

  6. Sjors force-pushed on Nov 7, 2025
  7. Sjors commented at 4:55 pm on November 7, 2025: member

    It’s ready for testing with:

    I also pushed a small change that switches ExtractCoinbaseTemplate to take CTransactionRef coinbase_tx as its argument rather than a CBlockTemplate. It doesn’t really make a difference here, but lets me copy-paste it to sv2-tp which doesn’t have CBlockTemplate.

  8. Sjors commented at 6:11 pm on November 7, 2025: member
    @plebhash can you try to see if it’s easy for you to support both getCoinbase() and getCoinbaseTx() in https://github.com/stratum-mining/sv2-apps/pull/59, preferring the first and falling back to the latter if it doesn’t exist?
  9. in test/functional/interface_ipc.py:226 in 0dfe602281
    223             coinbase.vout[0].scriptPubKey = miniwallet.get_output_script()
    224+            coinbase.vout[0].nValue = coinbase_template.valueRemaining
    225+            # Add SegWit OP_RETURN. This is currently always present even for
    226+            # empty blocks, but this may change.
    227+            found_witness_op_return = False
    228+            print(WITNESS_COMMITMENT_HEADER.hex())
    


    optout21 commented at 1:08 pm on November 11, 2025:
    Could you add a short text before the value to the printout? Or if this is a debug-leftover, remove it.

    Sjors commented at 1:21 pm on November 11, 2025:
    Sorry that’s leftover debugging code.
  10. in test/functional/interface_ipc.py:202 in 0dfe602281
    197@@ -188,10 +198,56 @@ async def async_routine():
    198             check_opts = self.capnp_modules['mining'].BlockCheckOptions()
    199             template = await mining.result.createNewBlock(opts)
    200             block = await self.parse_and_deserialize_block(template, ctx)
    201-            coinbase = await self.parse_and_deserialize_coinbase_tx(template, ctx)
    202-            balance = miniwallet.get_balance()
    203+
    204+            self.log.debug("Build coinbase transaction using getCoinbase()")
    


    optout21 commented at 1:11 pm on November 11, 2025:
    Maybe extract this part into a separate method; that can also serve as an example on how to build the cb tx from the getCoinbase() result, and leave the flow of this test more readable

    Sjors commented at 1:38 pm on November 11, 2025:
    Done
  11. optout21 commented at 1:16 pm on November 11, 2025: none
    There is no direct C++ unit test for getCoinbase/ExtractCoinbaseTemplate, would it make sense to add one? Or is the interface test sufficient (test/functional/interface_ipc.py)?
  12. in test/functional/interface_ipc.py:237 in 0dfe602281
    234+                coinbase.vout.append(output)
    235+                if output.scriptPubKey == coinbase_commitment:
    236+                    found_witness_op_return = True
    237+
    238+            assert_equal(has_witness, found_witness_op_return)
    239+
    


    optout21 commented at 1:34 pm on November 11, 2025:
    In case of has_witness, coinbase_commitment and coinbase_template.witness could be directly compared here.

    Sjors commented at 1:39 pm on November 11, 2025:
    I think the comparison below should be enough, and I want to make it as simple as possible to later drop the deprecated method.

    optout21 commented at 1:52 pm on November 11, 2025:
    My reasoning was to add extra assurance that the deprecated and new method gives the same result, it’s a simple test. But it’s fine as it is.
  13. Sjors force-pushed on Nov 11, 2025
  14. in src/node/interfaces.cpp:901 in 0dfe602281


    optout21 commented at 1:39 pm on November 11, 2025:
    As a progression towards getCoinbase & deprecation, this implementation could invoke ExtractCoinbaseTemplate, and return the witness field (after uint256 -> unsigned char array type conversion)

    Sjors commented at 1:44 pm on November 11, 2025:
    Refactoring these deprecated methods would add to the review burden. And there’s still ongoing discussion above about whether this PR should even keep them

    optout21 commented at 1:47 pm on November 11, 2025:
    As a progression towards getCoinbase & deprecation, this implementation could internally also do what is suggested in the deprecation node: invoke ExtractCoinbaseTemplate, scan outputs for the SegWit marker, and return the index.

    ryanofsky commented at 2:47 pm on December 2, 2025:

    re: #33819 (review)

    As a progression towards getCoinbase & deprecation, this implementation could internally also do what is suggested in the deprecation node: invoke ExtractCoinbaseTemplate, scan outputs for the SegWit marker, and return the index.

    I don’t see a clean way to get the index from ExtractCoinbaseTemplate, but even if there were, calling GetWitnessCommitmentIndex seems like the simplest implementation of this method. Changing this might make more sense if the GetWitnessCommitmentIndex function was only called this one place and could be eliminated, but this isn’t the case.


    Sjors commented at 12:41 pm on December 3, 2025:
    The latest push dropped ExtractCoinbaseTemplate entirely.
  15. Sjors commented at 1:39 pm on November 11, 2025: member

    There is no direct C++ unit test

    No strong opinion on whether we need one.

  16. in src/interfaces/mining.h:56 in 0dfe602281 outdated
    51+     **/
    52     virtual CTransactionRef getCoinbaseTx() = 0;
    53+    /**
    54+     * Return scriptPubKey with SegWit OP_RETURN.
    55+     *
    56+     * @note deprecated: use the output field from getCoinbase()
    


    Sjors commented at 1:42 pm on November 11, 2025:

    getCoinbase() returns a CoinbaseTemplate struct which has an outputs field, which itself is an array with zero or more elements.

    (I’ll rename output to outputs)


    optout21 commented at 1:42 pm on November 11, 2025:
    0     * [@note](/bitcoin-bitcoin/contributor/note/) deprecated: use the outputs field from getCoinbase()
    

    optout21 commented at 1:43 pm on November 11, 2025:
    The witness field could also be used, isn’t it?

    Sjors commented at 1:48 pm on November 11, 2025:
    This returns the full scriptPubKey as generated by GenerateCoinbaseCommitment. Although you can derive that from the witness field, I don’t think one should.
  17. in src/interfaces/mining.h:62 in 0dfe602281 outdated
    57+     */
    58     virtual std::vector<unsigned char> getCoinbaseCommitment() = 0;
    59+    /**
    60+     * Return which output in the dummy coinbase contains the SegWit OP_RETURN.
    61+     *
    62+     * @note deprecated. Scan outputs from getCoinbase() output field for the
    


    optout21 commented at 1:44 pm on November 11, 2025:
    0     * [@note](/bitcoin-bitcoin/contributor/note/) deprecated. Scan outputs from getCoinbase() outputs field for the
    
  18. optout21 commented at 1:48 pm on November 11, 2025: none

    Concept ACK (d926d31603316a10e3210f10480dd02bb34f667a)

    The added new method makes sense. The deprecating methods are only being marked in the header comments, they are kept, there is no formal plan for deprecation. But there is a fair plan for switching users, and keeping them is not an extra burden.

  19. Sjors force-pushed on Nov 11, 2025
  20. DrahtBot added the label CI failed on Nov 11, 2025
  21. DrahtBot commented at 1:50 pm on November 11, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/actions/runs/19267405324/job/55086515663 LLM reason (✨ experimental): Python lint failure (ruff): unused import detected in test/interface_ipc.py causing CI to fail.

    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.

  22. Sjors force-pushed on Nov 11, 2025
  23. Sjors force-pushed on Nov 11, 2025
  24. Sjors force-pushed on Nov 11, 2025
  25. Sjors commented at 2:51 pm on November 11, 2025: member
    CI failed because it runs against master and #33575 has been merged, which added interruptWait at position @11. I rebased and bumped getCoinbase() to @12.
  26. fanquake commented at 4:18 pm on November 11, 2025: member

    https://github.com/bitcoin/bitcoin/actions/runs/19269384275/job/55093373232?pr=33819#step:7:3549:

    0 Error:  node0 2025-11-11T15:38:48.659039Z [unknown] [validation.cpp:4535] [bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock> &, bool, bool, bool *)] [error] ProcessNewBlock: AcceptBlock FAILED (bad-version(0x00000000), rejected nVersion=0x00000000 block) 
    
  27. Sjors commented at 4:45 pm on November 11, 2025: member

    Odd, the invalid-version error is happening at the bottom of this block:

     0self.log.debug("Submit a block with a bad version")
     1block.nVersion = 0
     2block.solve()
     3res = await mining.result.checkBlock(block.serialize(), check_opts)
     4assert_equal(res.result, False)
     5assert_equal(res.reason, "bad-version(0x00000000)")
     6res = await template.result.submitSolution(ctx, block.nVersion, block.nTime, block.nNonce, coinbase.serialize())
     7assert_equal(res.result, False)
     8self.log.debug("Submit a valid block")
     9block.nVersion = original_version
    10block.solve()
    11res = await mining.result.checkBlock(block.serialize(), check_opts)
    12assert_equal(res.result, True)
    

    It’s about the block nVersion, not the coinbase, so this PR shouldn’t have changed that.


    Actually FAILED (bad-version(0x00000000) is not the reason the test fails, it’s an expected error log message emitted by the node.

    The actual error is a little further down: bad-cb-height, which seems more likely to be related to this PR.

  28. Sjors force-pushed on Nov 11, 2025
  29. Sjors commented at 6:02 pm on November 11, 2025: member

    It turns out that using the getCoinbase() result directly in the Python test led to memory management related errors. Parts of the coinbase were dropped by the time of the second checkBlock().

    This probably happened once I extracted the coinbase specific checks into build_coinbase_test, which returns the coinbase CTransaction but presumably let go of the getCoinbase() result.

    Rather than debug (our use of) PyCapnp, I just introduced a @dataclass for CoinbaseTemplateData, so we fully own (everything in) it.

  30. plebhash commented at 6:56 pm on November 11, 2025: none

    @plebhash can you try to see if it’s easy for you to support both getCoinbase() and getCoinbaseTx() in stratum-mining/sv2-apps#59, preferring the first and falling back to the latter if it doesn’t exist? @Sjors we’re not using getCoinbaseTx on that code

    we do getBlock and that provides everything we need, including relevant coinbase info

  31. DrahtBot removed the label CI failed on Nov 11, 2025
  32. Sjors commented at 8:23 am on November 12, 2025: member

    we do getBlock and that provides everything we need

    Ok, that’s not really how you’re supposed to use it :-)

    It does make a breaking change potentially unpainful.

    However there’s another gotcha if we remove a method from the interface: there’s no gaps allowed in the .capnp method numbering. This could be worked around as follows:

     0interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
     1    destroy [@0](/bitcoin-bitcoin/contributor/0/) (context :Proxy.Context) -> ();
     2    getBlockHeader [@1](/bitcoin-bitcoin/contributor/1/) (context: Proxy.Context) -> (result: Data);
     3    getBlock [@2](/bitcoin-bitcoin/contributor/2/) (context: Proxy.Context) -> (result: Data);
     4    getTxFees [@3](/bitcoin-bitcoin/contributor/3/) (context: Proxy.Context) -> (result: List(Int64));
     5    getTxSigops [@4](/bitcoin-bitcoin/contributor/4/) (context: Proxy.Context) -> (result: List(Int64));
     6    getCoinbase [@5](/bitcoin-bitcoin/contributor/5/) (context: Proxy.Context) -> (result: CoinbaseTemplate);
     7    # getCoinbaseTx [@5](/bitcoin-bitcoin/contributor/5/) (context: Proxy.Context) -> (result: Data);
     8    # getCoinbaseCommitment [@6](/bitcoin-bitcoin/contributor/6/) (context: Proxy.Context) -> (result: Data);
     9    getWitnessCommitmentIndex [@7](/bitcoin-bitcoin/contributor/7/) (context: Proxy.Context) -> (result: Int32);
    10    getCoinbaseMerklePath [@8](/bitcoin-bitcoin/contributor/8/) (context: Proxy.Context) -> (result: List(Data));
    11    submitSolution [@9](/bitcoin-bitcoin/contributor/9/) (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
    12    waitNext [@10](/bitcoin-bitcoin/contributor/10/) (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
    13    interruptWait [@6](/bitcoin-bitcoin/contributor/6/)() -> ();
    14}
    

    So @5 is assigned from getCoinbaseTx to getCoinbase, @6 is changed from getCoinbaseCommitment to interruptWait (not in v30 and not yet in an sv2-tp release), but getWitnessCommitmentIndex is kept (but still deprecated and made to error).

    But such an intermediate state seems needlessly confusing, so my first preference is still to not make a breaking change here, my second preference to completely break things, not this intermediate approach.

  33. DrahtBot added the label Needs rebase on Nov 12, 2025
  34. in test/functional/interface_ipc.py:31 in 80acabaf93 outdated
    23 from test_framework.test_framework import (BitcoinTestFramework, assert_equal)
    24 from test_framework.wallet import MiniWallet
    25 
    26+
    27+@dataclass
    28+class CoinbaseTemplateData:
    


    ryanofsky commented at 7:08 pm on November 12, 2025:

    re: #33819 (comment)

    It turns out that using the getCoinbase() result directly in the Python test led to memory management related errors. Parts of the coinbase were dropped by the time of the second checkBlock().

    This probably happened once I extracted the coinbase specific checks into build_coinbase_test, which returns the coinbase CTransaction but presumably let go of the getCoinbase() result.

    Rather than debug (our use of) PyCapnp, I just introduced a @dataclass for CoinbaseTemplateData, so we fully own (everything in) it.

    I looked into the diff and CI failure described here and found this change pretty confusing because I couldn’t see how the dataclass would make any difference. Introducing a dataclass would seem to just move around the invalid references without changing them.

    The actual fix seems to be in the parse_and_deserialize_coinbase function which adds bytes casts. The bytes casts do nothing on my system which uses pycapnp-2.0.0, but in the CI job which uses pycapnp-2.2.1, it looks like casts have the effect of converting memoryview objects into bytes objects (https://github.com/capnproto/pycapnp/pull/380), fixing the crash that happens in CI.

    I feel like the way the test works isn’t very clear from looking at the code, and that the dataclass adds extra complexity. So a change like following could make sense:

     0--- a/test/functional/interface_ipc.py
     1+++ b/test/functional/interface_ipc.py
     2@@ -4,7 +4,6 @@
     3 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
     4 """Test the IPC (multiprocess) interface."""
     5 import asyncio
     6-from dataclasses import dataclass
     7 from io import BytesIO
     8 from pathlib import Path
     9 import shutil
    10@@ -23,16 +22,6 @@ from test_framework.test_framework import (BitcoinTestFramework, assert_equal)
    11 from test_framework.wallet import MiniWallet
    12 
    13 
    14-@dataclass
    15-class CoinbaseTemplateData:
    16-    version: int
    17-    inputSequence: int
    18-    scriptSigPrefix: bytes
    19-    witness: Optional[bytes]
    20-    valueRemaining: int
    21-    outputs: list[bytes]
    22-    lockTime: int
    23-
    24 # Test may be skipped and not have capnp installed
    25 try:
    26     import capnp  # type: ignore[import] # noqa: F401
    27@@ -106,21 +95,6 @@ class IPCInterfaceTest(BitcoinTestFramework):
    28         tx.deserialize(coinbase_data)
    29         return tx
    30 
    31-    async def parse_and_deserialize_coinbase(self, block_template, ctx) -> CoinbaseTemplateData:
    32-        template_capnp = (await block_template.result.getCoinbase(ctx)).result
    33-        witness: Optional[bytes] = None
    34-        if template_capnp._has("witness"):
    35-            witness = bytes(template_capnp.witness)
    36-        return CoinbaseTemplateData(
    37-            version=int(template_capnp.version),
    38-            inputSequence=int(template_capnp.inputSequence),
    39-            scriptSigPrefix=bytes(template_capnp.scriptSigPrefix),
    40-            witness=witness,
    41-            valueRemaining=int(template_capnp.valueRemaining),
    42-            outputs=[bytes(output) for output in template_capnp.outputs],
    43-            lockTime=int(template_capnp.lockTime),
    44-        )
    45-
    46     def run_echo_test(self):
    47         self.log.info("Running echo test")
    48         async def async_routine():
    49@@ -137,21 +111,25 @@ class IPCInterfaceTest(BitcoinTestFramework):
    50 
    51     async def build_coinbase_test(self, template, ctx, miniwallet):
    52         self.log.debug("Build coinbase transaction using getCoinbase()")
    53-        coinbase_template = await self.parse_and_deserialize_coinbase(template, ctx)
    54+        # Note: the coinbase_template struct will be garbage-collected when this
    55+        # method returns, so it is important to copy any Data fields from it
    56+        # which need to be accessed later using the bytes() cast. Starting with
    57+        # pycapnp v2.2.0, Data fields have type `memoryview` and are ephemeral.
    58+        coinbase_template = (await template.result.getCoinbase(ctx)).result
    59         coinbase = CTransaction()
    60         coinbase.version = coinbase_template.version
    61         coinbase.vin = [CTxIn()]
    62         coinbase.vin[0].prevout = NULL_OUTPOINT
    63         coinbase.vin[0].nSequence = coinbase_template.inputSequence
    64         # Typically a mining pool appends its name and an extraNonce
    65-        coinbase.vin[0].scriptSig = coinbase_template.scriptSigPrefix
    66+        coinbase.vin[0].scriptSig = bytes(coinbase_template.scriptSigPrefix) # copy data with bytes()
    67 
    68         # We currently always provide a coinbase witness, even for empty
    69         # blocks, but this may change, so always check:
    70-        has_witness = coinbase_template.witness is not None
    71+        has_witness = coinbase_template._has("witness")
    72         if has_witness:
    73             coinbase.wit.vtxinwit = [CTxInWitness()]
    74-            coinbase.wit.vtxinwit[0].scriptWitness.stack = [coinbase_template.witness]
    75+            coinbase.wit.vtxinwit[0].scriptWitness.stack = [bytes(coinbase_template.witness)] # copy data with bytes()
    76 
    77         # First output is our payout
    78         coinbase.vout = [CTxOut()]
    

    Alternately, if the current approach is preferred, it would at least be good to add some comments explaining the bytes() calls.


    Sjors commented at 10:37 am on November 17, 2025:

    I think it’s good to have parse_and_deserialize_coinbase be safe to use, because we might eventually move it into the test framework as IPC coverage increases. I’ll add some documentation.

    Having CoinbaseTemplateData as a @dataclass also gives us type checking.

  35. in src/node/miner.cpp:605 in 80acabaf93 outdated
    590+node::CoinbaseTemplate ExtractCoinbaseTemplate(const CTransactionRef coinbase_tx)
    591+{
    592+    node::CoinbaseTemplate coinbase;
    593+
    594+    coinbase.version = coinbase_tx->CURRENT_VERSION;
    595+    coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig;
    


    ryanofsky commented at 7:14 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    Might be good to assert that vin[0] exists

  36. in src/node/miner.cpp:599 in 80acabaf93
    594+    coinbase.version = coinbase_tx->CURRENT_VERSION;
    595+    coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig;
    596+    // The CoinbaseTemplate interface guarantees a size limit. Raising it (e.g.
    597+    // if a future softfork needs to commit more than BIP34) is a
    598+    // breaking change for clients.
    599+    Assume(coinbase.script_sig_prefix.size() <= 8);
    


    ryanofsky commented at 7:25 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    It seems like if this change could break clients, it might be better to return an error than a template that violates their expectations. Maybe would suggest:

    0if (!Assume(coinbase.script_sig_prefix.size() <= 8)) {
    1  throw std::logic_error("Internal bug: coinbase data is inconsistent with CoinbaseTemplate definition. Data may be corrupt or definition may need to be updated");
    2}
    

    Or could replace Assume with Assert, or log a warning. it just doesn’t seem good to ignore the condition completely if it happens.


    Sjors commented at 10:55 am on November 17, 2025:

    I don’t think miners use the full 100 bytes of scriptSig space, so if we slightly go over it, it shouldn’t immediately result in invalid blocks. So it seems overkill to abort the template generation entirely.

    I’ll log a warning.

    The main purpose of this Assume is to remind ourselves to inform the mining ecosystem if we ever expand the scriptSig prefix substantially.


    Sjors commented at 11:04 am on November 17, 2025:

    I also clarified the comment that it might be a silently breaking change. Although the spec requires NewTemplate to keep it at max 8 bytes, the serialisation supports up to 256 and a Template Provider implementation might just not check / enforce this.

    https://stratumprotocol.org/specification/07-Template-Distribution-Protocol/#72-newtemplate-server-client

  37. in src/node/miner.cpp:605 in 80acabaf93
    600+
    601+    if (coinbase_tx->HasWitness()) {
    602+        const auto& witness_stack{coinbase_tx->vin[0].scriptWitness.stack};
    603+        // Consensus requires the coinbase witness stack to have exactly one
    604+        // element of 32 bytes.
    605+        Assert(witness_stack.size() == 1 || witness_stack[0].size() == 32);
    


    ryanofsky commented at 7:26 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    Looks like || should be &&. Alternately could use two Assert statements.

  38. in src/node/miner.cpp:613 in 80acabaf93
    608+
    609+    coinbase.input_sequence = coinbase_tx->vin[0].nSequence;
    610+
    611+    // The first dummy coinbase output produced by CreateBlock() has an nValue
    612+    // set to nFee + the Block Subsidy
    613+    coinbase.value_remaining = coinbase_tx->vout[0].nValue;
    


    ryanofsky commented at 7:30 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    Would seem good to assert vout[0] exists. Or maybe drop this line and unify with the loop below to make the relationship between output and value_remaining fields more direct.

    0    for (const auto& output : coinbase_tx->vout) {
    1        if (!output.scriptPubKey.empty() && output.scriptPubKey[0] == OP_RETURN) {
    2            coinbase.outputs.push_back(output);
    3        } else {
    4            coinbase.value_remaining += output.nValue:
    5        }
    6    }
    

    Sjors commented at 11:10 am on November 17, 2025:
    I’ll go for the latter approach.
  39. in src/node/miner.h:269 in 80acabaf93 outdated
    264+ * Extract relevant fields from the dummy coinbase transaction in the template.
    265+ *
    266+ * Extracts only OP_RETURN coinbase outputs, i.e. the witness commitment. If
    267+ * BlockAssembler::CreateNewBlock() is patched to add more OP_RETURN outputs
    268+ * e.g. for merge mining, those will be included. The dummy output that spends
    269+ * the full reward is excluded.
    


    ryanofsky commented at 7:41 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    I think this comment might would be more helpful if moved to the CoinbaseTemplate::output field since it describes what that field contains. Also it should be visible there since that type is part of the external interface.


    Sjors commented at 11:40 am on November 17, 2025:

    I added documentation there, but we also need it here. The output field is not limited to OP_RETURN, that’s just a heuristic used by ExtractCoinbaseTemplate.

    That heuristic won’t work anymore if we add a way for clients to specify custom outputs when constructing a block, so it’s important to document.


    ryanofsky commented at 2:57 pm on December 2, 2025:

    re: #33819 (review)

    I added documentation there, but we also need it here. The output field is not limited to OP_RETURN, that’s just a heuristic used by ExtractCoinbaseTemplate.

    Seem like with latest update, this comment is no longer accurate and should be removed? I guess still I don’t understand why it would be important to document the outputs field in this function declaration instead of only in the struct definition, but maybe that is moot now.

  40. in src/node/types.h:117 in 80acabaf93
    112+     * to be less than 8 bytes (not including the length byte). This allows
    113+     * clients to add up to 92 bytes.
    114+     */
    115+    CScript script_sig_prefix;
    116+    /**
    117+     * The first (and only) witness stack element of the coinbase.
    


    ryanofsky commented at 8:36 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    Maybe s/coinbase/coinbase input/, would feel a little clearer

  41. in src/node/types.h:127 in 80acabaf93 outdated
    118+     *
    119+     * Omitted for block templates without witness data.
    120+     *
    121+     * This is currently the BIP 141 witness reserved value. A future soft fork
    122+     * may move the witness reserved value elsewhere, but there will still be a
    123+     * coinbase witness.
    


    ryanofsky commented at 8:37 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    I’m a little confused by “may move the witness reserved value” in this comment. I would think a future soft fork could restrict this value but not move it anywhere. BIP 141 says the coinbase witness has to be a single 32-byte so it doesn’t seem like there is more flexibility here.


    Sjors commented at 11:22 am on November 17, 2025:

    The BIP says:

    For backward compatibility, the Hash(new commitment|witness reserved value) will go to the coinbase witness, and the witness reserved value will be recorded in another location specified by the future softfork. Any number of new commitment could be added in this way.

    So we provide clients with the coinbase witness, which is not necessarily the “witness reserved value”.


    ryanofsky commented at 11:14 pm on December 1, 2025:

    re: #33819 (review)

    The BIP says:

    For backward compatibility, the Hash(new commitment|witness reserved value) will go to the coinbase witness, and the witness reserved value will be recorded in another location specified by the future softfork. Any number of new commitment could be added in this way.

    So we provide clients with the coinbase witness, which is not necessarily the “witness reserved value”.

    Wow, I see. So in BIP 141 terminology, instead of assigning meaning to the existing “reserved value” and introducing a new “reserved value”, a future soft fork would be “moving” the reserved value.

    I still find comment confusing, though. Would suggest shortening to something like “This is currently the BIP 141 witness reserved value, and can be chosen arbitrarily by the node, but future soft forks may constrain it.”


    Sjors commented at 12:47 pm on December 3, 2025:
    Taken
  42. in src/node/types.h:109 in 80acabaf93
    101@@ -99,6 +102,35 @@ struct BlockCheckOptions {
    102     bool check_pow{true};
    103 };
    104 
    105+struct CoinbaseTemplate {
    106+    /* nVersion */
    107+    uint32_t version;
    108+    /* nSequence for the only coinbase transaction input */
    109+    uint32_t input_sequence;
    


    ryanofsky commented at 8:47 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    Seems like it would be more consistent to either drop the input_ prefix from this field or add it to script_sig_prefix field. I’d slightly prefer dropping the input_ field so CoinbaseTemplate field names just try to describe what the fields mean, not how the transaction needs to be physically constructed.


    Sjors commented at 11:28 am on November 17, 2025:
    I’ll drop input_
  43. in src/node/types.h:128 in 80acabaf93 outdated
    120+     *
    121+     * This is currently the BIP 141 witness reserved value. A future soft fork
    122+     * may move the witness reserved value elsewhere, but there will still be a
    123+     * coinbase witness.
    124+     */
    125+    std::optional<uint256> witness;
    


    ryanofsky commented at 8:49 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    IMO witness_reserved_value would be a more descriptive name than just witness.


    ryanofsky commented at 11:28 pm on December 1, 2025:

    re: #33819 (review)

    IMO witness_reserved_value would be a more descriptive name than just witness.

    Other response #33819 (review) explains why this name would be inconsistent with terminology of BIP141.

  44. in src/node/types.h:131 in 80acabaf93 outdated
    122+     * may move the witness reserved value elsewhere, but there will still be a
    123+     * coinbase witness.
    124+     */
    125+    std::optional<uint256> witness;
    126+    // Block subsidy plus fees, minus any non-zero coinbase outputs
    127+    CAmount value_remaining;
    


    ryanofsky commented at 8:59 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    I think block_reward could be a more descriptive name than value_remaining


    Sjors commented at 11:36 am on November 17, 2025:
    That seems incorrect, because a pool (like Ocean or P2pool) might let you take part of the reward directly in an output.

    ryanofsky commented at 1:52 pm on December 2, 2025:

    re: #33819 (review)

    That seems incorrect, because a pool (like Ocean or P2pool) might let you take part of the reward directly in an output.

    Hmm, it sounds like the idea here is maybe that pools will reuse this CoinbaseTemplate struct, and prepend their own outputs into required_outputs and subtract their own rewards from value_remaining leaving the individual miner to take the rest? I’m not sure how realistic it is to expect pools to reuse this struct but maybe something like block_reward_remaining or available_reward or reward_remaining could be a more descriptive name which is still compatible with that idea.


    Sjors commented at 12:47 pm on December 3, 2025:

    I renamed it to block_reward_remaining.

    My understanding is that with merged-mining (see e.g. #33890) miners are expected to add their own outputs after we give them a block template. These are typically 0 value OP_RETURN outputs, but - if the pool permits - they could be non-zero.

    If we assume that nobody is going to patch Bitcoin Core to add outputs here (instead of downstream), then block_reward is accurate. For now…

    However, there’s still the possibility of the hypothetical soft fork below: #33819 (review)

    In that scenario part of the block reward is locked up and not available to miners.

  45. in src/node/types.h:130 in 80acabaf93
    125+    std::optional<uint256> witness;
    126+    // Block subsidy plus fees, minus any non-zero coinbase outputs
    127+    CAmount value_remaining;
    128+    // To be included as the last outputs in the coinbase transaction,
    129+    // e.g. the SegWit commitment.
    130+    std::vector<CTxOut> outputs;
    


    ryanofsky commented at 9:04 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    I think required_outputs could be a more descriptive name than outputs since this is not the full list of outputs.

  46. in src/node/types.h:126 in 80acabaf93
    121+     * This is currently the BIP 141 witness reserved value. A future soft fork
    122+     * may move the witness reserved value elsewhere, but there will still be a
    123+     * coinbase witness.
    124+     */
    125+    std::optional<uint256> witness;
    126+    // Block subsidy plus fees, minus any non-zero coinbase outputs
    


    ryanofsky commented at 9:17 pm on November 12, 2025:

    In commit “mining: add getCoinbase()” (80acabaf9353245f0839b18d9552ab39f595eda6)

    “outputs” here seem a little vague and it might be worth clarifying this is referring to the required outputs below. Also may be good to clarify that “non-zero” outputs refers (I assume) to something theoretical, that there shouldn’t currently be any outputs below with nonzero values?

    This also make me wonder if it would be clearer to change the output field type from std::vector<CTxOut> to std::vector<CScript> and for the CoinbaseTemplate not to put any requirements on output amounts since BIP141 doesn’t seem to care about them, and I would imagine any potential soft fork also not caring?


    Sjors commented at 11:52 am on November 17, 2025:

    Changed to “minus any non-zero required outputs” and a comment to point out that those don’t exist atm.

    See #33819 (review) above, I think having non-zero outputs is quite conceivable and it would be a shame if that requires another breaking change.

    I would imagine any potential soft fork also not caring?

    Drivechains lock coins into a scriptPubKey where they can’t be spent until they’re pegged out. BIP300 doesn’t use the coinbase for pegging in though.

    But I could imagine a soft-fork where, from the perspective of old nodes, transactions pay outrageous fees which go to an anyone-can-spend address. From an upgraded node point of view, those “fees” are really outputs into a new consensus environment that doesn’t use our UTXO model. Moving coins back into “legacy” UTXO’s looks like taking a bit from the anyone-can-spend.


    ryanofsky commented at 2:17 pm on December 2, 2025:

    re: #33819 (review)

    Thanks for spelling out the hypothetical here. The new comments are also nice and make intent more clear.

  47. plebhash commented at 9:44 pm on November 12, 2025: none

    Ok, that’s not really how you’re supposed to use it :-)

    hmm I guess this would indeed be a misuse of the interface if my end goal was to get Coinbase Tx data, and nothing more.

    however, we always need to call getBlock specifically because it contains the header + txdata of the template… and as a byproduct, we happen to also get the Coinbase Tx data, which saves us one extra call to getCoinbase/getCoinbaseTx

    at some point we’ll start exploring the so called “Sv2 JD Coinbase-only mode”, in which case fetching txdata might become superfluous (or optional) and we can probably get away with getBlockHeader + getCoinbase/getCoinbaseTx

    but it will take a while before we start exploring this avenue, so for now it’s probably better for us to stick with getBlock (which I assume will remain stable?), instead of trying to use getCoinbase/getCoinbaseTx (which seems to be undergoing potentially breaking changes?)

  48. ryanofsky approved
  49. ryanofsky commented at 9:44 pm on November 12, 2025: contributor

    Code review ACK 80acabaf9353245f0839b18d9552ab39f595eda6. I left various suggestions below but none are critical. This seems like a thoughtful change that should make clients easier to write and be compatible with potential soft forks, without placing a lot of unnecessary requirements on them.


    re: #33819 (comment)

    However there’s another gotcha if we remove a method from the interface: there’s no gaps allowed in the .capnp method numbering. This could be worked around as follows

    Agree with you that workaround seems pretty confusing and would not recommend it.

    Also agree with you in #33819 (review) that it makes sense to deprecate rather than remove the old methods for the time being.

    When it actually comes time to remove the methods I think there are three potential approaches:

    1 - Bump the Init.makeMining() ordinal and return an error to old clients as soon as they connect. This way we are free to renumber everything in the Mining interface and make other backwards incompatible changes, because old clients will never gain access to the new interface. 2 - Define some annotation like $removed that could return “method not implemented” or errors to clients that attempt to call them. 3 - Just renumber the interface without caring about compatibility with old clients.

    I think approach (1) makes sense if there are other backwards incompatible changes we want to make and batch together. Otherwise approach (2) is probably preferable so clients like plebhash’s that do not call the removed methods are not affected. Approach (3) is probably best to avoid, but it is an option. I also think it would be fine to keep the deprecated methods around indefinitely, since they are very simple, as long as they are renamed to discourage use in new code.

  50. DrahtBot requested review from optout21 on Nov 12, 2025
  51. Sjors marked this as a draft on Nov 14, 2025
  52. Sjors commented at 10:50 am on November 14, 2025: member

    From #33777 (comment) I gather that we should not reuse the method ordinals. Since cap&proto doesn’t allow gaps, when dropping a deprecated method, it’s best to just have them throw an error.

    In #33777 (comment) @ryanofsky suggested replacing the dummy coinbase with an empty transaction, so that clients are not tempted to use the dummy coinbase via getBlock() or getCoinbaseTx().

    I think I’m going to combine these two observations and make this PR a breaking change. Marking as draft for now.

  53. ryanofsky commented at 12:02 pm on November 14, 2025: contributor

    I think I’m going to combine these two observations and make this PR a breaking change. Marking as draft for now.

    This seems ok, but isn’t really what I would suggest. My suggestion would be for this PR to just introduce a new getCoinBase() method like it’s doing and not remove other methods so existing mining clients do not immediately stop working. The other methods, which are all simple accessors, can easily be removed later.

    Adding a new method now and removing older methods later should make both PR’s simpler and more focused. It would also let existing clients continue to work with the latest version of bitcoin while new clients are developed. The second PR removing the deprecated methods could also swap the coinbase TX with an empty one as suggested to avoid getBlock returning it.

    The only additional thing I might consider doing in this PR (in a second commit) is renaming methods intended to be deprecated, as shown in the diff #33819 (review). But even this could be overkill. It should be fine to just add getCoinBase() here and remove other ways of accessing coinbase info in a followup. Ok to take a different approach though. Just wanted to clarify my opinion.

  54. Sjors commented at 6:35 pm on November 14, 2025: member

    The second PR removing the deprecated methods could also swap the coinbase TX with an empty one as suggested to avoid getBlock returning it.

    It makes sense to split this idea over two PR’s. I have a local branch already, but still need to split out the commits and get the commit order right.

  55. ajtowns commented at 4:56 am on November 17, 2025: contributor

    Deprecate getCoinbaseTx() in favor of a new method that provides a struct with everything clients need to construct a coinbase. This is safer than providing a raw dummy coinbase that clients then have to manipulate.

    That seems backwards to me:

    1. Constructing a coinbase requires getting various consensus requirements correct, and new requirements may be added in future, such as the nlocktime requirement proposed in bip54. Why not keep all this logic in the node / template generator?
    2. Isn’t the point of the sv2 approach that mining software can just worry about adding random data, and leave worrying about consensus requirements up to the node software? The mining protocol is just sending a coinbase_tx_prefix, coinbase_tx_suffix and an extranonce, encoded as a prefix that’s already filled in along with how many additional bytes are still available.

    The only problem with having the template generator construct the coinbase that jumps out at me is that some miners want to have multiple payout addresses or additional OP_RETURN outputs in the coinbase; but isn’t that easily solved just by passing those as a vector of scriptPubKey and amounts, rather than just a single m_options.coinbase_output_script, and adding them as outputs in CreateNewBlock(), then leaving coinbase_output_script for whatever funds remain?

    Taking the prefix+suffix+fixed-size-extranonce approach to its logical conclusion would perhaps also mean adding an option for how big the extranonce push should be, and using that instead of the OP_0 dummy, and returning the serialized coinbase tx plus the offset/size where the extranonce data is, but that seems fairly reasonable to me.

    This doesn’t match the CoinbaseOutputConstraints and NewTemplate messages in the sv2 spec, however it does seem to be a better match for the AllocateMiningJob.Success data retrieved from the server (ie, you know the coinbase outputs the pool wants before asking for a template) and the DeclareMiningJob response (ie, the pool just gets the coinbase tx prefix/suffix). ie, this also seems like a way to simplify the sv2 spec?

  56. Sjors commented at 9:02 am on November 17, 2025: member

    @ajtowns the CoinbaseTemplate struct introduced by this PR closely matches the NewTemplate message in Stratum v2. The main difference is that the coinbase_tx_outputs field of NewTemplate uses an ad hoc serialisation that I don’t think we should embrace, so I keep the outputs in a vector.

    Note that the code in ExtractCoinbaseTemplate() was mostly copied from sv2-tp. So the main difference is that we now control how our dummy coinbase is “mined” for fields, rather that hope that clients implement it correctly.

    Constructing a coinbase requires getting various consensus requirements correct, and new requirements may be added in future, such as the nlocktime requirement proposed in bip54.

    Unless we overhaul the sv2 NewTemplate message it doesn’t help if our code implements such a new rule correctly, because the IPC client (like sv2-tp) might ignore the new field when it takes apart the dummy coinbase.

    In the short run, bip54 is already taken care of because coinbase_tx_locktime is a field (whereas getblocktemplate doesn’t have this yet).

    If a future soft fork adds a new consensus required field, we would have to add it to the CoinbaseTemplate struct (not a breaking change), inform Stratum v2 protocol designers that they need to add it to NewTemplate and any downstream tooling.

    However, I can’t think of anything that we can add to the coinbase that’s not covered by existing fields:

    • version is provided
    • a coinbase can only have one input
      • its prevhash and n are pinned by consensus
      • witness must be a single 32 byte push by consensus, we provide the value
      • scriptSig (coinbase) prefix is provided (making it longer than 8 bytes is a breaking change in sv2)
      • sequence is provided
    • all mandatory outputs are provided
    • lockTime is provided

    I like the idea of replacing m_options.coinbase_output_script with a vector. It would allow the client to add merge mining outputs and potentially avoid patching Bitcoin Core.

  57. Sjors commented at 10:29 am on November 17, 2025: member

    @plebhash wrote:

    however, we always need to call getBlock specifically because it contains the header + txdata of the template… and as a byproduct, we happen to also get the Coinbase Tx data, which saves us one extra call to getCoinbase/getCoinbaseTx

    Regarding the header, there’s a separate getHeader() method.

    It’s true that you need txdata anyway, but you don’t need it immediately. You need it when the pool asks using the RequestTransactionData message.

    When the node and the Template Provider run on the same machine, none of this matters. IPC round trips are very fast and it doesn’t matter if you fetch the whole block via getBlock() and then extract the header, or if you first do getHeader() and do getBlock() later.

    The interface design is mostly based on the Stratum v2 message design and that design in turn is built on the assumption that network latency does matter. So you want to be able to very quickly construct and broadcast the NewTemplate message, while taking your time with RequestTransactionData.Success and getting a template approved by the pool.

    but it will take a while before we start exploring this avenue, so for now it’s probably better for us to stick with getBlock (which I assume will remain stable?

    This pull request doesn’t break getBlock(), but it’s possible that v31 will break it by wiping the dummy coinbase (in a followup PR).

  58. Sjors force-pushed on Nov 17, 2025
  59. Sjors commented at 12:22 pm on November 17, 2025: member

    Rebased after #33745.

    I have a branch 2025/11/coinbase-template-break that shows what I have in mind for dropping the deprecated methods and clearing the dummy coinbase. Update: absorbed into https://github.com/Sjors/bitcoin/pull/106.

    Additionally we can (deprecate and) drop getCoinbaseCommitment(), which that branch does but is somewhat orthogonal. I’ll PR that as a followup. I plan to integrate the rest of the branch into https://github.com/Sjors/bitcoin/pull/106, which is a collecting of breaking changes that I have in mind.

    Also added a release note and addressed the inline comments.

  60. Sjors force-pushed on Nov 17, 2025
  61. DrahtBot added the label CI failed on Nov 17, 2025
  62. DrahtBot commented at 12:49 pm on November 17, 2025: contributor

    🚧 At least one of the CI tasks failed. Task previous releases: https://github.com/bitcoin/bitcoin/actions/runs/19429286100/job/55584101649 LLM reason (✨ experimental): Miner tests aborted (subprocess failure) causing the CI to fail.

    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.

  63. Sjors force-pushed on Nov 17, 2025
  64. DrahtBot removed the label Needs rebase on Nov 17, 2025
  65. Sjors commented at 1:35 pm on November 17, 2025: member

    Fixed MSan uninitialised coinbase complaint, which should also fix the `coinbase.value_remaining == 0’ failure.

    I also switched coinbase.version from using CURRENT_VERSION to version.

    Updated some comments above to make it clear that this PR does deprecate getCoinbaseTx() and getWitnessCommitmentIndex() and an additional remark about getCoinbaseCommitment().

  66. Sjors marked this as ready for review on Nov 17, 2025
  67. DrahtBot removed the label CI failed on Nov 17, 2025
  68. plebhash commented at 3:38 pm on November 17, 2025: none

    @Sjors said:

    It’s true that you need txdata anyway, but you don’t need it immediately. You need it when the pool asks using the RequestTransactionData message.

    not really, we also need it for NewTemplate.merkle_path

    that would be true if there were a getMerklePath() method for a given template, in which case we’d be able to do getHeader() + getCoinbase() + getMerklePath()

    but with the current interface, getBlock() is the only way we can properly build a NewTemplate message

  69. plebhash commented at 5:18 pm on November 17, 2025: none

    sorry, I should retract the message above

    I just realized there is a getCoinbaseMerklePath!

    so indeed, there’s no need to do a getBlock every time!

  70. in src/node/miner.cpp:625 in eeba240bfa
    620+
    621+    // Extract only OP_RETURN coinbase outputs (witness commitment, merge
    622+    // mining, etc). BlockAssembler::CreateNewBlock adds a dummy output with
    623+    // the full reward that we must exclude.
    624+    for (const auto& output : coinbase_tx->vout) {
    625+        if (!output.scriptPubKey.empty() && output.scriptPubKey[0] == OP_RETURN) {
    


    Sjors commented at 5:58 pm on November 17, 2025:
    Even simpler would be to just rely on the fact that the dummy output is always at index 0. I didn’t want to make that assumption in sv2-tp, but in our codebase it should be fine. It’s also what I ended up doing in #33890.

    Sjors commented at 8:41 am on November 18, 2025:
    Done in the latest push.
  71. plebhash commented at 6:08 pm on November 17, 2025: none

    @plebhash can you try to see if it’s easy for you to support both getCoinbase() and getCoinbaseTx() in stratum-mining/sv2-apps#59, preferring the first and falling back to the latter if it doesn’t exist?

    ok @Sjors second answer to this:

    currently, bitcoin-capnp-types only provides getCoinbaseTx

    we would need @rustaceanrob to replicate what you have on https://github.com/Sjors/bitcoin/blob/2025/11/coinbase-template/src/ipc/capnp/mining.capnp in order to have both options available

    I’m working to remove the unnecessary getBlock, but for now the replacement will be getBlockHeader + getCoinbaseTx + getCoinbaseMerklePath (with no getCoinbase as first option)


    moreover, I’m not sure what to expect in case getCoinbase doesn’t exist? I guess capnp-rpc would bubble up some kind of error? does @rustaceanrob know?

  72. ajtowns commented at 6:04 am on November 18, 2025: contributor

    Unless we overhaul the sv2 NewTemplate message it doesn’t help

    Yes, I’m saying I think NewTemplate (and CoinbaseOutputConstraints) should be overhauled, to avoid sv2 software generating or modifying the coinbase, beyond adjusting the values in a reserved extranonce area of known/fixed size. (Parsing the coinbase tx is still necessary for the pool to determine whether to accept a DeclareMiningJob message, and I don’t think that can be avoided)

    If there’s better ways (simpler, more robust, more forward compatible) of designing the APIs we offer, we shouldn’t avoid offering a better API because of a flawed standard.

    OTOH, overhauling NewTemplate shouldn’t be a blocker for supporting sv2 efficiently, so Approach ACK for this PR in the meantime.

    However, I can’t think of anything that we can add to the coinbase that’s not covered by existing fields:

    Yeah; it seems pretty robust, but just as getblocktemplate didn’t think of everything, I don’t think “I can’t think of anything missing” should really be the goal here.

    I like the idea of replacing m_options.coinbase_output_script with a vector. It would allow the client to add merge mining outputs and potentially avoid patching Bitcoin Core.

    Yep. :+1:

  73. Sjors commented at 8:26 am on November 18, 2025: member

    OTOH, overhauling NewTemplate shouldn’t be a blocker for supporting sv2 efficiently, so Approach ACK for this PR in the meantime.

    Thanks, I opened https://github.com/stratum-mining/sv2-spec/issues/167 to continue that discussion.

    replacing m_options.coinbase_output_script with a vector.

    See https://github.com/bitcoin/bitcoin/pull/33890

  74. Sjors force-pushed on Nov 18, 2025
  75. Sjors commented at 8:42 am on November 18, 2025: member
    I slightly simplified ExtractCoinbaseTemplate by having it assume that the dummy is always the first output. Also TIL about | std::views::drop(1).
  76. rustaceanrob commented at 10:11 am on November 18, 2025: contributor

    moreover, I’m not sure what to expect in case getCoinbase doesn’t exist? I guess capnp-rpc would bubble up some kind of error? does @rustaceanrob know?

    I would assume a new client requesting getCoinbase of an old server would result in the ErrorKind::Unimplemented. In the case of this response then a getCoinbaseTx could be used as a fallback.

    As far as updating the capnp files in the Rust types, I can do so on a development branch that you may point your toml at.

  77. Sjors commented at 12:53 pm on November 24, 2025: member
    If the important but breaking change in #33936 lands, then I might as well add the getCoinbaseTx() deletion here. It would be still be a separate commit, because it’s useful to demonstrate equivalence before deletion.
  78. in src/node/miner.cpp:612 in 73553c809e
    607+    // if a future softfork needs to commit more than BIP34) is a
    608+    // (potentially silent) breaking change for clients.
    609+    if (!Assume(coinbase.script_sig_prefix.size() <= 8)) {
    610+        LogWarning("Unexpected %d byte scriptSig prefix size.",
    611+                    coinbase.script_sig_prefix.size());
    612+    }
    


    ryanofsky commented at 10:55 pm on December 1, 2025:

    In commit “mining: add getCoinbase()” (73553c809ef6517839c4d1b02b657e6fa401f8a8)

    Instead of having a warning here it would seem better to document the compatibility concern directly in the code generating the scriptsig. This way, a potentially breaking change would be obvious just looking at this code and not rely on noticing a warning elsewhere. Also it would seem more future-proof for IPC documentation to just say that the final scriptsig needs to be <= 100 bytes, instead of trying to guarantee miners can always add 92 bytes. So would suggest a change like:

     0--- a/src/node/miner.cpp
     1+++ b/src/node/miner.cpp
     2@@ -168,6 +168,11 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
     3     coinbaseTx.vout.resize(1);
     4     coinbaseTx.vout[0].scriptPubKey = m_options.coinbase_output_script;
     5     coinbaseTx.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, chainparams.GetConsensus());
     6+    // Start the coinbase scriptSig with the block height as required by BIP34.
     7+    // The trailing OP_0 is optional padding and could be removed without a
     8+    // consensus change. Mining clients are expected to append extra data to
     9+    // this prefix, so increasing its length would reduce the space they can use
    10+    // and may break existing clients.
    11     coinbaseTx.vin[0].scriptSig = CScript() << nHeight << OP_0;
    12     Assert(nHeight > 0);
    13     coinbaseTx.nLockTime = static_cast<uint32_t>(nHeight - 1);
    14@@ -603,13 +608,6 @@ node::CoinbaseTemplate ExtractCoinbaseTemplate(const node::CBlockTemplate& block
    15     coinbase.version = coinbase_tx->version;
    16     Assert(coinbase_tx->vin.size() == 1);
    17     coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig;
    18-    // The CoinbaseTemplate interface guarantees a size limit. Raising it (e.g.
    19-    // if a future softfork needs to commit more than BIP34) is a
    20-    // (potentially silent) breaking change for clients.
    21-    if (!Assume(coinbase.script_sig_prefix.size() <= 8)) {
    22-        LogWarning("Unexpected %d byte scriptSig prefix size.",
    23-                    coinbase.script_sig_prefix.size());
    24-    }
    25 
    26     if (coinbase_tx->HasWitness()) {
    27         const auto& witness_stack{coinbase_tx->vin[0].scriptWitness.stack};
    28--- a/src/node/types.h
    29+++ b/src/node/types.h
    30@@ -108,9 +108,11 @@ struct CoinbaseTemplate {
    31     /* nSequence for the only coinbase transaction input */
    32     uint32_t sequence;
    33     /**
    34-     * Bytes which are to be placed at the beginning of scriptSig. Guaranteed
    35-     * to be less than 8 bytes (not including the length byte). This allows
    36-     * clients to add up to 92 bytes.
    37+     * Prefix which needs to be placed at the beginning of the scriptSig.
    38+     * Clients may append extra data to this as long as the overall scriptSig
    39+     * size is 100 bytes or less, to avoid the block being rejected with
    40+     * "bad-cb-length" error. Currently with BIP 34, the prefix should be less
    41+     * than 8 bytes, but future soft forks could require longer prefixes.
    42      */
    43     CScript script_sig_prefix;
    44     /**
    

    Sjors commented at 12:48 pm on December 3, 2025:

    Taken, except:

    • I used stronger wording for the 8 byte guarantee
    • mention that OP_0 is historically an extranonce (which hopefully goes away, see #32420)
  79. in src/interfaces/mining.h:60 in 73553c809e outdated
    51+     **/
    52     virtual CTransactionRef getCoinbaseTx() = 0;
    53+    /**
    54+     * Return scriptPubKey with SegWit OP_RETURN.
    55+     */
    56     virtual std::vector<unsigned char> getCoinbaseCommitment() = 0;
    


    ryanofsky commented at 3:00 pm on December 2, 2025:

    In commit “mining: add getCoinbase()” (73553c809ef6517839c4d1b02b657e6fa401f8a8)

    Was it intentional not to include the “note deprecated: use getCoinbase()” comment on this method? Would be good to include if not.


    Sjors commented at 12:48 pm on December 3, 2025:
    Added it back
  80. in src/node/miner.cpp:622 in 73553c809e
    617+        // element of 32 bytes.
    618+        Assert(witness_stack.size() == 1 && witness_stack[0].size() == 32);
    619+        coinbase.witness = uint256(witness_stack[0]);
    620+    }
    621+
    622+    coinbase.sequence = coinbase_tx->vin[0].nSequence;
    


    ryanofsky commented at 3:07 pm on December 2, 2025:

    In commit “mining: add getCoinbase()” (73553c809ef6517839c4d1b02b657e6fa401f8a8)

    Not important, but it would be nice if fields were extracted in same order they are declared: version, sequence, script_sig, etc.


    Sjors commented at 12:48 pm on December 3, 2025:
    This should be better now.
  81. ryanofsky approved
  82. ryanofsky commented at 3:39 pm on December 2, 2025: contributor

    Code review ACK 73553c809ef6517839c4d1b02b657e6fa401f8a8. I left some minor suggestions below but they are not important. Thanks for responding to all the previous ones.

    AJ’s bigger suggestions #33819 (comment) and #33819 (comment) definitely make a lot of sense to me, and would seem be a simpler & more future-proof direction to go in. They seem directionally compatible with this change, though. They could be implemented by expanding the BlockCreateOptions struct and changing the CoinbaseTemplate struct to have prefix and suffix fields instead of version, sequence, etc fields, I think.

    I do have one question about the implementation of this PR, not the interface: It seems like the ExtractCoinbaseTemplate function would be unnecessary if you would add the CoinbaseTemplate m_coinbase_template field to the CBlockTemplate class instead of the BlockTemplateImpl class. Then the BlockAssembler::CreateNewBlock method could just fill the coinbase template out directly and not require another method to extract it later. Wonder if you considered this or think it might be a good idea.

  83. DrahtBot requested review from ajtowns on Dec 2, 2025
  84. mining: rename getCoinbaseTx() to ..RawTx()
    This frees up the name getCoinbaseTx() for the next commit.
    
    Changing a function name does not impact IPC clients, as they only
    consider the function signature and sequence number.
    6bdde0ed98
  85. Sjors force-pushed on Dec 3, 2025
  86. Sjors renamed this:
    mining: add getCoinbase()
    mining: getCoinbase() returns struct instead of raw tx
    on Dec 3, 2025
  87. DrahtBot renamed this:
    mining: getCoinbase() returns struct instead of raw tx
    mining: getCoinbase() returns struct instead of raw tx
    on Dec 3, 2025
  88. Sjors force-pushed on Dec 3, 2025
  89. DrahtBot added the label CI failed on Dec 3, 2025
  90. DrahtBot commented at 12:47 pm on December 3, 2025: contributor

    🚧 At least one of the CI tasks failed. Task Windows-cross to x86_64, msvcrt: https://github.com/bitcoin/bitcoin/actions/runs/19894055796/job/57019808575 LLM reason (✨ experimental): C++ compilation failed because -Werror promoted missing-field-initializers in CoinbaseTxTemplate (miner.cpp), turning a warning into an error.

    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.

  91. Sjors commented at 12:48 pm on December 3, 2025: member

    I got rid of ExtractCoinbaseTemplate in favour of having CreateNewBlock() produce the CoinbaseTemplate right as it constructs the dummy template. It’s then stored on the CBlockTemplate as suggested by @ryanofsky.

    Since “coinbase” refers to the scriptSig of the coinbase transaction, I renamed some things:

    • CoinbaseTemplate -> CoinbaseTxTemplate
    • getCoinbaseTx() to getCoinbaseRawTx() (IPC clients are not affected by renames)
    • getCoinbase() to getCoinbaseTx()
  92. DrahtBot removed the label CI failed on Dec 3, 2025
  93. in src/node/miner.cpp:203 in 60f74dd315 outdated
    198+        // element of 32 bytes.
    199+        Assert(witness_stack.size() == 1 && witness_stack[0].size() == 32);
    200+        coinbase_tx_template.witness = uint256(witness_stack[0]);
    201+    }
    202+    if (const int witness_index = GetWitnessCommitmentIndex(*pblock); witness_index != NO_WITNESS_COMMITMENT) {
    203+        coinbase_tx_template.required_outputs.push_back(final_coinbase->vout[witness_index]);
    


    ryanofsky commented at 5:23 pm on December 3, 2025:

    In commit “mining: add new getCoinbaseTx() returning a struct” (60f74dd315811db427797a606f4b5611a8c59993)

    Maybe good to assert witness_index is between 0 and the vout size. Obviously it should be, but good to check array bounds since the index is coming from a different place.

  94. in src/node/miner.cpp:205 in 60f74dd315
    201+    }
    202+    if (const int witness_index = GetWitnessCommitmentIndex(*pblock); witness_index != NO_WITNESS_COMMITMENT) {
    203+        coinbase_tx_template.required_outputs.push_back(final_coinbase->vout[witness_index]);
    204+    }
    205+
    206+    pblocktemplate->m_coinbase_tx_template = std::move(coinbase_tx_template);
    


    ryanofsky commented at 5:27 pm on December 3, 2025:

    In commit “mining: add new getCoinbaseTx() returning a struct” (60f74dd315811db427797a606f4b5611a8c59993)

    Would be nice to avoid the move and treat pblock and coinbase_tx_template local variables more consistently. Would suggest defining a reference like CoinbaseTxTemplate& coinbase_tx_template{pblocktemplate->m_coinbase_tx_template} above similar to pblock.

  95. ryanofsky approved
  96. ryanofsky commented at 5:30 pm on December 3, 2025: contributor
    Code review ACK 60f74dd315811db427797a606f4b5611a8c59993. New comments and renaming both seem great, and it is nice to get rid of the extract function. PR feels much more straightforward now.
  97. mining: add new getCoinbaseTx() returning a struct
    Introduce a new method intended to replace getCoinbaseRawTx(), which
    provides a struct with everything clients need to construct a coinbase.
    This is safer than providing a raw dummy coinbase that clients then have
    to manipulate.
    
    The CoinbaseTxTemplate data is populated during the dummy transaction
    generation and stored in struct CBlockTemplate.
    
    Expand the interface_ipc.py functional test to document its usage
    and ensure equivalence.
    5761dac2f5
  98. Sjors force-pushed on Dec 4, 2025
  99. Sjors commented at 8:28 am on December 4, 2025: member
    Applied both suggestions from @ryanofsky: #33819#pullrequestreview-3536150722
  100. ryanofsky approved
  101. ryanofsky commented at 5:16 pm on December 4, 2025: contributor
    Code review ACK 5761dac2f5481c27550483279a007c2b850bc990, with the two suggestions applied since last review.
  102. DrahtBot added the label Needs rebase on Dec 6, 2025
  103. DrahtBot commented at 2:43 pm on December 6, 2025: contributor
    🐙 This pull request conflicts with the target branch and needs rebase.

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-12-07 15:13 UTC

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