test: adjust chainstate tests to use recognized snapshot base

In future commits, loading the block index while making use of a
snapshot is contingent on the snapshot being recognized by chainparams.

Ensure all existing unittests that use snapshots use a recognized
snapshot (at height 110).

Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
This commit is contained in:
James O'Beirne 2023-08-25 14:05:07 -04:00
parent 1019c39982
commit 49ef778158
2 changed files with 77 additions and 29 deletions

View file

@ -30,12 +30,12 @@ using node::BlockManager;
using node::KernelNotifications; using node::KernelNotifications;
using node::SnapshotMetadata; using node::SnapshotMetadata;
BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, ChainTestingSetup) BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
//! Basic tests for ChainstateManager. //! Basic tests for ChainstateManager.
//! //!
//! First create a legacy (IBD) chainstate, then create a snapshot chainstate. //! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
BOOST_AUTO_TEST_CASE(chainstatemanager) BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
{ {
ChainstateManager& manager = *m_node.chainman; ChainstateManager& manager = *m_node.chainman;
CTxMemPool& mempool = *m_node.mempool; CTxMemPool& mempool = *m_node.mempool;
@ -46,14 +46,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
// Create a legacy (IBD) chainstate. // Create a legacy (IBD) chainstate.
// //
Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool)); Chainstate& c1 = manager.ActiveChainstate();
chainstates.push_back(&c1); chainstates.push_back(&c1);
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
c1.LoadGenesisBlock();
BlockValidationState val_state;
BOOST_CHECK(c1.ActivateBestChain(val_state, nullptr));
BOOST_CHECK(!manager.IsSnapshotActive()); BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
@ -63,8 +57,9 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain); BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 0); // Get to a valid assumeutxo tip (per chainparams);
mineBlocks(10);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip = c1.m_chain.Tip(); auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip); BOOST_CHECK_EQUAL(active_tip, exp_tip);
@ -77,16 +72,19 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot( Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
&mempool, snapshot_blockhash)); &mempool, snapshot_blockhash));
chainstates.push_back(&c2); chainstates.push_back(&c2);
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
c2.InitCoinsDB( c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23)); {
c2.m_chain.SetTip(*active_tip); LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
c2.CoinsTip().SetBestBlock(active_tip->GetBlockHash());
c2.setBlockIndexCandidates.insert(manager.m_blockman.LookupBlockIndex(active_tip->GetBlockHash()));
c2.LoadChainTip();
}
BlockValidationState _; BlockValidationState _;
BOOST_CHECK(c2.ActivateBestChain(_, nullptr)); BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
BOOST_CHECK(manager.IsSnapshotActive()); BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate()); BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
@ -97,13 +95,15 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain); BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 0); BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
mineBlocks(1);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 111);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return c1.m_chain.Height()), 110);
auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip2 = c2.m_chain.Tip(); BOOST_CHECK_EQUAL(active_tip, active_tip2->pprev);
BOOST_CHECK_EQUAL(active_tip2, exp_tip2); BOOST_CHECK_EQUAL(active_tip, c1.m_chain.Tip());
BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip());
BOOST_CHECK_EQUAL(exp_tip, exp_tip2);
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded // Let scheduler events finish running to avoid accessing memory that is going to be unloaded
SyncWithValidationInterfaceQueue(); SyncWithValidationInterfaceQueue();
@ -125,9 +125,6 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
// //
Chainstate& c1 = manager.ActiveChainstate(); Chainstate& c1 = manager.ActiveChainstate();
chainstates.push_back(&c1); chainstates.push_back(&c1);
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
{ {
LOCK(::cs_main); LOCK(::cs_main);
c1.InitCoinsCache(1 << 23); c1.InitCoinsCache(1 << 23);
@ -431,13 +428,20 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
int num_indexes{0}; int num_indexes{0};
int num_assumed_valid{0}; int num_assumed_valid{0};
// Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
// marked as assumed-valid and not having data.
const int expected_assumed_valid{20}; const int expected_assumed_valid{20};
const int last_assumed_valid_idx{40}; const int last_assumed_valid_idx{111};
const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid; const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
// Mine to height 120, past the hardcoded regtest assumeutxo snapshot at
// height 110
mineBlocks(20);
CBlockIndex* validated_tip{nullptr}; CBlockIndex* validated_tip{nullptr};
CBlockIndex* assumed_base{nullptr}; CBlockIndex* assumed_base{nullptr};
CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())}; CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
auto reload_all_block_indexes = [&]() { auto reload_all_block_indexes = [&]() {
// For completeness, we also reset the block sequence counters to // For completeness, we also reset the block sequence counters to
@ -463,7 +467,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
LOCK(::cs_main); LOCK(::cs_main);
auto index = cs1.m_chain[i]; auto index = cs1.m_chain[i];
// Blocks with heights in range [20, 40) are marked ASSUMED_VALID // Blocks with heights in range [91, 110] are marked ASSUMED_VALID
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) { if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID; index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
} }
@ -497,10 +501,36 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
// Set tip of the assume-valid-based chain to the assume-valid block // Set tip of the assume-valid-based chain to the assume-valid block
cs2.m_chain.SetTip(*assumed_base); cs2.m_chain.SetTip(*assumed_base);
// Sanity check test variables.
BOOST_CHECK_EQUAL(num_indexes, 121); // 121 total blocks, including genesis
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120); // original chain has height 120
BOOST_CHECK_EQUAL(validated_tip->nHeight, 90); // current cs1 chain has height 90
BOOST_CHECK_EQUAL(assumed_base->nHeight, 110); // current cs2 chain has height 110
// Regenerate cs1.setBlockIndexCandidates and cs2.setBlockIndexCandidate and
// check contents below.
reload_all_block_indexes(); reload_all_block_indexes();
// The fully validated chain should have the current validated tip // The fully validated chain should only have the current validated tip and
// and the assumed valid base as candidates. // the assumed valid base as candidates, blocks 90 and 110. Specifically:
//
// - It does not have blocks 0-89 because they contain less work than the
// chain tip.
//
// - It has block 90 because it has data and equal work to the chain tip,
// (since it is the chain tip).
//
// - It does not have blocks 91-109 because they do not contain data.
//
// - It has block 110 even though it does not have data, because
// LoadBlockIndex has a special case to always add the snapshot block as a
// candidate. The special case is only actually intended to apply to the
// snapshot chainstate cs2, not the background chainstate cs1, but it is
// written broadly and applies to both.
//
// - It does not have any blocks after height 110 because cs1 is a background
// chainstate, and only blocks where are ancestors of the snapshot block
// are added as candidates for the background chainstate.
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 2); BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 2);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1); BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1); BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1);
@ -508,8 +538,25 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
// The assumed-valid tolerant chain has the assumed valid base as a // The assumed-valid tolerant chain has the assumed valid base as a
// candidate, but otherwise has none of the assumed-valid (which do not // candidate, but otherwise has none of the assumed-valid (which do not
// HAVE_DATA) blocks as candidates. // HAVE_DATA) blocks as candidates.
//
// Specifically:
// - All blocks below height 110 are not candidates, because cs2 chain tip
// has height 110 and they have less work than it does.
//
// - Block 110 is a candidate even though it does not have data, because it
// is the snapshot block, which is assumed valid.
//
// - Blocks 111-120 are added because they have data.
// Check that block 90 is absent
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0); BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
// Check that block 109 is absent
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base->pprev), 0);
// Check that block 110 is present
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base), 1);
// Check that block 120 is present
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1); BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
// Check that 11 blocks total are present.
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1); BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1);
} }

View file

@ -3539,7 +3539,8 @@ void Chainstate::ResetBlockFailureFlags(CBlockIndex *pindex) {
void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex) void Chainstate::TryAddBlockIndexCandidate(CBlockIndex* pindex)
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
// The block only is a candidate for the most-work-chain if it has more work than our current tip. // The block only is a candidate for the most-work-chain if it has the same
// or more work than our current tip.
if (m_chain.Tip() != nullptr && setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) { if (m_chain.Tip() != nullptr && setBlockIndexCandidates.value_comp()(pindex, m_chain.Tip())) {
return; return;
} }