mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-06 03:36:18 +00:00
1c1de334e9c0e3b4c72c3f3fe45b1f4df5ce4502 test: avoid interface_ipc.py race and null pointer dereference (Ryan Ofsky) Pull request description: Avoid race condition in `run_deprecated_mining_test` caused by creating and immediately destroying an unused worker thread. This is leading to test failures reported by maflcko in #34711 ACKs for top commit: optout21: utACK 1c1de334e9c0e3b4c72c3f3fe45b1f4df5ce4502 l0rinc: Tested ACK 1c1de334e9c0e3b4c72c3f3fe45b1f4df5ce4502 w0xlt: ACK 1c1de334e9c0e3b4c72c3f3fe45b1f4df5ce4502 enirox001: ACK 1c1de33 Tree-SHA512: d0af9676a46e991a3f4fda3795c02d1998d30de24991436b8ada425585c6699ff32a7057ca7a0ef2925f782fd3bf73e0381f5d4325e4f1c09f487fce1de49e45
181 lines
8.0 KiB
Python
Executable File
181 lines
8.0 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():
|
|
node = self.nodes[0]
|
|
connection = await capnp.AsyncIoStream.create_unix_connection(node.ipc_socket_path)
|
|
init = capnp.TwoPartyClient(connection).bootstrap().cast_as(self.capnp_modules['init'].Init)
|
|
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()
|