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) 2021-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"""Tests related to node initialization."""6from concurrent.futures import ThreadPoolExecutor7from pathlib import Path8import os9import platform10import re11import shutil12import signal13import subprocess15from test_framework.test_framework import BitcoinTestFramework16from test_framework.test_node import (17BITCOIN_PID_FILENAME_DEFAULT,18ErrorMatch,19)20from test_framework.util import assert_equal22ALL_INDEX_ARGS = [23'-txindex=1',24'-blockfilterindex=1',25'-coinstatsindex=1',26'-txospenderindex=1',27]30def get_process_group_kwargs():31# CTRL_BREAK_EVENT must target a separate process group to avoid32# terminating the Python test runner.33return {"creationflags": subprocess.CREATE_NEW_PROCESS_GROUP} if platform.system() == 'Windows' else {}36def sigterm_node(node):37if platform.system() == 'Windows':38# Don't call Python's terminate() since it calls39# TerminateProcess(), which unlike SIGTERM doesn't allow40# bitcoind to perform any shutdown logic.41os.kill(node.process.pid, signal.CTRL_BREAK_EVENT)42else:43node.process.terminate()44node.wait_until_stopped()47class InitTest(BitcoinTestFramework):48"""49Ensure that initialization can be interrupted at a number of points and not impair50subsequent starts.51"""53def set_test_params(self):54self.setup_clean_chain = True55self.num_nodes = 256self.uses_wallet = None58def check_clean_start(self, node, extra_args):59"""Ensure that node restarts successfully after various interrupts."""60node.start(extra_args)61node.wait_for_rpc_connection()62height = node.getblockcount()63assert_equal(200, height)64self.wait_until(lambda: all(i["synced"] and i["best_block_height"] == height for i in node.getindexinfo().values()))66def init_stress_test_interrupt(self):67"""68- test terminating initialization after seeing a certain log line.69"""70self.start_node(0)71node = self.nodes[0]72self.generate(node, 200, sync_fun=self.no_op)73self.stop_node(0)75reindex_log_line = b'Reindexing block file blk00000.dat'76lines_to_terminate_after = [77b'Validating signatures for all blocks',78b'scheduler thread start',79b'Starting HTTP server',80b'Loading P2P addresses',81b'Loading banlist',82b'Loading block index',83b'Checking all blk files are present',84b'Loaded best chain:',85b'init message: Verifying blocks',86b'init message: Starting network threads',87b'net thread start',88b'addcon thread start',89b'initload thread start',90b'txidx thread start',91b'blkfltbscidx thread start',92b'coinstatsidx thread start',93b'txospenderidx thread start',94b'msghand thread start',95b'net thread start',96b'addcon thread start',97]98if self.is_wallet_compiled():99lines_to_terminate_after.append(b'Verifying wallet')100lines_to_terminate_after.append(reindex_log_line)102for terminate_line in lines_to_terminate_after:103self.log.info(f"Starting node and will terminate after line {terminate_line}")104with node.busy_wait_for_debug_log([terminate_line]):105extra_args = [*ALL_INDEX_ARGS]106if terminate_line == reindex_log_line:107extra_args += ['-reindex']108node.start(extra_args=extra_args, **get_process_group_kwargs())109self.log.debug("Terminating node after terminate line was found")110sigterm_node(node)112# Prior to deleting/perturbing index files, start node with all indexes enabled.113# 'check_clean_start' will ensure indexes are synchronized (i.e., data exists to modify)114self.check_clean_start(node, ALL_INDEX_ARGS)115self.stop_node(0)117def init_stress_test_removals(self):118"""119- test removing certain essential files to test startup error paths.120"""121self.log.info("Test startup errors after removing certain essential files")122node = self.nodes[0]124def start_expecting_error(err_fragment, args):125node.assert_start_raises_init_error(126extra_args=args,127expected_msg=err_fragment,128match=ErrorMatch.PARTIAL_REGEX,129)131deletion_rounds = [132{133'filepath_glob': 'blocks/index/*.ldb',134'error_message': 'Error opening block database.',135'startup_args': [],136},137{138'filepath_glob': 'chainstate/*.ldb',139'error_message': 'Error opening coins database.',140'startup_args': ['-checklevel=4'],141},142{143'filepath_glob': 'blocks/blk*.dat',144'error_message': 'Error loading block database.',145'startup_args': ['-checkblocks=200', '-checklevel=4'],146},147{148'filepath_glob': 'indexes/txindex/MANIFEST*',149'error_message': 'LevelDB error: Corruption: CURRENT points to a non-existent file',150'startup_args': ['-txindex=1'],151},152{153'filepath_glob': 'indexes/txospenderindex/db/MANIFEST*',154'error_message': 'LevelDB error: Corruption: CURRENT points to a non-existent file',155'startup_args': ['-txospenderindex=1'],156},157# Removing these files does not result in a startup error:158# 'indexes/blockfilter/basic/*.dat', 'indexes/blockfilter/basic/db/*.*', 'indexes/coinstatsindex/db/*.*',159# 'indexes/txindex/*.log', 'indexes/txindex/CURRENT', 'indexes/txindex/LOCK'160]162perturbation_rounds = [163{164'filepath_glob': 'blocks/index/*.ldb',165'error_message': 'Error loading block database.',166'startup_args': [],167},168{169'filepath_glob': 'chainstate/*.ldb',170'error_message': 'Error opening coins database.',171'startup_args': [],172},173{174'filepath_glob': 'blocks/blk*.dat',175'error_message': 'Corrupted block database detected.',176'startup_args': ['-checkblocks=200', '-checklevel=4'],177},178{179'filepath_glob': 'indexes/blockfilter/basic/db/*.*',180'error_message': 'LevelDB error: Corruption',181'startup_args': ['-blockfilterindex=1'],182},183{184'filepath_glob': 'indexes/coinstatsindex/db/*.*',185'error_message': 'LevelDB error: Corruption',186'startup_args': ['-coinstatsindex=1'],187},188{189'filepath_glob': 'indexes/txindex/*.log',190'error_message': 'LevelDB error: Corruption',191'startup_args': ['-txindex=1'],192},193{194'filepath_glob': 'indexes/txindex/CURRENT',195'error_message': 'LevelDB error: Corruption',196'startup_args': ['-txindex=1'],197},198{199'filepath_glob': 'indexes/txospenderindex/db/*',200'error_message': 'LevelDB error: Corruption',201'startup_args': ['-txospenderindex=1'],202},203# Perturbing these files does not result in a startup error:204# 'indexes/blockfilter/basic/*.dat', 'indexes/txindex/MANIFEST*', 'indexes/txindex/LOCK'205]207for round_info in deletion_rounds:208file_patt = round_info['filepath_glob']209err_fragment = round_info['error_message']210startup_args = round_info['startup_args']211target_files = list(node.chain_path.glob(file_patt))212assert target_files, f"Failed to find expected files: {file_patt}"214for target_file in target_files:215self.log.info(f"Deleting file to ensure failure {target_file}")216bak_path = str(target_file) + ".bak"217target_file.rename(bak_path)219start_expecting_error(err_fragment, startup_args)221for target_file in target_files:222bak_path = str(target_file) + ".bak"223self.log.debug(f"Restoring file from {bak_path} and restarting")224Path(bak_path).rename(target_file)226self.check_clean_start(node, ALL_INDEX_ARGS)227self.stop_node(0)229self.log.info("Test startup errors after perturbing certain essential files")230dirs = ["blocks", "chainstate", "indexes"]231for round_info in perturbation_rounds:232file_patt = round_info['filepath_glob']233err_fragment = round_info['error_message']234startup_args = round_info['startup_args']236for dir in dirs:237shutil.copytree(node.chain_path / dir, node.chain_path / f"{dir}_bak")238target_files = list(node.chain_path.glob(file_patt))239assert target_files, f"Failed to find expected files: {file_patt}"241for target_file in target_files:242self.log.info(f"Perturbing file to ensure failure {target_file}")243with open(target_file, "r+b") as tf:244# Since the genesis block is not checked by -checkblocks, the245# perturbation window must be chosen such that a higher block246# in blk*.dat is affected.247tf.seek(150)248tf.write(b"1" * 200)250start_expecting_error(err_fragment, startup_args)
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.