miner: add block_max_weight to BlockCreateOptions
What changed, and why it matters
This commit refactors how Bitcoin Core's block builder (the miner) handles the maximum block weight setting. It replaces an older signed-size field with a new optional unsigned field and fixes a parsing quirk: a negative command-line value for -blockmaxweight is now treated as 0 before validation rejects it, instead of silently wrapping around to a huge positive number. The change is mostly a cleanup, but it removes a small overflow/underflow footgun in argument parsing.
No urgent action required. Treat as a hardening/refactoring change. Reviewers should verify that ClampOptions correctly handles the case where block_max_weight is less than block_reserved_weight and that downstream callers (including tests and RPC) set the new option appropriately. Fuzz test update is consistent with the refactor.
Security signals we found
Fixes signed-to-unsigned conversion/underflow in -blockmaxweight argument parsing
Replaces size_t with uint64_t for block weight accounting consistency
Adds optional block_max_weight to BlockCreateOptions, centralizing option handling
Does not expose new option to IPC clients, limiting remote attack surface
Evidence from the diff
The patch moves nBlockMaxWeight from BlockAssembler::Options into the shared BlockCreateOptions base as block_max_weight (std::optional
Changed components
src/node/miner.cppsrc/node/miner.hsrc/node/mining_types.hsrc/test/fuzz/tx_pool.cppInspect captured patch +18 / −11
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index c9a491ef..41c1d997 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -78,12 +78,12 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
{
- // Apply DEFAULT_BLOCK_RESERVED_WEIGHT when the caller left it unset.
- options.block_reserved_weight = std::clamp<size_t>(options.block_reserved_weight.value_or(DEFAULT_BLOCK_RESERVED_WEIGHT), MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
+ // Apply DEFAULT_BLOCK_RESERVED_WEIGHT and DEFAULT_BLOCK_MAX_WEIGHT when the caller left it unset.
+ options.block_reserved_weight = std::clamp<uint64_t>(options.block_reserved_weight.value_or(DEFAULT_BLOCK_RESERVED_WEIGHT), MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
- options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
+ options.block_max_weight = std::clamp<uint64_t>(options.block_max_weight.value_or(DEFAULT_BLOCK_MAX_WEIGHT), *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
return options;
}
@@ -98,13 +98,15 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool
void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options)
{
// Block resource limits
- options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight);
+ if (!options.block_max_weight) {
+ options.block_max_weight = args.GetArg<uint64_t>("-blockmaxweight");
+ }
if (const auto blockmintxfee{args.GetArg("-blockmintxfee")}) {
if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
}
options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
if (!options.block_reserved_weight) {
- options.block_reserved_weight = args.GetIntArg("-blockreservedweight");
+ options.block_reserved_weight = args.GetArg<uint64_t>("-blockreservedweight");
}
}
@@ -241,7 +243,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock()
bool BlockAssembler::TestChunkBlockLimits(FeePerWeight chunk_feerate, int64_t chunk_sigops_cost) const
{
- if (nBlockWeight + chunk_feerate.size >= m_options.nBlockMaxWeight) {
+ if (nBlockWeight + chunk_feerate.size >= m_options.block_max_weight) {
return false;
}
if (nBlockSigOpsCost + chunk_sigops_cost >= MAX_BLOCK_SIGOPS_COST) {
@@ -315,7 +317,7 @@ void BlockAssembler::addChunks()
++nConsecutiveFailed;
if (nConsecutiveFailed > MAX_CONSECUTIVE_FAILURES && nBlockWeight +
- BLOCK_FULL_ENOUGH_WEIGHT_DELTA > m_options.nBlockMaxWeight) {
+ BLOCK_FULL_ENOUGH_WEIGHT_DELTA > m_options.block_max_weight) {
// Give up if we're close to full and haven't succeeded in a while
return;
}
diff --git a/src/node/miner.h b/src/node/miner.h
index 73003d18..b579dc68 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -80,8 +80,6 @@ private:
public:
struct Options : BlockCreateOptions {
- // Configuration parameters for the block size
- size_t nBlockMaxWeight{DEFAULT_BLOCK_MAX_WEIGHT};
CFeeRate blockMinFeeRate{DEFAULT_BLOCK_MIN_TX_FEE};
bool print_modified_fee{DEFAULT_PRINT_MODIFIED_FEE};
};
diff --git a/src/node/mining_types.h b/src/node/mining_types.h
index 4a2ef052..30206c2e 100644
--- a/src/node/mining_types.h
+++ b/src/node/mining_types.h
@@ -40,7 +40,14 @@ struct BlockCreateOptions {
* Cap'n Proto IPC clients currently cannot leave this field unset, so they
* always provide a value.
*/
- std::optional<size_t> block_reserved_weight{};
+ std::optional<uint64_t> block_reserved_weight{};
+ /**
+ * Maximum block weight, defaults to -maxblockweight
+ *
+ * block_reserved_weight can safely exceed block_max_weight, but the rest of
+ * the block template will be empty.
+ */
+ std::optional<uint64_t> block_max_weight{};
/**
* The maximum additional sigops which the pool will add in coinbase
* transaction outputs.
diff --git a/src/test/fuzz/tx_pool.cpp b/src/test/fuzz/tx_pool.cpp
index 7daed723..9cada5df 100644
--- a/src/test/fuzz/tx_pool.cpp
+++ b/src/test/fuzz/tx_pool.cpp
@@ -94,7 +94,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, Cha
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
{
BlockAssembler::Options options;
- options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
+ options.block_max_weight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
options.blockMinFeeRate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
auto assembler = BlockAssembler{chainstate, &tx_pool, options};
auto block_template = assembler.CreateNewBlock();
Why this scored 27/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.