Skip to content

Commit e883903

Browse files
committed
mining: make non-mandatory coinbase commitment opt-in
Previously the dummy coinbase returned by the Mining IPC interface always had a witness and SegWit OP_RETURN output. This is unnecessary for empty blocks as well as for blocks without any SegWit spends. This commit makes this behavior configurable via always_add_coinbase_commitment on BlockCreateOptions. It defaults to false, making this a potentially breaking change to clients. The presence of this field in requests makes it incompatible with Bitcoin Core v30, so it's a breaking change in any case. Preserve historical getblocktemplate RPC behavior. Although it doesn't return a coinbase transaction, it does always provide a default_witness_commitment. This is set in BlockAssembler::CreateNewBlock() via a call GenerateCoinbaseCommitment(), which has the side-effect of adding a coinbase witness.
1 parent 99ead21 commit e883903

File tree

7 files changed

+45
-10
lines changed

7 files changed

+45
-10
lines changed

src/ipc/capnp/mining.capnp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
4444
useMempool @0 :Bool $Proxy.name("use_mempool");
4545
blockReservedWeight @1 :UInt64 $Proxy.name("block_reserved_weight");
4646
coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
47+
alwaysAddCoinbaseCommitment @3 :Bool $Proxy.name("always_add_coinbase_commitment");
4748
}
4849

4950
struct BlockWaitOptions $Proxy.wrap("node::BlockWaitOptions") {

src/node/miner.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
169169
Assert(nHeight > 0);
170170
coinbaseTx.nLockTime = static_cast<uint32_t>(nHeight - 1);
171171
pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx));
172-
pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev);
172+
if (m_options.always_add_coinbase_commitment || std::any_of(
173+
pblock->vtx.begin(), pblock->vtx.end(),
174+
[&](const CTransactionRef tx) { return tx->HasWitness(); }
175+
)) {
176+
pblocktemplate->vchCoinbaseCommitment = m_chainstate.m_chainman.GenerateCoinbaseCommitment(*pblock, pindexPrev);
177+
}
173178

174179
LogPrintf("CreateNewBlock(): block weight: %u txs: %u fees: %ld sigops %d\n", GetBlockWeight(*pblock), nBlockTx, nFees, nBlockSigOpsCost);
175180

src/node/types.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ struct BlockCreateOptions {
6767
* coinbase_max_additional_weight and coinbase_output_max_additional_sigops.
6868
*/
6969
CScript coinbase_output_script{CScript() << OP_TRUE};
70+
7071
/**
7172
* Whether to clear the dummy coinbase from the block template.
7273
*
@@ -75,6 +76,14 @@ struct BlockCreateOptions {
7576
* can opt-in to keeping it.
7677
*/
7778
bool clear_coinbase{true};
79+
80+
/*
81+
* Whether blocks without SegWit transactions (e.g. empty blocks) should
82+
* have a SegWit commitment, i.e. the coinbase witness and OP_RETURN.
83+
*
84+
* @note IPC clients should omit this field for compatibility with v30.0
85+
*/
86+
bool always_add_coinbase_commitment{false};
7887
};
7988

8089
struct BlockWaitOptions {

src/rpc/mining.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const
167167
while (nGenerate > 0 && !chainman.m_interrupt) {
168168
std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({
169169
.coinbase_output_script = coinbase_output_script,
170-
.clear_coinbase = false
170+
.clear_coinbase = false,
171+
.always_add_coinbase_commitment = true
171172
}));
172173
CHECK_NONFATAL(block_template);
173174

@@ -382,7 +383,8 @@ static RPCHelpMan generateblock()
382383
std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({
383384
.use_mempool = false,
384385
.coinbase_output_script = coinbase_output_script,
385-
.clear_coinbase = false
386+
.clear_coinbase = false,
387+
.always_add_coinbase_commitment = true
386388
})};
387389
CHECK_NONFATAL(block_template);
388390

@@ -878,7 +880,10 @@ static RPCHelpMan getblocktemplate()
878880
time_start = GetTime();
879881

880882
// Create new block
881-
block_template = miner.createNewBlock({.clear_coinbase = false});
883+
block_template = miner.createNewBlock({
884+
.clear_coinbase = false,
885+
.always_add_coinbase_commitment = true
886+
});
882887
CHECK_NONFATAL(block_template);
883888

884889

src/test/fuzz/utxo_total_supply.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ FUZZ_TARGET(utxo_total_supply)
4545
};
4646
BlockAssembler::Options options;
4747
options.coinbase_output_script = CScript() << OP_FALSE;
48+
options.always_add_coinbase_commitment = true;
4849
const auto PrepareNextBlock = [&]() {
4950
// Use OP_FALSE to avoid BIP30 check from hitting early
5051
auto block = PrepareBlock(node, options);

src/test/util/setup_common.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ CBlock TestChain100Setup::CreateBlock(
404404
{
405405
BlockAssembler::Options options;
406406
options.coinbase_output_script = scriptPubKey;
407+
options.always_add_coinbase_commitment = true;
407408
CBlock block = BlockAssembler{chainstate, nullptr, options}.CreateNewBlock()->block;
408409

409410
Assert(block.vtx.size() == 1);

test/functional/interface_ipc.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ async def async_routine():
146146
echo.destroy(ctx)
147147
asyncio.run(capnp.run(async_routine()))
148148

149-
async def build_coinbase_test(self, template, ctx, miniwallet):
150-
self.log.debug("Build coinbase transaction using getCoinbase()")
149+
async def build_coinbase_test(self, template, ctx, miniwallet, *, expect_witness: bool):
150+
self.log.debug("Build coinbase transaction using getCoinbase() " +
151+
("with" if expect_witness else "without") + " a witness")
152+
151153
coinbase_template = await self.parse_and_deserialize_coinbase(template, ctx)
152154
coinbase = CTransaction()
153155
coinbase.version = coinbase_template.version
@@ -168,8 +170,7 @@ async def build_coinbase_test(self, template, ctx, miniwallet):
168170
coinbase.vout = [CTxOut()]
169171
coinbase.vout[0].scriptPubKey = miniwallet.get_output_script()
170172
coinbase.vout[0].nValue = coinbase_template.valueRemaining
171-
# Add SegWit OP_RETURN. This is currently always present even for
172-
# empty blocks, but this may change.
173+
# Add SegWit OP_RETURN if provided
173174
found_witness_op_return = False
174175
for output_data in coinbase_template.requiredOutputs:
175176
output = CTxOut()
@@ -179,6 +180,7 @@ async def build_coinbase_test(self, template, ctx, miniwallet):
179180
found_witness_op_return = True
180181

181182
assert_equal(has_witness, found_witness_op_return)
183+
assert_equal(has_witness, expect_witness)
182184

183185
coinbase.nLockTime = coinbase_template.lockTime
184186

@@ -227,7 +229,17 @@ async def async_routine():
227229
assert_equal(len(header.result), block_header_size)
228230
block = await self.parse_and_deserialize_block(template, ctx)
229231
assert_equal(ser_uint256(block.hashPrevBlock), newblockref.result.hash)
230-
assert len(block.vtx) >= 1
232+
233+
# Template block.vtx only contains a (dummy) coinbase
234+
assert_equal(len(block.vtx), 1)
235+
# Check that coinbase for an empty block doesn't have a witness")
236+
await self.build_coinbase_test(template, ctx, miniwallet, expect_witness=False)
237+
# Unless requested otherwise
238+
opts.alwaysAddCoinbaseCommitment = True
239+
template = mining.result.createNewBlock(ctx, opts)
240+
await self.build_coinbase_test(template, ctx, miniwallet, expect_witness=True)
241+
opts.alwaysAddCoinbaseCommitment = False
242+
231243
txfees = await template.result.getTxFees(ctx)
232244
assert_equal(len(txfees.result), 0)
233245
txsigops = await template.result.getTxSigops(ctx)
@@ -262,6 +274,7 @@ async def async_routine():
262274
template6 = await waitnext
263275
block4 = await self.parse_and_deserialize_block(template6, ctx)
264276
assert_equal(len(block4.vtx), 3)
277+
265278
self.log.debug("Wait for another, but time out, since the fee threshold is set now")
266279
template7 = await template6.result.waitNext(ctx, waitoptions)
267280
assert_equal(template7.to_dict(), {})
@@ -293,7 +306,7 @@ async def interrupt_wait():
293306
template = await mining.result.createNewBlock(ctx, opts)
294307
block = await self.parse_and_deserialize_block(template, ctx)
295308

296-
coinbase = await self.build_coinbase_test(template, ctx, miniwallet)
309+
coinbase = await self.build_coinbase_test(template, ctx, miniwallet, expect_witness=True)
297310

298311
# Reduce payout for balance comparison simplicity
299312
coinbase.vout[0].nValue = COIN

0 commit comments

Comments
 (0)