diff --git a/src/interfaces/mining.h b/src/interfaces/mining.h index 150295e5b7b..a30c4afb526 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 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 8ee4745b858..ed01e44a32a 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 fd3fa226cae..509528d9c06 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -918,15 +918,21 @@ public: std::unique_ptr 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(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 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 28e9048a4dd..39f0879ce2c 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -454,12 +454,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 WaitAndCreateNewBlock(ChainstateManager& chainman, KernelNotifications& kernel_notifications, CTxMemPool* mempool, const std::unique_ptr& 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. @@ -484,8 +492,12 @@ std::unique_ptr 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 a9a88b39cf2..0790835d8f1 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 WaitAndCreateNewBlock(ChainstateManager& chainma CTxMemPool* mempool, const std::unique_ptr& 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 GetTip(ChainstateManager& chainman); diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py index abcc4d6b5d1..2c9a0022442 100755 --- a/test/functional/interface_ipc.py +++ b/test/functional/interface_ipc.py @@ -184,6 +184,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)