Skip to content

Commit 87a1108

Browse files
committed
test: add snapshot completion unittests
Also adjusts the previous snapshot chainstate init tests to account for the fact that the init process is now attempting to validate and complete background chainstates whose tip is at the snapshot base block. We use a DisconnectTip() hack to preserve the nature of the test.
1 parent d70919a commit 87a1108

File tree

1 file changed

+168
-3
lines changed

1 file changed

+168
-3
lines changed

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 168 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,10 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
472472
//! Ensure that snapshot chainstates initialize properly when found on disk.
473473
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
474474
{
475-
this->SetupSnapshot();
476-
477475
ChainstateManager& chainman = *Assert(m_node.chainman);
476+
Chainstate& bg_chainstate = chainman.ActiveChainstate();
477+
478+
this->SetupSnapshot();
478479

479480
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir();
480481
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
@@ -487,6 +488,20 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
487488
auto all_chainstates = chainman.GetAll();
488489
BOOST_CHECK_EQUAL(all_chainstates.size(), 2);
489490

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+
490505
// Test that simulating a shutdown (resetting ChainstateManager) and then performing
491506
// chainstate reinitializing successfully cleans up the background-validation
492507
// 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)
518533
// chainstate.
519534
for (Chainstate* cs : chainman_restarted.GetAll()) {
520535
if (cs != &chainman_restarted.ActiveChainstate()) {
521-
BOOST_CHECK_EQUAL(cs->m_chain.Height(), 110);
536+
BOOST_CHECK_EQUAL(cs->m_chain.Height(), 109);
522537
}
523538
}
524539
}
525540
}
526541

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+
527692
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)