From e7a918b69a5e6aaf3f02ca84ca6cde14401928ed Mon Sep 17 00:00:00 2001 From: Enoch Azariah Date: Mon, 9 Mar 2026 11:06:55 +0100 Subject: [PATCH] test: verify IPC error handling for invalid coinbase Add a test case to interface_ipc_mining.py to verify that the IPC server correctly handles and reports serialization errors rather than crashing the node. This covers the scenario where submitSolution is called with data that cannot be deserialized, as discussed in #33341 Also introduces the assert_capnp_failed helper in ipc_util.py to cleanly handle macOS-specific Cap'n Proto exception strings, and refactors an existing block weight test to use it. --- test/functional/interface_ipc_mining.py | 19 +++++++++---------- test/functional/test_framework/ipc_util.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/test/functional/interface_ipc_mining.py b/test/functional/interface_ipc_mining.py index 6f2237a883d..0c3b9cf869c 100755 --- a/test/functional/interface_ipc_mining.py +++ b/test/functional/interface_ipc_mining.py @@ -7,7 +7,6 @@ import asyncio import time from contextlib import AsyncExitStack from io import BytesIO -import platform from test_framework.blocktools import NULL_OUTPOINT from test_framework.messages import ( MAX_BLOCK_WEIGHT, @@ -42,6 +41,7 @@ from test_framework.ipc_util import ( mining_wait_next_template, wait_and_do, make_mining_ctx, + assert_capnp_failed ) # Test may be skipped and not have capnp installed @@ -325,15 +325,7 @@ class IPCMiningTest(BitcoinTestFramework): await mining.createNewBlock(ctx, opts) raise AssertionError("createNewBlock unexpectedly succeeded") except capnp.lib.capnp.KjException as e: - if e.description == "remote exception: unknown non-KJ exception of type: kj::Exception": - # macOS + REDUCE_EXPORTS bug: Cap'n Proto fails to recognize - # its own exception type and returns a generic error instead. - # https://github.com/bitcoin/bitcoin/pull/34422#discussion_r2863852691 - # Assert this only occurs on Darwin until fixed. - assert_equal(platform.system(), "Darwin") - else: - assert_equal(e.description, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units") - assert_equal(e.type, "FAILED") + assert_capnp_failed(e, "remote exception: std::exception: block_reserved_weight (0) must be at least 2000 weight units") asyncio.run(capnp.run(async_routine())) @@ -357,6 +349,13 @@ class IPCMiningTest(BitcoinTestFramework): block.hashMerkleRoot = block.calc_merkle_root() original_version = block.nVersion + self.log.debug("Submit solution that can't be deserialized") + try: + await template.submitSolution(ctx, 0, 0, 0, b"") + raise AssertionError("submitSolution unexpectedly succeeded") + except capnp.lib.capnp.KjException as e: + assert_capnp_failed(e, "remote exception: std::exception: SpanReader::read(): end of data:") + self.log.debug("Submit a block with a bad version") block.nVersion = 0 block.solve() diff --git a/test/functional/test_framework/ipc_util.py b/test/functional/test_framework/ipc_util.py index c80f78f79b7..03e3b5d511b 100644 --- a/test/functional/test_framework/ipc_util.py +++ b/test/functional/test_framework/ipc_util.py @@ -10,9 +10,13 @@ from dataclasses import dataclass from io import BytesIO from pathlib import Path import shutil +import platform from typing import Optional from test_framework.messages import CBlock +from test_framework.util import ( + assert_equal +) # Test may be skipped and not have capnp installed try: @@ -155,3 +159,14 @@ async def make_mining_ctx(self): self.log.debug("Create Mining proxy object") mining = init.makeMining(ctx).result return ctx, mining + +def assert_capnp_failed(e, description_prefix): + if e.description == "remote exception: unknown non-KJ exception of type: kj::Exception": + # macOS + REDUCE_EXPORTS bug: Cap'n Proto fails to recognize + # its own exception type and returns a generic error instead. + # https://github.com/bitcoin/bitcoin/pull/34422#discussion_r2863852691 + # Assert this only occurs on Darwin until fixed. + assert_equal(platform.system(), "Darwin") + else: + assert e.description.startswith(description_prefix), f"Expected description starting with '{description_prefix}', got '{e.description}'" + assert_equal(e.type, "FAILED")