From eeba240bfa148050eaa588f737814f750bf2101f Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Mon, 17 Nov 2025 13:54:22 +0100 Subject: [PATCH 1/6] mining: add getCoinbase() Introduce a new method intended to replace getCoinbaseTx(), 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. A new helper method ExtractCoinbaseTemplate() processes the dummy coinbase transaction generated by BlockAssembler::CreateNewBlock and produces a CoinbaseTemplate. Expand the interface_ipc.py functional test to document its usage and ensure equivalence. --- doc/release-notes-33819.md | 6 ++ src/interfaces/mining.h | 16 +++ src/ipc/capnp/mining.capnp | 11 ++ src/node/interfaces.cpp | 9 ++ src/node/miner.cpp | 45 +++++++++ src/node/miner.h | 11 ++ src/node/types.h | 42 ++++++++ test/functional/interface_ipc.py | 101 ++++++++++++++++++- test/functional/test_framework/blocktools.py | 4 +- 9 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 doc/release-notes-33819.md diff --git a/doc/release-notes-33819.md b/doc/release-notes-33819.md new file mode 100644 index 000000000000..7b04846085b5 --- /dev/null +++ b/doc/release-notes-33819.md @@ -0,0 +1,6 @@ +Mining IPC +---------- + +- Adds `getCoinbase()` which clients should use instead of `getCoinbaseTx()`. It + contains all fields required to construct a coinbase transaction, and omits the + dummy output which Bitcoin Core uses internally. (#33819) diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 6e0376915d8a..a0efdba8d9c3 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -42,8 +42,24 @@ class BlockTemplate // Sigop cost per transaction, not including coinbase transaction. virtual std::vector getTxSigops() = 0; + /** Return fields needed to construct a coinbase transaction */ + virtual node::CoinbaseTemplate getCoinbase() = 0; + /** + * Return dummy coinbase transaction. + * + * @note deprecated: use getCoinbase() + **/ virtual CTransactionRef getCoinbaseTx() = 0; + /** + * Return scriptPubKey with SegWit OP_RETURN. + */ virtual std::vector getCoinbaseCommitment() = 0; + /** + * Return which output in the dummy coinbase contains the SegWit OP_RETURN. + * + * @note deprecated. Scan outputs from getCoinbase() outputs field for the + * SegWit marker. + */ virtual int getWitnessCommitmentIndex() = 0; /** diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp index ed01e44a32aa..399c7e26eaff 100644 --- a/src/ipc/capnp/mining.capnp +++ b/src/ipc/capnp/mining.capnp @@ -27,6 +27,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") { getBlock @2 (context: Proxy.Context) -> (result: Data); getTxFees @3 (context: Proxy.Context) -> (result: List(Int64)); getTxSigops @4 (context: Proxy.Context) -> (result: List(Int64)); + getCoinbase @12 (context: Proxy.Context) -> (result: CoinbaseTemplate); getCoinbaseTx @5 (context: Proxy.Context) -> (result: Data); getCoinbaseCommitment @6 (context: Proxy.Context) -> (result: Data); getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32); @@ -51,3 +52,13 @@ struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") { checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root"); checkPow @1 :Bool $Proxy.name("check_pow"); } + +struct CoinbaseTemplate $Proxy.wrap("node::CoinbaseTemplate") { + version @0 :UInt32 $Proxy.name("version"); + sequence @1 :UInt32 $Proxy.name("sequence"); + scriptSigPrefix @2 :Data $Proxy.name("script_sig_prefix"); + witness @3 :Data $Proxy.name("witness"); + valueRemaining @4 :Int64 $Proxy.name("value_remaining"); + requiredOutputs @5 :List(Data) $Proxy.name("required_outputs"); + lockTime @6 :UInt32 $Proxy.name("lock_time"); +} diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 059f4894f703..63ad02b93da3 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -83,6 +83,7 @@ using interfaces::Node; using interfaces::WalletLoader; using node::BlockAssembler; using node::BlockWaitOptions; +using node::CoinbaseTemplate; using util::Join; namespace node { @@ -863,6 +864,7 @@ class BlockTemplateImpl : public BlockTemplate explicit BlockTemplateImpl(BlockAssembler::Options assemble_options, std::unique_ptr block_template, NodeContext& node) : m_assemble_options(std::move(assemble_options)), + m_coinbase_template(ExtractCoinbaseTemplate(*Assert(block_template))), m_block_template(std::move(block_template)), m_node(node) { @@ -889,6 +891,11 @@ class BlockTemplateImpl : public BlockTemplate return m_block_template->vTxSigOpsCost; } + CoinbaseTemplate getCoinbase() override + { + return m_coinbase_template; + } + CTransactionRef getCoinbaseTx() override { return m_block_template->block.vtx[0]; @@ -929,6 +936,8 @@ class BlockTemplateImpl : public BlockTemplate const BlockAssembler::Options m_assemble_options; + const CoinbaseTemplate m_coinbase_template; + const std::unique_ptr m_block_template; bool m_interrupt_wait{false}; diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 14e5ce0a7be5..9a915a5f75d8 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -591,4 +591,49 @@ std::optional WaitTipChanged(ChainstateManager& chainman, KernelNotifi // avoid deadlocks. return GetTip(chainman); } + +node::CoinbaseTemplate ExtractCoinbaseTemplate(const node::CBlockTemplate& block_template) +{ + CTransactionRef coinbase_tx{block_template.block.vtx[0]}; + node::CoinbaseTemplate coinbase{}; + + coinbase.version = coinbase_tx->version; + Assert(coinbase_tx->vin.size() == 1); + coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig; + // The CoinbaseTemplate interface guarantees a size limit. Raising it (e.g. + // if a future softfork needs to commit more than BIP34) is a + // (potentially silent) breaking change for clients. + if (!Assume(coinbase.script_sig_prefix.size() <= 8)) { + LogWarning("Unexpected %d byte scriptSig prefix size.", + coinbase.script_sig_prefix.size()); + } + + if (coinbase_tx->HasWitness()) { + const auto& witness_stack{coinbase_tx->vin[0].scriptWitness.stack}; + // Consensus requires the coinbase witness stack to have exactly one + // element of 32 bytes. + Assert(witness_stack.size() == 1 && witness_stack[0].size() == 32); + coinbase.witness = uint256(witness_stack[0]); + } + + coinbase.sequence = coinbase_tx->vin[0].nSequence; + + // Extract only OP_RETURN coinbase outputs (witness commitment, merge + // mining, etc). BlockAssembler::CreateNewBlock adds a dummy output with + // the full reward that we must exclude. + for (const auto& output : coinbase_tx->vout) { + if (!output.scriptPubKey.empty() && output.scriptPubKey[0] == OP_RETURN) { + coinbase.required_outputs.push_back(output); + } else { + // The (single) dummy coinbase output produced by CreateBlock() has + // an nValue set to nFee + the Block Subsidy. + Assume(coinbase.value_remaining == 0); + coinbase.value_remaining = output.nValue; + } + } + + coinbase.lock_time = coinbase_tx->nLockTime; + + return coinbase; +} } // namespace node diff --git a/src/node/miner.h b/src/node/miner.h index 0790835d8f15..d80276029c61 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -259,6 +259,17 @@ std::optional GetTip(ChainstateManager& chainman); /* Waits for the connected tip to change until timeout has elapsed. During node initialization, this will wait until the tip is connected (regardless of `timeout`). * Returns the current tip, or nullopt if the node is shutting down. */ std::optional WaitTipChanged(ChainstateManager& chainman, KernelNotifications& kernel_notifications, const uint256& current_tip, MillisecondsDouble& timeout); + +/* + * Extract relevant fields from the dummy coinbase transaction in the template. + * + * Extracts only OP_RETURN coinbase outputs, i.e. the witness commitment. If + * BlockAssembler::CreateNewBlock() is patched to add more OP_RETURN outputs + * e.g. for merge mining, those will be included. The dummy output that spends + * the full reward is excluded. + */ +node::CoinbaseTemplate ExtractCoinbaseTemplate(const node::CBlockTemplate& block_template); + } // namespace node #endif // BITCOIN_NODE_MINER_H diff --git a/src/node/types.h b/src/node/types.h index 6c2687626c98..f7de25a7009e 100644 --- a/src/node/types.h +++ b/src/node/types.h @@ -16,10 +16,13 @@ #include #include #include +#include #include +#include #include