From 63ee9cd15b628abe3f1ce1427938c945fe1534d6 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 12 Feb 2026 12:14:12 +0100 Subject: [PATCH 01/15] test: misc interface_ipc_mining.py improvements - clarify run_ipc_option_override_test description: https://github.com/bitcoin/bitcoin/pull/33965#discussion_r2784515535 - trailing comma after includes: https://github.com/bitcoin/bitcoin/pull/34452#discussion_r2775183035 - include order: https://github.com/bitcoin/bitcoin/pull/34452#discussion_r2774730966 - reuse the shared miniwallet in the low block height test: https://github.com/bitcoin/bitcoin/pull/34860#discussion_r3255163123 --- test/functional/interface_ipc_mining.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 2740662f1c0..389b7f13d6d 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -9,35 +9,35 @@ from contextlib import AsyncExitStack from io import BytesIO from test_framework.blocktools import NULL_OUTPOINT, script_BIP34_coinbase_height from test_framework.messages import ( - MAX_BLOCK_WEIGHT, CBlockHeader, + COIN, CTransaction, CTxIn, - CTxOut, CTxInWitness, - ser_uint256, - COIN, + CTxOut, + MAX_BLOCK_WEIGHT, from_hex, msg_headers, + ser_uint256, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than_or_equal, - assert_not_equal + assert_not_equal, ) from test_framework.wallet import MiniWallet from test_framework.p2p import P2PInterface from test_framework.ipc_util import ( + assert_capnp_failed, destroying, - mining_create_block_template, load_capnp_modules, + make_mining_ctx, + mining_create_block_template, mining_get_block, mining_get_coinbase_tx, mining_wait_next_template, wait_and_do, - make_mining_ctx, - assert_capnp_failed ) # Test may be skipped and not have capnp installed @@ -291,8 +291,9 @@ class IPCMiningTest(BitcoinTestFramework): def run_ipc_option_override_test(self): self.log.info("Running IPC option override test") - # Set an absurd reserved weight. `-blockreservedweight` is RPC-only, so - # with this setting RPC templates would be empty. IPC clients set + # Confirm that BlockCreateOptions.blockReservedWeight takes precedence + # over -blockreservedweight. Set an absurdly high -blockreservedweight + # value that would result in empty blocks to verify this. IPC clients set # blockReservedWeight per template request and are unaffected; later in # the test the IPC template includes a mempool transaction. self.restart_node(0, extra_args=[f"-blockreservedweight={MAX_BLOCK_WEIGHT}"]) @@ -421,8 +422,6 @@ class IPCMiningTest(BitcoinTestFramework): node.wait_for_rpc_connection() assert_equal(node.getblockcount(), 0) - miniwallet = MiniWallet(node) - async def async_routine(): ctx, mining = await make_mining_ctx(self) opts = self.capnp_modules['mining'].BlockCreateOptions() @@ -437,7 +436,7 @@ class IPCMiningTest(BitcoinTestFramework): block = await mining_get_block(template, ctx) # Heights <= 16 need extra nonce padding. extra_nonce = b'\xaa\xbb\xcc\xdd' if height <= 16 else b"" - coinbase = await self.build_coinbase_test(template, ctx, miniwallet, extra_nonce=extra_nonce) + coinbase = await self.build_coinbase_test(template, ctx, self.miniwallet, extra_nonce=extra_nonce) block.vtx[0] = coinbase block.hashMerkleRoot = block.calc_merkle_root() block.solve() From 24750f8b31a42c468e49baa2c584ebddb4bc15fb Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Wed, 25 Mar 2026 18:10:10 +0100 Subject: [PATCH 02/15] test: add createNewBlock failure helper Move the IPC createNewBlock failure assertion into ipc_util.py so later invalid mining option tests can share the same remote exception check. --- test/functional/interface_ipc_mining.py | 8 +++----- test/functional/test_framework/ipc_util.py | 9 +++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 389b7f13d6d..e78a21f7c02 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -30,6 +30,7 @@ from test_framework.wallet import MiniWallet from test_framework.p2p import P2PInterface from test_framework.ipc_util import ( assert_capnp_failed, + assert_create_new_block_fails, destroying, load_capnp_modules, make_mining_ctx, @@ -318,11 +319,8 @@ class IPCMiningTest(BitcoinTestFramework): self.log.debug("Enforce minimum reserved weight for IPC clients too") opts.blockReservedWeight = 0 - try: - await mining.createNewBlock(ctx, opts) - raise AssertionError("createNewBlock unexpectedly succeeded") - except capnp.lib.capnp.KjException as e: - assert_capnp_failed(e, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units") + await assert_create_new_block_fails(ctx, mining, opts, + "block_reserved_weight (0) must be at least 2000 weight units") asyncio.run(capnp.run(async_routine())) diff --git a/test/functional/test_framework/ipc_util.py b/test/functional/test_framework/ipc_util.py index 340cd15c618..5ce582372f8 100644 --- a/test/functional/test_framework/ipc_util.py +++ b/test/functional/test_framework/ipc_util.py @@ -162,3 +162,12 @@ async def make_mining_ctx(self): def assert_capnp_failed(e, description_prefix): assert e.description.startswith(description_prefix), f"Expected description starting with '{description_prefix}', got '{e.description}'" assert_equal(e.type, "FAILED") + + +async def assert_create_new_block_fails(ctx, mining, opts, expected_msg): + """Assert that mining.createNewBlock fails with the expected remote exception.""" + try: + await mining.createNewBlock(ctx, opts) + raise AssertionError("createNewBlock unexpectedly succeeded") + except capnp.lib.capnp.KjException as e: + assert_capnp_failed(e, f"remote exception: std::exception: {expected_msg}") From 63b23ea1e94f8f1f74e24abbadf2e5de3148f396 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Tue, 12 May 2026 15:56:56 +0200 Subject: [PATCH 03/15] test: regression test for waitNext mining policy Demonstrates that BlockTemplateImpl::waitNext() must respect the mining policy supplied via -blockmintxfee, not silently fall back to defaults. Co-authored-by: Enoch Azariah --- test/functional/interface_ipc_mining.py | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index e78a21f7c02..d09bc538819 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -6,6 +6,7 @@ import asyncio import time from contextlib import AsyncExitStack +from decimal import Decimal from io import BytesIO from test_framework.blocktools import NULL_OUTPOINT, script_BIP34_coinbase_height from test_framework.messages import ( @@ -324,6 +325,57 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) + def run_waitnext_mining_policy_test(self): + """Verify that waitNext() preserves the mining policy from -blockmintxfee + instead of falling back to defaults.""" + self.log.info("Running waitNext mining policy test") + block_min_tx_fee = Decimal("0.00002000") + below_block_min_tx_fee = Decimal("0.00001000") + above_block_min_tx_fee = Decimal("0.00003000") + + self.restart_node(0, extra_args=[ + f"-blockmintxfee={block_min_tx_fee:.8f}", + "-minrelaytxfee=0", + "-persistmempool=0", + ]) + + async def async_routine(): + ctx, mining = await make_mining_ctx(self) + + self.log.debug("Create a below -blockmintxfee transaction") + low_fee_tx = self.miniwallet.send_self_transfer( + fee_rate=below_block_min_tx_fee, + from_node=self.nodes[0], + confirmed_only=True, + ) + assert low_fee_tx["txid"] in self.nodes[0].getrawmempool() + + async with AsyncExitStack() as stack: + self.log.debug("createNewBlock should respect -blockmintxfee") + template = await mining_create_block_template(mining, stack, ctx, self.default_block_create_options) + assert template is not None + block = await mining_get_block(template, ctx) + assert low_fee_tx["txid"] not in {tx.txid_hex for tx in block.vtx[1:]} + + self.log.debug("waitNext should preserve the same mining policy") + high_fee_tx = self.miniwallet.send_self_transfer( + fee_rate=above_block_min_tx_fee, + from_node=self.nodes[0], + confirmed_only=True, + ) + mempool_txids = self.nodes[0].getrawmempool() + assert high_fee_tx["txid"] in mempool_txids + assert low_fee_tx["txid"] in mempool_txids + template_next = await mining_wait_next_template(template, stack, ctx, self.default_block_wait_options) + assert template_next is not None + + block_next = await mining_get_block(template_next, ctx) + block_next_txids = {tx.txid_hex for tx in block_next.vtx[1:]} + assert high_fee_tx["txid"] in block_next_txids + assert low_fee_tx["txid"] not in block_next_txids + + asyncio.run(capnp.run(async_routine())) + def run_coinbase_and_submission_test(self): """Test coinbase construction (getCoinbaseTx) and block submission (submitSolution).""" self.log.info("Running coinbase construction and submission test") @@ -447,10 +499,14 @@ class IPCMiningTest(BitcoinTestFramework): def run_test(self): self.miniwallet = MiniWallet(self.nodes[0]) self.default_block_create_options = self.capnp_modules['mining'].BlockCreateOptions() + self.default_block_wait_options = self.capnp_modules['mining'].BlockWaitOptions() + self.default_block_wait_options.timeout = 1000.0 * self.options.timeout_factor + self.default_block_wait_options.feeThreshold = 1 self.run_mining_interface_test() self.run_early_startup_test() self.run_block_template_test() self.run_coinbase_and_submission_test() + self.run_waitnext_mining_policy_test() self.run_ipc_option_override_test() # Needs to run last because it resets the chain. From 6aeb1fbea2399bf28b00d128126cd28409e9b0c0 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 14 May 2026 08:25:35 +0200 Subject: [PATCH 04/15] test: cover IPC blockmaxweight policy Verify that -blockmaxweight is honored both when an IPC block template is first created and after waitNext() refreshes the template. Co-authored-by: w0xlt <94266259+w0xlt@users.noreply.github.com> --- test/functional/interface_ipc_mining.py | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index d09bc538819..710a9a2d82c 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -16,6 +16,7 @@ from test_framework.messages import ( CTxIn, CTxInWitness, CTxOut, + DEFAULT_BLOCK_RESERVED_WEIGHT, MAX_BLOCK_WEIGHT, from_hex, msg_headers, @@ -376,6 +377,65 @@ class IPCMiningTest(BitcoinTestFramework): asyncio.run(capnp.run(async_routine())) + def run_block_max_weight_test(self): + """Verify IPC createNewBlock() and waitNext() preserve the -blockmaxweight policy.""" + self.log.info("Running block_max_weight test") + + # Cap that leaves room for only a handful of mempool transactions + # above DEFAULT_BLOCK_RESERVED_WEIGHT (8000). Well below MAX_BLOCK_WEIGHT + # (4_000_000), so any truncation observed here is attributable to the + # cap, not to consensus limits or wallet chain limits. + small_cap = DEFAULT_BLOCK_RESERVED_WEIGHT + 4000 + NUM_TXS = 20 + + self.restart_node(0, extra_args=[ + f"-blockmaxweight={small_cap}", + "-minrelaytxfee=0", + "-persistmempool=0", + ]) + # Refresh miniwallet's UTXO view from the chain after restart. + self.miniwallet.rescan_utxos() + + # Fill the mempool enough that the configured block weight cap forces + # template truncation. + for _ in range(NUM_TXS): + self.miniwallet.send_self_transfer(from_node=self.nodes[0], confirmed_only=True) + assert_equal(self.nodes[0].getmempoolinfo()["size"], NUM_TXS) + + async def async_routine(): + ctx, mining = await make_mining_ctx(self) + async with AsyncExitStack() as stack: + template = await mining_create_block_template(mining, stack, ctx, self.default_block_create_options) + assert template is not None + block = await mining_get_block(template, ctx) + assert_greater_than_or_equal(small_cap, block.get_weight()) + # Exclude the coinbase; the cap must have forced truncation. + initial_included = len(block.vtx) - 1 + assert initial_included < NUM_TXS, ( + f"Expected -blockmaxweight={small_cap} to truncate; " + f"included {initial_included}/{NUM_TXS} mempool txs" + ) + + self.log.debug("waitNext should preserve -blockmaxweight") + high_fee_tx = self.miniwallet.send_self_transfer( + from_node=self.nodes[0], + confirmed_only=True, + fee_rate=10, + ) + template_next = await mining_wait_next_template(template, stack, ctx, self.default_block_wait_options) + assert template_next is not None + + block_next = await mining_get_block(template_next, ctx) + assert_greater_than_or_equal(small_cap, block_next.get_weight()) + assert high_fee_tx["txid"] in {tx.txid_hex for tx in block_next.vtx[1:]} + next_included = len(block_next.vtx) - 1 + assert next_included < NUM_TXS + 1, ( + f"Expected -blockmaxweight={small_cap} to remain capped after waitNext; " + f"included {next_included}/{NUM_TXS + 1} mempool txs" + ) + + asyncio.run(capnp.run(async_routine())) + def run_coinbase_and_submission_test(self): """Test coinbase construction (getCoinbaseTx) and block submission (submitSolution).""" self.log.info("Running coinbase construction and submission test") @@ -507,6 +567,7 @@ class IPCMiningTest(BitcoinTestFramework): self.run_block_template_test() self.run_coinbase_and_submission_test() self.run_waitnext_mining_policy_test() + self.run_block_max_weight_test() self.run_ipc_option_override_test() # Needs to run last because it resets the chain. From d4368e059cfe09d973a238d08cbe6812511366d3 Mon Sep 17 00:00:00 2001 From: Sjors Provoost Date: Thu, 4 Dec 2025 14:10:20 +0100 Subject: [PATCH 05/15] move-only: add node/mining_types.h Move mining related structs there. This simplifies includes in later commits and makes the code easier to understand for Mining IPC client developers. --- src/interfaces/mining.h | 4 +- src/node/miner.h | 1 + src/node/mining_types.h | 151 ++++++++++++++++++++++++++++++++++++++++ src/node/types.h | 129 ---------------------------------- src/test/util/mining.h | 2 +- 5 files changed, 154 insertions(+), 133 deletions(-) create mode 100644 src/node/mining_types.h diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 943d34e75d9..ca4f73813b9 100644 --- a/src/interfaces/mining.h +++ b/src/interfaces/mining.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,9 +23,6 @@ namespace node { struct NodeContext; } // namespace node -class BlockValidationState; -class CScript; - namespace interfaces { //! Block template interface diff --git a/src/node/miner.h b/src/node/miner.h index 5c8668771f5..c5d21cfc322 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include diff --git a/src/node/mining_types.h b/src/node/mining_types.h new file mode 100644 index 00000000000..69088bb7187 --- /dev/null +++ b/src/node/mining_types.h @@ -0,0 +1,151 @@ +// Copyright (c) The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! @file node/mining_types.h is used externally by mining IPC clients, so it should +//! only declare simple data definitions. +//! +//! Avoid declaring functions or classes with methods here unless they are +//! header-only or provided by the util library. + +#ifndef BITCOIN_NODE_MINING_TYPES_H +#define BITCOIN_NODE_MINING_TYPES_H + +#include +#include +#include +#include +#include