test: improve interface_ipc.py waitNext tests
What changed, and why it matters
This commit only fixes and improves automated tests for Bitcoin Core's inter-process communication (IPC) interface. It does not change any production code that runs the Bitcoin network or wallets, so it cannot directly affect security, funds, or node behavior. The changes make the tests more meaningful by ensuring that actions like mining a block or sending a transaction happen while the code is waiting, rather than before.
No security action needed. This is a test-only improvement and can be reviewed as normal code quality/test coverage work.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies test/functional/interface_ipc.py. It adds a wait_and_do() helper that runs a wait call and a triggering action concurrently, so the action occurs during the wait. It updates several test cases (waitTipChanged, waitNext fee-threshold tests, and interruptWait) to use this helper. It also fixes the interruptWait test, which was missing an await and therefore never actually calling interruptWait(). No production code is changed.
Changed components
test/functional/interface_ipc.pyInspect captured patch +37 / −22
diff --git a/test/functional/interface_ipc.py b/test/functional/interface_ipc.py
index c04a0576..7c8f51a8 100755
--- a/test/functional/interface_ipc.py
+++ b/test/functional/interface_ipc.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the IPC (multiprocess) interface."""
import asyncio
+import inspect
from contextlib import asynccontextmanager, AsyncExitStack
from io import BytesIO
from pathlib import Path
@@ -38,6 +39,29 @@ async def wait_next_template(template, stack, ctx, opts):
"""Call template.waitNext() and return template, then call template.destroy() when stack exits."""
return await stack.enter_async_context(destroying((await template.waitNext(ctx, opts)).result, ctx))
+async def wait_and_do(wait_fn, do_fn):
+ """Call wait_fn, then sleep, then call do_fn in a parallel task. Wait for
+ both tasks to complete."""
+ wait_started = asyncio.Event()
+ result = None
+
+ async def wait():
+ nonlocal result
+ wait_started.set()
+ result = await wait_fn
+
+ async def do():
+ await wait_started.wait()
+ await asyncio.sleep(0.1)
+ # Let do_fn be either a callable or an awaitable object
+ if inspect.isawaitable(do_fn):
+ await do_fn
+ else:
+ do_fn()
+
+ await asyncio.gather(wait(), do())
+ return result
+
class IPCInterfaceTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
@@ -138,8 +162,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
current_block_height = self.nodes[0].getchaintips()[0]["height"]
assert blockref.result.height == current_block_height
self.log.debug("Mine a block")
- self.generate(self.nodes[0], 1)
- newblockref = (await mining.waitTipChanged(ctx, blockref.result.hash)).result
+ newblockref = (await wait_and_do(
+ mining.waitTipChanged(ctx, blockref.result.hash, timeout),
+ lambda: self.generate(self.nodes[0], 1))).result
assert_equal(len(newblockref.hash), block_hash_size)
assert_equal(newblockref.height, current_block_height + 1)
self.log.debug("Wait for timeout")
@@ -175,8 +200,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
waitoptions.timeout = timeout
waitoptions.feeThreshold = 1
- self.generate(self.nodes[0], 1)
- template2 = await wait_next_template(template, stack, ctx, waitoptions)
+ template2 = await wait_and_do(
+ wait_next_template(template, stack, ctx, waitoptions),
+ lambda: self.generate(self.nodes[0], 1))
block2 = await self.parse_and_deserialize_block(template2, ctx)
assert_equal(len(block2.vtx), 1)
@@ -185,8 +211,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
assert_equal(template3._has("result"), False)
self.log.debug("Wait for another, get one after increase in fees in the mempool")
- miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
- template4 = await wait_next_template(template2, stack, ctx, waitoptions)
+ template4 = await wait_and_do(
+ wait_next_template(template2, stack, ctx, waitoptions),
+ lambda: miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
block3 = await self.parse_and_deserialize_block(template4, ctx)
assert_equal(len(block3.vtx), 2)
@@ -198,8 +225,9 @@ class IPCInterfaceTest(BitcoinTestFramework):
waitoptions.feeThreshold = 1
self.log.debug("Wait for another, get one after increase in fees in the mempool")
- miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0])
- template6 = await wait_next_template(template5, stack, ctx, waitoptions)
+ template6 = await wait_and_do(
+ wait_next_template(template5, stack, ctx, waitoptions),
+ lambda: miniwallet.send_self_transfer(fee_rate=10, from_node=self.nodes[0]))
block4 = await self.parse_and_deserialize_block(template6, ctx)
assert_equal(len(block4.vtx), 3)
@@ -208,26 +236,13 @@ class IPCInterfaceTest(BitcoinTestFramework):
assert_equal(template7._has("result"), False)
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()
template7 = await template6.waitNext(ctx, new_waitoptions)
assert_equal(template7._has("result"), False)
-
- async def interrupt_wait():
- await wait_started.wait() # Wait for confirmation wait started
- await asyncio.sleep(0.1) # Minimal buffer
- template6.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
+ await wait_and_do(wait_for_block(), template6.interruptWait())
current_block_height = self.nodes[0].getchaintips()[0]["height"]
check_opts = self.capnp_modules['mining'].BlockCheckOptions()
Why this scored 15/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.