mining: fix -blockreservedweight shadows IPC option #33965

pull Sjors wants to merge 3 commits into bitcoin:master from Sjors:2025/11/ipc-reserve changing 6 files +87 −15
  1. Sjors commented at 1:38 pm on November 28, 2025: member

    Also enforce MINIMUM_BLOCK_RESERVED_WEIGHT for IPC clients.

    The -blockreservedweight startup option should only affect RPC code, because IPC clients (currently) do not have a way to signal their intent to use the node default (the BlockCreateOptions struct defaults merely document a recommendation for client software).

    Before this PR however, if the user set -blockreservedweight then ApplyArgsManOptions would cause the block_reserved_weight option passed by IPC clients to be ignored. Users who don’t set this value were not affected.

    Fix this by making BlockCreateOptions::block_reserved_weight an std::optional.

    Internal interface users, such as the RPC call sites, don’t set a value so -blockreservedweight is used. Whereas IPC clients do set a value which is no longer ignored.

    Test coverage is added, with a preliminary commit that refactors the create_block_template and wait_next_template helpers.

    mining_basic.py already ensured -blockreservedweight is enforced by mining RPC methods. The second commit adds coverage for Mining interface IPC clients. It also verifies that -blockreservedweight has no effect on them.

    The third commit enforces MINIMUM_BLOCK_RESERVED_WEIGHT for IPC clients. Previously lower values were quietly clamped.


    Merge order preference: #34452 should ideally go first.

  2. DrahtBot added the label Mining on Nov 28, 2025
  3. DrahtBot commented at 1:38 pm on November 28, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33965.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK sedited, ryanofsky, ismaelsadeeq

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34184 (mining: add cooldown to createNewBlock() immediately after IBD by Sjors)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  4. Sjors force-pushed on Nov 28, 2025
  5. DrahtBot added the label CI failed on Nov 28, 2025
  6. DrahtBot commented at 1:49 pm on November 28, 2025: contributor

    🚧 At least one of the CI tasks failed. Task i686, no IPC: https://github.com/bitcoin/bitcoin/actions/runs/19765451883/job/56637015664 LLM reason (✨ experimental): Compile error: ISO C++ reorder-init-list warnings promoted to errors in src/rpc/mining.cpp, causing CI failure.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  7. in src/rpc/mining.cpp:882 in 46d901c253
    877@@ -871,7 +878,9 @@ static RPCHelpMan getblocktemplate()
    878         time_start = GetTime();
    879 
    880         // Create new block
    881-        block_template = miner.createNewBlock();
    882+        block_template = miner.createNewBlock({
    883+            .block_reserved_weight = size_t(node.args->GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT))
    


    Sjors commented at 1:52 pm on November 28, 2025:
    The big refactor in #33966 simplifies this to node.mining_args.default_block_reserved_weight. But using GetIntArg directly here is easier to backport.

    Sjors commented at 10:42 am on December 2, 2025:
    The latest push leaves RPC code untouched.
  8. DrahtBot removed the label CI failed on Nov 28, 2025
  9. ryanofsky commented at 8:22 pm on December 1, 2025: contributor

    Concept ACK. Would suggest updating -blockreservedweight documentation to say it only affects RPC mining clients, not IPC clients.

    I think implementation of this could be improved a bit, depending on if you think it would be useful for IPC clients to “have a way to signal their intent to use the node default”?

    If yes, would suggest just making the field std::optional:

     0--- a/src/node/miner.cpp
     1+++ b/src/node/miner.cpp
     2@@ -78,11 +78,11 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
     3 
     4 static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
     5 {
     6-    options.block_reserved_weight = std::clamp<size_t>(options.block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
     7+    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);
     8     options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
     9     // Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
    10     // block_reserved_weight can safely exceed -blockmaxweight, but the rest of the block template will be empty.
    11-    options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, options.block_reserved_weight, MAX_BLOCK_WEIGHT);
    12+    options.nBlockMaxWeight = std::clamp<size_t>(options.nBlockMaxWeight, *options.block_reserved_weight, MAX_BLOCK_WEIGHT);
    13     return options;
    14 }
    15 
    16@@ -102,13 +102,15 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
    17         if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
    18     }
    19     options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
    20-    options.block_reserved_weight = args.GetIntArg("-blockreservedweight", options.block_reserved_weight);
    21+    if (!options.block_reserved_weight) {
    22+        options.block_reserved_weight = args.GetIntArg("-blockreservedweight");
    23+    }
    24 }
    25 
    26 void BlockAssembler::resetBlock()
    27 {
    28     // Reserve space for fixed-size block header, txs count, and coinbase tx.
    29-    nBlockWeight = m_options.block_reserved_weight;
    30+    nBlockWeight = *m_options.block_reserved_weight;
    31     nBlockSigOpsCost = m_options.coinbase_output_max_additional_sigops;
    32 
    33     // These counters do not include coinbase tx
    34--- a/src/node/types.h
    35+++ b/src/node/types.h
    36@@ -42,7 +42,7 @@ struct BlockCreateOptions {
    37      * The default reserved weight for the fixed-size block header,
    38      * transaction count and coinbase transaction.
    39      */
    40-    size_t block_reserved_weight{DEFAULT_BLOCK_RESERVED_WEIGHT};
    41+    std::optional<size_t> block_reserved_weight{};
    42     /**
    43      * The maximum additional sigops which the pool will add in coinbase
    44      * transaction outputs.
    

    If no, would suggest having separate functions to read options which IPC clients can and can’t control:

      0--- a/src/node/interfaces.cpp
      1+++ b/src/node/interfaces.cpp
      2@@ -963,8 +963,12 @@ public:
      3         // Ensure m_tip_block is set so consumers of BlockTemplate can rely on that.
      4         if (!waitTipChanged(uint256::ZERO, MillisecondsDouble::max())) return {};
      5 
      6+        // Construct block assembler options from block create options and
      7+        // ArgsManager preferences. Intentionally do not call
      8+        // ReadBlockCreateArgs, so IPC create options will take precedence over
      9+        // ArgsManager values.
     10         BlockAssembler::Options assemble_options{options};
     11-        ApplyArgsManOptions(*Assert(m_node.args), assemble_options);
     12+        ReadBlockAssemblerArgs(*Assert(m_node.args), assemble_options);
     13         return std::make_unique<BlockTemplateImpl>(assemble_options, BlockAssembler{chainman().ActiveChainstate(), context()->mempool.get(), assemble_options}.CreateNewBlock(), m_node);
     14     }
     15 
     16--- a/src/node/miner.cpp
     17+++ b/src/node/miner.cpp
     18@@ -94,7 +94,7 @@ BlockAssembler::BlockAssembler(Chainstate& chainstate, const CTxMemPool* mempool
     19 {
     20 }
     21 
     22-void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& options)
     23+void ReadBlockAssemblerArgs(const ArgsManager& args, BlockAssembler::Options& options)
     24 {
     25     // Block resource limits
     26     options.nBlockMaxWeight = args.GetIntArg("-blockmaxweight", options.nBlockMaxWeight);
     27@@ -102,6 +102,10 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
     28         if (const auto parsed{ParseMoney(*blockmintxfee)}) options.blockMinFeeRate = CFeeRate{*parsed};
     29     }
     30     options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
     31+}
     32+
     33+void ReadBlockCreateArgs(const ArgsManager& args, BlockCreateOptions& options)
     34+{
     35     options.block_reserved_weight = args.GetIntArg("-blockreservedweight", options.block_reserved_weight);
     36 }
     37 
     38--- a/src/node/miner.h
     39+++ b/src/node/miner.h
     40@@ -131,8 +131,11 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam
     41 /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */
     42 void RegenerateCommitments(CBlock& block, ChainstateManager& chainman);
     43 
     44+/** Apply -blockreservedweight options from ArgsManager to BlockCreateOptions. */
     45+void ReadBlockCreateArgs(const ArgsManager& args, BlockCreateOptions& options);
     46+
     47 /** Apply -blockmintxfee and -blockmaxweight options from ArgsManager to BlockAssembler options. */
     48-void ApplyArgsManOptions(const ArgsManager& gArgs, BlockAssembler::Options& options);
     49+void ReadBlockAssemblerArgs(const ArgsManager& args, BlockAssembler::Options& options);
     50 
     51 /* Compute the block's merkle root, insert or replace the coinbase transaction and the merkle root into the block */
     52 void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce);
     53--- a/src/rpc/mining.cpp
     54+++ b/src/rpc/mining.cpp
     55@@ -161,11 +161,13 @@ static bool GenerateBlock(ChainstateManager& chainman, CBlock&& block, uint64_t&
     56     return true;
     57 }
     58 
     59-static UniValue generateBlocks(ChainstateManager& chainman, Mining& miner, const CScript& coinbase_output_script, int nGenerate, uint64_t nMaxTries)
     60+static UniValue generateBlocks(ChainstateManager& chainman, ArgsManager& args, Mining& miner, const CScript& coinbase_output_script, int nGenerate, uint64_t nMaxTries)
     61 {
     62     UniValue blockHashes(UniValue::VARR);
     63+    node::BlockCreateOptions options{ .coinbase_output_script = coinbase_output_script };
     64+    ReadBlockCreateArgs(args, options);
     65     while (nGenerate > 0 && !chainman.m_interrupt) {
     66-        std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock({ .coinbase_output_script = coinbase_output_script }));
     67+        std::unique_ptr<BlockTemplate> block_template(miner.createNewBlock(options));
     68         CHECK_NONFATAL(block_template);
     69 
     70         std::shared_ptr<const CBlock> block_out;
     71@@ -247,9 +249,10 @@ static RPCHelpMan generatetodescriptor()
     72 
     73     NodeContext& node = EnsureAnyNodeContext(request.context);
     74     Mining& miner = EnsureMining(node);
     75+    ArgsManager& args = EnsureArgsman(node);
     76     ChainstateManager& chainman = EnsureChainman(node);
     77 
     78-    return generateBlocks(chainman, miner, coinbase_output_script, num_blocks, max_tries);
     79+    return generateBlocks(chainman, args, miner, coinbase_output_script, num_blocks, max_tries);
     80 },
     81     };
     82 }
     83@@ -293,11 +296,12 @@ static RPCHelpMan generatetoaddress()
     84 
     85     NodeContext& node = EnsureAnyNodeContext(request.context);
     86     Mining& miner = EnsureMining(node);
     87+    ArgsManager& args = EnsureArgsman(node);
     88     ChainstateManager& chainman = EnsureChainman(node);
     89 
     90     CScript coinbase_output_script = GetScriptForDestination(destination);
     91 
     92-    return generateBlocks(chainman, miner, coinbase_output_script, num_blocks, max_tries);
     93+    return generateBlocks(chainman, args, miner, coinbase_output_script, num_blocks, max_tries);
     94 },
     95     };
     96 }
     97@@ -345,6 +349,7 @@ static RPCHelpMan generateblock()
     98 
     99     NodeContext& node = EnsureAnyNodeContext(request.context);
    100     Mining& miner = EnsureMining(node);
    101+    ArgsManager& args = EnsureArgsman(node);
    102     const CTxMemPool& mempool = EnsureMemPool(node);
    103 
    104     std::vector<CTransactionRef> txs;
    105@@ -374,9 +379,11 @@ static RPCHelpMan generateblock()
    106 
    107     ChainstateManager& chainman = EnsureChainman(node);
    108     {
    109+        node::BlockCreateOptions options{.use_mempool = false, .coinbase_output_script = coinbase_output_script};
    110+        ReadBlockCreateArgs(args, options);
    111         LOCK(chainman.GetMutex());
    112         {
    113-            std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script})};
    114+            std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock(options)};
    115             CHECK_NONFATAL(block_template);
    116 
    117             block = block_template->getBlock();
    118@@ -471,7 +478,8 @@ static RPCHelpMan getmininginfo()
    119     obj.pushKV("networkhashps",    getnetworkhashps().HandleRequest(request));
    120     obj.pushKV("pooledtx",         (uint64_t)mempool.size());
    121     BlockAssembler::Options assembler_options;
    122-    ApplyArgsManOptions(*node.args, assembler_options);
    123+    ReadBlockCreateArgs(*node.args, assembler_options);
    124+    ReadBlockAssemblerArgs(*node.args, assembler_options);
    125     obj.pushKV("blockmintxfee", ValueFromAmount(assembler_options.blockMinFeeRate.GetFeePerK()));
    126     obj.pushKV("chain", chainman.GetParams().GetChainTypeString());
    127 
    128@@ -871,7 +879,10 @@ static RPCHelpMan getblocktemplate()
    129         time_start = GetTime();
    130 
    131         // Create new block
    132-        block_template = miner.createNewBlock();
    133+        ArgsManager& args = EnsureArgsman(node);
    134+        node::BlockCreateOptions options;
    135+        ReadBlockCreateArgs(args, options);
    136+        block_template = miner.createNewBlock(options);
    137         CHECK_NONFATAL(block_template);
    138 
    139 
    140--- a/src/test/util/mining.cpp
    141+++ b/src/test/util/mining.cpp
    142@@ -138,6 +138,7 @@ std::shared_ptr<CBlock> PrepareBlock(const NodeContext& node, const CScript& coi
    143 {
    144     BlockAssembler::Options assembler_options;
    145     assembler_options.coinbase_output_script = coinbase_scriptPubKey;
    146-    ApplyArgsManOptions(*node.args, assembler_options);
    147+    ReadBlockCreateArgs(*node.args, assembler_options);
    148+    ReadBlockAssemblerArgs(*node.args, assembler_options);
    149     return PrepareBlock(node, assembler_options);
    150 }
    

    The current implementation 46d901c253107ffba14ddce6c3154b46bd471fc2 seems a little less ideal using repeated GetIntArg calls instead a function to read the options.

  10. Sjors force-pushed on Dec 2, 2025
  11. Sjors force-pushed on Dec 2, 2025
  12. Sjors commented at 10:41 am on December 2, 2025: member

    if you think it would be useful for IPC clients to “have a way to signal their intent to use the node default”

    sv2-tp always sets the value (based on coinbase_output_max_additional_size in CoinbaseOutputConstraints), so it wouldn’t benefit. But it does seem better to allow clients to omit this value.

    For that to work however, we’d also need to change mining.capnp, e.g.:

    0blockReservedWeight [@1](/bitcoin-bitcoin/contributor/1/) :UInt64 $Proxy.name("block_reserved_weight");
    1hasBlockReservedWeight [@3](/bitcoin-bitcoin/contributor/3/) :Bool = true $Proxy.skip;
    

    Adding the has field and having it default to true means it’s not a breaking change. But this doesn’t actually work :-)

    Also, if we really wanted to make this optional for IPC clients, I would prefer to add direct support for optionals to capnp files, as a breaking change.

    However, I did take your patch because it allows this PR to simplified:

    • internal callers will see coinbase_output_max_additional_size as optional and can omit it. That lets me avoid the four GetIntArg calls (and leave rpc/mining.cpp alone).
    • IPC callers don’t see it as optional and always provide a value (which we can change later)

    I added documentation to make this behavior clear.

  13. DrahtBot added the label CI failed on Dec 2, 2025
  14. Sjors force-pushed on Dec 2, 2025
  15. Sjors commented at 2:25 pm on December 2, 2025: member
    Rebased after #33591 because #33966 has a trivial merge conflict with it and I want to keep that cleanly based on this PR.
  16. DrahtBot removed the label CI failed on Dec 2, 2025
  17. in src/node/miner.cpp:84 in 1f2f4ab9d3 outdated
    77@@ -78,11 +78,14 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
    78 
    79 static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
    80 {
    81-    options.block_reserved_weight = std::clamp<size_t>(options.block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
    82+    // Typically block_reserved_weight is set by ApplyArgsManOptions before
    83+    // the constructor calls this; value_or(DEFAULT_BLOCK_RESERVED_WEIGHT) only
    84+    // affects (test) call sites that don't go through the Mining interface.
    85+    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);
    


    ryanofsky commented at 3:57 pm on December 2, 2025:

    In commit “mining: add getCoinbase()” (73553c809ef6517839c4d1b02b657e6fa401f8a8)

    Is there any particular reason to apply DEFAULT_BLOCK_RESERVED_WEIGHT in ApplyArgsManOptions? It would seem better to leave the option unset if the argument is unset, and only apply the default one place instead of two.

    Would suggest:

     0--- a/src/node/miner.cpp
     1+++ b/src/node/miner.cpp
     2@@ -78,9 +78,6 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
     3 
     4 static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
     5 {
     6-    // Typically block_reserved_weight is set by ApplyArgsManOptions before
     7-    // the constructor calls this; value_or(DEFAULT_BLOCK_RESERVED_WEIGHT) only
     8-    // affects (test) call sites that don't go through the Mining interface.
     9     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);
    10     options.coinbase_output_max_additional_sigops = std::clamp<size_t>(options.coinbase_output_max_additional_sigops, 0, MAX_BLOCK_SIGOPS_COST);
    11     // Limit weight to between block_reserved_weight and MAX_BLOCK_WEIGHT for sanity:
    12@@ -106,7 +103,7 @@ void ApplyArgsManOptions(const ArgsManager& args, BlockAssembler::Options& optio
    13     }
    14     options.print_modified_fee = args.GetBoolArg("-printpriority", options.print_modified_fee);
    15     if (!options.block_reserved_weight) {
    16-        options.block_reserved_weight = args.GetIntArg("-blockreservedweight", DEFAULT_BLOCK_RESERVED_WEIGHT);
    17+        options.block_reserved_weight = args.GetIntArg("-blockreservedweight");
    18     }
    19 }
    20 
    

    Sjors commented at 6:45 pm on December 2, 2025:

    Since -blockreservedweight has a default value, I don’t think the absence of this setting should behave any different from when the user manually sets it to the default. This makes that intention clear.

    It also makes the transition to ApplyMiningDefaults in https://github.com/bitcoin/bitcoin/pull/33966/commits/5f3e67efddd2a8a8743bb19765a5b38755d3f460 simpler, because default_block_max_weight and default_block_reserved_weight on MiningArgs are not optionals. That’s for the same reason: both -blockreservedweight and -maxblockweight have a default value.


    ryanofsky commented at 5:52 pm on December 3, 2025:

    re: #33965 (review)

    Yes that makes sense. I completely agree MiningArgs in #33966 should not use std::optional and that PR is correctly applying DEFAULT_BLOCK_RESERVED_WEIGHT in one place at the ArgsMan level, not the mining interface level.

    I just don’t think the implementation of this PR should be muddled in anticipation of #33966 before MiningArgs is introduced. In this PR, it makes sense to apply DEFAULT_BLOCK_RESERVED_WEIGHT at the mining interface level, not the ArgsMan level, so it is applied once instead of twice, and there are not separate test-only and nontest code paths. I also think that nullopt is a better way to represent an unset setting in a field that is std::optional.

    So I still think suggested diff above would be a nice simplification for this PR. Not important though, since #33966 should clean everything up in the end.


    Sjors commented at 12:53 pm on December 4, 2025:
    Ok, done and I rebased #33966 on that change.
  18. in src/node/types.h:46 in 1f2f4ab9d3
    41@@ -41,8 +42,10 @@ struct BlockCreateOptions {
    42     /**
    43      * The default reserved weight for the fixed-size block header,
    44      * transaction count and coinbase transaction.
    45+     *
    46+     * Not optional for IPC clients.
    


    ryanofsky commented at 4:04 pm on December 2, 2025:

    In commit “mining: fix -blockreservedweight shadows IPC option” (1f2f4ab9d3fbdd3071810fa78a17563df3f56f91)

    Might be good to clarify this to say something like “Cap’n Proto IPC clients do not currently have a way of leaving this field unset and will always provide a value.”

    Otherwise it sounds likes requiring a value is actually intentional. Also documenting an optional field as not optional sounds contradictory and is potentially confusing.


    Sjors commented at 6:48 pm on December 2, 2025:
    I took the suggested message.
  19. ryanofsky approved
  20. ryanofsky commented at 5:08 pm on December 2, 2025: contributor

    Code review ACK 1f2f4ab9d3fbdd3071810fa78a17563df3f56f91. This is a pretty clean way of letting the IPC reserved weight value take precedence over the -blockreservedweight, and a good first step towards making the IPC value optional in the future.

    re: #33965 (comment)

    For that to work however, we’d also need to change mining.capnp, e.g.:

    Yes, sorry, I should have mentioned that. I think it makes sense to add support in the c++ implementation first, and the capn’proto interface later. There are many ways the capnproto interface could be modified to support this and be backwards compatible if desired. I think the nicest way to support making the value optional might be to use a union like:

    0blockReservedWeight : union { none [@1](/bitcoin-bitcoin/contributor/1/) :Void; value [@2](/bitcoin-bitcoin/contributor/2/) :UInt64; };
    

    (Interestingly according to https://capnproto.org/language.html#unions, you can even convert a non-union field into a union field without breaking backwards compatibility, as long as the old field is the lowest-numbered field in the union. However, this numbering would also affect the union default value so probably not worth it.)

    Another way to support making the value optional would be to add a boolean has field like you suggested. Another way would be to “box” the value, putting it in a struct with a single field. Probably all approaches will require modifying libmultiprocess somehow, so would be better to implement in a separate PR.

  21. Sjors force-pushed on Dec 2, 2025
  22. in test/functional/interface_ipc.py:67 in 4d686142e0
    62@@ -57,6 +63,9 @@ def set_test_params(self):
    63 
    64     def setup_nodes(self):
    65         self.extra_init = [{"ipcbind": True}, {}]
    66+        # The -blockreservedweight startup option has no effect on IPC clients.
    67+        # Templates generated via RPC will be empty.
    


    ryanofsky commented at 5:59 pm on December 3, 2025:

    In commit “mining: fix -blockreservedweight shadows IPC option” (4d686142e079765cd31851481deb70659a9e9376)

    Maybe update the comment to say this is being set to a high value to confirm that the -blockreservedweight option has no effect (IIUC). Current comment raises question of why the test is going out of its way to set an option that has no effect.

  23. in test/functional/interface_ipc.py:225 in 4d686142e0
    220@@ -210,6 +221,13 @@ async def interrupt_wait():
    221             await interrupt_task
    222             assert_equal(result.to_dict(), {})
    223 
    224+            # Use absurdly large reserved weight to force an empty template
    225+            opts.blockReservedWeight = 4000000
    


    ryanofsky commented at 6:17 pm on December 3, 2025:

    In commit “mining: fix -blockreservedweight shadows IPC option” (4d686142e079765cd31851481deb70659a9e9376)

    Use MAX_BLOCK_WEIGHT constant?

  24. in test/functional/interface_ipc.py:183 in 4d686142e0 outdated
    178@@ -170,6 +179,8 @@ async def async_routine():
    179             waitnext = template2.result.waitNext(ctx, waitoptions)
    180             miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
    181             template4 = await waitnext
    182+            # Ensure it did not timeout
    183+            assert_not_equal(template4.to_dict(), {})
    


    ryanofsky commented at 6:20 pm on December 3, 2025:

    In commit “mining: fix -blockreservedweight shadows IPC option” (4d686142e079765cd31851481deb70659a9e9376)

    Note: I guess if it did timeout you would get a confusing server error about trying to call a method on a null capability, so this error should be clearer

  25. ryanofsky approved
  26. ryanofsky commented at 6:23 pm on December 3, 2025: contributor
    Code review ACK 4d686142e079765cd31851481deb70659a9e9376. Just updated a comment since last review. Still looks good! Only minor comments below, not important
  27. Sjors force-pushed on Dec 4, 2025
  28. ryanofsky approved
  29. ryanofsky commented at 5:10 pm on December 4, 2025: contributor

    Code review ACK 54d3054db129d4dd5aaf9faa3f6e6922159059b0. Just some more review suggestions applied since last review. This is a pretty clean fix preventing the -blockreservedweight configuration value from overriding the IPC requested value.

    The followup #33966 will make the code merging IPC options and configuration options more clear. And a different followup could give IPC clients ability to use the -blockreservedweight value instead of needing to provide their own value.

  30. DrahtBot added the label Needs rebase on Dec 16, 2025
  31. Sjors force-pushed on Dec 19, 2025
  32. Sjors commented at 7:49 am on December 19, 2025: member

    Rebased after #34003. I added an initial test refactor commit to have the {create_block,wait_next}_template helpers introduced in that PR to return None if there’s a timeout or failure.

    I also added a commit to enforce (rather than clamp) the minimum reserved block value, as noticed by @plebhash in https://github.com/stratum-mining/sv2-tp/pull/51#issuecomment-3670613634. I could make that it’s own PR but it’s rather trivial.

  33. DrahtBot removed the label Needs rebase on Dec 19, 2025
  34. in src/node/interfaces.cpp:966 in 6d7c3ee3fa
    962@@ -963,6 +963,11 @@ class MinerImpl : public Mining
    963 
    964     std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
    965     {
    966+        // Enfore minimum block_reserved_weight to avoid silently clamping it
    


    maflcko commented at 8:22 am on December 19, 2025:

    from the LLM:

    Enfore -> Enforce [misspelling in comment "Enfore minimum block_reserved_weight..." makes the word invalid]
    wait -> weight [comment "Enforce minimum reserved wait for IPC clients too" should say "reserved weight" to match intended meaning]
    

    Sjors commented at 9:01 am on December 19, 2025:
    How many LLM’s does it take to fix a typo? :-)
  35. Sjors force-pushed on Dec 19, 2025
  36. Sjors requested review from ryanofsky on Dec 30, 2025
  37. Sjors commented at 2:53 am on December 30, 2025: member
    @plebhash maybe you can you give this a quick test against the SRI integration? Without this PR, if you set -blockreservedweigh it will ignore the corresponding IPC argument.
  38. plebhash commented at 11:10 pm on December 30, 2025: none

    @plebhash maybe you can you give this a quick test against the SRI integration? Without this PR, if you set -blockreservedweigh it will ignore the corresponding IPC argument.

    what methodology would you suggest for this?

    I assume we want to mine a block that’s filled up to its capacity, and then check the consumed weight to see how far it is from the consensus limit, right?

    I’m trying to leverage integration_tests_sv2 crate, which allows us to write some Rust code to interact with a regtest node

    but I’m having a really hard time to reliably populate the mempool with enough transactions to fill an entire block (at least in a test that wouldn’t take hours to execute)

  39. Sjors commented at 2:39 am on December 31, 2025: member
    @plebhash in the functional test for this PR I simply set -blockreservedweight=4000000. That should result in empty blocks. Then just use a normal value in the IPC calls and the blocks will still be empty (before this PR).
  40. plebhash commented at 5:00 pm on December 31, 2025: none

    ok I wrote an Integration Test with the following methodology:

    • Bitcoin Core in regtest mode with the following variations
      • v30 vs this PR
      • with vs without -blockreservedweight=4000000
    • Pool (which connects to Bitcoin Core via IPC)
    • Sniffer (between Pool and tProxy)
    • tProxy
    • Sv1 CPU miner

    we loop while creating mempool transactions and monitoring the Extended Jobs sent by Pool

    the test only exits once we see an Extended Job with merkle_path.len() > 0 (✅)… if the test never returns (❌), we conclude that all templates sent by Bitcoin Core are empty

    note: Pool originally generates CoinbaseOutputConstraints.coinbase_output_max_additional_size = 31

    but internally, its BitcoinCoreSv2 abstraction multiplies it by 4 and then clamps the result to 2000 while crafting the CreateNewBlock IPC request… so I also added a variation on the tests with and without clamping this to 2000


    here are the results:

    WITH clamping 4 * CoinbaseOutputConstraints.coinbase_output_max_additional_size to 2000 while sending CreateNewBlock:

    v30 #33965
    WITH -blockreservedweight=4000000 only sends EMPTY templates ❌ sends NON-EMPTY template ✅
    WITHOUT -blockreservedweight=4000000 sends NON-EMPTY template ✅ sends NON-EMPTY template ✅

    WITHOUT clamping 4 * CoinbaseOutputConstraints.coinbase_output_max_additional_size to 2000 while sending CreateNewBlock:

    v30 #33965
    with -blockreservedweight=4000000 only sends EMPTY templates ❌ CreateNewBlock fails 💥
    without -blockreservedweight=4000000 sends NON-EMPTY template ✅ CreateNewBlock fails 💥

    both errors for CreateNewBlock were:

    02025-12-31T16:42:09.307986Z ERROR bitcoin_core_sv2: Failed to get template IPC client result: Message contains null capability pointer.
    12025-12-31T16:42:09.308014Z ERROR bitcoin_core_sv2: Failed to create new template IPC client: CapnpError(Error { kind: MessageContainsNullCapabilityPointer, extra: "" })
    

    this makes me conclude that Bitcoin Core will not accept a CreateNewBlock with set_block_reserved_weight < 2000, which feels aligned with the rationale behind https://github.com/stratum-mining/sv2-tp/pull/79


    the test was saved on branch 2025-12-31-test-bitcoin-core-33965 of my fork, with the following commits:

    the integration test can be executed with:

    0git clone https://github.com/plebhash/sv2-apps -b 2025-12-31-test-bitcoin-core-33965
    1cd sv2-apps/integration-tests
    2cargo t test_33965 -- --nocapture
    

    (I actually recommend cargo nextest run test_33965 --nocapture because it makes it easier to kill the tests without leaving zombie processes behind, but it’s optional)

  41. Sjors commented at 3:21 am on January 1, 2026: member

    @plebhash thanks for testing! So it looks like this PR works.

    this makes me conclude that Bitcoin Core will not accept a CreateNewBlock with set_block_reserved_weight < 2000, which feels aligned with the rationale behind stratum-mining/sv2-tp#79

    Good to know that it’s not quietly increasing the value, but rather just fails. Even though the error could be better.

  42. in src/node/types.h:44 in 31325c1782
    40@@ -41,7 +41,7 @@ struct BlockCreateOptions {
    41     bool use_mempool{true};
    42     /**
    43      * The default reserved weight for the fixed-size block header,
    44-     * transaction count and coinbase transaction.
    45+     * transaction count and coinbase transaction. Minimum: 2000 weight units.
    


    ryanofsky commented at 7:40 pm on January 7, 2026:

    In commit “mining: fix -blockreservedweight shadows IPC option” (336826ad0e70978783dcc0777ad1f01d3dbc1698)

    Maybe update the comment to say “2000 weight units (MINIMUM_BLOCK_RESERVED_WEIGHT)”. This would be helpful when grepping the constant and seeing how it is used & what may need to be updated if it changes.

  43. in src/node/interfaces.cpp:969 in 31325c1782
    962@@ -963,6 +963,11 @@ class MinerImpl : public Mining
    963 
    964     std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
    965     {
    966+        // Enforce minimum block_reserved_weight to avoid silently clamping it
    967+        if (options.block_reserved_weight && options.block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
    968+            return {};
    969+        }
    


    ryanofsky commented at 7:53 pm on January 7, 2026:

    In commit “mining: fix -blockreservedweight shadows IPC option” (336826ad0e70978783dcc0777ad1f01d3dbc1698)

    It would seem better to raise an exception with an error message here instead of returning null, since this probably indicates a programming error, and it would be helpful to callers to know what specifically is wrong. (Also, this method is only currently documented to return null on shutdown.)

    Separately, it would also would be good to enforce this limit in the mining code instead of the interfaces code, so all the limits are enforced in one place. But this could be something for the followup #33966


    Sjors commented at 3:10 am on January 8, 2026:
    Added the exception. Followup is implemented in the other PR.
  44. in test/functional/interface_ipc.py:235 in 336826ad0e
    230@@ -222,6 +231,8 @@ async def async_routine():
    231                 template4 = await wait_and_do(
    232                     wait_next_template(template2, stack, ctx, waitoptions),
    233                     lambda: miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
    234+                # Before bitcoin/bitcoin#33965 this would timeout due to -blockreservedweight
    235+                # shadowing our waitoptions.blockReservedWeight.
    


    ryanofsky commented at 8:05 pm on January 7, 2026:

    In commit “mining: fix -blockreservedweight shadows IPC option” (336826ad0e70978783dcc0777ad1f01d3dbc1698)

    Would suggest deleting this comment because it’s confusing to reference past behavior here. Instead I think it would be better to expand the “Set an absurd reserved weight” comment above. It can say the test confirms that the -blockreservedweight value is ignored by sending a transaction that would not fit with in a template with a huge reserved weight, and confirming the transaction does appear.


    Sjors commented at 3:10 am on January 8, 2026:
    Done, and expanded the comment at the top.
  45. in src/node/interfaces.cpp:966 in 31325c1782
    962@@ -963,6 +963,11 @@ class MinerImpl : public Mining
    963 
    964     std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
    965     {
    966+        // Enforce minimum block_reserved_weight to avoid silently clamping it
    


    ryanofsky commented at 8:16 pm on January 7, 2026:

    In commit “mining: enforce minimum reserved weight for IPC” (31325c1782e542601b3de8bd8bafe0af372830e9)

    Could comment mention why this wants to reject a lower value instead of clamping it? I saw previous discussion https://github.com/stratum-mining/sv2-tp/pull/51 but couldn’t really follow it.

    I would agree in general explicitly enforcing a limit is better than silently changing a value, but was wondering if there’s some more specific problem which led to this change?


    Sjors commented at 2:33 am on January 8, 2026:

    I initially thought that block_reserved_weight wasn’t clamped via IPC, and that there was no minimum enforced. But now I think it’s better to just enforce the same limit as we do for -blockreservedweight (and do so through an error, not clamping).

    I guess the specific problem was confusion over the different behaviours.

  46. ryanofsky approved
  47. ryanofsky commented at 8:28 pm on January 7, 2026: contributor

    Code review ACK 31325c1782e542601b3de8bd8bafe0af372830e9

    Many changes since last review.

    Main code change in second commit is a straightforward fix using std::optional to prevent ApplyArgsManOptions from overriding the block_reserved_weight value provided by IPC and ignoring it.

    The test improvements in the first commit are also nice, and the change in the third commit to enforce the MINIMUM_BLOCK_RESERVED_WEIGHT instead of silently clamping it seem good.

    I left some suggestions below but none are important.

  48. Sjors force-pushed on Jan 8, 2026
  49. Sjors force-pushed on Jan 8, 2026
  50. Sjors commented at 3:19 am on January 8, 2026: member

    Addressed comments and also rebased (needed for #33966).

    Even though the error could be better. @plebhash it should throw a more useful error now

  51. DrahtBot added the label CI failed on Jan 8, 2026
  52. DrahtBot removed the label CI failed on Jan 8, 2026
  53. in test/functional/interface_ipc.py:281 in d3db528598
    276+                self.log.debug("Enforce minimum reserved weight for IPC clients too")
    277+                opts.blockReservedWeight = 0
    278+                try:
    279+                    await mining.createNewBlock(opts)
    280+                    raise AssertionError("createNewBlock unexpectedly succeeded")
    281+                except Exception as e:
    


    ryanofsky commented at 12:19 pm on January 8, 2026:

    In commit “mining: enforce minimum reserved weight for IPC” (d3db5285989f7b3fef7436a37ce928065be2804e)

    Might be nice to make exception more specific to expected error like:

     0--- a/test/functional/interface_ipc.py
     1+++ b/test/functional/interface_ipc.py
     2@@ -278,8 +278,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
     3                 try:
     4                     await mining.createNewBlock(opts)
     5                     raise AssertionError("createNewBlock unexpectedly succeeded")
     6-                except Exception as e:
     7-                    assert "must be at least" in str(e)
     8+                except capnp.lib.capnp.KjException as e:
     9+                    assert_equal(e.description, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units")
    10+                    assert_equal(e.type, "FAILED")
    11 
    12                 # Restore opts
    13                 opts.blockReservedWeight = 4000
    

    Sjors commented at 2:04 am on January 9, 2026:
    Done
  54. ryanofsky approved
  55. ryanofsky commented at 12:24 pm on January 8, 2026: contributor
    Code review ACK d3db5285989f7b3fef7436a37ce928065be2804e just making reserved weight check into an exception and improving comments since last review. Thanks for the updates!
  56. Sjors force-pushed on Jan 9, 2026
  57. ryanofsky approved
  58. ryanofsky commented at 12:28 pm on January 13, 2026: contributor
    Code review ACK d443301666ba4ad4fbf5388c85862a6cae290d53, just catching more specific python exception in test since last review
  59. DrahtBot added the label Needs rebase on Jan 13, 2026
  60. Sjors force-pushed on Jan 14, 2026
  61. Sjors commented at 9:27 am on January 14, 2026: member

    Rebased after:

    • #33819
    • just some include / import conflicts
  62. DrahtBot removed the label Needs rebase on Jan 14, 2026
  63. ryanofsky approved
  64. ryanofsky commented at 2:59 pm on January 14, 2026: contributor
    Code review ACK 8bc1cb77fada68796c9599a04a66025a4b53ddab, just rebased with include/import changes since last review
  65. ryanofsky requested review from ismaelsadeeq on Jan 15, 2026
  66. in src/node/miner.cpp:114 in f69320d327 outdated
    111 
    112 void BlockAssembler::resetBlock()
    113 {
    114     // Reserve space for fixed-size block header, txs count, and coinbase tx.
    115-    nBlockWeight = m_options.block_reserved_weight;
    116+    nBlockWeight = *m_options.block_reserved_weight;
    


    sedited commented at 9:31 am on January 31, 2026:
    Nit: Should we sprinkle in an Assert( here?

    Sjors commented at 12:23 pm on February 7, 2026:
    Sprinkled.
  67. in src/node/interfaces.cpp:977 in 8bc1cb77fa outdated
    969@@ -969,6 +970,16 @@ class MinerImpl : public Mining
    970 
    971     std::unique_ptr<BlockTemplate> createNewBlock(const BlockCreateOptions& options) override
    972     {
    973+        // Reject too-small values instead of clamping so callers don't silently
    974+        // end up mining with different options than requested. This matches the
    975+        // behavior of the `-blockreservedweight` startup option, which rejects
    976+        // values below MINIMUM_BLOCK_RESERVED_WEIGHT.
    977+        if (options.block_reserved_weight && options.block_reserved_weight < MINIMUM_BLOCK_RESERVED_WEIGHT) {
    


    sedited commented at 9:40 am on January 31, 2026:
    Just a question: Is there a good reason to only enforce the minimum here?

    ismaelsadeeq commented at 3:15 pm on February 2, 2026:
    Agreed, since the rationale is to match the startup option checks, it should be verified as well.

    ismaelsadeeq commented at 3:17 pm on February 2, 2026:

    In “mining: enforce minimum reserved weight for IPC” 8bc1cb77fada68796c9599a04a66025a4b53ddab

    The interface implementation has a goal of not having implementation code in here, since we are doing this, perhaps it’s time to have a wrapper for all this.

    which should include https://github.com/bitcoin/bitcoin/pull/33965/changes/8bc1cb77fada68796c9599a04a66025a4b53ddab#r2749269557 suggestion and any other future additions?


    ryanofsky commented at 7:02 pm on February 5, 2026:

    re: #33965 (review)

    Agreed, since the rationale is to match the startup option checks, it should be verified as well.

    Maybe this check could be expanded but followup #33966 should unify this with startup checks in init.cpp:

    https://github.com/bitcoin/bitcoin/blob/28d860788286ec31981f5509a8cbe6a9ba4cddc5/src/init.cpp#L1071-L1077


    ryanofsky commented at 7:05 pm on February 5, 2026:

    re: #33965 (review)

    The interface implementation has a goal of not having implementation code in here, since we are doing this, perhaps it’s time to have a wrapper for all this.

    This should be happening in followup #33966 which unifies the startup & ipc checks.


    Sjors commented at 12:22 pm on February 7, 2026:
    I’ll look into this for the followup.

    ismaelsadeeq commented at 3:35 pm on February 11, 2026:
    nice

    Sjors commented at 11:32 am on February 12, 2026:
    Added maximum check in #33966.
  68. sedited approved
  69. sedited commented at 9:40 am on January 31, 2026: contributor
    ACK 8bc1cb77fada68796c9599a04a66025a4b53ddab
  70. in test/functional/interface_ipc.py:58 in 8bf30b3713
    54@@ -55,11 +55,17 @@ async def destroying(obj, ctx):
    55 
    56 async def create_block_template(mining, stack, ctx, opts):
    57     """Call mining.createNewBlock() and return template, then call template.destroy() when stack exits."""
    58-    return await stack.enter_async_context(destroying((await mining.createNewBlock(opts)).result, ctx))
    


    ismaelsadeeq commented at 1:27 pm on February 2, 2026:

    In “test: have {create_block,wait_next}_template return None” 8bf30b371379532a4d2c8e7414a1595a89002c71

    Is there a realistic scenario where the template returned by create_block_template is None?


    ryanofsky commented at 6:58 pm on February 5, 2026:

    re: #33965 (review)

    Is there a realistic scenario where the template returned by create_block_template is None?

    This could happen either when the node is shutting down of if the future interrupt method from #34184 is called

  71. in src/node/miner.cpp:81 in f69320d327 outdated
    77@@ -78,11 +78,12 @@ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman)
    78 
    79 static BlockAssembler::Options ClampOptions(BlockAssembler::Options options)
    80 {
    81-    options.block_reserved_weight = std::clamp<size_t>(options.block_reserved_weight, MINIMUM_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT);
    82+    // Apply DEFAULT_BLOCK_RESERVED_WEIGHT when the caller left it unset.
    


    ismaelsadeeq commented at 2:23 pm on February 2, 2026:

    In “mining: fix -blockreservedweight shadows IPC option” f69320d3279e2abe1e5d47f496ad54deb4c20fcf

    Why make the distinction between RPC and IPC clients?

    The narrative of this commit should rather be future proof and changed to: it can be overridded by runtime via the IPC or RPC.


    ryanofsky commented at 6:54 pm on February 5, 2026:

    re: #33965 (review)

    In “mining: fix -blockreservedweight shadows IPC option” f69320d

    Why make the distinction between RPC and IPC clients?

    The narrative of this commit should rather be future proof and changed to: it can be overridded by runtime via the IPC or RPC.

    The commit description is just describing what the commit is changing, which is only IPC clients. The IPC and RPC interfaces are different, with IPC clients currently required to specify block_reserved_weight and RPC clients not able to specify it at all.

    RPC behavior is unchanged by this commit. IPC behavior is just being changed so the IPC value is applied instead of ignored when the -blockreservedweight config option is used.

  72. in test/functional/interface_ipc.py:133 in f69320d327
    125@@ -125,6 +126,11 @@ def set_test_params(self):
    126 
    127     def setup_nodes(self):
    128         self.extra_init = [{"ipcbind": True}, {}]
    129+        # Set an absurd reserved weight. `-blockreservedweight` is RPC-only, so
    130+        # with this setting RPC templates would be empty. IPC clients set
    131+        # blockReservedWeight per template request and are unaffected; later in
    132+        # the test the IPC template includes a mempool transaction.
    133+        self.extra_args =[{f"-blockreservedweight={MAX_BLOCK_WEIGHT}"}, {}]
    


    ismaelsadeeq commented at 3:10 pm on February 2, 2026:

    In “mining: fix -blockreservedweight shadows IPC option” f69320d3279e2abe1e5d47f496ad54deb4c20fcf

    This seems like a weird way to test this. It would be better if we didn’t generalise here and had a separate subtest for this.

      0diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
      1index 02f3e0dd35d..677244e1a44 100755
      2--- a/test/functional/interface_ipc.py
      3+++ b/test/functional/interface_ipc.py
      4@@ -126,11 +126,6 @@ class IPCInterfaceTest(BitcoinTestFramework):
      5
      6     def setup_nodes(self):
      7         self.extra_init = [{"ipcbind": True}, {}]
      8-        # Set an absurd reserved weight. `-blockreservedweight` is RPC-only, so
      9-        # with this setting RPC templates would be empty. IPC clients set
     10-        # blockReservedWeight per template request and are unaffected; later in
     11-        # the test the IPC template includes a mempool transaction.
     12-        self.extra_args =[{f"-blockreservedweight={MAX_BLOCK_WEIGHT}"}, {}]
     13         super().setup_nodes()
     14         # Use this function to also load the capnp modules (we cannot use set_test_params for this,
     15         # as it is being called before knowing whether capnp is available).
     16@@ -250,7 +245,6 @@ class IPCInterfaceTest(BitcoinTestFramework):
     17         block_hash_size = 32
     18         block_header_size = 80
     19         timeout = 1000.0 # 1000 milliseconds
     20-        miniwallet = MiniWallet(self.nodes[0])
     21
     22         async def async_routine():
     23             ctx, init = await self.make_capnp_init_ctx()
     24@@ -318,7 +312,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
     25                 self.log.debug("Wait for another, get one after increase in fees in the mempool")
     26                 template4 = await wait_and_do(
     27                     wait_next_template(template2, stack, ctx, waitoptions),
     28-                    lambda: miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
     29+                    lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
     30                 assert template4 is not None
     31                 block3 = await self.parse_and_deserialize_block(template4, ctx)
     32                 assert_equal(len(block3.vtx), 2)
     33@@ -334,7 +328,7 @@ class IPCInterfaceTest(BitcoinTestFramework):
     34                 self.log.debug("Wait for another, get one after increase in fees in the mempool")
     35                 template6 = await wait_and_do(
     36                     wait_next_template(template5, stack, ctx, waitoptions),
     37-                    lambda: miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
     38+                    lambda: self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
     39                 assert template6 is not None
     40                 block4 = await self.parse_and_deserialize_block(template6, ctx)
     41                 assert_equal(len(block4.vtx), 3)
     42@@ -352,21 +346,12 @@ class IPCInterfaceTest(BitcoinTestFramework):
     43                     assert template7 is None
     44                 await wait_and_do(wait_for_block(), template6.interruptWait())
     45
     46-                self.log.debug("Use absurdly large reserved weight to force an empty template")
     47-                opts.blockReservedWeight = MAX_BLOCK_WEIGHT
     48-                empty_template = await create_block_template(mining, stack, ctx, opts)
     49-                assert empty_template is not None
     50-                block = await self.parse_and_deserialize_block(empty_template, ctx)
     51-                assert_equal(len(block.vtx), 1)
     52-                # Restore opts
     53-                opts.blockReservedWeight = 4000
     54-
     55             current_block_height = self.nodes[0].getchaintips()[0]["height"]
     56             check_opts = self.capnp_modules['mining'].BlockCheckOptions()
     57             async with destroying((await mining.createNewBlock(opts)).result, ctx) as template:
     58                 block = await self.parse_and_deserialize_block(template, ctx)
     59-                balance = miniwallet.get_balance()
     60-                coinbase = await self.build_coinbase_test(template, ctx, miniwallet)
     61+                balance = self.miniwallet.get_balance()
     62+                coinbase = await self.build_coinbase_test(template, ctx, self.miniwallet)
     63                 # Reduce payout for balance comparison simplicity
     64                 coinbase.vout[0].nValue = COIN
     65                 block.vtx[0] = coinbase
     66@@ -414,8 +399,8 @@ class IPCInterfaceTest(BitcoinTestFramework):
     67             # Check that the other node accepts the block
     68             assert_equal(self.nodes[0].getchaintips()[0], self.nodes[1].getchaintips()[0])
     69
     70-            miniwallet.rescan_utxos()
     71-            assert_equal(miniwallet.get_balance(), balance + 1)
     72+            self.miniwallet.rescan_utxos()
     73+            assert_equal(self.miniwallet.get_balance(), balance + 1)
     74             self.log.debug("Check block should fail now, since it is a duplicate")
     75             check = await mining.checkBlock(block.serialize(), check_opts)
     76             assert_equal(check.result, False)
     77@@ -423,9 +408,40 @@ class IPCInterfaceTest(BitcoinTestFramework):
     78
     79         asyncio.run(capnp.run(async_routine()))
     80
     81+    def run_ipc_option_test(self):
     82+        self.log.debug("Test that IPC blockReservedReight runtime value will override startup -blockreservedweight")
     83+        # The absurd blockreservedweight startup option should not force us to have empty blocks when
     84+        # a lower value is added is passed via the IPC.
     85+        self.restart_node(0, extra_args=[f"-blockreservedweight={MAX_BLOCK_WEIGHT}"])
     86+        async def async_routine():
     87+            ctx, init = await self.make_capnp_init_ctx()
     88+            mining = init.makeMining(ctx).result
     89+            async with AsyncExitStack() as stack:
     90+                opts = self.capnp_modules['mining'].BlockCreateOptions()
     91+                opts.useMempool = True
     92+                opts.blockReservedWeight = 4000
     93+                opts.coinbaseOutputMaxAdditionalSigops = 0
     94+                self.miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
     95+                template = await create_block_template(mining, stack, ctx, opts)
     96+                assert template is not None
     97+                block = await self.parse_and_deserialize_block(template, ctx)
     98+                assert_equal(len(block.vtx), 2)
     99+                self.log.debug("Use absurdly large reserved weight to force an empty template")
    100+                opts.blockReservedWeight = MAX_BLOCK_WEIGHT
    101+                empty_template = await create_block_template(mining, stack, ctx, opts)
    102+                assert empty_template is not None
    103+                block = await self.parse_and_deserialize_block(empty_template, ctx)
    104+                assert_equal(len(block.vtx), 1)
    105+
    106+        asyncio.run(capnp.run(async_routine()))
    107+
    108+
    109     def run_test(self):
    110+        self.miniwallet = MiniWallet(self.nodes[0])
    111         self.run_echo_test()
    112         self.run_mining_test()
    113+        self.run_ipc_option_test()
    114+
    115
    116 if __name__ == '__main__':
    117     IPCInterfaceTest(__file__).main()
    

    ryanofsky commented at 6:55 pm on February 5, 2026:

    re: #33965 (review)

    Agree it would be nice to use a separate test


    Sjors commented at 12:51 pm on February 7, 2026:
    It’s in the spirit of #34452 to continue to split this, so I added run_ipc_option_override_test.
  73. in src/node/types.h:49 in f69320d327
    43@@ -44,8 +44,11 @@ struct BlockCreateOptions {
    44     /**
    45      * The default reserved weight for the fixed-size block header,
    46      * transaction count and coinbase transaction.
    47+     *
    48+     * Cap'n Proto IPC clients do not currently have a way of leaving this field
    49+     * unset and will always provide a value.
    


    ismaelsadeeq commented at 3:35 pm on February 2, 2026:
    In “mining: fix -blockreservedweight shadows IPC option” f69320d3279e2abe1e5d47f496ad54deb4c20fcf I think you should add a comment documenting the new behaviour “Providing a value to block_reserved_weight will override the startup default”

    ryanofsky commented at 6:55 pm on February 5, 2026:

    re: #33965 (review)

    Agree it would seem nice to say that when this is unset the -blockreservedweight configuration value is used


    Sjors commented at 12:57 pm on February 7, 2026:
    I updated the comment.
  74. ismaelsadeeq commented at 4:11 pm on February 2, 2026: member

    Code review ACK 8bc1cb77fada68796c9599a04a66025a4b53ddab

    I verified that we now make block_reserved_weight optional, such that whenever a value is provided at runtime, that value is used; it is not silently changed to the startup value in ApplyArgsManOptions.

    I also verify that the current change does not allow for a default or overridden blockreservedweight set via startup to be lower (when the rpc is used), by generating a block template and ensuring its weight does not go beyond 4000000 - -blockreservedweight.

    IMO, the way the current functionality is implemented now using std::optional is a bit awkward, and not going to be easy to extend. for e.g if IPC has other options in the future that get set at runtime, and we don’t want to override like blockmaxweight? Would those also need to be wrapped in std::optional?

    A better solution is to split ApplyArgsManOptions into two functions, instead ApplyMandatoryOptions for mandatory overrides that must be called, and ApplyStartupOptions for startup-specific options that only mining RPCs call, since they don’t have runtime overrides. IPC would only call ApplyMandatoryOptions.

    Alternatively, we could also use a boolean parameter in the options struct so that ApplyArgsManOptions can skip certain overrides altogether, which is also easy to extend, but booleans can be brittle.

  75. ryanofsky commented at 6:09 pm on February 5, 2026: contributor

    Note: this has 3 acks, but conflicts with #34452 where Sjors expressed a preference in #34452 (comment) for merging that PR before conflicting PRs due to the difficultly of that rebase.

    Might be good to point to #34452 in the PR description if that should be reviewed first. Also there are new review comments here that don’t have responses yet.

  76. ryanofsky commented at 7:19 pm on February 5, 2026: contributor

    re: #33965#pullrequestreview-3739393889

    IMO, the way the current functionality is implemented now using std::optional is a bit awkward, and not going to be easy to extend. for e.g if IPC has other options in the future that get set at runtime, and we don’t want to override like blockmaxweight? Would those also need to be wrapped in std::optional?

    It seems natural to use a std::optional type for values that createNewBlock callers may specify but should not be required to specify. But if you are just saying the implementation of this interface is currently awkward, that is true and followup #33966 should improve it. Additionally, we will probably want another followup PR exposing the std::optional capability to capnproto (https://github.com/bitcoin/bitcoin/pull/33965#pullrequestreview-3530907741), so clients can use the node -blockreservedweight value if desired.

    A better solution is to split ApplyArgsManOptions into two functions, instead ApplyMandatoryOptions for mandatory overrides that must be called, and ApplyStartupOptions for startup-specific options that only mining RPCs call, since they don’t have runtime overrides. IPC would only call ApplyMandatoryOptions.

    I think followup #33966 should do what you are describing. There is an ApplyArgsManOptions options parsing startup options and ApplyMiningDefaults applying them in the mining code.

  77. DrahtBot added the label Needs rebase on Feb 7, 2026
  78. test: have mining template helpers return None
    Refactor the mining_create_block_template and mining_wait_next_template
    helpers in ipc_util.py to return None if they time out or fail. It makes
    the test easier to read and provides a more clear error message in case
    of a regression.
    
    There were a few spots that didn't use mining_wait_next_template yet,
    which now do.
    418b7995dd
  79. mining: fix -blockreservedweight shadows IPC option
    The -blockreservedweight startup option should only affect RPC code,
    because IPC clients (currently) do not have a way to signal their intent
    to use the node default (the BlockCreateOptions struct defaults
    merely document a recommendation for client software).
    
    Before this commit however, if the user set -blockreservedweight
    then ApplyArgsManOptions would cause the block_reserved_weight
    option passed by IPC clients to be ignored. Users who don't set
    this value were not affected.
    
    Fix this by making BlockCreateOptions::block_reserved_weight an
    std::optional.
    
    Internal interface users, such as the RPC call sites, don't set a
    value so -blockreservedweight is used. Whereas IPC clients do set
    a value which is no longer ignored.
    
    Test coverage is added.
    
    mining_basic.py already ensured -blockreservedweight is enforced by
    mining RPC methods. This commit adds coverage for Mining interface IPC
    clients. It also verifies that -blockreservedweight has no effect on
    them.
    
    Co-Authored-By: Russell Yanofsky <russ@yanofsky.org>
    d3e49528d4
  80. mining: enforce minimum reserved weight for IPC
    Previously a lower value was silently clamped to MINIMUM_BLOCK_RESERVED_WEIGHT.
    b623fab1ba
  81. Sjors force-pushed on Feb 7, 2026
  82. Sjors commented at 1:03 pm on February 7, 2026: member

    Rebased after #34452 and addressed feedback when it was a small change.

    Use git range-diff for the non-test changes, but you may want to review the tests from scratch given then churn from #34452, and because I split out run_ipc_option_override_test as requested.

    I want to keep this PR limited to fixing the bug, so I’ll take the more involved feedback with me to #33966.

  83. sedited approved
  84. sedited commented at 2:24 pm on February 7, 2026: contributor
    Re-ACK b623fab1ba87ea93dd7e302b8c927e55f2036173
  85. DrahtBot requested review from ryanofsky on Feb 7, 2026
  86. DrahtBot requested review from ismaelsadeeq on Feb 7, 2026
  87. DrahtBot removed the label Needs rebase on Feb 7, 2026
  88. in test/functional/interface_ipc_mining.py:233 in d3e49528d4
    227@@ -227,6 +228,37 @@ async def wait_for_block():
    228 
    229         asyncio.run(capnp.run(async_routine()))
    230 
    231+    def run_ipc_option_override_test(self):
    232+        self.log.info("Running IPC option override test")
    233+        # Set an absurd reserved weight. `-blockreservedweight` is RPC-only, so
    


    ryanofsky commented at 9:01 pm on February 9, 2026:

    In commit “mining: fix -blockreservedweight shadows IPC option” (d3e49528d479613ebe50088d530a621861463fa4)

    I feel like this description would be easier to understand if it began with a sentence saying what this is trying to check like “Confirm that BlockCreateOptions.blockReservedWeight takes precedence over -blockreservedweight. Set an absurdly high -blockreservedweight value that would result in empty blocks to verify this.”


    Sjors commented at 9:18 am on February 10, 2026:
    Thanks, will improve if I need to retouch.

    Sjors commented at 10:56 am on February 12, 2026:
    Taken in #33966.
  89. ryanofsky approved
  90. ryanofsky commented at 9:03 pm on February 9, 2026: contributor
    Code review ACK b623fab1ba87ea93dd7e302b8c927e55f2036173. Was rebased and test split up and comment updated since last review.
  91. ismaelsadeeq approved
  92. ismaelsadeeq commented at 3:37 pm on February 11, 2026: member
    ACK b623fab1ba87ea93dd7e302b8c927e55f2036173
  93. ryanofsky assigned ryanofsky on Feb 12, 2026
  94. ryanofsky merged this on Feb 12, 2026
  95. ryanofsky closed this on Feb 12, 2026

  96. Sjors commented at 10:55 am on February 12, 2026: member

    Thanks for the reviews!

    I also just realized the PR description doesn’t link to the followup #33966, which may have led to some confusion (about the limited scope of changes here).

  97. Sjors deleted the branch on Feb 12, 2026
  98. Sjors referenced this in commit c301ccb0d1 on Feb 12, 2026
  99. Sjors referenced this in commit 5eb78b3213 on Feb 19, 2026
  100. Sjors referenced this in commit 4933afe4ed on Feb 20, 2026
  101. ryanofsky commented at 10:50 pm on February 20, 2026: contributor

    re: #33965 (comment)

    For that to work however, we’d also need to change mining.capnp, e.g.:

    0blockReservedWeight [@1](/bitcoin-bitcoin/contributor/1/) :UInt64 $Proxy.name("block_reserved_weight");
    1hasBlockReservedWeight [@3](/bitcoin-bitcoin/contributor/3/) :Bool = true $Proxy.skip;
    

    Adding the has field and having it default to true means it’s not a breaking change. But this doesn’t actually work :-)

    https://github.com/bitcoin-core/libmultiprocess/pull/243 will allow adding the “has” field so IPC clients can choose to leave this field unset and use the -blockreservedweight value.

    I think it would be better to default the “has” field to false instead of true if added, though either value would work.

    (Using std::optional and “has” fields defaulted to false could also allow simplifying the .capnp files and dropping the .defaultBlockReservedWeight and .defaultCoinbaseOutputMaxAdditionalSigops constants recently added in c6638fa7c5e97f9fd7a5ea8feb29f8caeac788bd. Probably would have been better to just make these fields optional instead of adding the default integer values at all, but both can coexist, and the default integer values could be dropped in a future version bump.)


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-02-22 03:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me