From abd07cf7332e4e15fa4ac0e756f3610b0c7b4b6f Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 24 Jun 2025 12:31:54 -0400 Subject: [PATCH 1/2] test: feature_init, only init what's needed per perturbation/deletion round Avoids initializing and syncing components not under test. This not only speeds up execution a bit but also helps isolate and debug issues more easily, as logs aren't flooded with unrelated details. --- test/functional/feature_init.py | 93 +++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 15b3e8595fc..5cf02ffea1c 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -47,9 +47,9 @@ class InitTest(BitcoinTestFramework): node.process.terminate() node.process.wait() - def start_expecting_error(err_fragment): + def start_expecting_error(err_fragment, args): node.assert_start_raises_init_error( - extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1', '-checkblocks=200', '-checklevel=4'], + extra_args=args, expected_msg=err_fragment, match=ErrorMatch.PARTIAL_REGEX, ) @@ -102,29 +102,76 @@ class InitTest(BitcoinTestFramework): self.log.info("Test startup errors after removing certain essential files") - files_to_delete = { - 'blocks/index/*.ldb': 'Error opening block database.', - 'chainstate/*.ldb': 'Error opening coins database.', - 'blocks/blk*.dat': 'Error loading block database.', - 'indexes/txindex/MANIFEST*': 'LevelDB error: Corruption: CURRENT points to a non-existent file', + deletion_rounds = [ + { + 'filepath_glob': 'blocks/index/*.ldb', + 'error_message': 'Error opening block database.', + 'startup_args': [], + }, + { + 'filepath_glob': 'chainstate/*.ldb', + 'error_message': 'Error opening coins database.', + 'startup_args': ['-checklevel=4'], + }, + { + 'filepath_glob': 'blocks/blk*.dat', + 'error_message': 'Error loading block database.', + 'startup_args': ['-checkblocks=200', '-checklevel=4'], + }, + { + 'filepath_glob': 'indexes/txindex/MANIFEST*', + 'error_message': 'LevelDB error: Corruption: CURRENT points to a non-existent file', + 'startup_args': ['-txindex=1'], + }, # Removing these files does not result in a startup error: # 'indexes/blockfilter/basic/*.dat', 'indexes/blockfilter/basic/db/*.*', 'indexes/coinstats/db/*.*', # 'indexes/txindex/*.log', 'indexes/txindex/CURRENT', 'indexes/txindex/LOCK' - } + ] - files_to_perturb = { - 'blocks/index/*.ldb': 'Error loading block database.', - 'chainstate/*.ldb': 'Error opening coins database.', - 'blocks/blk*.dat': 'Corrupted block database detected.', - 'indexes/blockfilter/basic/db/*.*': 'LevelDB error: Corruption', - 'indexes/coinstats/db/*.*': 'LevelDB error: Corruption', - 'indexes/txindex/*.log': 'LevelDB error: Corruption', - 'indexes/txindex/CURRENT': 'LevelDB error: Corruption', + perturbation_rounds = [ + { + 'filepath_glob': 'blocks/index/*.ldb', + 'error_message': 'Error loading block database.', + 'startup_args': [], + }, + { + 'filepath_glob': 'chainstate/*.ldb', + 'error_message': 'Error opening coins database.', + 'startup_args': [], + }, + { + 'filepath_glob': 'blocks/blk*.dat', + 'error_message': 'Corrupted block database detected.', + 'startup_args': ['-checkblocks=200', '-checklevel=4'], + }, + { + 'filepath_glob': 'indexes/blockfilter/basic/db/*.*', + 'error_message': 'LevelDB error: Corruption', + 'startup_args': ['-blockfilterindex=1'], + }, + { + 'filepath_glob': 'indexes/coinstats/db/*.*', + 'error_message': 'LevelDB error: Corruption', + 'startup_args': ['-coinstatsindex=1'], + }, + { + 'filepath_glob': 'indexes/txindex/*.log', + 'error_message': 'LevelDB error: Corruption', + 'startup_args': ['-txindex=1'], + }, + { + 'filepath_glob': 'indexes/txindex/CURRENT', + 'error_message': 'LevelDB error: Corruption', + 'startup_args': ['-txindex=1'], + }, # Perturbing these files does not result in a startup error: # 'indexes/blockfilter/basic/*.dat', 'indexes/txindex/MANIFEST*', 'indexes/txindex/LOCK' - } + ] - for file_patt, err_fragment in files_to_delete.items(): + for round_info in deletion_rounds: + file_patt = round_info['filepath_glob'] + err_fragment = round_info['error_message'] + startup_args = round_info['startup_args'] target_files = list(node.chain_path.glob(file_patt)) for target_file in target_files: @@ -132,7 +179,7 @@ class InitTest(BitcoinTestFramework): bak_path = str(target_file) + ".bak" target_file.rename(bak_path) - start_expecting_error(err_fragment) + start_expecting_error(err_fragment, startup_args) for target_file in target_files: bak_path = str(target_file) + ".bak" @@ -144,7 +191,11 @@ class InitTest(BitcoinTestFramework): self.log.info("Test startup errors after perturbing certain essential files") dirs = ["blocks", "chainstate", "indexes"] - for file_patt, err_fragment in files_to_perturb.items(): + for round_info in perturbation_rounds: + file_patt = round_info['filepath_glob'] + err_fragment = round_info['error_message'] + startup_args = round_info['startup_args'] + for dir in dirs: shutil.copytree(node.chain_path / dir, node.chain_path / f"{dir}_bak") target_files = list(node.chain_path.glob(file_patt)) @@ -158,7 +209,7 @@ class InitTest(BitcoinTestFramework): tf.seek(150) tf.write(b"1" * 200) - start_expecting_error(err_fragment) + start_expecting_error(err_fragment, startup_args) for dir in dirs: shutil.rmtree(node.chain_path / dir) From 4207d9bf823bea9f94b484ebf3c9274ca781b245 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 25 Jun 2025 17:55:04 -0400 Subject: [PATCH 2/2] test: feature_init, ensure indexes are synced prior to perturbing files --- test/functional/feature_init.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 5cf02ffea1c..a7b7e0c6760 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -54,11 +54,13 @@ class InitTest(BitcoinTestFramework): match=ErrorMatch.PARTIAL_REGEX, ) - def check_clean_start(): + def check_clean_start(extra_args): """Ensure that node restarts successfully after various interrupts.""" - node.start() + node.start(extra_args) node.wait_for_rpc_connection() - assert_equal(200, node.getblockcount()) + height = node.getblockcount() + assert_equal(200, height) + self.wait_until(lambda: all(i["synced"] and i["best_block_height"] == height for i in node.getindexinfo().values())) lines_to_terminate_after = [ b'Validating signatures for all blocks', @@ -97,7 +99,9 @@ class InitTest(BitcoinTestFramework): self.log.debug("Terminating node after terminate line was found") sigterm_node() - check_clean_start() + # Prior to deleting/perturbing index files, start node with all indexes enabled. + # 'check_clean_start' will ensure indexes are synchronized (i.e., data exists to modify) + check_clean_start(args) self.stop_node(0) self.log.info("Test startup errors after removing certain essential files") @@ -186,7 +190,7 @@ class InitTest(BitcoinTestFramework): self.log.debug(f"Restoring file from {bak_path} and restarting") Path(bak_path).rename(target_file) - check_clean_start() + check_clean_start(args) self.stop_node(0) self.log.info("Test startup errors after perturbing certain essential files")