interfaces: enable cancelling running waitNext calls #33676

pull ismaelsadeeq wants to merge 1 commits into bitcoin:master from ismaelsadeeq:10-2025-add-interruptWaitNext changing 6 files +54 −4
  1. ismaelsadeeq commented at 10:14 am on October 22, 2025: member

    This is an attempt to fix #33575 see the issue for background and the usefulness of this feature.

    This PR uses one of the suggested approaches: adding a new interruptWaitNext() method to the mining interface.

    It introduces a new boolean variable, m_interrupt_wait, which is set to false when the thread starts waiting. The interruptWaitNext() method wakes the thread and sets m_interrupt_wait to true. Whenever the thread wakes up, it checks whether the wait was aborted; if so, it simply set m_interrupt_wait to false and returnnullptr.

    This PR also adds a functional test for the new method. The test uses asyncio to spawn two tasks and attempts to ensure that the wait is executed before the interrupt by using an event monitor. It adds a 0.1-second buffer to ensure the wait has started executing. If that buffer elapses without waitNext executing, the test will fail because a transaction is created after the buffer.

  2. DrahtBot renamed this:
    interfaces: enable cancelling running `waitNext` calls
    interfaces: enable cancelling running `waitNext` calls
    on Oct 22, 2025
  3. DrahtBot added the label interfaces on Oct 22, 2025
  4. DrahtBot commented at 10:14 am on October 22, 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/33676.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky, furszy, Sjors
    Concept ACK Eunovo

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33421 (node: add BlockTemplate{Cache, Manager} by ismaelsadeeq)

    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.

  5. ismaelsadeeq marked this as ready for review on Oct 22, 2025
  6. ryanofsky commented at 3:48 pm on October 22, 2025: contributor
    Code review 0e0cf1822416956b6853e593517f4a9fab157a62. This looks very good and I did not see any issues at a quick glance. I think I might suggest shortening interruptWaitNext to interruptWait, but no strong opinion.
  7. ismaelsadeeq force-pushed on Oct 23, 2025
  8. ismaelsadeeq commented at 9:58 am on October 23, 2025: member

    I took your suggestion, make the test robust by generating a transaction after interrupting, and took @furszy suggestion by initializing the waiting inside the lock.

    cc @Eunovo

  9. furszy commented at 3:48 pm on October 25, 2025: member
    Concept ACK. Will review soon.
  10. in src/node/interfaces.cpp:935 in f4e63cc807
    931+
    932     const BlockAssembler::Options m_assemble_options;
    933 
    934     const std::unique_ptr<CBlockTemplate> m_block_template;
    935 
    936+    bool waiting{false};
    


    Eunovo commented at 3:09 pm on October 27, 2025:
    Consider using m_interrupt_wait instead.

    ismaelsadeeq commented at 6:22 pm on October 27, 2025:
    Done
  11. in src/node/miner.h:254 in f4e63cc807
    249@@ -247,7 +250,8 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
    250                                                       CTxMemPool* mempool,
    251                                                       const std::unique_ptr<CBlockTemplate>& block_template,
    252                                                       const BlockWaitOptions& options,
    253-                                                      const BlockAssembler::Options& assemble_options);
    254+                                                      const BlockAssembler::Options& assemble_options,
    255+                                                      bool& wait);
    


    Eunovo commented at 3:11 pm on October 27, 2025:
    consider bool& interrupt_wait

    ismaelsadeeq commented at 6:22 pm on October 27, 2025:
    Done
  12. Eunovo commented at 3:39 pm on October 27, 2025: contributor

    Concept ACK https://github.com/bitcoin/bitcoin/pull/33676/commits/f4e63cc807f8c34a780604bd3802739c37209b75

    I took a first look and left some nits. I’ll take a second look.

  13. ryanofsky approved
  14. ryanofsky commented at 4:14 pm on October 27, 2025: contributor

    Code review ACK f4e63cc807f8c34a780604bd3802739c37209b75. Thanks for the PR! This should be very helpful and close a significant gap in the mining interface, so clients are not forced to use shorter timeouts or keep around extra waitNext threads they don’t actually need.

    I do think there is a minor race condition that would be good to address here, but it could also be addressed in a followup:

    In current implementation if a client makes a waitNext call followed by an interruptWait call, because the waitNext call is asynchronous while the interruptWait call is synchronous, it means InterruptWait could set waiting = false before waitNext() sets waiting = true, and the interrupt might be ignored. This could be a real problem because the clients wouldn’t have a good way of knowing whether a particular interruptWait call was effective and if they needed to make another one.

    Would recommend changing the implementation slightly to avoid this. Could replace bool waiting{false} variable with an bool m_interrupt_wait{false} variable as eunovo suggested. And instead of having the WaitAndCreateNewBlock method set waiting = true when it is first called, instead of have it set interrupt_wait = false before it returns. This might make the WaitAndCreateNewBlock implementation a little more complicated, but should be nicer for clients because they can rely on interruptWait causing the current waitNext call to return even if hasn’t started waiting yet.

  15. DrahtBot requested review from Eunovo on Oct 27, 2025
  16. DrahtBot requested review from furszy on Oct 27, 2025
  17. interfaces: add interruptWait method
    - This method can be used to cancel a running
      waitNext().
    
    - This commit also adds a test case for interruptWait method
    dcb56fd4cb
  18. ismaelsadeeq force-pushed on Oct 27, 2025
  19. ismaelsadeeq commented at 6:23 pm on October 27, 2025: member

    Thanks @Eunovo and @ryanofsky

    I’ve fixed the comments in latest push diff

  20. furszy commented at 6:49 pm on October 27, 2025: member

    Code ACK dcb56fd4cb59e6857c110dd87019459989dc1ec3

    nit: is there any way you could verify that there is someone waiting before setting interrupt_wait? It seems fragile to be able to interrupt a procedure that hasn’t started.

  21. DrahtBot requested review from ryanofsky on Oct 27, 2025
  22. furszy commented at 7:48 pm on October 27, 2025: member

    nit: is there any way you could verify that there is someone waiting before setting interrupt_wait? It seems fragile to be able to interrupt a procedure that hasn’t started.

    Quick idea; could provide an optional flag. So the value exists whenever someone is waiting on it and is destroyed (nullopt) when there is no one doing it.

     0Index: src/node/interfaces.cpp
     1===================================================================
     2diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
     3--- a/src/node/interfaces.cpp	(revision dcb56fd4cb59e6857c110dd87019459989dc1ec3)
     4+++ b/src/node/interfaces.cpp	(date 1761593941511)
     5@@ -932,7 +932,7 @@
     6 
     7     const std::unique_ptr<CBlockTemplate> m_block_template;
     8 
     9-    bool m_interrupt_wait{false};
    10+    std::optional<bool> m_interrupt_wait{std::nullopt};
    11     ChainstateManager& chainman() { return *Assert(m_node.chainman); }
    12     KernelNotifications& notifications() { return *Assert(m_node.notifications); }
    13     NodeContext& m_node;
    14Index: src/node/miner.cpp
    15===================================================================
    16diff --git a/src/node/miner.cpp b/src/node/miner.cpp
    17--- a/src/node/miner.cpp	(revision dcb56fd4cb59e6857c110dd87019459989dc1ec3)
    18+++ b/src/node/miner.cpp	(date 1761594250147)
    19@@ -453,9 +453,10 @@
    20     block.hashMerkleRoot = BlockMerkleRoot(block);
    21 }
    22 
    23-void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait)
    24+void InterruptWait(KernelNotifications& kernel_notifications, std::optional<bool>& interrupt_wait)
    25 {
    26     LOCK(kernel_notifications.m_tip_block_mutex);
    27+    if (!interrupt_wait) throw std::runtime_error("Interrupt requested while no wait operation is active");
    28     interrupt_wait = true;
    29     kernel_notifications.m_tip_block_cv.notify_all();
    30 }
    31@@ -466,7 +467,7 @@
    32                                                       const std::unique_ptr<CBlockTemplate>& block_template,
    33                                                       const BlockWaitOptions& options,
    34                                                       const BlockAssembler::Options& assemble_options,
    35-                                                      bool& interrupt_wait)
    36+                                                      std::optional<bool>& interrupt_wait)
    37 {
    38     // Delay calculating the current template fees, just in case a new block
    39     // comes in before the next tick.
    40@@ -483,6 +484,7 @@
    41         bool tip_changed{false};
    42         {
    43             WAIT_LOCK(kernel_notifications.m_tip_block_mutex, lock);
    44+            interrupt_wait = false;
    45             // Note that wait_until() checks the predicate before waiting
    46             kernel_notifications.m_tip_block_cv.wait_until(lock, std::min(now + tick, deadline), [&]() EXCLUSIVE_LOCKS_REQUIRED(kernel_notifications.m_tip_block_mutex) {
    47                 AssertLockHeld(kernel_notifications.m_tip_block_mutex);
    48@@ -491,10 +493,10 @@
    49                 // method on BlockTemplate and no template could have been
    50                 // generated before a tip exists.
    51                 tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
    52-                return tip_changed || chainman.m_interrupt || interrupt_wait;
    53+                return tip_changed || chainman.m_interrupt || (!interrupt_wait || interrupt_wait.value());
    54             });
    55             if (interrupt_wait) {
    56-                interrupt_wait = false;
    57+                interrupt_wait.reset();
    58                 return nullptr;
    59             }
    60         }
    61Index: src/node/miner.h
    62===================================================================
    63diff --git a/src/node/miner.h b/src/node/miner.h
    64--- a/src/node/miner.h	(revision dcb56fd4cb59e6857c110dd87019459989dc1ec3)
    65+++ b/src/node/miner.h	(date 1761594137149)
    66@@ -240,7 +240,7 @@
    67 
    68 
    69 /* Interrupt the current wait for the next block template. */
    70-void InterruptWait(KernelNotifications& kernel_notifications, bool& interrupt_wait);
    71+void InterruptWait(KernelNotifications& kernel_notifications, std::optional<bool>& interrupt_wait);
    72 /**
    73  * Return a new block template when fees rise to a certain threshold or after a
    74  * new tip; return nullopt if timeout is reached.
    75@@ -251,7 +251,7 @@
    76                                                       const std::unique_ptr<CBlockTemplate>& block_template,
    77                                                       const BlockWaitOptions& options,
    78                                                       const BlockAssembler::Options& assemble_options,
    79-                                                      bool& interrupt_wait);
    80+                                                      std::optional<bool>& interrupt_wait);
    81 
    82 /* Locks cs_main and returns the block hash and block height of the active chain if it exists; otherwise, returns nullopt.*/
    83 std::optional<BlockRef> GetTip(ChainstateManager& chainman);
    

    Note: Haven’t thought much about what would happen with multiple waiters. But it shouldn’t be a problem.

    Still, I might have gone too far and this might not be needed. Could enforce this rule at the API doc level too.

  23. ryanofsky commented at 8:41 pm on October 27, 2025: contributor

    re: #33676#pullrequestreview-3384985314, #33676 (comment)

    0+    if (!interrupt_wait) throw std::runtime_error("Interrupt requested while no wait operation is active");
    

    I think this would just re-introduce the race condition we were trying to get rid of. The waitNext call is asynchronous and there’s no way for a client to know if a waitNext is active or not. They can only know if they’ve made a call, not whether the IPC request has been sent to the socket, or whether server has received the request, or whether the server has started to execute the call after receiving it.

    In theory, the server could send notifications to the client as execution progresses, or let the client query the current state, or throw exceptions in certain states, but these approaches would add complexity and expose clients to information they couldn’t use reliably and probably shouldn’t have. It should be simpler to just decide interruptWait calls will take precedence over waitNext calls and not care about the order of the calls like the current implementation in dcb56fd4cb59e6857c110dd87019459989dc1ec3.

    I will say that current implementation will not work well if clients make multiple waitNext calls on the same block simultaneously, because clients will need to make separate interuptWait calls to cancel each waitNext, but the cancellations will be lost each time a waitNext call returns. A reasonable solution to this could be to change m_interrupt_wait from a bool to an int and increment it when interruptWait is called (instead of setting it to true) and decrement it when a waitNext call is interrupted (instead of setting it it to false). I think in practice though we are not expecting multiple waitNext calls on the same block, and could even potentially return an error if simultaneous calls are attempted.

  24. ryanofsky approved
  25. ryanofsky commented at 12:00 pm on October 28, 2025: contributor
    Code review ACK dcb56fd4cb59e6857c110dd87019459989dc1ec3, just tweaking semantics slightly since last review so if an interruptWait call is made shortly after a waitNext call it will reliably cause the waitNext call to return right away without blocking, even if the waitNext call had not begun to execute or wait yet.
  26. furszy commented at 1:59 pm on October 28, 2025: member
  27. in test/functional/interface_ipc.py:198 in dcb56fd4cb
    193+
    194+            async def interrupt_wait():
    195+                await wait_started.wait() # Wait for confirmation wait started
    196+                await asyncio.sleep(0.1)  # Minimal buffer
    197+                template6.result.interruptWait()
    198+                miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
    


    Eunovo commented at 6:07 pm on October 28, 2025:

    ismaelsadeeq commented at 6:43 pm on October 28, 2025:
    which line? you highlighted four lines.

    Eunovo commented at 7:07 pm on October 28, 2025:
    Line 198

    ismaelsadeeq commented at 7:17 pm on October 28, 2025:
    The line is important, it created a new transaction in the mempool it’s what will make the test fail when we don’t really interrupt a wait. Another thing that could be an indicator is also chain tip change.
  28. in src/node/miner.cpp:497 in dcb56fd4cb
    490@@ -483,8 +491,12 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
    491                 // method on BlockTemplate and no template could have been
    492                 // generated before a tip exists.
    493                 tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
    494-                return tip_changed || chainman.m_interrupt;
    495+                return tip_changed || chainman.m_interrupt || interrupt_wait;
    496             });
    497+            if (interrupt_wait) {
    498+                interrupt_wait = false;
    


    Eunovo commented at 6:21 pm on October 28, 2025:

    ismaelsadeeq commented at 6:46 pm on October 28, 2025:

    Interesting (that should never happen), can you share the logs and steps to reproduce? I can verify that it failed for me.

     02025-10-28T18:42:24.302035Z TestFramework (INFO): PRNG seed is: 1841123518053179096
     12025-10-28T18:42:24.302446Z TestFramework (INFO): Initializing test directory /var/folders/dj/d8p8jhd172n7wnq81ryfl6rc0000gn/T/bitcoin_func_test_kcqf80gj
     22025-10-28T18:42:25.106568Z TestFramework (INFO): Running echo test
     32025-10-28T18:42:25.113270Z TestFramework (INFO): Running mining test
     42025-10-28T18:42:31.292648Z TestFramework (ERROR): Unexpected exception
     5Traceback (most recent call last):
     6  File "/Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/test/functional/test_framework/test_framework.py", line 142, in main
     7    self.run_test()
     8    ~~~~~~~~~~~~~^^
     9  File "/Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/build/test/functional/interface_ipc.py", line 255, in run_test
    10    self.run_mining_test()
    11    ~~~~~~~~~~~~~~~~~~~~^^
    12  File "/Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/build/test/functional/interface_ipc.py", line 251, in run_mining_test
    13    asyncio.run(capnp.run(async_routine()))
    14    ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    15  File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 195, in run
    16    return runner.run(main)
    17           ~~~~~~~~~~^^^^^^
    18  File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/runners.py", line 118, in run
    19    return self._loop.run_until_complete(task)
    20           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
    21  File "/opt/homebrew/Cellar/python@3.13/3.13.7/Frameworks/Python.framework/Versions/3.13/lib/python3.13/asyncio/base_events.py", line 725, in run_until_complete
    22    return future.result()
    23           ~~~~~~~~~~~~~^^
    24  File "capnp/lib/capnp.pyx", line 1965, in run
    25  File "capnp/lib/capnp.pyx", line 1966, in capnp.lib.capnp.run
    26  File "/Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/build/test/functional/interface_ipc.py", line 207, in async_routine
    27    assert_equal(result.to_dict(), {})
    28    ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
    29  File "/Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/test/functional/test_framework/util.py", line 78, in assert_equal
    30    raise AssertionError("not(%s == %s)\n  in particular not(%s == %s)" % (thing1, thing2, d1, d2))
    31AssertionError: not({'result': <capnp.lib.capnp._DynamicCapabilityClient object at 0x104bea930>} == {})
    32  in particular not({'result': <capnp.lib.capnp._DynamicCapabilityClient object at 0x104bea930>} == {})
    332025-10-28T18:42:31.355024Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
    342025-10-28T18:42:31.355217Z TestFramework (WARNING): Not cleaning up dir /var/folders/dj/d8p8jhd172n7wnq81ryfl6rc0000gn/T/bitcoin_func_test_kcqf80gj
    352025-10-28T18:42:31.355287Z TestFramework (ERROR): Test failed. Test logging available at /var/folders/dj/d8p8jhd172n7wnq81ryfl6rc0000gn/T/bitcoin_func_test_kcqf80gj/test_framework.log
    362025-10-28T18:42:31.355462Z TestFramework (ERROR): 
    372025-10-28T18:42:31.355687Z TestFramework (ERROR): Hint: Call /Users/abubakarismail/Desktop/Work/bitcoin-dev/bitcoin-wait/test/functional/combine_logs.py '/var/folders/dj/d8p8jhd172n7wnq81ryfl6rc0000gn/T/bitcoin_func_test_kcqf80gj' to consolidate all logs
    382025-10-28T18:42:31.355759Z TestFramework (ERROR): 
    392025-10-28T18:42:31.355809Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
    402025-10-28T18:42:31.355892Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
    412025-10-28T18:42:31.355942Z TestFramework (ERROR): 
    42[node 0] Cleaning up leftover process
    

    Eunovo commented at 7:05 pm on October 28, 2025:

    Comment out line 497 and rebuild. The interface_ipc tests still pass successfully. They fail if you modify them to use two waitNext calls as shown below:

     0diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
     1index 0c9de28da0..a3dc559008 100755
     2--- a/test/functional/interface_ipc.py
     3+++ b/test/functional/interface_ipc.py
     4@@ -199,10 +199,14 @@ class IPCInterfaceTest(BitcoinTestFramework):
     5 
     6             wait_task = asyncio.create_task(wait_for_block())
     7             interrupt_task = asyncio.create_task(interrupt_wait())
     8+            wait2_task = asyncio.create_task(wait_for_block())
     9 
    10             result = await wait_task
    11             await interrupt_task
    12             assert_equal(result.to_dict(), {})
    13+            result2 = await wait2_task
    14+            block5 = await self.parse_and_deserialize_block(result2, ctx)
    15+            assert_equal(len(block5.vtx), 4)
    16 
    17             current_block_height = self.nodes[0].getchaintips()[0]["height"]
    18             check_opts = self.capnp_modules['mining'].BlockCheckOptions()
    

    This verifies that the second waitNext call completes after the interrupt


    ismaelsadeeq commented at 7:15 pm on October 28, 2025:
    Thanks for your swift response, you highlighted multiple lines, so I commented them all that’s why it failed for me. It should pass because we still return (The issue with commenting that line is that subsequent wait for same block template will return instantly). We can add a test case to verify that after interrupting a wait, you can wait again.
  29. in test/functional/interface_ipc.py:200 in dcb56fd4cb
    195+                await wait_started.wait() # Wait for confirmation wait started
    196+                await asyncio.sleep(0.1)  # Minimal buffer
    197+                template6.result.interruptWait()
    198+                miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
    199+
    200+            wait_task = asyncio.create_task(wait_for_block())
    


    Eunovo commented at 6:22 pm on October 28, 2025:

    https://github.com/bitcoin/bitcoin/pull/33676/commits/dcb56fd4cb59e6857c110dd87019459989dc1ec3:

    You can also add a test that interrupts before calling waitNext and check that waitNext returns immediately.


    ismaelsadeeq commented at 6:47 pm on October 28, 2025:
    Good idea, will do if retouching.
  30. Eunovo commented at 6:24 pm on October 28, 2025: contributor
    Looks Good. I left some comments on the tests.
  31. DrahtBot requested review from Eunovo on Oct 28, 2025
  32. Sjors commented at 11:28 am on October 31, 2025: member

    Concept ACK

    Can there ever only be one waitNext() call on per BlockTemplate? If not, does interruptWait() just stop all of them?

  33. ryanofsky commented at 2:05 pm on October 31, 2025: contributor

    re: #33676 (comment)

    Can there ever only be one waitNext() call on per BlockTemplate? If not, does interruptWait() just stop all of them?

    There can be multiple waitNext() calls and the current implementation of interruptWait() will interrupt exactly one of them arbitrarily. I speculated on how this could be improved in #33676 (comment), if there is a use-case for multiple waitNext calls. But I don’t think there is a good use-case, so it might be a good if the current interruptWait implemention is a soft discouragment for trying to use multiple simultaneous wait calls with the same block creation options from the same client.

  34. Sjors commented at 2:09 pm on October 31, 2025: member
    I also can’t think of a use case, but maybe we should fail if a client (accidentally) tries?
  35. ismaelsadeeq commented at 2:36 pm on October 31, 2025: member
    Yes, I think it’s a good idea to prevent multiple calls to waitNext if there is no explicit use case, or have interruptWait identify and cancel a targeted wait?
  36. ryanofsky commented at 2:53 pm on October 31, 2025: contributor

    Yes, I think it’s a good idea to prevent multiple calls to waitNext if there is no explicit use case, or have interruptWait identify and cancel a targeted wait?

    I think I like the current implementation more than either of these two ideas, because they both seem like they would bring unneeded complexity. It seems ok to allow multiple wait calls, since they should work perfectly well, even if there is not a very convenient way to cancel them. If it ever becomes important for interruptWait to provide other ways of cancelling it could be added when that becomes apparent.

  37. Sjors referenced this in commit b4c7d9761a on Oct 31, 2025
  38. Sjors commented at 2:56 pm on October 31, 2025: member
    I successfully tested this PR against my Template Provider implementation, see https://github.com/Sjors/sv2-tp/pull/57.
  39. Sjors referenced this in commit 8c2c9f661f on Oct 31, 2025
  40. in test/functional/interface_ipc.py:187 in dcb56fd4cb
    181@@ -184,6 +182,28 @@ async def async_routine():
    182             template7 = await template6.result.waitNext(ctx, waitoptions)
    183             assert_equal(template7.to_dict(), {})
    184 
    185+            self.log.debug("interruptWait should abort the current wait")
    186+            wait_started = asyncio.Event()
    187+            async def wait_for_block():
    


    Sjors commented at 3:26 pm on October 31, 2025:

    nit: you could move these helper methods out of the test:

     0diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
     1index 0c9de28da0..7a447eba92 100755
     2--- a/test/functional/interface_ipc.py
     3+++ b/test/functional/interface_ipc.py
     4@@ -82,10 +82,20 @@ class IPCInterfaceTest(BitcoinTestFramework):
     5         coinbase_data = BytesIO((await block_template.result.getCoinbaseTx(ctx)).result)
     6         tx = CTransaction()
     7         tx.deserialize(coinbase_data)
     8         return tx
     9
    10+    async def wait_for_block(self, template, waitoptions, ctx, wait_started):
    11+        wait_started.set()
    12+        return await template.result.waitNext(ctx, waitoptions)
    13+
    14+    async def interrupt_wait(self, template, miniwallet, wait_started):
    15+        await wait_started.wait() # Wait for confirmation wait started
    16+        await asyncio.sleep(0.1)  # Minimal buffer
    17+        template.result.interruptWait()
    18+        miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
    19+
    20     def run_echo_test(self):
    21         self.log.info("Running echo test")
    22         async def async_routine():
    23             ctx, init = await self.make_capnp_init_ctx()
    24             self.log.debug("Create Echo proxy object")
    25@@ -182,25 +192,15 @@ class IPCInterfaceTest(BitcoinTestFramework):
    26             template7 = await template6.result.waitNext(ctx, waitoptions)
    27             assert_equal(template7.to_dict(), {})
    28
    29             self.log.debug("interruptWait should abort the current wait")
    30             wait_started = asyncio.Event()
    31-            async def wait_for_block():
    32-                new_waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
    33-                new_waitoptions.timeout = waitoptions.timeout * 60 # 1 minute wait
    34-                new_waitoptions.feeThreshold = 1
    35-                wait_started.set()
    36-                return await template6.result.waitNext(ctx, new_waitoptions)
    37
    38-            async def interrupt_wait():
    39-                await wait_started.wait() # Wait for confirmation wait started
    40-                await asyncio.sleep(0.1)  # Minimal buffer
    41-                template6.result.interruptWait()
    42-                miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
    43-
    44-            wait_task = asyncio.create_task(wait_for_block())
    45-            interrupt_task = asyncio.create_task(interrupt_wait())
    46+            waitoptions.timeout = waitoptions.timeout * 60 # 1 minute wait
    47+            waitoptions.feeThreshold = 1
    48+            wait_task = asyncio.create_task(self.wait_for_block(template6, waitoptions, ctx, wait_started))
    49+            interrupt_task = asyncio.create_task(self.interrupt_wait(template6, miniwallet, wait_started))
    50
    51             result = await wait_task
    52             await interrupt_task
    53             assert_equal(result.to_dict(), {})
    
  41. in test/functional/interface_ipc.py:205 in dcb56fd4cb
    200+            wait_task = asyncio.create_task(wait_for_block())
    201+            interrupt_task = asyncio.create_task(interrupt_wait())
    202+
    203+            result = await wait_task
    204+            await interrupt_task
    205+            assert_equal(result.to_dict(), {})
    


    Sjors commented at 3:29 pm on October 31, 2025:
    Is this checking that the response to waitNext() is a null template?
  42. in src/node/miner.cpp:496 in dcb56fd4cb
    490@@ -484,8 +491,12 @@ std::unique_ptr<CBlockTemplate> WaitAndCreateNewBlock(ChainstateManager& chainma
    491                 // method on BlockTemplate and no template could have been
    492                 // generated before a tip exists.
    493                 tip_changed = Assume(tip_block) && tip_block != block_template->block.hashPrevBlock;
    494-                return tip_changed || chainman.m_interrupt;
    495+                return tip_changed || chainman.m_interrupt || interrupt_wait;
    496             });
    497+            if (interrupt_wait) {
    


    Sjors commented at 3:51 pm on October 31, 2025:
    It’s somewhat unintuitive that we’re waiting for a local variable to change. It might be more clear if we make a trivial struct TemplateNotifications and have this line check template_notifications.m_interrupt_wait instead. It’s more clear that something called “notifications” can change from under us.

    ryanofsky commented at 4:43 pm on October 31, 2025:

    re: #33676 (review)

    It’s somewhat unintuitive that we’re waiting for a local variable to change. It might be more clear if we make a trivial struct TemplateNotifications and have this line check template_notifications.m_interrupt_wait instead. It’s more clear that something called “notifications” can change from under us.

    I feel like it’s not that confusing once you notice it’s declared as outside reference (bool& interrupt_wait), but a struct could be useful. Another solution could be to make to use a pointer (bool* interrupt_wait) to make it more obvious that it points to something. Any approach seems fine to me.

  43. Sjors commented at 3:52 pm on October 31, 2025: member

    tACK dcb56fd4cb59e6857c110dd87019459989dc1ec3

    I left some suggestions for extra clarity.


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: 2025-10-31 18:13 UTC

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