Skip to content

Commit 3f68384

Browse files
committed
Use CoinbaseTemplate if available
Avoid the brittle process of constructing the template coinbase fields from Bitcoin Core's dummy coinbase transaction. This uses the new getCoinbaseTx() interface method. Fall back to the old approach if that method is not available.
1 parent a62cb70 commit 3f68384

File tree

9 files changed

+178
-17
lines changed

9 files changed

+178
-17
lines changed

src/interfaces/mining.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <consensus/amount.h>
99
#include <interfaces/types.h>
1010
#include <sv2/block_options.h>
11+
#include <sv2/coinbase_template.h>
1112
#include <primitives/block.h>
1213
#include <primitives/transaction.h>
1314
#include <uint256.h>
@@ -38,18 +39,28 @@ class BlockTemplate
3839
// Sigop cost per transaction, not including coinbase transaction.
3940
virtual std::vector<int64_t> getTxSigops() = 0;
4041

42+
/** Return fields needed to construct a coinbase transaction */
43+
virtual node::CoinbaseTxTemplate getCoinbaseTx() = 0;
44+
4145
/**
4246
* Return serialized dummy coinbase transaction.
47+
*
48+
* @note deprecated: use getCoinbaseTx()
4349
*/
4450
virtual CTransactionRef getCoinbaseRawTx() = 0;
4551

4652
/**
4753
* Return scriptPubKey with SegWit OP_RETURN.
54+
*
55+
* @note deprecated: use getCoinbaseRawTx()
4856
*/
4957
virtual std::vector<unsigned char> getCoinbaseCommitment() = 0;
5058

5159
/**
5260
* Return which output in the dummy coinbase contains the SegWit OP_RETURN.
61+
*
62+
* @note deprecated. Scan outputs from getCoinbaseRawTx() outputs field for the
63+
* SegWit marker.
5364
*/
5465
virtual int getWitnessCommitmentIndex() = 0;
5566

src/ipc/capnp/mining-types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <ipc/capnp/common-types.h>
1111
#include <ipc/capnp/mining.capnp.proxy.h>
1212
#include <sv2/block_options.h>
13+
#include <sv2/coinbase_template.h>
1314

1415
namespace mp {
1516
// Custom serializations

src/ipc/capnp/mining.capnp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
2727
getBlock @2 (context: Proxy.Context) -> (result: Data);
2828
getTxFees @3 (context: Proxy.Context) -> (result: List(Int64));
2929
getTxSigops @4 (context: Proxy.Context) -> (result: List(Int64));
30+
getCoinbaseTx @12 (context: Proxy.Context) -> (result: CoinbaseTxTemplate);
3031
getCoinbaseRawTx @5 (context: Proxy.Context) -> (result: Data);
3132
getCoinbaseCommitment @6 (context: Proxy.Context) -> (result: Data);
3233
getWitnessCommitmentIndex @7 (context: Proxy.Context) -> (result: Int32);
@@ -51,3 +52,13 @@ struct BlockCheckOptions $Proxy.wrap("node::BlockCheckOptions") {
5152
checkMerkleRoot @0 :Bool $Proxy.name("check_merkle_root");
5253
checkPow @1 :Bool $Proxy.name("check_pow");
5354
}
55+
56+
struct CoinbaseTxTemplate $Proxy.wrap("node::CoinbaseTxTemplate") {
57+
version @0 :UInt32 $Proxy.name("version");
58+
sequence @1 :UInt32 $Proxy.name("sequence");
59+
scriptSigPrefix @2 :Data $Proxy.name("script_sig_prefix");
60+
witness @3 :Data $Proxy.name("witness");
61+
blockRewardRemaining @4 :Int64 $Proxy.name("block_reward_remaining");
62+
requiredOutputs @5 :List(Data) $Proxy.name("required_outputs");
63+
lockTime @6 :UInt32 $Proxy.name("lock_time");
64+
}

src/sv2/coinbase_template.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_SV2_COINBASE_TEMPLATE_H
6+
#define BITCOIN_SV2_COINBASE_TEMPLATE_H
7+
8+
#include <consensus/amount.h>
9+
#include <cstddef>
10+
#include <cstdint>
11+
#include <optional>
12+
#include <script/script.h>
13+
#include <primitives/transaction.h>
14+
#include <util/time.h>
15+
#include <vector>
16+
17+
namespace node {
18+
19+
struct CoinbaseTxTemplate {
20+
/* nVersion */
21+
uint32_t version;
22+
/* nSequence for the only coinbase transaction input */
23+
uint32_t sequence;
24+
/**
25+
* Prefix which needs to be placed at the beginning of the scriptSig.
26+
* Clients may append extra data to this as long as the overall scriptSig
27+
* size is 100 bytes or less, to avoid the block being rejected with
28+
* "bad-cb-length" error.
29+
*
30+
* Currently with BIP 34, the prefix is guaranteed to be less than 8 bytes,
31+
* but future soft forks could require longer prefixes.
32+
*/
33+
CScript script_sig_prefix;
34+
/**
35+
* The first (and only) witness stack element of the coinbase input.
36+
*
37+
* Omitted for block templates without witness data.
38+
*
39+
* This is currently the BIP 141 witness reserved value, and can be chosen
40+
* arbitrarily by the node, but future soft forks may constrain it.
41+
*/
42+
std::optional<uint256> witness;
43+
/**
44+
* Block subsidy plus fees, minus any non-zero required_outputs.
45+
*
46+
* Currently there are no non-zero required_outputs, so block_reward_remaining
47+
* is the entire block reward. See also required_outputs.
48+
*/
49+
CAmount block_reward_remaining;
50+
/*
51+
* To be included as the last outputs in the coinbase transaction.
52+
* Currently this is only the witness commitment OP_RETURN, but future
53+
* softforks or a custom mining patch could add more.
54+
*
55+
* The dummy output that spends the full reward is excluded.
56+
*/
57+
std::vector<CTxOut> required_outputs;
58+
uint32_t lock_time;
59+
};
60+
61+
} // namespace node
62+
63+
#endif // BITCOIN_SV2_COINBASE_TEMPLATE_H

src/sv2/messages.cpp

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,82 @@
66
#include <consensus/validation.h> // NO_WITNESS_COMMITMENT
77
#include <script/script.h>
88

9-
node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template)
9+
node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const node::CoinbaseTxTemplate coinbase, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template)
1010
: m_template_id{template_id}, m_future_template{future_template}
1111
{
1212
m_version = header.nVersion;
1313

14-
m_coinbase_tx_version = coinbase_tx->CURRENT_VERSION;
15-
m_coinbase_prefix = coinbase_tx->vin[0].scriptSig;
16-
m_coinbase_tx_input_sequence = coinbase_tx->vin[0].nSequence;
14+
m_coinbase_tx_version = coinbase.version;
15+
m_coinbase_prefix = coinbase.script_sig_prefix;
16+
m_coinbase_tx_input_sequence = coinbase.sequence;
1717

1818
// The coinbase nValue already contains the nFee + the Block Subsidy when built using CreateBlock().
19-
m_coinbase_tx_value_remaining = static_cast<uint64_t>(coinbase_tx->vout[0].nValue);
19+
m_coinbase_tx_value_remaining = static_cast<uint64_t>(coinbase.block_reward_remaining);
2020

2121
// Extract only OP_RETURN coinbase outputs (witness commitment, merge mining, etc.)
2222
// Bitcoin Core adds a dummy output with the full reward that we must exclude,
2323
// otherwise the pool would create an invalid block trying to spend that amount again.
2424
m_coinbase_tx_outputs.clear();
25-
for (const auto& output : coinbase_tx->vout) {
26-
if (!output.scriptPubKey.empty() && output.scriptPubKey[0] == OP_RETURN) {
27-
m_coinbase_tx_outputs.push_back(output);
28-
}
25+
for (const auto& output : coinbase.required_outputs) {
26+
m_coinbase_tx_outputs.push_back(output);
2927
}
30-
m_coinbase_tx_outputs_count = m_coinbase_tx_outputs.size();
28+
m_coinbase_tx_outputs_count = coinbase.required_outputs.size();
3129

32-
m_coinbase_tx_locktime = coinbase_tx->nLockTime;
30+
m_coinbase_tx_locktime = coinbase.lock_time;
3331

3432
for (const auto& hash : coinbase_merkle_path) {
3533
m_merkle_path.push_back(hash);
3634
}
3735

3836
}
3937

38+
node::CoinbaseTxTemplate ExtractCoinbaseTxTemplate(const CTransactionRef coinbase_tx)
39+
{
40+
node::CoinbaseTxTemplate coinbase{};
41+
42+
coinbase.version = coinbase_tx->version;
43+
Assert(coinbase_tx->vin.size() == 1);
44+
coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig;
45+
// The CoinbaseTxTemplate interface guarantees a size limit. Raising it (e.g.
46+
// if a future softfork needs to commit more than BIP34) is a
47+
// (potentially silent) breaking change for clients.
48+
if (!Assume(coinbase.script_sig_prefix.size() <= 8)) {
49+
LogWarning("Unexpected %d byte scriptSig prefix size.",
50+
coinbase.script_sig_prefix.size());
51+
}
52+
53+
if (coinbase_tx->HasWitness()) {
54+
const auto& witness_stack{coinbase_tx->vin[0].scriptWitness.stack};
55+
// Consensus requires the coinbase witness stack to have exactly one
56+
// element of 32 bytes.
57+
Assert(witness_stack.size() == 1 && witness_stack[0].size() == 32);
58+
coinbase.witness = uint256(witness_stack[0]);
59+
}
60+
61+
coinbase.sequence = coinbase_tx->vin[0].nSequence;
62+
63+
// Extract only OP_RETURN coinbase outputs (witness commitment, merge
64+
// mining, etc). BlockAssembler::CreateNewBlock adds a dummy output with
65+
// the full reward that we must exclude.
66+
for (const auto& output : coinbase_tx->vout) {
67+
if (!output.scriptPubKey.empty() && output.scriptPubKey[0] == OP_RETURN) {
68+
coinbase.required_outputs.push_back(output);
69+
} else {
70+
// The (single) dummy coinbase output produced by CreateBlock() has
71+
// an nValue set to nFee + the Block Subsidy.
72+
Assume(coinbase.block_reward_remaining == 0);
73+
coinbase.block_reward_remaining = output.nValue;
74+
}
75+
}
76+
77+
coinbase.lock_time = coinbase_tx->nLockTime;
78+
79+
return coinbase;
80+
}
81+
82+
node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template) :
83+
node::Sv2NewTemplateMsg(header, ExtractCoinbaseTxTemplate(coinbase_tx), coinbase_merkle_path, template_id, future_template) {};
84+
4085
node::Sv2SetNewPrevHashMsg::Sv2SetNewPrevHashMsg(const CBlockHeader& header, uint64_t template_id) : m_template_id{template_id}
4186
{
4287
m_prev_hash = header.hashPrevBlock;

src/sv2/messages.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <primitives/transaction.h>
1212
#include <script/script.h>
1313
#include <span.h>
14+
#include <sv2/coinbase_template.h>
1415
#include <streams.h>
1516
#include <string>
1617
#include <vector>
@@ -304,7 +305,7 @@ struct Sv2NewTemplateMsg
304305
std::vector<CTxOut> m_coinbase_tx_outputs;
305306

306307
/**
307-
* The locktime field in the coinbase transaction.
308+
* The lock_time field in the coinbase transaction.
308309
*/
309310
uint32_t m_coinbase_tx_locktime;
310311

@@ -315,6 +316,7 @@ struct Sv2NewTemplateMsg
315316

316317
Sv2NewTemplateMsg() = default;
317318
explicit Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template);
319+
explicit Sv2NewTemplateMsg(const CBlockHeader& header, const node::CoinbaseTxTemplate coinbase, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template);
318320

319321
template <typename Stream>
320322
void Serialize(Stream& s) const
@@ -738,4 +740,14 @@ class Sv2NetMsg
738740

739741
}
740742

743+
/*
744+
* Extract relevant fields from the dummy coinbase transaction in the template.
745+
*
746+
* Extracts only OP_RETURN coinbase outputs, i.e. the witness commitment. If
747+
* BlockAssembler::CreateNewBlock() is patched to add more OP_RETURN outputs
748+
* e.g. for merge mining, those will be included. The dummy output that spends
749+
* the full reward is excluded.
750+
*/
751+
node::CoinbaseTxTemplate ExtractCoinbaseTxTemplate(const CTransactionRef coinbase_tx);
752+
741753
#endif // BITCOIN_SV2_MESSAGES_H

src/sv2/template_provider.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,26 @@ bool Sv2TemplateProvider::SendWork(Sv2Client& client, uint64_t template_id, Bloc
575575
return false;
576576
}
577577

578-
node::Sv2NewTemplateMsg new_template{header,
579-
block_template.getCoinbaseRawTx(),
580-
block_template.getCoinbaseMerklePath(),
581-
template_id,
582-
future_template};
578+
node::Sv2NewTemplateMsg new_template;
579+
try {
580+
node::CoinbaseTxTemplate coinbase{block_template.getCoinbaseTx()};
581+
582+
new_template = node::Sv2NewTemplateMsg{header,
583+
coinbase,
584+
block_template.getCoinbaseMerklePath(),
585+
template_id,
586+
future_template};
587+
} catch (const std::exception&) {
588+
// Fall back to getCoinbaseRawTx()
589+
CTransactionRef coinbase_tx{block_template.getCoinbaseRawTx()};
590+
591+
new_template = node::Sv2NewTemplateMsg{header,
592+
coinbase_tx,
593+
block_template.getCoinbaseMerklePath(),
594+
template_id,
595+
future_template};
596+
597+
}
583598

584599
LogPrintLevel(BCLog::SV2, BCLog::Level::Debug, "Send 0x71 NewTemplate id=%lu future=%d to client id=%zu\n", template_id, future_template, client.m_id);
585600
{

src/test/sv2_mock_mining.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <sync.h>
88
#include <cassert>
99
#include <logging.h>
10+
#include <sv2/messages.h>
1011

1112
namespace {
1213
static inline uint256 HashFromHeight(uint64_t h)
@@ -56,6 +57,7 @@ CBlockHeader MockBlockTemplate::getBlockHeader() { return block.GetBlockHeader()
5657
CBlock MockBlockTemplate::getBlock() { return block; }
5758
std::vector<CAmount> MockBlockTemplate::getTxFees() { return {}; }
5859
std::vector<int64_t> MockBlockTemplate::getTxSigops() { return {}; }
60+
node::CoinbaseTxTemplate MockBlockTemplate::getCoinbaseTx() { return ExtractCoinbaseTxTemplate(block.vtx[0]); }
5961
CTransactionRef MockBlockTemplate::getCoinbaseRawTx() { return block.vtx[0]; }
6062
std::vector<unsigned char> MockBlockTemplate::getCoinbaseCommitment() { return {}; }
6163
int MockBlockTemplate::getWitnessCommitmentIndex() { return -1; }

src/test/sv2_mock_mining.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class MockBlockTemplate : public interfaces::BlockTemplate {
5858
CBlock getBlock() override;
5959
std::vector<CAmount> getTxFees() override;
6060
std::vector<int64_t> getTxSigops() override;
61+
node::CoinbaseTxTemplate getCoinbaseTx() override;
6162
CTransactionRef getCoinbaseRawTx() override;
6263
std::vector<unsigned char> getCoinbaseCommitment() override;
6364
int getWitnessCommitmentIndex() override;

0 commit comments

Comments
 (0)