mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-06 03:36:18 +00:00
Upstream PR bitcoin-core/libmultiprocess#240 fixed various issues which require updates to python IPC tests. Those changes are made in this commit.
179 lines
7.9 KiB
Python
Executable File
179 lines
7.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# 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.
|
|
"""Test the IPC (multiprocess) interface."""
|
|
import asyncio
|
|
|
|
from contextlib import ExitStack
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal
|
|
from test_framework.ipc_util import (
|
|
load_capnp_modules,
|
|
make_capnp_init_ctx,
|
|
)
|
|
|
|
# Test may be skipped and not have capnp installed
|
|
try:
|
|
import capnp # type: ignore[import] # noqa: F401
|
|
except ModuleNotFoundError:
|
|
pass
|
|
|
|
|
|
class IPCInterfaceTest(BitcoinTestFramework):
|
|
|
|
def skip_test_if_missing_module(self):
|
|
self.skip_if_no_ipc()
|
|
self.skip_if_no_py_capnp()
|
|
|
|
def set_test_params(self):
|
|
self.num_nodes = 1
|
|
|
|
def setup_nodes(self):
|
|
self.extra_init = [{"ipcbind": True}]
|
|
super().setup_nodes()
|
|
# Use this function to also load the capnp modules (we cannot use set_test_params for this,
|
|
# as it is being called before knowing whether capnp is available).
|
|
self.capnp_modules = load_capnp_modules(self.config)
|
|
|
|
def run_echo_test(self):
|
|
self.log.info("Running echo test")
|
|
async def async_routine():
|
|
ctx, init = await make_capnp_init_ctx(self)
|
|
self.log.debug("Create Echo proxy object")
|
|
echo = init.makeEcho(ctx).result
|
|
self.log.debug("Test a few invocations of echo")
|
|
for s in ["hallo", "", "haha"]:
|
|
result_eval = (await echo.echo(ctx, s)).result
|
|
assert_equal(s, result_eval)
|
|
self.log.debug("Destroy the Echo object")
|
|
echo.destroy(ctx)
|
|
asyncio.run(capnp.run(async_routine()))
|
|
|
|
def run_mining_test(self):
|
|
self.log.info("Running mining test")
|
|
block_hash_size = 32
|
|
|
|
async def async_routine():
|
|
ctx, init = await make_capnp_init_ctx(self)
|
|
self.log.debug("Create Mining proxy object")
|
|
mining = init.makeMining(ctx).result
|
|
self.log.debug("Test simple inspectors")
|
|
assert (await mining.isTestChain(ctx)).result
|
|
assert not (await mining.isInitialBlockDownload(ctx)).result
|
|
blockref = await mining.getTip(ctx)
|
|
assert blockref.hasResult
|
|
assert_equal(len(blockref.result.hash), block_hash_size)
|
|
current_block_height = self.nodes[0].getchaintips()[0]["height"]
|
|
assert_equal(blockref.result.height, current_block_height)
|
|
|
|
asyncio.run(capnp.run(async_routine()))
|
|
|
|
def run_deprecated_mining_test(self):
|
|
self.log.info("Running deprecated mining interface test")
|
|
async def async_routine():
|
|
ctx, init = await make_capnp_init_ctx(self)
|
|
self.log.debug("Calling deprecated makeMiningOld2 should raise an error")
|
|
try:
|
|
await init.makeMiningOld2()
|
|
raise AssertionError("makeMiningOld2 unexpectedly succeeded")
|
|
except capnp.KjException as e:
|
|
assert_equal(e.description, "remote exception: std::exception: Old mining interface (@2) not supported. Please update your client!")
|
|
assert_equal(e.type, "FAILED")
|
|
asyncio.run(capnp.run(async_routine()))
|
|
|
|
def run_unclean_disconnect_test(self):
|
|
"""Test behavior when disconnecting during an IPC call that later
|
|
returns a non-null interface pointer. This used to cause a crash as
|
|
reported https://github.com/bitcoin/bitcoin/issues/34250, but now just
|
|
results in a cancellation log message"""
|
|
node = self.nodes[0]
|
|
self.log.info("Running disconnect during BlockTemplate.waitNext")
|
|
timeout = self.rpc_timeout * 1000.0
|
|
disconnected_log_check = ExitStack()
|
|
|
|
async def async_routine():
|
|
ctx, init = await make_capnp_init_ctx(self)
|
|
self.log.debug("Create Mining proxy object")
|
|
mining = init.makeMining(ctx).result
|
|
|
|
self.log.debug("Create a template")
|
|
opts = self.capnp_modules['mining'].BlockCreateOptions()
|
|
template = (await mining.createNewBlock(ctx, opts)).result
|
|
|
|
self.log.debug("Wait for a new template")
|
|
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
|
|
waitoptions.timeout = timeout
|
|
waitoptions.feeThreshold = 1
|
|
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
|
|
promise = template.waitNext(ctx, waitoptions)
|
|
await asyncio.sleep(0.1)
|
|
disconnected_log_check.enter_context(node.assert_debug_log(expected_msgs=["IPC server: socket disconnected", "canceled while executing"], timeout=2))
|
|
del promise
|
|
|
|
asyncio.run(capnp.run(async_routine()))
|
|
|
|
# Wait for socket disconnected log message, then generate a block to
|
|
# cause the waitNext() call to return a new template. Look for a
|
|
# canceled IPC log message after waitNext returns.
|
|
with node.assert_debug_log(expected_msgs=["interrupted (canceled)"], timeout=2):
|
|
disconnected_log_check.close()
|
|
self.generate(node, 1)
|
|
|
|
def run_thread_busy_test(self):
|
|
"""Test behavior when sending multiple calls to the same server thread
|
|
which used to cause a crash as reported
|
|
https://github.com/bitcoin/bitcoin/issues/33923."""
|
|
node = self.nodes[0]
|
|
self.log.info("Running thread busy test")
|
|
timeout = self.rpc_timeout * 1000.0
|
|
|
|
async def async_routine():
|
|
ctx, init = await make_capnp_init_ctx(self)
|
|
self.log.debug("Create Mining proxy object")
|
|
mining = init.makeMining(ctx).result
|
|
|
|
self.log.debug("Create a template")
|
|
opts = self.capnp_modules['mining'].BlockCreateOptions()
|
|
template = (await mining.createNewBlock(ctx, opts)).result
|
|
|
|
self.log.debug("Wait for a new template")
|
|
waitoptions = self.capnp_modules['mining'].BlockWaitOptions()
|
|
waitoptions.timeout = timeout
|
|
waitoptions.feeThreshold = 1
|
|
|
|
# Make multiple waitNext calls where the first will start to
|
|
# execute, and the second and third will be posted waiting to
|
|
# execute. Previously, the third call would fail calling
|
|
# mp::Waiter::post() because the waiting function slot is occupied,
|
|
# but now posts are queued.
|
|
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
|
|
promise1 = template.waitNext(ctx, waitoptions)
|
|
await asyncio.sleep(0.1)
|
|
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
|
|
promise2 = template.waitNext(ctx, waitoptions)
|
|
await asyncio.sleep(0.1)
|
|
with node.assert_debug_log(expected_msgs=["BlockTemplate.waitNext", "IPC server post request"], timeout=2):
|
|
promise3 = template.waitNext(ctx, waitoptions)
|
|
await asyncio.sleep(0.1)
|
|
|
|
# Generate a new block to make the active waitNext calls return, then clean up.
|
|
with node.assert_debug_log(expected_msgs=["IPC server send response"], timeout=2):
|
|
self.generate(node, 1, sync_fun=self.no_op)
|
|
await ((await promise1).result).destroy(ctx)
|
|
await ((await promise2).result).destroy(ctx)
|
|
await ((await promise3).result).destroy(ctx)
|
|
await template.destroy(ctx)
|
|
|
|
asyncio.run(capnp.run(async_routine()))
|
|
|
|
def run_test(self):
|
|
self.run_echo_test()
|
|
self.run_mining_test()
|
|
self.run_deprecated_mining_test()
|
|
self.run_unclean_disconnect_test()
|
|
self.run_thread_busy_test()
|
|
|
|
if __name__ == '__main__':
|
|
IPCInterfaceTest(__file__).main()
|