Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/interfaces/mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <consensus/amount.h>
#include <interfaces/types.h>
#include <sv2/block_options.h>
#include <sv2/coinbase_template.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <uint256.h>
Expand Down Expand Up @@ -38,7 +39,10 @@ class BlockTemplate
// Sigop cost per transaction, not including coinbase transaction.
virtual std::vector<int64_t> getTxSigops() = 0;

virtual node::CoinbaseTemplate getCoinbase() = 0;

virtual CTransactionRef getCoinbaseTx() = 0;

virtual std::vector<unsigned char> getCoinbaseCommitment() = 0;
virtual int getWitnessCommitmentIndex() = 0;

Expand Down
1 change: 1 addition & 0 deletions src/ipc/capnp/mining-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <ipc/capnp/common-types.h>
#include <ipc/capnp/mining.capnp.proxy.h>
#include <sv2/block_options.h>
#include <sv2/coinbase_template.h>

namespace mp {
// Custom serializations
Expand Down
11 changes: 11 additions & 0 deletions src/ipc/capnp/mining.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
inputSequence @1 :UInt32 $Proxy.name("input_sequence");
scriptSigPrefix @2 :Data $Proxy.name("script_sig_prefix");
witness @3 :Data $Proxy.name("witness");
valueRemaining @4 :Int64 $Proxy.name("value_remaining");
outputs @5 :List(Data) $Proxy.name("outputs");
lockTime @6 :UInt32 $Proxy.name("lock_time");
}
49 changes: 49 additions & 0 deletions src/sv2/coinbase_template.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2025 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_SV2_COINBASE_TEMPLATE_H
#define BITCOIN_SV2_COINBASE_TEMPLATE_H

#include <consensus/amount.h>
#include <cstddef>
#include <cstdint>
#include <script/script.h>
#include <primitives/transaction.h>
#include <util/time.h>
#include <vector>

namespace node {

struct CoinbaseTemplate {
/* nVersion */
uint32_t version;
/* nSequence for the only coinbase transaction input */
uint32_t input_sequence;
/**
* Bytes which are to be placed at the beginning of scriptSig. Guaranteed
* to be less than 8 bytes (not including the length byte). This allows
* clients to add up to 92 bytes.
*/
CScript script_sig_prefix;
/**
* The first (and only) witness stack element of the coinbase.
*
* Omitted for block templates without witness data.
*
* This is currently the BIP 141 witness reserved value. A future soft fork
* may move the witness reserved value elsewhere, but there will still be a
* coinbase witness.
*/
std::optional<uint256> witness;
// Block subsidy plus fees, minus any non-zero coinbase outputs
CAmount value_remaining;
// To be included as the last outputs in the coinbase transaction,
// e.g. the SegWit commitment.
std::vector<CTxOut> outputs;
uint32_t lock_time;
};

} // namespace node

#endif // BITCOIN_SV2_COINBASE_TEMPLATE_H
62 changes: 51 additions & 11 deletions src/sv2/messages.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,77 @@
#include <consensus/validation.h> // NO_WITNESS_COMMITMENT
#include <script/script.h>

node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template)
node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const node::CoinbaseTemplate coinbase, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template)
: m_template_id{template_id}, m_future_template{future_template}
{
m_version = header.nVersion;

m_coinbase_tx_version = coinbase_tx->CURRENT_VERSION;
m_coinbase_prefix = coinbase_tx->vin[0].scriptSig;
m_coinbase_tx_input_sequence = coinbase_tx->vin[0].nSequence;
m_coinbase_tx_version = coinbase.version;
m_coinbase_prefix = coinbase.script_sig_prefix;
m_coinbase_tx_input_sequence = coinbase.input_sequence;

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

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

m_coinbase_tx_locktime = coinbase_tx->nLockTime;
m_coinbase_tx_locktime = coinbase.lock_time;

for (const auto& hash : coinbase_merkle_path) {
m_merkle_path.push_back(hash);
}

}

node::CoinbaseTemplate ExtractCoinbaseTemplate(const CTransactionRef coinbase_tx)
{
node::CoinbaseTemplate coinbase;

coinbase.version = coinbase_tx->CURRENT_VERSION;
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
// breaking change for clients.
Assume(coinbase.script_sig_prefix.size() <= 8);

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.input_sequence = coinbase_tx->vin[0].nSequence;

// The first dummy coinbase output produced by CreateBlock() has an nValue
// set to nFee + the Block Subsidy
coinbase.value_remaining = coinbase_tx->vout[0].nValue;

// 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.outputs.push_back(output);
}
}

coinbase.lock_time = coinbase_tx->nLockTime;

return coinbase;
}

node::Sv2NewTemplateMsg::Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template) :
node::Sv2NewTemplateMsg(header, ExtractCoinbaseTemplate(coinbase_tx), coinbase_merkle_path, template_id, future_template) {};

node::Sv2SetNewPrevHashMsg::Sv2SetNewPrevHashMsg(const CBlockHeader& header, uint64_t template_id) : m_template_id{template_id}
{
m_prev_hash = header.hashPrevBlock;
Expand Down
14 changes: 13 additions & 1 deletion src/sv2/messages.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <primitives/transaction.h>
#include <script/script.h>
#include <span.h>
#include <sv2/coinbase_template.h>
#include <streams.h>
#include <string>
#include <vector>
Expand Down Expand Up @@ -304,7 +305,7 @@ struct Sv2NewTemplateMsg
std::vector<CTxOut> m_coinbase_tx_outputs;

/**
* The locktime field in the coinbase transaction.
* The lock_time field in the coinbase transaction.
*/
uint32_t m_coinbase_tx_locktime;

Expand All @@ -315,6 +316,7 @@ struct Sv2NewTemplateMsg

Sv2NewTemplateMsg() = default;
explicit Sv2NewTemplateMsg(const CBlockHeader& header, const CTransactionRef coinbase_tx, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template);
explicit Sv2NewTemplateMsg(const CBlockHeader& header, const node::CoinbaseTemplate coinbase, std::vector<uint256> coinbase_merkle_path, uint64_t template_id, bool future_template);

template <typename Stream>
void Serialize(Stream& s) const
Expand Down Expand Up @@ -738,4 +740,14 @@ class Sv2NetMsg

}

/*
* 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 CTransactionRef coinbase_tx);

#endif // BITCOIN_SV2_MESSAGES_H
43 changes: 29 additions & 14 deletions src/sv2/template_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ void Sv2TemplateProvider::Interrupt()
LOCK(m_tp_mutex);
try {
for (auto& t : GetBlockTemplates()) {
t.second->interruptWait();
t.second.second->interruptWait();
}
} catch (const ipc::Exception& e) {
// Bitcoin Core v30 does not yet implement interruptWait(), fall back
Expand Down Expand Up @@ -266,6 +266,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)
};

while (!m_flag_interrupt_sv2) {
uint256 prev_hash;
if (!block_template) {
LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Generate initial block template for client id=%zu\n",
client_id);
Expand All @@ -288,7 +289,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)
LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Assemble template: %.2fms\n",
Ticks<MillisecondsDouble>(SteadyClock::now() - time_start));

uint256 prev_hash{block_template->getBlockHeader().hashPrevBlock};
prev_hash = block_template->getBlockHeader().hashPrevBlock;
{
LOCK(m_tp_mutex);
if (prev_hash != m_best_prev_hash) {
Expand All @@ -299,7 +300,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)

// Add template to cache before sending it, to prevent race
// condition: https://github.com/stratum-mining/stratum/issues/1773
m_block_template_cache.insert({template_id,block_template});
m_block_template_cache.insert({template_id,std::make_pair(prev_hash, block_template)});
}

{
Expand Down Expand Up @@ -346,7 +347,6 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)
client_id);
}

uint256 old_prev_hash{block_template->getBlockHeader().hashPrevBlock};
std::shared_ptr<BlockTemplate> tmpl = block_template->waitNext(options);
// The client may have disconnected during the wait, check now to avoid
// a spurious IPC call and confusing log statements.
Expand All @@ -362,7 +362,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)

{
LOCK(m_tp_mutex);
if (new_prev_hash != old_prev_hash) {
if (new_prev_hash != prev_hash) {
LogPrintLevel(BCLog::SV2, BCLog::Level::Trace, "Tip changed, client id=%zu\n",
client_id);
future_template = true;
Expand All @@ -375,7 +375,7 @@ void Sv2TemplateProvider::ThreadSv2ClientHandler(size_t client_id)

// Add template to cache before sending it, to prevent race
// condition: https://github.com/stratum-mining/stratum/issues/1773
m_block_template_cache.insert({m_template_id,block_template});
m_block_template_cache.insert({m_template_id, std::make_pair(new_prev_hash,block_template)});
}

{
Expand Down Expand Up @@ -422,7 +422,7 @@ void Sv2TemplateProvider::RequestTransactionData(Sv2Client& client, node::Sv2Req

return;
}
block = (*cached_block->second).getBlock();
block = (*cached_block->second.second).getBlock();
}

{
Expand Down Expand Up @@ -497,7 +497,7 @@ void Sv2TemplateProvider::SubmitSolution(node::Sv2SubmitSolutionMsg solution)
* on the network. In case of a reorg the node will be able to switch
* faster because it already has (but not fully validated) the block.
*/
block_template = cached_block_template->second;
block_template = cached_block_template->second.second;
}

// Submit the solution to construct and process the block
Expand Down Expand Up @@ -555,7 +555,7 @@ void Sv2TemplateProvider::PruneBlockTemplateCache()
// If the blocks prevout is not the tip's prevout, delete it.
uint256 prev_hash = m_best_prev_hash;
std::erase_if(m_block_template_cache, [prev_hash] (const auto& kv) {
if (kv.second->getBlockHeader().hashPrevBlock != prev_hash) {
if (kv.second.first != prev_hash) {
return true;
}
return false;
Expand All @@ -574,11 +574,26 @@ bool Sv2TemplateProvider::SendWork(Sv2Client& client, uint64_t template_id, Bloc
return false;
}

node::Sv2NewTemplateMsg new_template{header,
block_template.getCoinbaseTx(),
block_template.getCoinbaseMerklePath(),
template_id,
future_template};
node::Sv2NewTemplateMsg new_template;
try {
node::CoinbaseTemplate coinbase{block_template.getCoinbase()};

new_template = node::Sv2NewTemplateMsg{header,
coinbase,
block_template.getCoinbaseMerklePath(),
template_id,
future_template};
} catch (const std::exception& exception) {
// Fall back to getCoinbaseTx()
CTransactionRef coinbase_tx{block_template.getCoinbaseTx()};

new_template = node::Sv2NewTemplateMsg{header,
coinbase_tx,
block_template.getCoinbaseMerklePath(),
template_id,
future_template};

}

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);
{
Expand Down
5 changes: 3 additions & 2 deletions src/sv2/template_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@ class Sv2TemplateProvider : public Sv2EventsInterface
std::chrono::nanoseconds m_last_block_time GUARDED_BY(m_tp_mutex);

/**
* A cache that maps ids used in NewTemplate messages and its associated block template.
* A cache that maps ids used in NewTemplate messages and its associated
* <prevhash,block template>.
*/
using BlockTemplateCache = std::map<uint64_t, std::shared_ptr<BlockTemplate>>;
using BlockTemplateCache = std::map<uint64_t, std::pair<uint256, std::shared_ptr<BlockTemplate>>>;
BlockTemplateCache m_block_template_cache GUARDED_BY(m_tp_mutex);

public:
Expand Down
2 changes: 2 additions & 0 deletions src/test/sv2_mock_mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <sync.h>
#include <cassert>
#include <logging.h>
#include <sv2/messages.h>

namespace {
static inline uint256 HashFromHeight(uint64_t h)
Expand Down Expand Up @@ -56,6 +57,7 @@ CBlockHeader MockBlockTemplate::getBlockHeader() { return block.GetBlockHeader()
CBlock MockBlockTemplate::getBlock() { return block; }
std::vector<CAmount> MockBlockTemplate::getTxFees() { return {}; }
std::vector<int64_t> MockBlockTemplate::getTxSigops() { return {}; }
node::CoinbaseTemplate MockBlockTemplate::getCoinbase() { return ExtractCoinbaseTemplate(block.vtx[0]); }
CTransactionRef MockBlockTemplate::getCoinbaseTx() { return block.vtx[0]; }
std::vector<unsigned char> MockBlockTemplate::getCoinbaseCommitment() { return {}; }
int MockBlockTemplate::getWitnessCommitmentIndex() { return -1; }
Expand Down
1 change: 1 addition & 0 deletions src/test/sv2_mock_mining.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class MockBlockTemplate : public interfaces::BlockTemplate {
CBlock getBlock() override;
std::vector<CAmount> getTxFees() override;
std::vector<int64_t> getTxSigops() override;
node::CoinbaseTemplate getCoinbase() override;
CTransactionRef getCoinbaseTx() override;
std::vector<unsigned char> getCoinbaseCommitment() override;
int getWitnessCommitmentIndex() override;
Expand Down
Loading