1010#include < sync.h>
1111#include < test/util/chainstate.h>
1212#include < test/util/setup_common.h>
13+ #include < timedata.h>
1314#include < uint256.h>
1415#include < validation.h>
1516#include < validationinterface.h>
@@ -155,12 +156,32 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
155156}
156157
157158struct SnapshotTestSetup : TestChain100Setup {
159+ // Run with coinsdb on the filesystem to support, e.g., moving invalidated
160+ // chainstate dirs to "*_invalid".
161+ //
162+ // Note that this means the tests run considerably slower than in-memory DB
163+ // tests, but we can't otherwise test this functionality since it relies on
164+ // destructive filesystem operations.
165+ SnapshotTestSetup () : TestChain100Setup{
166+ {},
167+ {},
168+ /* coins_db_in_memory=*/ false ,
169+ /* block_tree_db_in_memory=*/ false ,
170+ }
171+ {
172+ }
173+
158174 std::tuple<Chainstate*, Chainstate*> SetupSnapshot ()
159175 {
160176 ChainstateManager& chainman = *Assert (m_node.chainman );
161177
162178 BOOST_CHECK (!chainman.IsSnapshotActive ());
163- WITH_LOCK (::cs_main, BOOST_CHECK (!chainman.IsSnapshotValidated ()));
179+
180+ {
181+ LOCK (::cs_main);
182+ BOOST_CHECK (!chainman.IsSnapshotValidated ());
183+ BOOST_CHECK (!node::FindSnapshotChainstateDir ());
184+ }
164185
165186 size_t initial_size;
166187 size_t initial_total_coins{100 };
@@ -208,6 +229,9 @@ struct SnapshotTestSetup : TestChain100Setup {
208229 auto_infile >> outpoint;
209230 auto_infile >> coin;
210231 }));
232+
233+ BOOST_CHECK (!node::FindSnapshotChainstateDir ());
234+
211235 BOOST_REQUIRE (!CreateAndActivateUTXOSnapshot (
212236 this , [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
213237 // Coins count is larger than coins in file
@@ -230,6 +254,7 @@ struct SnapshotTestSetup : TestChain100Setup {
230254 }));
231255
232256 BOOST_REQUIRE (CreateAndActivateUTXOSnapshot (this ));
257+ BOOST_CHECK (fs::exists (*node::FindSnapshotChainstateDir ()));
233258
234259 // Ensure our active chain is the snapshot chainstate.
235260 BOOST_CHECK (!chainman.ActiveChainstate ().m_from_snapshot_blockhash ->IsNull ());
@@ -242,9 +267,11 @@ struct SnapshotTestSetup : TestChain100Setup {
242267 {
243268 LOCK (::cs_main);
244269
270+ fs::path found = *node::FindSnapshotChainstateDir ();
271+
245272 // Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
246273 BOOST_CHECK_EQUAL (
247- *node::ReadSnapshotBaseBlockhash (m_args. GetDataDirNet () / " chainstate_snapshot " ),
274+ *node::ReadSnapshotBaseBlockhash (found ),
248275 *chainman.SnapshotBlockhash ());
249276
250277 // Ensure that the genesis block was not marked assumed-valid.
@@ -328,6 +355,34 @@ struct SnapshotTestSetup : TestChain100Setup {
328355 loaded_snapshot_blockhash);
329356 return std::make_tuple (&validation_chainstate, &snapshot_chainstate);
330357 }
358+
359+ // Simulate a restart of the node by flushing all state to disk, clearing the
360+ // existing ChainstateManager, and unloading the block index.
361+ //
362+ // @returns a reference to the "restarted" ChainstateManager
363+ ChainstateManager& SimulateNodeRestart ()
364+ {
365+ ChainstateManager& chainman = *Assert (m_node.chainman );
366+
367+ BOOST_TEST_MESSAGE (" Simulating node restart" );
368+ {
369+ LOCK (::cs_main);
370+ for (Chainstate* cs : chainman.GetAll ()) {
371+ cs->ForceFlushStateToDisk ();
372+ }
373+ chainman.ResetChainstates ();
374+ BOOST_CHECK_EQUAL (chainman.GetAll ().size (), 0 );
375+ const ChainstateManager::Options chainman_opts{
376+ .chainparams = ::Params (),
377+ .adjusted_time_callback = GetAdjustedTime,
378+ };
379+ // For robustness, ensure the old manager is destroyed before creating a
380+ // new one.
381+ m_node.chainman .reset ();
382+ m_node.chainman .reset (new ChainstateManager (chainman_opts));
383+ }
384+ return *Assert (m_node.chainman );
385+ }
331386};
332387
333388// ! Test basic snapshot activation.
@@ -414,4 +469,59 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
414469 BOOST_CHECK_EQUAL (cs2.setBlockIndexCandidates .size (), num_indexes);
415470}
416471
472+ // ! Ensure that snapshot chainstates initialize properly when found on disk.
473+ BOOST_FIXTURE_TEST_CASE (chainstatemanager_snapshot_init, SnapshotTestSetup)
474+ {
475+ this ->SetupSnapshot ();
476+
477+ ChainstateManager& chainman = *Assert (m_node.chainman );
478+
479+ fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir ();
480+ BOOST_CHECK (fs::exists (snapshot_chainstate_dir));
481+ BOOST_CHECK_EQUAL (snapshot_chainstate_dir, gArgs .GetDataDirNet () / " chainstate_snapshot" );
482+
483+ BOOST_CHECK (chainman.IsSnapshotActive ());
484+ const uint256 snapshot_tip_hash = WITH_LOCK (chainman.GetMutex (),
485+ return chainman.ActiveTip ()->GetBlockHash ());
486+
487+ auto all_chainstates = chainman.GetAll ();
488+ BOOST_CHECK_EQUAL (all_chainstates.size (), 2 );
489+
490+ // Test that simulating a shutdown (resetting ChainstateManager) and then performing
491+ // chainstate reinitializing successfully cleans up the background-validation
492+ // chainstate data, and we end up with a single chainstate that is at tip.
493+ ChainstateManager& chainman_restarted = this ->SimulateNodeRestart ();
494+
495+ BOOST_TEST_MESSAGE (" Performing Load/Verify/Activate of chainstate" );
496+
497+ // This call reinitializes the chainstates.
498+ this ->LoadVerifyActivateChainstate ();
499+
500+ {
501+ LOCK (chainman_restarted.GetMutex ());
502+ BOOST_CHECK_EQUAL (chainman_restarted.GetAll ().size (), 2 );
503+ BOOST_CHECK (chainman_restarted.IsSnapshotActive ());
504+ BOOST_CHECK (!chainman_restarted.IsSnapshotValidated ());
505+
506+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveTip ()->GetBlockHash (), snapshot_tip_hash);
507+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 210 );
508+ }
509+
510+ BOOST_TEST_MESSAGE (
511+ " Ensure we can mine blocks on top of the initialized snapshot chainstate" );
512+ mineBlocks (10 );
513+ {
514+ LOCK (chainman_restarted.GetMutex ());
515+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 220 );
516+
517+ // Background chainstate should be unaware of new blocks on the snapshot
518+ // chainstate.
519+ for (Chainstate* cs : chainman_restarted.GetAll ()) {
520+ if (cs != &chainman_restarted.ActiveChainstate ()) {
521+ BOOST_CHECK_EQUAL (cs->m_chain .Height (), 110 );
522+ }
523+ }
524+ }
525+ }
526+
417527BOOST_AUTO_TEST_SUITE_END ()
0 commit comments