Skip to content

Commit a0d4a73

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 getCoinbase() interface method. Fall back to the old approach if that method is not available.
1 parent 5f77626 commit a0d4a73

File tree

9 files changed

+152
-17
lines changed

9 files changed

+152
-17
lines changed

src/interfaces/mining.h

Lines changed: 4 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,7 +39,10 @@ class BlockTemplate
3839
// Sigop cost per transaction, not including coinbase transaction.
3940
virtual std::vector<int64_t> getTxSigops() = 0;
4041

42+
virtual node::CoinbaseTemplate getCoinbase() = 0;
43+
4144
virtual CTransactionRef getCoinbaseTx() = 0;
45+
4246
virtual std::vector<unsigned char> getCoinbaseCommitment() = 0;
4347
virtual int getWitnessCommitmentIndex() = 0;
4448

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+
getCoinbase @12 (context: Proxy.Context) -> (result: CoinbaseTemplate);
3031
getCoinbaseTx @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 CoinbaseTemplate $Proxy.wrap("node::CoinbaseTemplate") {
57+
version @0 :UInt32 $Proxy.name("version");
58+
inputSequence @1 :UInt32 $Proxy.name("input_sequence");
59+
scriptSigPrefix @2 :Data $Proxy.name("script_sig_prefix");
60+
witness @3 :Data $Proxy.name("witness");
61+
valueRemaining @4 :Int64 $Proxy.name("value_remaining");
62+
outputs @5 :List(Data) $Proxy.name("outputs");
63+
lockTime @6 :UInt32 $Proxy.name("lock_time");
64+
}

src/sv2/coinbase_template.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 <script/script.h>
12+
#include <primitives/transaction.h>
13+
#include <util/time.h>
14+
#include <vector>
15+
16+
namespace node {
17+
18+
struct CoinbaseTemplate {
19+
/* nVersion */
20+
uint32_t version;
21+
/* nSequence for the only coinbase transaction input */
22+
uint32_t input_sequence;
23+
/**
24+
* Bytes which are to be placed at the beginning of scriptSig. Guaranteed
25+
* to be less than 8 bytes (not including the length byte). This allows
26+
* clients to add up to 92 bytes.
27+
*/
28+
CScript script_sig_prefix;
29+
/**
30+
* The first (and only) witness stack element of the coinbase.
31+
*
32+
* Omitted for block templates without witness data.
33+
*
34+
* This is currently the BIP 141 witness reserved value. A future soft fork
35+
* may move the witness reserved value elsewhere, but there will still be a
36+
* coinbase witness.
37+
*/
38+
std::optional<uint256> witness;
39+
// Block subsidy plus fees, minus any non-zero coinbase outputs
40+
CAmount value_remaining;
41+
// To be included as the last outputs in the coinbase transaction,
42+
// e.g. the SegWit commitment.
43+
std::vector<CTxOut> outputs;
44+
uint32_t lock_time;
45+
};
46+
47+
} // namespace node
48+
49+
#endif // BITCOIN_SV2_COINBASE_TEMPLATE_H

src/sv2/messages.cpp

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,77 @@
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::CoinbaseTemplate 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.input_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.value_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.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.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::CoinbaseTemplate ExtractCoinbaseTemplate(const CTransactionRef coinbase_tx)
39+
{
40+
node::CoinbaseTemplate coinbase;
41+
42+
coinbase.version = coinbase_tx->CURRENT_VERSION;
43+
coinbase.script_sig_prefix = coinbase_tx->vin[0].scriptSig;
44+
// The CoinbaseTemplate interface guarantees a size limit. Raising it (e.g.
45+
// if a future softfork needs to commit more than BIP34) is a
46+
// breaking change for clients.
47+
Assume(coinbase.script_sig_prefix.size() <= 8);
48+
49+
if (coinbase_tx->HasWitness()) {
50+
const auto& witness_stack{coinbase_tx->vin[0].scriptWitness.stack};
51+
// Consensus requires the coinbase witness stack to have exactly one
52+
// element of 32 bytes.
53+
Assert(witness_stack.size() == 1 || witness_stack[0].size() == 32);
54+
coinbase.witness = uint256(witness_stack[0]);
55+
}
56+
57+
coinbase.input_sequence = coinbase_tx->vin[0].nSequence;
58+
59+
// The first dummy coinbase output produced by CreateBlock() has an nValue
60+
// set to nFee + the Block Subsidy
61+
coinbase.value_remaining = coinbase_tx->vout[0].nValue;
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.outputs.push_back(output);
69+
}
70+
}
71+
72+
coinbase.lock_time = coinbase_tx->nLockTime;
73+
74+
return coinbase;
75+
}
76+
77+
node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template) :
78+
node::Sv2NewTemplateMsg(header, ExtractCoinbaseTemplate(coinbase_tx), coinbase_merkle_path, template_id, future_template) {};
79+
4080
node::Sv2SetNewPrevHashMsg::Sv2SetNewPrevHashMsg(const CBlockHeader& header, uint64_t template_id) : m_template_id{template_id}
4181
{
4282
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::CoinbaseTemplate 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::CoinbaseTemplate ExtractCoinbaseTemplate(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
@@ -574,11 +574,26 @@ bool Sv2TemplateProvider::SendWork(Sv2Client& client, uint64_t template_id, Bloc
574574
return false;
575575
}
576576

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

583598
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);
584599
{

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::CoinbaseTemplate MockBlockTemplate::getCoinbase() { return ExtractCoinbaseTemplate(block.vtx[0]); }
5961
CTransactionRef MockBlockTemplate::getCoinbaseTx() { 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::CoinbaseTemplate getCoinbase() override;
6162
CTransactionRef getCoinbaseTx() override;
6263
std::vector<unsigned char> getCoinbaseCommitment() override;
6364
int getWitnessCommitmentIndex() override;

0 commit comments

Comments
 (0)