What changed, and why it matters
This commit adds a new 'interruptWait' feature to Bitcoin Core's mining interface so that a long-running wait for the next block template can be safely cancelled. It is a normal feature addition with no obvious security bug. The change includes a test showing the cancellation works as intended.
No security action required. Review as a normal feature/API addition; ensure callers handle the returned nullptr after interruption.
Security signals we found
No security-relevant signals in the diff: no memory corruption, no authentication/authorization changes, no cryptographic changes, no consensus changes.
New IPC method is stateful but only affects the local waiter's own block-template wait; it does not alter chain state or mempool policy.
Condition-variable wake pattern is standard and does not introduce new deadlock or use-after-free paths visible in the diff.
Evidence from the diff
The patch introduces an interruptWait() method on the BlockTemplate interface (C++ header, Cap’n Proto IPC definition, and implementation). It threads a boolean interrupt_wait flag into WaitAndCreateNewBlock(), sets it via InterruptWait(), and uses the existing KernelNotifications condition variable to wake the waiting thread. A functional IPC test verifies that interruptWait aborts a waitNext call before its timeout.
Changed components
src/interfaces/mining.hsrc/ipc/capnp/mining.capnpsrc/node/interfaces.cppsrc/node/miner.cppsrc/node/miner.htest/functional/interface_ipc.pyInspect captured patch +54 / −4
diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h
index 150295e5..a30c4afb 100644
--- a/src/interfaces/mining.h
+++ b/src/interfaces/mining.h
@@ -72,6 +72,11 @@ public:
* the tip is more than 20 minutes old.
*/
virtual std::unique_ptr<BlockTemplate> waitNext(const node::BlockWaitOptions options = {}) = 0;
+
+ /**
+ * Interrupts the current wait for the next block template.
+ */
+ virtual void interruptWait() = 0;
};
//! Interface giving clients (RPC, Stratum v2 Template Provider in the future)
diff --git a/src/ipc/capnp/mining.capnp b/src/ipc/capnp/mining.capnp
index 8ee4745b..ed01e44a 100644
--- a/src/ipc/capnp/mining.capnp
+++ b/src/ipc/capnp/mining.capnp
@@ -33,6 +33,7 @@ interface BlockTemplate $Proxy.wrap("interfaces::BlockTemplate") {
getCoinbaseMerklePath @8 (context: Proxy.Context) -> (result: List(Data));
submitSolution @9 (context: Proxy.Context, version: UInt32, timestamp: UInt32, nonce: UInt32, coinbase :Data) -> (result: Bool);
waitNext @10 (context: Proxy.Context, options: BlockWaitOptions) -> (result: BlockTemplate);
+ interruptWait @11() -> ();
}
struct BlockCreateOptions $Proxy.wrap("node::BlockCreateOptions") {
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index fd3fa226..509528d9 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -918,15 +918,21 @@ public:
std::unique_ptr<BlockTemplate> waitNext(BlockWaitOptions options) override
{
- auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), m_block_template, options, m_assemble_options);
+ auto new_template = WaitAndCreateNewBlock(chainman(), notifications(), m_node.mempool.get(), m_block_template, options, m_assemble_options, m_interrupt_wait);
if (new_template) return std::make_unique<BlockTemplateImpl>(m_assemble_options, std::move(new_template), m_node);
return nullptr;
}
+ void interruptWait() override
+ {
+ InterruptWait(notifications(), m_interrupt_wait);
+ }
+
const BlockAssembler::Options m_assemble_options;
const std::unique_ptr<CBlockTemplate> m_block_template;
+ bool m_interrupt_wait{false};
ChainstateManager& chainman() { return *Assert(m_node.chainman); }
KernelNotifications& notifications() { return *Assert(m_node.notifications); }
NodeContext& m_node;
diff --git a/src/node/miner.cpp b/src/node/miner.cpp
index e75bc3a6..ffdf1d9f 100644
--- a/src/node/miner.cpp
+++ b/src/node/miner.cpp
@@ -453,12 +453,20 @@ void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t
block.hashMerkleRoot = BlockMerkleRoot(block);
}
+void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait)
+{
+ LOCK(kernel_notifications.m_tip_block_mutex);
+ interrupt_wait = true;
+ kernel_notifications.m_tip_block_cv.notify_all();
+}
+
std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainman,
KernelNotifications& kernel_notifications,
CTxMemPool* mempool,
const std::unique_ptr<CBlockTemplate>& block_template,
const BlockWaitOptions& options,
- const BlockAssembler::Options& assemble_options)
+ const BlockAssembler::Options& assemble_options,
+ bool& interrupt_wait)
{
// Delay calculating the current template fees, just in case a new block
// comes in before the next tick.
@@ -483,8 +491,12 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
// method on BlockTemplate and no template could have been
// generated before a tip exists.
tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
- return tip_changed || chainman.m_interrupt;
+ return tip_changed || chainman.m_interrupt || interrupt_wait;
});
+ if (interrupt_wait) {
+ interrupt_wait = false;
+ return nullptr;
+ }
}
if (chainman.m_interrupt) return nullptr;
diff --git a/src/node/miner.h b/src/node/miner.h
index a9a88b39..0790835d 100644
--- a/src/node/miner.h
+++ b/src/node/miner.h
@@ -238,6 +238,9 @@ void ApplyArgsManOptions(const ArgsManager& gArgs, BlockAssembler::Options& opti
/* Compute the block's merkle root, insert or replace the coinbase transaction and the merkle root into the block */
void AddMerkleRootAndCoinbase(CBlock& block, CTransactionRef coinbase, uint32_t version, uint32_t timestamp, uint32_t nonce);
+
+/* Interrupt the current wait for the next block template. */
+void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);
/**
* Return a new block template when fees rise to a certain threshold or after a
* new tip; return nullopt if timeout is reached.
@@ -247,7 +250,8 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
CTxMemPool* mempool,
const std::unique_ptr<CBlockTemplate>& block_template,
const BlockWaitOptions& options,
- const BlockAssembler::Options& assemble_options);
+ const BlockAssembler::Options& assemble_options,
+ bool& interrupt_wait);
/* Locks cs_main and returns the block hash and block height of the active chain if it exists; otherwise, returns nullopt.*/
std::optional<BlockRef> GetTip(ChainstateManager& chainman);
diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
index 4231e95b..0c9de28d 100755
--- a/test/functional/interface_ipc.py
+++ b/test/functional/interface_ipc.py
@@ -182,6 +182,28 @@ class IPCInterfaceTest(BitcoinTestFramework):
template7 = await template6.result.waitNext(ctx, waitoptions)
assert_equal(template7.to_dict(), {})
+ self.log.debug("interruptWait should abort the current wait")
+ wait_started = asyncio.Event()
+ async def wait_for_block():
+ new_waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
+ new_waitoptions.timeout = waitoptions.timeout * 60 # 1 minute wait
+ new_waitoptions.feeThreshold = 1
+ wait_started.set()
+ return await template6.result.waitNext(ctx, new_waitoptions)
+
+ async def interrupt_wait():
+ await wait_started.wait() # Wait for confirmation wait started
+ await asyncio.sleep(0.1) # Minimal buffer
+ template6.result.interruptWait()
+ miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
+
+ wait_task = asyncio.create_task(wait_for_block())
+ interrupt_task = asyncio.create_task(interrupt_wait())
+
+ result = await wait_task
+ await interrupt_task
+ assert_equal(result.to_dict(), {})
+
current_block_height = self.nodes[0].getchaintips()[0]["height"]
check_opts = self.capnp_modules['mining'].BlockCheckOptions()
template = await mining.result.createNewBlock(opts)
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.