From fa09de8b68614ff0a8c71420f6f36d25acdfc03f Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 26 May 2026 11:59:43 +0200 Subject: [PATCH 1/4] test: [refactor] Simplify submit_block_catch_error Make it catch any Exception and let the caller verify it. This refactor does not change any behavior, but the code is simpler, more flexible and still correct, because wait_for_node_exit enforces the crash to happen. --- test/functional/feature_dbcrash.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 98aabb8d493..b5b2f4e6b1b 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -25,10 +25,7 @@ - restart until recovery succeeds - check that utxo matches node3 using gettxoutsetinfo""" -import errno -import http.client import random -import subprocess import time from test_framework.blocktools import COINBASE_MATURITY @@ -90,7 +87,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): except Exception: # An exception here should mean the node is about to crash. # If bitcoind exits, then try again. wait_for_node_exit() - # should raise an exception if bitcoind doesn't exit. + # enforces that bitcoind crashed. self.wait_for_node_exit(node_index, timeout=10) self.crashed_on_restart += 1 @@ -105,25 +102,15 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): """Try submitting a block to the given node. Catch any exceptions that indicate the node has crashed. + The caller will check that a crash happened. Returns true if the block was submitted successfully; false otherwise.""" try: self.nodes[node_index].submitblock(block) return True - except (http.client.CannotSendRequest, http.client.RemoteDisconnected) as e: + except Exception as e: self.log.debug(f"node {node_index} submitblock raised exception: {e}") return False - except subprocess.CalledProcessError as e: - self.log.debug(f"node {node_index} submitblock raised CalledProcessError: {e}") - return False - except OSError as e: - self.log.debug(f"node {node_index} submitblock raised OSError exception: errno={e.errno}") - if e.errno in [errno.EPIPE, errno.ECONNREFUSED, errno.ECONNRESET]: - # The node has likely crashed - return False - else: - # Unexpected exception, raise - raise def sync_node3blocks(self, block_hashes): """Use submitblock to sync node3's chain with the other nodes @@ -149,6 +136,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): if not self.submit_block_catch_error(i, block): # TODO: more carefully check that the crash is due to -dbcrashratio # (change the exit code perhaps, and check that here?) + # wait_for_node_exit() enforces that bitcoind crashed. self.wait_for_node_exit(i, timeout=30) self.log.debug(f"Restarting node {i} after block hash {block_hash}") nodei_utxo_hash = self.restart_node(i, block_hash) From fac27d702fd1fb9b0d8fd7676b54e75e06b35c0e Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 26 May 2026 12:04:31 +0200 Subject: [PATCH 2/4] test: Fix feature_dbcrash.py --usecli intermittent error Catch any Exception in verify_utxo_hash and let restart_node verify the crash via wait_for_node_exit. (Also, use named args in restart_node, while touching this test) Catching any Exception covers possible subprocess.CalledProcessError that may happen in a --usecli run. E.g. TestFramework (INFO): Verifying utxo hash matches for all nodes TestFramework.bitcoincli (DEBUG): Running bitcoin-cli ['-datadir=/tmp/bitcoin_func_test_gzufs0ht/node0', '-rpcclienttimeout=240', '-rpcconnect=127.0.0.1', '-rpcport=20963', 'gettxoutsetinfo'] TestFramework.bitcoincli (DEBUG): Running bitcoin-cli ['-datadir=/tmp/bitcoin_func_test_gzufs0ht/node1', '-rpcclienttimeout=240', '-rpcconnect=127.0.0.1', '-rpcport=20964', 'gettxoutsetinfo'] TestFramework (ERROR): Called Process failed with stdout='error: timeout on transient error: Could not connect to the server 127.0.0.1:20964 (error code 1 - "EOF reached") Make sure the bitcoind server is running and that you are connecting to the correct RPC port. Use "bitcoin-cli -help" for more info. '; stderr='None'; Traceback (most recent call last): File "./test/functional/test_framework/test_framework.py", line 143, in main self.run_test() ~~~~~~~~~~~~~^^ File "./test/functional/feature_dbcrash.py", line 273, in run_test self.verify_utxo_hash() ~~~~~~~~~~~~~~~~~~~~~^^ File "./test/functional/feature_dbcrash.py", line 182, in verify_utxo_hash nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['hash_serialized_3'] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "./test/functional/test_framework/test_node.py", line 963, in __call__ return self.cli.send_cli(self.command, *args, **kwargs) ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "./test/functional/test_framework/test_node.py", line 1043, in send_cli raise subprocess.CalledProcessError(returncode, p_args, output=cli_stderr) --- test/functional/feature_dbcrash.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index b5b2f4e6b1b..693bc50aa8d 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -70,7 +70,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): self.start_nodes() # Leave them unconnected, we'll use submitblock directly in this test - def restart_node(self, node_index, expected_tip): + def restart_node(self, node_index, *, expected_tip): """Start up a given node id, wait for the tip to reach the given block hash, and calculate the utxo hash. Exceptions during startup or subsequent RPC calls should indicate a node crash (due to -dbcrashratio), in which case we try again. Give up @@ -139,7 +139,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): # wait_for_node_exit() enforces that bitcoind crashed. self.wait_for_node_exit(i, timeout=30) self.log.debug(f"Restarting node {i} after block hash {block_hash}") - nodei_utxo_hash = self.restart_node(i, block_hash) + nodei_utxo_hash = self.restart_node(i, expected_tip=block_hash) assert nodei_utxo_hash is not None self.restart_counts[i] += 1 else: @@ -168,9 +168,9 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): for i in range(3): try: nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['hash_serialized_3'] - except OSError: + except Exception: # probably a crash on db flushing - nodei_utxo_hash = self.restart_node(i, self.nodes[3].getbestblockhash()) + nodei_utxo_hash = self.restart_node(i, expected_tip=self.nodes[3].getbestblockhash()) assert_equal(nodei_utxo_hash, node3_utxo_hash) def generate_small_transactions(self, node, count, utxo_list): From faf14755149988af693a267e5073a4b559b961ee Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 26 May 2026 18:15:09 +0200 Subject: [PATCH 3/4] ci: Exclude feature_dbcrash.py under --v2transport --usecli MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test should now be passing, but it is slow. For example, the commit that enabled the test, took ~47 minutes on a fast CPU, but using a heavy debug build: https://github.com/bitcoin/bitcoin/actions/runs/26434786214/job/77815064664?pr=35363#step:11:4101 ... Model name: AMD Ryzen 9 7950X3D 16-Core Processor ... C++ compiler .......................... GNU 15.2.0, /usr/bin/g++ CMAKE_BUILD_TYPE ...................... Debug Preprocessor defined macros ........... DEBUG DEBUG_LOCKORDER DEBUG_LOCKCONTENTION RPC_DOC_CHECK ABORT_ON_FAILED_ASSUME _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG C++ compiler flags .................... -m64 -O0 -ftrapv -O1 -g3 -g3 -std=c++20 ... ... feature_dbcrash.py | ✓ Passed | 2806 s --- ci/test/00_setup_env_native_alpine_musl.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/test/00_setup_env_native_alpine_musl.sh b/ci/test/00_setup_env_native_alpine_musl.sh index 3ab56159a3a..3bad08d6853 100755 --- a/ci/test/00_setup_env_native_alpine_musl.sh +++ b/ci/test/00_setup_env_native_alpine_musl.sh @@ -17,5 +17,5 @@ export BITCOIN_CONFIG="\ -DREDUCE_EXPORTS=ON \ -DCMAKE_BUILD_TYPE=Debug \ " -export TEST_RUNNER_EXTRA="--v2transport --usecli --extended" +export TEST_RUNNER_EXTRA="--v2transport --usecli --extended --exclude feature_dbcrash" # Run extended tests under --usecli and --v2transport, but exclude the very slow dbcrash export BITCOIN_CMD="bitcoin -m" # Used in functional tests From fad585b6e59452bcd0344653537001adc99450dc Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Thu, 28 May 2026 12:04:14 +0200 Subject: [PATCH 4/4] test: Wait for node exit after crash in verify_utxo_hash --- test/functional/feature_dbcrash.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 693bc50aa8d..24e663af821 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -170,6 +170,7 @@ class ChainstateWriteCrashTest(BitcoinTestFramework): nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()['hash_serialized_3'] except Exception: # probably a crash on db flushing + self.wait_for_node_exit(i, timeout=10) nodei_utxo_hash = self.restart_node(i, expected_tip=self.nodes[3].getbestblockhash()) assert_equal(nodei_utxo_hash, node3_utxo_hash)