@@ -472,9 +472,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
472
472
// ! Ensure that snapshot chainstates initialize properly when found on disk.
473
473
BOOST_FIXTURE_TEST_CASE (chainstatemanager_snapshot_init, SnapshotTestSetup)
474
474
{
475
- this ->SetupSnapshot ();
476
-
477
475
ChainstateManager& chainman = *Assert (m_node.chainman );
476
+ Chainstate& bg_chainstate = chainman.ActiveChainstate ();
477
+
478
+ this ->SetupSnapshot ();
478
479
479
480
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir ();
480
481
BOOST_CHECK (fs::exists (snapshot_chainstate_dir));
@@ -487,6 +488,20 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
487
488
auto all_chainstates = chainman.GetAll ();
488
489
BOOST_CHECK_EQUAL (all_chainstates.size (), 2 );
489
490
491
+ // "Rewind" the background chainstate so that its tip is not at the
492
+ // base block of the snapshot - this is so after simulating a node restart,
493
+ // it will initialize instead of attempting to complete validation.
494
+ //
495
+ // Note that this is not a realistic use of DisconnectTip().
496
+ DisconnectedBlockTransactions unused_pool;
497
+ BlockValidationState unused_state;
498
+ {
499
+ LOCK2 (::cs_main, bg_chainstate.MempoolMutex ());
500
+ BOOST_CHECK (bg_chainstate.DisconnectTip (unused_state, &unused_pool));
501
+ unused_pool.clear (); // to avoid queuedTx assertion errors on teardown
502
+ }
503
+ BOOST_CHECK_EQUAL (bg_chainstate.m_chain .Height (), 109 );
504
+
490
505
// Test that simulating a shutdown (resetting ChainstateManager) and then performing
491
506
// chainstate reinitializing successfully cleans up the background-validation
492
507
// chainstate data, and we end up with a single chainstate that is at tip.
@@ -518,10 +533,160 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
518
533
// chainstate.
519
534
for (Chainstate* cs : chainman_restarted.GetAll ()) {
520
535
if (cs != &chainman_restarted.ActiveChainstate ()) {
521
- BOOST_CHECK_EQUAL (cs->m_chain .Height (), 110 );
536
+ BOOST_CHECK_EQUAL (cs->m_chain .Height (), 109 );
522
537
}
523
538
}
524
539
}
525
540
}
526
541
542
+ BOOST_FIXTURE_TEST_CASE (chainstatemanager_snapshot_completion, SnapshotTestSetup)
543
+ {
544
+ this ->SetupSnapshot ();
545
+
546
+ ChainstateManager& chainman = *Assert (m_node.chainman );
547
+ Chainstate& active_cs = chainman.ActiveChainstate ();
548
+ auto tip_cache_before_complete = active_cs.m_coinstip_cache_size_bytes ;
549
+ auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes ;
550
+
551
+ SnapshotCompletionResult res;
552
+ auto mock_shutdown = [](bilingual_str msg) {};
553
+
554
+ fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir ();
555
+ BOOST_CHECK (fs::exists (snapshot_chainstate_dir));
556
+ BOOST_CHECK_EQUAL (snapshot_chainstate_dir, gArgs .GetDataDirNet () / " chainstate_snapshot" );
557
+
558
+ BOOST_CHECK (chainman.IsSnapshotActive ());
559
+ const uint256 snapshot_tip_hash = WITH_LOCK (chainman.GetMutex (),
560
+ return chainman.ActiveTip ()->GetBlockHash ());
561
+
562
+ res = WITH_LOCK (::cs_main,
563
+ return chainman.MaybeCompleteSnapshotValidation (mock_shutdown));
564
+ BOOST_CHECK_EQUAL (res, SnapshotCompletionResult::SUCCESS);
565
+
566
+ WITH_LOCK (::cs_main, BOOST_CHECK (chainman.IsSnapshotValidated ()));
567
+ BOOST_CHECK (chainman.IsSnapshotActive ());
568
+
569
+ // Cache should have been rebalanced and reallocated to the "only" remaining
570
+ // chainstate.
571
+ BOOST_CHECK (active_cs.m_coinstip_cache_size_bytes > tip_cache_before_complete);
572
+ BOOST_CHECK (active_cs.m_coinsdb_cache_size_bytes > db_cache_before_complete);
573
+
574
+ auto all_chainstates = chainman.GetAll ();
575
+ BOOST_CHECK_EQUAL (all_chainstates.size (), 1 );
576
+ BOOST_CHECK_EQUAL (all_chainstates[0 ], &active_cs);
577
+
578
+ // Trying completion again should return false.
579
+ res = WITH_LOCK (::cs_main,
580
+ return chainman.MaybeCompleteSnapshotValidation (mock_shutdown));
581
+ BOOST_CHECK_EQUAL (res, SnapshotCompletionResult::SKIPPED);
582
+
583
+ // The invalid snapshot path should not have been used.
584
+ fs::path snapshot_invalid_dir = gArgs .GetDataDirNet () / " chainstate_snapshot_INVALID" ;
585
+ BOOST_CHECK (!fs::exists (snapshot_invalid_dir));
586
+ // chainstate_snapshot should still exist.
587
+ BOOST_CHECK (fs::exists (snapshot_chainstate_dir));
588
+
589
+ // Test that simulating a shutdown (reseting ChainstateManager) and then performing
590
+ // chainstate reinitializing successfully cleans up the background-validation
591
+ // chainstate data, and we end up with a single chainstate that is at tip.
592
+ ChainstateManager& chainman_restarted = this ->SimulateNodeRestart ();
593
+
594
+ BOOST_TEST_MESSAGE (" Performing Load/Verify/Activate of chainstate" );
595
+
596
+ // This call reinitializes the chainstates, and should clean up the now unnecessary
597
+ // background-validation leveldb contents.
598
+ this ->LoadVerifyActivateChainstate ();
599
+
600
+ BOOST_CHECK (!fs::exists (snapshot_invalid_dir));
601
+ // chainstate_snapshot should now *not* exist.
602
+ BOOST_CHECK (!fs::exists (snapshot_chainstate_dir));
603
+
604
+ const Chainstate& active_cs2 = chainman_restarted.ActiveChainstate ();
605
+
606
+ {
607
+ LOCK (chainman_restarted.GetMutex ());
608
+ BOOST_CHECK_EQUAL (chainman_restarted.GetAll ().size (), 1 );
609
+ BOOST_CHECK (!chainman_restarted.IsSnapshotActive ());
610
+ BOOST_CHECK (!chainman_restarted.IsSnapshotValidated ());
611
+ BOOST_CHECK (active_cs2.m_coinstip_cache_size_bytes > tip_cache_before_complete);
612
+ BOOST_CHECK (active_cs2.m_coinsdb_cache_size_bytes > db_cache_before_complete);
613
+
614
+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveTip ()->GetBlockHash (), snapshot_tip_hash);
615
+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 210 );
616
+ }
617
+
618
+ BOOST_TEST_MESSAGE (
619
+ " Ensure we can mine blocks on top of the \" new\" IBD chainstate" );
620
+ mineBlocks (10 );
621
+ {
622
+ LOCK (chainman_restarted.GetMutex ());
623
+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 220 );
624
+ }
625
+ }
626
+
627
+ BOOST_FIXTURE_TEST_CASE (chainstatemanager_snapshot_completion_hash_mismatch, SnapshotTestSetup)
628
+ {
629
+ auto chainstates = this ->SetupSnapshot ();
630
+ Chainstate& validation_chainstate = *std::get<0 >(chainstates);
631
+ ChainstateManager& chainman = *Assert (m_node.chainman );
632
+ SnapshotCompletionResult res;
633
+ auto mock_shutdown = [](bilingual_str msg) {};
634
+
635
+ // Test tampering with the IBD UTXO set with an extra coin to ensure it causes
636
+ // snapshot completion to fail.
637
+ CCoinsViewCache& ibd_coins = WITH_LOCK (::cs_main,
638
+ return validation_chainstate.CoinsTip ());
639
+ Coin badcoin;
640
+ badcoin.out .nValue = InsecureRand32 ();
641
+ badcoin.nHeight = 1 ;
642
+ badcoin.out .scriptPubKey .assign (InsecureRandBits (6 ), 0 );
643
+ uint256 txid = InsecureRand256 ();
644
+ ibd_coins.AddCoin (COutPoint (txid, 0 ), std::move (badcoin), false );
645
+
646
+ fs::path snapshot_chainstate_dir = gArgs .GetDataDirNet () / " chainstate_snapshot" ;
647
+ BOOST_CHECK (fs::exists (snapshot_chainstate_dir));
648
+
649
+ res = WITH_LOCK (::cs_main,
650
+ return chainman.MaybeCompleteSnapshotValidation (mock_shutdown));
651
+ BOOST_CHECK_EQUAL (res, SnapshotCompletionResult::HASH_MISMATCH);
652
+
653
+ auto all_chainstates = chainman.GetAll ();
654
+ BOOST_CHECK_EQUAL (all_chainstates.size (), 1 );
655
+ BOOST_CHECK_EQUAL (all_chainstates[0 ], &validation_chainstate);
656
+ BOOST_CHECK_EQUAL (&chainman.ActiveChainstate (), &validation_chainstate);
657
+
658
+ fs::path snapshot_invalid_dir = gArgs .GetDataDirNet () / " chainstate_snapshot_INVALID" ;
659
+ BOOST_CHECK (fs::exists (snapshot_invalid_dir));
660
+
661
+ // Test that simulating a shutdown (reseting ChainstateManager) and then performing
662
+ // chainstate reinitializing successfully loads only the fully-validated
663
+ // chainstate data, and we end up with a single chainstate that is at tip.
664
+ ChainstateManager& chainman_restarted = this ->SimulateNodeRestart ();
665
+
666
+ BOOST_TEST_MESSAGE (" Performing Load/Verify/Activate of chainstate" );
667
+
668
+ // This call reinitializes the chainstates, and should clean up the now unnecessary
669
+ // background-validation leveldb contents.
670
+ this ->LoadVerifyActivateChainstate ();
671
+
672
+ BOOST_CHECK (fs::exists (snapshot_invalid_dir));
673
+ BOOST_CHECK (!fs::exists (snapshot_chainstate_dir));
674
+
675
+ {
676
+ LOCK (::cs_main);
677
+ BOOST_CHECK_EQUAL (chainman_restarted.GetAll ().size (), 1 );
678
+ BOOST_CHECK (!chainman_restarted.IsSnapshotActive ());
679
+ BOOST_CHECK (!chainman_restarted.IsSnapshotValidated ());
680
+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 210 );
681
+ }
682
+
683
+ BOOST_TEST_MESSAGE (
684
+ " Ensure we can mine blocks on top of the \" new\" IBD chainstate" );
685
+ mineBlocks (10 );
686
+ {
687
+ LOCK (::cs_main);
688
+ BOOST_CHECK_EQUAL (chainman_restarted.ActiveHeight (), 220 );
689
+ }
690
+ }
691
+
527
692
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments