Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2025-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""6Verify framework startup failures only raise one exception since7multiple exceptions being raised muddies the waters of what actually8went wrong. We should maintain this bar of only raising one exception as9long as additional maintenance and complexity is low.11Test relaunches itself into child processes in order to trigger failures12without the parent process' BitcoinTestFramework also failing.13"""15from test_framework.util import (16assert_raises_message,17rpc_port,18)19from test_framework.test_framework import BitcoinTestFramework21from hashlib import md522from os import linesep23import re24import subprocess25import sys26import time28class FeatureFrameworkStartupFailures(BitcoinTestFramework):29def set_test_params(self):30self.num_nodes = 132def setup_network(self):33# Don't start the node yet, as we want to measure during run_test.34self.add_nodes(self.num_nodes, self.extra_args)36# Launches a child test process that runs this same file, but instantiates37# a child test. Verifies that it raises only the expected exception, once.38def _verify_startup_failure(self, test, internal_args, expected_exception):39name = test.__name__40def format_child_output(output):41return f"\n<{name} OUTPUT BEGIN>\n{output.strip()}\n<{name} OUTPUT END>\n"43# Inherit sys.argv from parent, only overriding tmpdir to a subdirectory44# so children don't fail due to colliding with the parent dir.45assert self.options.tmpdir, "Framework should always set tmpdir."46subdir = md5(expected_exception.encode('utf-8')).hexdigest()[:8]47args = [sys.executable] + sys.argv + [f"--tmpdir={self.options.tmpdir}/{subdir}", f"--internal_test={name}"] + internal_args49try:50output = subprocess.run(args, timeout=60 * self.options.timeout_factor,51stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout52except subprocess.TimeoutExpired as e:53sys.exit("Unexpected child process timeout!\n"54"WARNING: Timeouts like this halt execution of TestNode logic, "55"meaning dangling bitcoind processes are to be expected.\n" +56(format_child_output(e.output.decode("utf-8")) if e.output else "<EMPTY OUTPUT>"))58errors = []59if (n := output.count("Traceback")) != 1:60errors.append(f"Found {n}/1 tracebacks - expecting exactly one with no knock-on exceptions.")61if (n := len(re.findall(expected_exception, output))) != 1:62errors.append(f"Found {n}/1 occurrences of the specific exception: {expected_exception}")63if (n := output.count("Test failed. Test logging available at")) != 1:64errors.append(f"Found {n}/1 test failure output messages.")66assert not errors, (f"Child test did NOT contain (only) expected errors:\n{linesep.join(errors)}\n" +67format_child_output(output))69self.log.debug("Child test did contain (only) expected errors:\n" +70format_child_output(output))72def run_test(self):73self.log.info("Verifying _verify_startup_failure() functionality (self-check).")74assert_raises_message(75AssertionError,76( "Child test did NOT contain (only) expected errors:\n"77f"Found 0/1 tracebacks - expecting exactly one with no knock-on exceptions.{linesep}"78f"Found 0/1 occurrences of the specific exception: NonExistentError{linesep}"79"Found 0/1 test failure output messages."),80self._verify_startup_failure,81TestSuccess, [],82"NonExistentError",83)85self.log.info("Parent process is measuring node startup duration in order to obtain a reasonable timeout value for later tests...")86node_start_time = time.time()87self.nodes[0].start()88self.nodes[0].wait_for_rpc_connection()89node_start_duration = time.time() - node_start_time90self.nodes[0].stop_node()91self.log.info(f"...measured {node_start_duration:.1f}s.")93self.log.info("Verifying inability to connect to bitcoind's RPC interface due to wrong port results in one exception containing at least one OSError.")94self._verify_startup_failure(95TestWrongRpcPortStartupFailure, [f"--internal_node_start_duration={node_start_duration}"],96r"AssertionError: \[node 0\] Unable to connect to bitcoind after \d+s \(ignored errors: {[^}]*'OSError \w+'?: \d+[^}]*}, latest: '[\w ]+'/\w+\([^)]+\)\)"97)99self.log.info("Verifying timeout while waiting for init errors that do not occur results in only one exception.")100self._verify_startup_failure(101TestMissingInitErrorTimeout, [f"--internal_node_start_duration={node_start_duration}"],102r"AssertionError: \[node 0\] bitcoind should have exited within \d+s with an error \(cmd:"103)105self.log.info("Verifying startup failure due to invalid arg results in only one exception.")106self._verify_startup_failure(107TestInitErrorStartupFailure, [],108r"FailedToStartError: \[node 0\] bitcoind exited with status 1 during initialization\. Error: Error parsing command line arguments: Invalid parameter -nonexistentarg"109)111self.log.info("Verifying start() then stop_node() on a node without wait_for_rpc_connection() in between raises an assert.")112self._verify_startup_failure(113TestStartStopStartupFailure, [],114r"AssertionError: \[node 0\] Should only call stop_node\(\) on a running node after wait_for_rpc_connection\(\) succeeded\. Did you forget to call the latter after start\(\)\? Not connected to process: \d+"115)117class InternalTestMixin:118def add_options(self, parser):119# Just here to silence unrecognized argument error, we actually read the value in the if-main at the bottom.120parser.add_argument("--internal_test", dest="internal_never_read", help="ONLY TO BE USED WHEN TEST RELAUNCHES ITSELF")122class InternalDurationTestMixin(InternalTestMixin):123def add_options(self, parser):124# Receives the previously measured duration for node startup + RPC connection establishment.125parser.add_argument("--internal_node_start_duration", dest="node_start_duration", help="ONLY TO BE USED WHEN TEST RELAUNCHES ITSELF", type=float)126InternalTestMixin.add_options(self, parser)128def get_reasonable_rpc_timeout(self):129# 2 * the measured test startup duration should be enough.130# Divide by timeout_factor to counter multiplication in BitcoinTestFramework.131return max(3, 2 * self.options.node_start_duration) / self.options.timeout_factor133class TestWrongRpcPortStartupFailure(InternalDurationTestMixin, BitcoinTestFramework):134def set_test_params(self):135self.num_nodes = 1136# Override RPC listen port to something TestNode isn't expecting so that137# we are unable to establish an RPC connection.138self.extra_args = [[f"-rpcport={rpc_port(2)}"]]139# Override the timeout to avoid waiting unnecessarily long to realize140# nothing is on that port.141self.rpc_timeout = self.get_reasonable_rpc_timeout()143def run_test(self):144assert False, "Should have failed earlier during startup."146class TestMissingInitErrorTimeout(InternalDurationTestMixin, BitcoinTestFramework):147def set_test_params(self):148self.num_nodes = 1149# Override the timeout to avoid waiting unnecessarily long for an init150# error which never occurs.151self.rpc_timeout = self.get_reasonable_rpc_timeout()153def setup_network(self):154self.add_nodes(self.num_nodes, self.extra_args)155self.nodes[0].assert_start_raises_init_error()156assert False, "assert_start_raises_init_error() should raise an exception due to timeout since we don't expect an init error."158def run_test(self):159assert False, "Should have failed earlier during startup."161class TestInitErrorStartupFailure(InternalTestMixin, BitcoinTestFramework):162def set_test_params(self):163self.num_nodes = 1164self.extra_args = [["-nonexistentarg"]]166def run_test(self):167assert False, "Should have failed earlier during startup."169class TestStartStopStartupFailure(InternalTestMixin, BitcoinTestFramework):170def set_test_params(self):171self.num_nodes = 1173def setup_network(self):174self.add_nodes(self.num_nodes, self.extra_args)175self.nodes[0].start()176self.nodes[0].stop_node()177assert False, "stop_node() should raise an exception when we haven't called wait_for_rpc_connection()"179def run_test(self):180assert False, "Should have failed earlier during startup."182class TestSuccess(InternalTestMixin, BitcoinTestFramework):183def set_test_params(self):184self.num_nodes = 1186def setup_network(self):187pass # Don't need to start our node.189def run_test(self):190pass # Just succeed.193if __name__ == '__main__':194if class_name := next((m[1] for arg in sys.argv[1:] if (m := re.match(r'--internal_test=(.+)', arg))), None):195internal_test = globals().get(class_name)196assert internal_test, f"Unrecognized test: {class_name}"197internal_test(__file__).main()198else:199FeatureFrameworkStartupFailures(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.