test: add test for index commits ahead of the last flushed block
What changed, and why it matters
This commit only adds a new automated test to Bitcoin Core. It does not change any production code, so it cannot by itself introduce a vulnerability or fix one. The test checks that an internal database index does not save progress beyond the last point the main chain data was safely written to disk, which helps prevent corruption after an unexpected shutdown.
No security action required. Treat as routine test coverage improvement. If reviewing the related BaseIndex/ChainStateFlushed behavior, ensure the production code already enforces the invariant this test verifies.
Security signals we found
New regression test for index/chainstate flush consistency
No changes to consensus, networking, wallet, or index implementation code
Test-only change with no runtime attack surface
Evidence from the diff
The commit adds src/test/baseindex_tests.cpp and registers it in src/test/CMakeLists.txt. The new BOOST test, baseindex_no_commit_ahead_of_flush, uses CoinStatsIndex as a stand-in for BaseIndex and verifies that the index’s committed best-block height never exceeds the chainstate’s last flushed block height. It simulates crash-like scenarios (stop without flush) and a clean flush path. No production logic is modified.
Changed components
src/test/baseindex_tests.cppsrc/test/CMakeLists.txtInspect captured patch +60 / −0
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index e5619064..97a034de 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -15,6 +15,7 @@ add_executable(test_bitcoin
base32_tests.cpp
base58_tests.cpp
base64_tests.cpp
+ baseindex_tests.cpp
bech32_tests.cpp
bip32_tests.cpp
bip324_tests.cpp
diff --git a/src/test/baseindex_tests.cpp b/src/test/baseindex_tests.cpp
new file mode 100644
index 00000000..525309bf
--- /dev/null
+++ b/src/test/baseindex_tests.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2020-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.
+
+#include <index/coinstatsindex.h>
+#include <interfaces/chain.h>
+#include <script/script.h>
+#include <test/util/setup_common.h>
+#include <util/byte_units.h>
+#include <util/check.h>
+#include <validation.h>
+
+#include <boost/test/unit_test.hpp>
+
+// Tests of generic BaseIndex functionality that is independent of which
+// concrete index is being used. CoinStatsIndex is used here merely as a
+// convenient instantiation of BaseIndex.
+BOOST_AUTO_TEST_SUITE(baseindex_tests)
+
+// Test that the index does not commit ahead of the chainstate's last
+// flushed block. If it did, a subsequent unclean shutdown would corrupt
+// the index, because during reverting it would require blocks that were
+// never flushed to disk.
+BOOST_FIXTURE_TEST_CASE(baseindex_no_commit_ahead_of_flush, TestChain100Setup)
+{
+ Chainstate& chainstate = Assert(m_node.chainman)->ActiveChainstate();
+ auto sync_index = [&](bool do_flush, int expected_sync_height, int expected_commit_height) {
+ CoinStatsIndex index{interfaces::MakeChain(m_node), /*n_cache_size=*/1_MiB};
+ BOOST_REQUIRE(index.Init());
+ index.Sync();
+ if (do_flush) {
+ chainstate.ForceFlushStateToDisk();
+ m_node.chain->context()->validation_signals->SyncWithValidationInterfaceQueue();
+ }
+ BOOST_CHECK_EQUAL(index.GetSummary().best_block_height, expected_sync_height);
+ index.Stop();
+ // Reload index to see which block data was actually committed.
+ BOOST_REQUIRE(index.Init());
+ BOOST_CHECK_EQUAL(index.GetSummary().best_block_height, expected_commit_height);
+ index.Stop();
+ };
+
+ // Part 1: Sync, then "crash" (stop without flushing). Models a node that
+ // started up, had its index catch up, but never flushed before going down.
+ sync_index(false, 100, 100);
+
+ // Part 2: Restart cleanly. Sync, force a chainstate flush, and drain the
+ // validation queue so the index's ChainStateFlushed callback runs.
+ sync_index(true, 100, 100);
+
+ // Part 3: Connect a new block on the chain without flushing
+ // (m_last_flushed_block stays at 100). For a real node this would happen
+ // in parallel with Sync(). Here we do it before Sync() to make the race
+ // state deterministic.
+ CreateAndProcessBlock({}, CScript() << OP_TRUE);
+ sync_index(false, 101, 101);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.