Skip to content

Commit c22002a

Browse files
committed
mining: add helper for block constraint checks
The maximum block size, reserved weight and additional sigops constraints can be provided as startup arguments and/or IPC options. Avoid duplication of these checks by adding helpers. Have IPC check all of these, instead of only block_reserved_weight. This removes the need for clamping options, so ClampOptions is renamed to ApplyBlockCreateOptions. Currently these exceptions can only be triggered by IPC (and test) code, because for the RPC path the minimum -blockreservedweight is enforced during node startup. If in the future RPC methods like getblocktemplate allow a custom value per request, they would trigger this exception and the RPC call would return an error - consistent with IPC behavior.
1 parent 19604a8 commit c22002a

File tree

6 files changed

+86
-34
lines changed

6 files changed

+86
-34
lines changed

src/node/interfaces.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
#include <any>
6868
#include <memory>
6969
#include <optional>
70-
#include <stdexcept>
7170
#include <utility>
7271

7372
#include <boost/signals2/signal.hpp>
@@ -980,16 +979,6 @@ class MinerImpl : public Mining
980979

981980
std::unique_ptr<BlockTemplate> createNewBlock(BlockCreateOptions options) override
982981
{
983-
// Reject too-small values instead of clamping so callers don't silently
984-
// end up mining with different options than requested. This matches the
985-
// behavior of the `-blockreservedweight` startup option, which rejects
986-
// values below MINIMUM_BLOCK_RESERVED_WEIGHT.
987-
if (options.block_reserved_weight && options.block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
988-
throw std::runtime_error(strprintf("block_reserved_weight (%zu) must be at least %u weight units",
989-
*options.block_reserved_weight,
990-
MINIMUM_BLOCK_RESERVED_WEIGHT));
991-
}
992-
993982
// Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
994983
if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
995984
ApplyMiningDefaults(m_node.mining_args, options);

src/node/miner.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <algorithm>
3131
#include <utility>
3232
#include <numeric>
33+
#include <stdexcept>
3334

3435
namespace node {
3536

@@ -76,16 +77,26 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
7677
block.hashMerkleRoot = BlockMerkleRoot(block);
7778
}
7879

79-
static BlockCreateOptions ClampOptions(BlockCreateOptions options)
80+
static BlockCreateOptions ApplyBlockCreateOptions(BlockCreateOptions options)
8081
{
8182
// Typically block_reserved_weight and block_max_weight are set by
8283
// ApplyMiningDefaults before the constructor calls this; value_or(DEFAULT_...)
8384
// only affects (test) call sites that don't go through the Mining interface.
84-
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);
85-
options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
86-
// Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
87-
// block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
88-
options.block_max_weight = std::clamp<size_t>(options.block_max_weight.value_or(DEFAULT_BLOCK_MAX_WEIGHT), *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
85+
const size_t reserved_weight{options.block_reserved_weight.value_or(DEFAULT_BLOCK_RESERVED_WEIGHT)};
86+
if (auto result{CheckBlockReservedWeight(reserved_weight)}; !result) {
87+
throw std::runtime_error("block_reserved_weight " + util::ErrorString(result).original);
88+
}
89+
options.block_reserved_weight = reserved_weight;
90+
if (auto result{CheckCoinbaseOutputMaxAdditionalSigops(options.coinbase_output_max_additional_sigops)}; !result) {
91+
throw std::runtime_error("coinbase_output_max_additional_sigops " + util::ErrorString(result).original);
92+
}
93+
const size_t max_weight{options.block_max_weight.value_or(DEFAULT_BLOCK_MAX_WEIGHT)};
94+
// block_reserved_weight can safely exceed block_max_weight, but the rest
95+
// of the block template will be empty.
96+
if (auto result{CheckBlockMaxWeight(max_weight)}; !result) {
97+
throw std::runtime_error("block_max_weight " + util::ErrorString(result).original);
98+
}
99+
options.block_max_weight = max_weight;
89100
return options;
90101
}
91102

@@ -97,7 +108,7 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate,
97108
m_mempool{options.use_mempool ? mempool : nullptr},
98109
m_chainstate{chainstate},
99110
m_mining_args{mining_args},
100-
m_options{ClampOptions(options)}
111+
m_options{ApplyBlockCreateOptions(options)}
101112
{
102113
}
103114

src/node/mining_args.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
#include <common/args.h>
88
#include <common/messages.h>
9-
#include <consensus/consensus.h>
109
#include <node/mining_types.h>
11-
#include <tinyformat.h>
1210
#include <util/moneystr.h>
1311
#include <util/translation.h>
1412

@@ -25,17 +23,14 @@ util::Result<void> ReadMiningArgs(const ArgsManager& args, MiningArgs& mining_ar
2523
}
2624

2725
const size_t max_block_weight = args.GetIntArg("-blockmaxweight", DEFAULT_BLOCK_MAX_WEIGHT);
28-
if (max_block_weight > MAX_BLOCK_WEIGHT) {
29-
return util::Error{strprintf(_("Specified -blockmaxweight (%d) exceeds consensus maximum block weight (%d)"), max_block_weight, MAX_BLOCK_WEIGHT)};
26+
if (auto result{CheckBlockMaxWeight(max_block_weight)}; !result) {
27+
return util::Error{Untranslated("Specified -blockmaxweight " + util::ErrorString(result).original)};
3028
}
3129
mining_args.default_block_max_weight = max_block_weight;
3230

3331
const size_t block_reserved_weight = args.GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT);
34-
if (block_reserved_weight > MAX_BLOCK_WEIGHT) {
35-
return util::Error{strprintf(_("Specified -blockreservedweight (%d) exceeds consensus maximum block weight (%d)"), block_reserved_weight, MAX_BLOCK_WEIGHT)};
36-
}
37-
if (block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
38-
return util::Error{strprintf(_("Specified -blockreservedweight (%d) is lower than minimum safety value of (%d)"), block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT)};
32+
if (auto result{CheckBlockReservedWeight(block_reserved_weight)}; !result) {
33+
return util::Error{Untranslated("Specified -blockreservedweight " + util::ErrorString(result).original)};
3934
}
4035
mining_args.default_block_reserved_weight = block_reserved_weight;
4136

src/node/mining_types.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616
#include <policy/policy.h>
1717
#include <primitives/transaction.h>
1818
#include <script/script.h>
19+
#include <tinyformat.h>
1920
#include <uint256.h>
21+
#include <util/result.h>
2022
#include <util/time.h>
23+
#include <util/translation.h>
2124

2225
#include <cstddef>
2326
#include <optional>
@@ -163,6 +166,40 @@ struct CoinbaseTx {
163166
uint32_t lock_time;
164167
};
165168

169+
/** Check that block_max_weight does not exceed consensus limits. */
170+
[[nodiscard]] inline util::Result<void> CheckBlockMaxWeight(size_t block_max_weight)
171+
{
172+
if (block_max_weight > MAX_BLOCK_WEIGHT) {
173+
return util::Error{Untranslated(strprintf("(%zu) exceeds consensus maximum block weight (%u)",
174+
block_max_weight, MAX_BLOCK_WEIGHT))};
175+
}
176+
return {};
177+
}
178+
179+
/** Check that block_reserved_weight is within allowed bounds. */
180+
[[nodiscard]] inline util::Result<void> CheckBlockReservedWeight(size_t block_reserved_weight)
181+
{
182+
if (block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
183+
return util::Error{Untranslated(strprintf("(%zu) is lower than minimum safety value of (%u)",
184+
block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT))};
185+
}
186+
if (block_reserved_weight > MAX_BLOCK_WEIGHT) {
187+
return util::Error{Untranslated(strprintf("(%zu) exceeds consensus maximum block weight (%u)",
188+
block_reserved_weight, MAX_BLOCK_WEIGHT))};
189+
}
190+
return {};
191+
}
192+
193+
/** Check that coinbase_output_max_additional_sigops does not exceed consensus limits. */
194+
[[nodiscard]] inline util::Result<void> CheckCoinbaseOutputMaxAdditionalSigops(size_t sigops)
195+
{
196+
if (sigops > MAX_BLOCK_SIGOPS_COST) {
197+
return util::Error{Untranslated(strprintf("(%zu) exceeds consensus maximum block sigops cost (%d)",
198+
sigops, MAX_BLOCK_SIGOPS_COST))};
199+
}
200+
return {};
201+
}
202+
166203
} // namespace node
167204

168205
#endif // BITCOIN_NODE_MINING_TYPES_H

test/functional/interface_ipc_mining.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from io import BytesIO
99
from test_framework.blocktools import NULL_OUTPOINT
1010
from test_framework.messages import (
11-
MAX_BLOCK_WEIGHT,
1211
CTransaction,
1312
CTxIn,
1413
CTxOut,
1514
CTxInWitness,
1615
ser_uint256,
1716
COIN,
17+
MAX_BLOCK_WEIGHT,
18+
MAX_BLOCK_SIGOPS_COST,
1819
)
1920
from test_framework.script import (
2021
CScript,
@@ -122,6 +123,15 @@ async def make_mining_ctx(self):
122123
mining = init.makeMining(ctx).result
123124
return ctx, mining
124125

126+
async def assert_create_fails(self, mining, opts, expected_msg):
127+
"""Assert that createNewBlock raises a remote exception with the expected message."""
128+
try:
129+
await mining.createNewBlock(opts)
130+
raise AssertionError("createNewBlock unexpectedly succeeded")
131+
except capnp.lib.capnp.KjException as e:
132+
assert_equal(e.description, f"remote exception: std::exception: {expected_msg}")
133+
assert_equal(e.type, "FAILED")
134+
125135
def run_mining_interface_test(self):
126136
"""Test Mining interface methods."""
127137
self.log.info("Running Mining interface test")
@@ -260,12 +270,21 @@ async def async_routine():
260270

261271
self.log.debug("Enforce minimum reserved weight for IPC clients too")
262272
opts.blockReservedWeight = 0
263-
try:
264-
await mining.createNewBlock(opts)
265-
raise AssertionError("createNewBlock unexpectedly succeeded")
266-
except capnp.lib.capnp.KjException as e:
267-
assert_equal(e.description, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units")
268-
assert_equal(e.type, "FAILED")
273+
await self.assert_create_fails(mining, opts,
274+
"block_reserved_weight (0) is lower than minimum safety value of (2000)")
275+
276+
self.log.debug("Enforce maximum reserved weight for IPC clients too")
277+
opts.blockReservedWeight = MAX_BLOCK_WEIGHT + 1
278+
await self.assert_create_fails(mining, opts,
279+
f"block_reserved_weight ({MAX_BLOCK_WEIGHT + 1}) exceeds consensus maximum block weight ({MAX_BLOCK_WEIGHT})")
280+
281+
self.log.debug("Enforce sigops limit for IPC clients too")
282+
opts.blockReservedWeight = 4000
283+
opts.coinbaseOutputMaxAdditionalSigops = MAX_BLOCK_SIGOPS_COST + 1
284+
await self.assert_create_fails(mining, opts,
285+
f"coinbase_output_max_additional_sigops ({MAX_BLOCK_SIGOPS_COST + 1}) exceeds consensus maximum block sigops cost ({MAX_BLOCK_SIGOPS_COST})")
286+
opts.coinbaseOutputMaxAdditionalSigops = 0
287+
269288

270289
asyncio.run(capnp.run(async_routine()))
271290

test/functional/test_framework/messages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
MAX_LOCATOR_SZ = 101
3838
MAX_BLOCK_WEIGHT = 4000000
39+
MAX_BLOCK_SIGOPS_COST = 80000
3940
DEFAULT_BLOCK_RESERVED_WEIGHT = 8000
4041
MINIMUM_BLOCK_RESERVED_WEIGHT = 2000
4142
MAX_BLOOM_FILTER_SIZE = 36000

0 commit comments

Comments
 (0)