mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-03 11:53:16 +02:00
Merge bitcoin/bitcoin#32835: test: fix feature_init.py intermittencies
4207d9bf82
test: feature_init, ensure indexes are synced prior to perturbing files (furszy)abd07cf733
test: feature_init, only init what's needed per perturbation/deletion round (furszy) Pull request description: Aims to solve #32600. Found it while working on #26966 (this was really annoying there). This ensures the node is index-synced before perturbing files. If the index sync gets interrupted before it starts, the database could be empty, making any following perturbation ineffective (which explains why the node does not abort during startup in the #32600 logs). Also, the first commit avoids initializing components not under test. This reduces log flooding, which helped in understanding the issue. Patch to reproduce the issue on master using `feature_init.py` (this simulates a node shutting down before the index starts syncing): ``` diff --git a/src/index/base.cpp b/src/index/base.cpp --- a/src/index/base.cpp(revision 1e03052c3fefb188f047e72548f2c6b0cc019e50) +++ b/src/index/base.cpp(date 1751293306725) @@ -185,6 +185,7 @@ void BaseIndex::Sync() { const CBlockIndex* pindex = m_best_block_index.load(); + m_interrupt(); if (!m_synced) { std::chrono::steady_clock::time_point last_log_time{0s}; std::chrono::steady_clock::time_point last_locator_write_time{0s}; ``` ACKs for top commit: maflcko: lgtm ACK4207d9bf82
🍄 achow101: ACK4207d9bf82
hodlinator: ACK4207d9bf82
Tree-SHA512: c8c89c7af9d473a12756b6a59b97f8fb473500181620eb96ecc10da954fe185d13fbb1d00a4ecb181e8daf149ec93cc547e292da0877522a4d23425fa7fd646b
This commit is contained in:
@@ -47,18 +47,20 @@ 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,
|
||||
)
|
||||
|
||||
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,34 +99,83 @@ 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")
|
||||
|
||||
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,19 +183,23 @@ 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"
|
||||
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")
|
||||
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 +213,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)
|
||||
|
Reference in New Issue
Block a user