From 8be54e3b19677b02e19d054a4a5b2f1968bb1c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sun, 11 Jan 2026 23:57:55 +0100 Subject: [PATCH] test: cover IBD exit conditions Add a unit test that exercises the `IsInitialBlockDownload()` decision matrix by varying the cached latch, `BlockManager::LoadingBlocks()`, and tip work/recency inputs. This documents the current latching behavior and provides a baseline for later refactors. --- .../validation_chainstatemanager_tests.cpp | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 24122bd3157..d0b731b3716 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -143,11 +143,11 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup) /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); // Reset IBD state so IsInitialBlockDownload() returns true and causes - // MaybeRebalancesCaches() to prioritize the snapshot chainstate, giving it + // MaybeRebalanceCaches() to prioritize the snapshot chainstate, giving it // more cache space than the snapshot chainstate. Calling ResetIbd() is // necessary because m_cached_finished_ibd is already latched to true before - // the test starts due to the test setup. After ResetIbd() is called. - // IsInitialBlockDownload will return true because at this point the active + // the test starts due to the test setup. After ResetIbd() is called, + // IsInitialBlockDownload() will return true because at this point the active // chainstate has a null chain tip. static_cast(manager).ResetIbd(); @@ -163,6 +163,43 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup) BOOST_CHECK_CLOSE(double(c2.m_coinsdb_cache_size_bytes), max_cache * 0.95, 1); } +BOOST_FIXTURE_TEST_CASE(chainstatemanager_ibd_exit_after_loading_blocks, ChainTestingSetup) +{ + CBlockIndex tip; + ChainstateManager& chainman{*Assert(m_node.chainman)}; + auto apply{[&](bool cached_finished_ibd, bool loading_blocks, bool tip_exists, bool enough_work, bool tip_recent) { + LOCK(::cs_main); + chainman.ResetChainstates(); + chainman.InitializeChainstate(m_node.mempool.get()); + + const auto recent_time{Now() - chainman.m_options.max_tip_age}; + + chainman.m_cached_finished_ibd.store(cached_finished_ibd, std::memory_order_relaxed); + chainman.m_blockman.m_importing = loading_blocks; + if (tip_exists) { + tip.nChainWork = chainman.MinimumChainWork() - (enough_work ? 0 : 1); + tip.nTime = (recent_time - (tip_recent ? 0h : 100h)).time_since_epoch().count(); + chainman.ActiveChain().SetTip(tip); + } else { + assert(!chainman.ActiveChain().Tip()); + } + }}; + + for (const bool cached_finished_ibd : {false, true}) { + for (const bool loading_blocks : {false, true}) { + for (const bool tip_exists : {false, true}) { + for (const bool enough_work : {false, true}) { + for (const bool tip_recent : {false, true}) { + apply(cached_finished_ibd, loading_blocks, tip_exists, enough_work, tip_recent); + const bool expected_ibd = !cached_finished_ibd && (loading_blocks || !tip_exists || !enough_work || !tip_recent); + BOOST_CHECK_EQUAL(chainman.IsInitialBlockDownload(), expected_ibd); + } + } + } + } + } +} + struct SnapshotTestSetup : TestChain100Setup { // Run with coinsdb on the filesystem to support, e.g., moving invalidated // chainstate dirs to "*_invalid".