From 5a11d22973c18f2d9d6df3a1c635fc099120c600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Fri, 19 Dec 2025 18:19:39 +0100 Subject: [PATCH] test: add test for coins database read error handling The error was also reworded to avoid the leading repetition (see expected stderr). ### Reproducers The test failure can be triggered manually by: ```patch std::optional CCoinsViewErrorCatcher::GetCoin(const COutPoint& outpoint) const { - return ExecuteBackedWrapper>([&]() { return CCoinsViewBacked::GetCoin(outpoint); }, m_err_callbacks); + return CCoinsViewBacked::GetCoin(outpoint); } ``` ### Notes Note that CCoinsViewErrorCatcher::HaveCoin failure isn't tested since it doesn't seem to be used in production code, all tests pass with: ```patch bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const { - return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks); + throw "CCoinsViewBacked::HaveCoin"; } ``` ### Context: On 64-bit systems, LevelDB uses mmap which reflects file changes immediately. On 32-bit systems (i686, armhf), mmap is disabled (kDefaultMmapLimit=0 in leveldb/util/env_posix.cc), so LevelDB uses block cache which serves stale data. The node must be restarted after corruption to clear the cache. Corruption is placed at the middle to avoid LevelDB's paranoid_checks verification during database open, and -checkblocks=0 -checklevel=0 skips Bitcoin's block verification. Co-authored-by: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> --- src/init.cpp | 2 +- test/functional/feature_coinsdb_read_error.py | 48 +++++++++++++++++++ test/functional/test_runner.py | 1 + 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 test/functional/feature_coinsdb_read_error.py diff --git a/src/init.cpp b/src/init.cpp index f2af858eb46c..79e163756f59 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1364,7 +1364,7 @@ static ChainstateLoadResult InitAndLoadChainstate( options.require_full_verification = args.IsArgSet("-checkblocks") || args.IsArgSet("-checklevel"); options.coins_error_cb = [] { uiInterface.ThreadSafeMessageBox( - _("Error reading from database, shutting down."), + _("Cannot read from database, shutting down."), "", CClientUIInterface::MSG_ERROR); }; uiInterface.InitMessage(_("Loading block index…")); diff --git a/test/functional/feature_coinsdb_read_error.py b/test/functional/feature_coinsdb_read_error.py new file mode 100755 index 000000000000..e257ed103a3f --- /dev/null +++ b/test/functional/feature_coinsdb_read_error.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test that coins database read errors trigger a shutdown message.""" +import http +import subprocess + +from test_framework.test_framework import BitcoinTestFramework + + +class CoinsDBReadErrorTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + + def run_test(self): + node = self.nodes[0] + + # Stop node to clear LevelDB block cache (required for 32-bit where mmap is disabled) + self.stop_node(0) + + for ldb_path in (node.chain_path / "chainstate").glob("*.ldb"): + with open(ldb_path, "r+b") as f: + f.seek(ldb_path.stat().st_size // 2) + f.write(b'\xff') + + self.start_node(0, extra_args=["-checkblocks=0", "-checklevel=0"]) + + with node.assert_debug_log(["block checksum mismatch"]): + try: + for height in range(1, node.getblockcount() + 1): + txid = node.getblock(node.getblockhash(height))["tx"][0] + node.gettxout(txid, 0) + except (subprocess.CalledProcessError, + http.client.CannotSendRequest, + http.client.RemoteDisconnected, + ConnectionResetError): + pass + + node.wait_until(lambda: node.process.poll()) + assert node.is_node_stopped(expected_stderr="Error: Cannot read from database, shutting down.", + expected_ret_code=node.process.returncode) + + +if __name__ == '__main__': + CoinsDBReadErrorTest(__file__).main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a4af55784685..23c0f3312460 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -323,6 +323,7 @@ 'p2p_fingerprint.py', 'feature_uacomment.py', 'feature_init.py', + 'feature_coinsdb_read_error.py', 'wallet_coinbase_category.py', 'feature_filelock.py', 'feature_loadblock.py',