Skip to content

Commit 6b165f5

Browse files
committed
Merge bitcoin/bitcoin#31384: mining: bugfix: Fix duplicate coinbase tx weight reservation
386eecf doc: add release notes (ismaelsadeeq) 3eaa0a3 miner: init: add `-blockreservedweight` startup option (ismaelsadeeq) 777434a doc: rpc: improve `getmininginfo` help text (ismaelsadeeq) c8acd40 init: fail to start when `-blockmaxweight` exceeds `MAX_BLOCK_WEIGHT` (ismaelsadeeq) 5bb3163 test: add `-blockmaxweight` startup option functional test (ismaelsadeeq) 2c7d90a miner: bugfix: fix duplicate weight reservation in block assembler (ismaelsadeeq) Pull request description: * This PR attempts to fix the duplicate coinbase weight reservation issue we currently have. * Fixes #21950 We reserve 4000 weight units for coinbase transaction in `DEFAULT_BLOCK_MAX_WEIGHT` https://github.com/bitcoin/bitcoin/blob/7590e93bc73b3bbac641f05d490fd5c984156b33/src/policy/policy.h#L23 And also reserve additional `4000` weight units in the default `BlockCreationOptions` struct. https://github.com/bitcoin/bitcoin/blob/7590e93bc73b3bbac641f05d490fd5c984156b33/src/node/types.h#L36-L40 **Motivation** - This issue was first noticed during a review here bitcoin/bitcoin#11100 (comment)) - It was later reported in issue #21950. - I also came across the bug while writing a test for building the block template. I could not create a block template above `3,992,000` in the block assembler, and this was not documented anywhere. It took me a while to realize that we were reserving space for the coinbase transaction weight twice. --- This PR fixes this by consolidating the reservation to be in a single location in the codebase. This PR then adds a new startup option `-blockreservedweight` whose default is `8000` that can be used to lower or increase the block reserved weight for block header, txs count, coinbase tx. ACKs for top commit: Sjors: ACK 386eecf fjahr: Code review ACK 386eecf glozow: utACK 386eecf, nonblocking nits. I do think the release notes should be clarified more pinheadmz: ACK 386eecf Tree-SHA512: f27efa1da57947b7f4d42b9322b83d13afe73dd749dd9cac49360002824dd41c99a876a610554ac2d67bad7485020b9dcc423a8e6748fc79d6a10de6d4357d4c
2 parents 6a46be7 + 386eecf commit 6b165f5

File tree

11 files changed

+202
-19
lines changed

11 files changed

+202
-19
lines changed

doc/release-notes-31384.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- Node and Mining
2+
3+
---
4+
5+
- **PR #31384** fixed an issue where block reserved weight for fixed-size block header, transactions count,
6+
and coinbase transaction was done in two separate places.
7+
Before this pull request, the policy default for the maximum block weight was `3,996,000` WU, calculated by
8+
subtracting `4,000 WU` from the `4,000,000 WU` consensus limit to account for the fixed-size block header,
9+
transactions count, and coinbase transaction. During block assembly, Bitcoin Core clamped custom `-blockmaxweight`
10+
value to not be more than the policy default.
11+
12+
Additionally, the mining code added another `4,000 WU` to the initial reservation, reducing the effective block template
13+
size to `3,992,000 WU`.
14+
15+
Due to this issue, the total reserved weight was always `8,000 WU`, meaning that even when specifying a `-blockmaxweight`
16+
higher than the policy default, the actual block size never exceeded `3,992,000 WU`.
17+
18+
The fix consolidates the reservation into a single place and introduces a new startup option,
19+
`-blockreservedweight` (default: `8,000 WU`). This startup option specifies the reserved weight for
20+
the fixed-size block header, transactions count, and coinbase transaction.
21+
The default value of `-blockreservedweight` was chosen to preserve the previous behavior.
22+
23+
**Upgrade Note:** The default `-blockreservedweight` ensures backward compatibility for users who relied on the previous behavior.
24+
25+
Users who manually set `-blockmaxweight` to its maximum value of `4,000,000 WU` should be aware that this
26+
value previously had no effect since it was clamped to `3,996,000 WU`.
27+
28+
Users lowering `-blockreservedweight` should ensure that the total weight (for the block header, transaction count, and coinbase transaction)
29+
does not exceed the reduced value.
30+
31+
As a safety check, Bitcoin core will **fail to start** when `-blockreservedweight` init parameter value is lower than `2000` weight units.
32+
33+
Bitcoin Core will also **fail to start** if the `-blockmaxweight` or `-blockreservedweight` init parameter exceeds
34+
consensus limit of `4,000,000` weight units.

src/init.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <common/args.h>
2020
#include <common/system.h>
2121
#include <consensus/amount.h>
22+
#include <consensus/consensus.h>
2223
#include <deploymentstatus.h>
2324
#include <hash.h>
2425
#include <httprpc.h>
@@ -648,6 +649,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
648649

649650

650651
argsman.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
652+
argsman.AddArg("-blockreservedweight=<n>", strprintf("Reserve space for the fixed-size block header plus the largest coinbase transaction the mining software may add to the block. (default: %d).", DEFAULT_BLOCK_RESERVED_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
651653
argsman.AddArg("-blockmintxfee=<amt>", strprintf("Set lowest fee rate (in %s/kvB) for transactions to be included in block creation. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
652654
argsman.AddArg("-blockversion=<n>", "Override block version to test forking scenarios", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::BLOCK_CREATION);
653655

@@ -1015,6 +1017,23 @@ bool AppInitParameterInteraction(const ArgsManager& args)
10151017
}
10161018
}
10171019

1020+
if (args.IsArgSet("-blockmaxweight")) {
1021+
const auto max_block_weight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
1022+
if (max_block_weight > MAX_BLOCK_WEIGHT) {
1023+
return InitError(strprintf(_("Specified -blockmaxweight (%d) exceeds consensus maximum block weight (%d)"), max_block_weight, MAX_BLOCK_WEIGHT));
1024+
}
1025+
}
1026+
1027+
if (args.IsArgSet("-blockreservedweight")) {
1028+
const auto block_reserved_weight = args.GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT);
1029+
if (block_reserved_weight > MAX_BLOCK_WEIGHT) {
1030+
return InitError(strprintf(_("Specified -blockreservedweight (%d) exceeds consensus maximum block weight (%d)"), block_reserved_weight, MAX_BLOCK_WEIGHT));
1031+
}
1032+
if (block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
1033+
return InitError(strprintf(_("Specified -blockreservedweight (%d) is lower than minimum safety value of (%d)"), block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT));
1034+
}
1035+
}
1036+
10181037
nBytesPerSigOp = args.GetIntArg("-bytespersigop", nBytesPerSigOp);
10191038

10201039
if (!g_wallet_init_interface.ParameterInteraction()) return false;

src/ipc/capnp/mining.capnp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
3535

3636
struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
3737
useMempool @0 :Bool $Proxy.name("use_mempool");
38-
coinbaseMaxAdditionalWeight @1 :UInt64 $Proxy.name("coinbase_max_additional_weight");
38+
blockReservedWeight @1 :UInt64 $Proxy.name("block_reserved_weight");
3939
coinbaseOutputMaxAdditionalSigops @2 :UInt64 $Proxy.name("coinbase_output_max_additional_sigops");
4040
}
4141

src/node/miner.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,12 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
7474

7575
static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
7676
{
77-
Assert(options.coinbase_max_additional_weight <= DEFAULT_BLOCK_MAX_WEIGHT);
77+
Assert(options.block_reserved_weight <= MAX_BLOCK_WEIGHT);
78+
Assert(options.block_reserved_weight >= MINIMUM_BLOCK_RESERVED_WEIGHT);
7879
Assert(options.coinbase_output_max_additional_sigops <= MAX_BLOCK_SIGOPS_COST);
79-
// Limit weight to between coinbase_max_additional_weight and DEFAULT_BLOCK_MAX_WEIGHT for sanity:
80-
// Coinbase (reserved) outputs can safely exceed -blockmaxweight, but the rest of the block template will be empty.
81-
options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, options.coinbase_max_additional_weight, DEFAULT_BLOCK_MAX_WEIGHT);
80+
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
81+
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
82+
options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, options.block_reserved_weight, MAX_BLOCK_WEIGHT);
8283
return options;
8384
}
8485

@@ -98,14 +99,15 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
9899
if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
99100
}
100101
options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
102+
options.block_reserved_weight = args.GetIntArg("-blockreservedweight", options.block_reserved_weight);
101103
}
102104

103105
void BlockAssembler::resetBlock()
104106
{
105107
inBlock.clear();
106108

107-
// Reserve space for coinbase tx
108-
nBlockWeight = m_options.coinbase_max_additional_weight;
109+
// Reserve space for fixed-size block header, txs count, and coinbase tx.
110+
nBlockWeight = m_options.block_reserved_weight;
109111
nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops;
110112

111113
// These counters do not include coinbase tx
@@ -393,7 +395,7 @@ void BlockAssembler::addPackageTxs(int& nPackagesSelected, int& nDescendantsUpda
393395
++nConsecutiveFailed;
394396

395397
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight >
396-
m_options.nBlockMaxWeight - m_options.coinbase_max_additional_weight) {
398+
m_options.nBlockMaxWeight - m_options.block_reserved_weight) {
397399
// Give up if we're close to full and haven't succeeded in a while
398400
break;
399401
}

src/node/miner.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ class BlockAssembler
176176
/** Construct a new block template */
177177
std::unique_ptr<CBlockTemplate> CreateNewBlock();
178178

179+
/** The number of transactions in the last assembled block (excluding coinbase transaction) */
179180
inline static std::optional<int64_t> m_last_block_num_txs{};
181+
/** The weight of the last assembled block (including reserved weight for block header, txs count and coinbase tx) */
180182
inline static std::optional<int64_t> m_last_block_weight{};
181183

182184
private:

src/node/types.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define BITCOIN_NODE_TYPES_H
1515

1616
#include <cstddef>
17+
#include <policy/policy.h>
1718
#include <script/script.h>
1819

1920
namespace node {
@@ -34,11 +35,10 @@ struct BlockCreateOptions {
3435
*/
3536
bool use_mempool{true};
3637
/**
37-
* The maximum additional weight which the pool will add to the coinbase
38-
* scriptSig, witness and outputs. This must include any additional
39-
* weight needed for larger CompactSize encoded lengths.
38+
* The default reserved weight for the fixed-size block header,
39+
* transaction count and coinbase transaction.
4040
*/
41-
size_t coinbase_max_additional_weight{4000};
41+
size_t block_reserved_weight{DEFAULT_BLOCK_RESERVED_WEIGHT};
4242
/**
4343
* The maximum additional sigops which the pool will add in coinbase
4444
* transaction outputs.

src/policy/policy.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ class CFeeRate;
2020
class CScript;
2121

2222
/** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/
23-
static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT - 4000};
23+
static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT};
24+
/** Default for -blockreservedweight **/
25+
static constexpr unsigned int DEFAULT_BLOCK_RESERVED_WEIGHT{8000};
26+
/** This accounts for the block header, var_int encoding of the transaction count and a minimally viable
27+
* coinbase transaction. It adds an additional safety margin, because even with a thorough understanding
28+
* of block serialization, it's easy to make a costly mistake when trying to squeeze every last byte.
29+
* Setting a lower value is prevented at startup. */
30+
static constexpr unsigned int MINIMUM_BLOCK_RESERVED_WEIGHT{2000};
2431
/** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/
2532
static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1000};
2633
/** The maximum weight for transactions we're willing to relay/mine */

src/rpc/mining.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ static RPCHelpMan getmininginfo()
420420
RPCResult::Type::OBJ, "", "",
421421
{
422422
{RPCResult::Type::NUM, "blocks", "The current block"},
423-
{RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight of the last assembled block (only present if a block was ever assembled)"},
424-
{RPCResult::Type::NUM, "currentblocktx", /*optional=*/true, "The number of block transactions of the last assembled block (only present if a block was ever assembled)"},
423+
{RPCResult::Type::NUM, "currentblockweight", /*optional=*/true, "The block weight (including reserved weight for block header, txs count and coinbase tx) of the last assembled block (only present if a block was ever assembled)"},
424+
{RPCResult::Type::NUM, "currentblocktx", /*optional=*/true, "The number of block transactions (excluding coinbase) of the last assembled block (only present if a block was ever assembled)"},
425425
{RPCResult::Type::STR_HEX, "bits", "The current nBits, compact representation of the block difficulty target"},
426426
{RPCResult::Type::NUM, "difficulty", "The current difficulty"},
427427
{RPCResult::Type::STR_HEX, "target", "The current target"},

src/test/fuzz/mini_miner.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include <node/miner.h>
1515
#include <node/mini_miner.h>
16+
#include <node/types.h>
1617
#include <primitives/transaction.h>
1718
#include <random.h>
1819
#include <txmempool.h>
@@ -157,9 +158,10 @@ FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
157158
}
158159
}
159160

160-
// Stop if pool reaches DEFAULT_BLOCK_MAX_WEIGHT because BlockAssembler will stop when the
161+
const auto block_adjusted_max_weight = MAX_BLOCK_WEIGHT - DEFAULT_BLOCK_RESERVED_WEIGHT;
162+
// Stop if pool reaches block_adjusted_max_weight because BlockAssembler will stop when the
161163
// block template reaches that, but the MiniMiner will keep going.
162-
if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= DEFAULT_BLOCK_MAX_WEIGHT) break;
164+
if (pool.GetTotalTxSize() + GetVirtualTransactionSize(*tx) >= block_adjusted_max_weight) break;
163165
TestMemPoolEntryHelper entry;
164166
const CAmount fee{ConsumeMoney(fuzzed_data_provider, /*max=*/MAX_MONEY/100000)};
165167
assert(MoneyRange(fee));
@@ -181,7 +183,7 @@ FUZZ_TARGET(mini_miner_selection, .init = initialize_miner)
181183

182184
node::BlockAssembler::Options miner_options;
183185
miner_options.blockMinFeeRate = target_feerate;
184-
miner_options.nBlockMaxWeight = DEFAULT_BLOCK_MAX_WEIGHT;
186+
miner_options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
185187
miner_options.test_block_validity = false;
186188
miner_options.coinbase_output_script = CScript() << OP_0;
187189

0 commit comments

Comments
 (0)