bitcoin/src/test/validation_chainstatemanager_tests.cpp
Andrew Chow 6912a28f08
Merge bitcoin/bitcoin#25667: assumeutxo: snapshot initialization
bf9597606166323158bbf631137b82d41f39334f doc: add note about snapshot chainstate init (James O'Beirne)
e4d799528696c5ede38c257afaffd367917e0de8 test: add testcases for snapshot initialization (James O'Beirne)
cced4e7336d93a2dc88e4a61c49941887766bd72 test: move-only-ish: factor out LoadVerifyActivateChainstate() (James O'Beirne)
51fc9241c08a00f1f407f1534853a5cddbbc0a23 test: allow on-disk coins and block tree dbs in tests (James O'Beirne)
3c361391b8f5971eb3c7b620aa7ad9b437cc515e test: add reset_chainstate parameter for snapshot unittests (James O'Beirne)
00b357c215ed900145bd770525a341ba0ed9c027 validation: add ResetChainstates() (James O'Beirne)
3a29dfbfb2c16a50d854f6f81428a68aa9180509 move-only: test: make snapshot chainstate setup reusable (James O'Beirne)
8153bd9247dad3982d54488bcdb3960470315290 blockmanager: avoid undefined behavior during FlushBlockFile (James O'Beirne)
ad67ff377c2b271cb4683da2fb25fd295557f731 validation: remove snapshot datadirs upon validation failure (James O'Beirne)
34d159033106cc595cfa852695610bfe419c989c add utilities for deleting on-disk leveldb data (James O'Beirne)
252abd1e8bc5cdf4368ad55e827a873240535b28 init: add utxo snapshot detection (James O'Beirne)
f9f1735f139b6a1f1c7fea50717ff90dc4ba2bce validation: rename snapshot chainstate dir (James O'Beirne)
d14bebf100aaaa25c7558eeed8b5c536da99885f db: add StoragePath to CDBWrapper/CCoinsViewDB (James O'Beirne)

Pull request description:

  This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11) (parent PR: https://github.com/bitcoin/bitcoin/pull/15606)

  ---

  Half of the replacement for #24232. The original PR grew larger than expected throughout the review process.

  This change adds the ability to initialize a snapshot-based chainstate during init if one is detected on disk. This is of course unused as of now (aside from in unittests) given that we haven't yet enabled actually loading snapshots.

  Don't be scared! There are some big move-only commits in here.

  Accompanying changes include:

  - moving the snapshot coinsdb directory from being called `chainstate_[base blockhash]` to `chainstate_snapshot`, since we only support one snapshot in use at a time. This simplifies some logic, but it necessitates writing that base blockhash out to a file within the coinsdb dir. See [discussion here](https://github.com/bitcoin/bitcoin/pull/24232#discussion_r832762880).
  - adding a simple fix in `FlushBlockFile()` that avoids a crash when attemping to flush to disk before `LoadBlockIndexDB()` is called, which happens when calling `MaybeRebalanceCaches()` during multiple chainstate init.
  - improving the unittest to allow testing with on-disk chainstates - necessary to test a simulated restart and re-initialization.

ACKs for top commit:
  naumenkogs:
    utACK bf9597606166323158bbf631137b82d41f39334f
  ariard:
    Code Review ACK bf9597606
  ryanofsky:
    Code review ACK bf9597606166323158bbf631137b82d41f39334f. Changes since last review: rebasing, switching from CAutoFile to AutoFile, adding comments, switching from BOOST_CHECK to Assert in test util, using chainman.GetMutex() in tests, destroying one ChainstateManager before creating a new one in tests
  fjahr:
    utACK bf9597606166323158bbf631137b82d41f39334f
  aureleoules:
    ACK bf9597606166323158bbf631137b82d41f39334f

Tree-SHA512: 15ae75caf19f8d12a12d2647c52897904d27b265a7af6b4ae7b858592eeadb8f9da6c2394b6baebec90adc28742c053e3eb506119577dae7c1e722ebb3b7bcc0
2022-10-13 10:19:27 -04:00

528 lines
20 KiB
C++

// Copyright (c) 2019-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
#include <chainparams.h>
#include <consensus/validation.h>
#include <node/utxo_snapshot.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <sync.h>
#include <test/util/chainstate.h>
#include <test/util/setup_common.h>
#include <timedata.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>
#include <tinyformat.h>
#include <vector>
#include <boost/test/unit_test.hpp>
using node::SnapshotMetadata;
BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, ChainTestingSetup)
//! Basic tests for ChainstateManager.
//!
//! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
BOOST_AUTO_TEST_CASE(chainstatemanager)
{
ChainstateManager& manager = *m_node.chainman;
CTxMemPool& mempool = *m_node.mempool;
std::vector<Chainstate*> chainstates;
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a legacy (IBD) chainstate.
//
Chainstate& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool));
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));
BOOST_CHECK(!manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
auto all = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), -1);
auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
// Create a snapshot-based chainstate.
//
const uint256 snapshot_blockhash = GetRandHash();
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
&mempool, snapshot_blockhash));
chainstates.push_back(&c2);
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
// Unlike c1, which doesn't have any blocks. Gets us different tip, height.
c2.LoadGenesisBlock();
BlockValidationState _;
BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
BOOST_CHECK(&c1 != &manager.ActiveChainstate());
auto all2 = manager.GetAll();
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 0);
auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
auto exp_tip2 = c2.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip2, exp_tip2);
// Ensure that these pointers actually correspond to different
// CCoinsViewCache instances.
BOOST_CHECK(exp_tip != exp_tip2);
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded
SyncWithValidationInterfaceQueue();
}
//! Test rebalancing the caches associated with each chainstate.
BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
{
ChainstateManager& manager = *m_node.chainman;
CTxMemPool& mempool = *m_node.mempool;
size_t max_cache = 10000;
manager.m_total_coinsdb_cache = max_cache;
manager.m_total_coinstip_cache = max_cache;
std::vector<Chainstate*> chainstates;
// Create a legacy (IBD) chainstate.
//
Chainstate& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool));
chainstates.push_back(&c1);
c1.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
{
LOCK(::cs_main);
c1.InitCoinsCache(1 << 23);
BOOST_REQUIRE(c1.LoadGenesisBlock());
c1.CoinsTip().SetBestBlock(InsecureRand256());
manager.MaybeRebalanceCaches();
}
BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache);
BOOST_CHECK_EQUAL(c1.m_coinsdb_cache_size_bytes, max_cache);
// Create a snapshot-based chainstate.
//
Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
chainstates.push_back(&c2);
c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
{
LOCK(::cs_main);
c2.InitCoinsCache(1 << 23);
BOOST_REQUIRE(c2.LoadGenesisBlock());
c2.CoinsTip().SetBestBlock(InsecureRand256());
manager.MaybeRebalanceCaches();
}
// Since both chainstates are considered to be in initial block download,
// the snapshot chainstate should take priority.
BOOST_CHECK_CLOSE(c1.m_coinstip_cache_size_bytes, max_cache * 0.05, 1);
BOOST_CHECK_CLOSE(c1.m_coinsdb_cache_size_bytes, max_cache * 0.05, 1);
BOOST_CHECK_CLOSE(c2.m_coinstip_cache_size_bytes, max_cache * 0.95, 1);
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}
struct SnapshotTestSetup : TestChain100Setup {
// Run with coinsdb on the filesystem to support, e.g., moving invalidated
// chainstate dirs to "*_invalid".
//
// Note that this means the tests run considerably slower than in-memory DB
// tests, but we can't otherwise test this functionality since it relies on
// destructive filesystem operations.
SnapshotTestSetup() : TestChain100Setup{
{},
{},
/*coins_db_in_memory=*/false,
/*block_tree_db_in_memory=*/false,
}
{
}
std::tuple<Chainstate*, Chainstate*> SetupSnapshot()
{
ChainstateManager& chainman = *Assert(m_node.chainman);
BOOST_CHECK(!chainman.IsSnapshotActive());
{
LOCK(::cs_main);
BOOST_CHECK(!chainman.IsSnapshotValidated());
BOOST_CHECK(!node::FindSnapshotChainstateDir());
}
size_t initial_size;
size_t initial_total_coins{100};
// Make some initial assertions about the contents of the chainstate.
{
LOCK(::cs_main);
CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
initial_size = ibd_coinscache.GetCacheSize();
size_t total_coins{0};
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
BOOST_CHECK(ibd_coinscache.HaveCoin(op));
total_coins++;
}
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
}
Chainstate& validation_chainstate = chainman.ActiveChainstate();
// Snapshot should refuse to load at this height.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
BOOST_CHECK(!chainman.SnapshotBlockhash());
// Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
// be found.
constexpr int snapshot_height = 110;
mineBlocks(10);
initial_size += 10;
initial_total_coins += 10;
// Should not load malleated snapshots
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
// A UTXO is missing but count is correct
metadata.m_coins_count -= 1;
COutPoint outpoint;
Coin coin;
auto_infile >> outpoint;
auto_infile >> coin;
}));
BOOST_CHECK(!node::FindSnapshotChainstateDir());
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
// Coins count is larger than coins in file
metadata.m_coins_count += 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
// Coins count is smaller than coins in file
metadata.m_coins_count -= 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
// Wrong hash
metadata.m_base_blockhash = uint256::ZERO;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
// Wrong hash
metadata.m_base_blockhash = uint256::ONE;
}));
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir()));
// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
BOOST_CHECK_EQUAL(
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
*chainman.SnapshotBlockhash());
Chainstate& snapshot_chainstate = chainman.ActiveChainstate();
{
LOCK(::cs_main);
fs::path found = *node::FindSnapshotChainstateDir();
// Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
BOOST_CHECK_EQUAL(
*node::ReadSnapshotBaseBlockhash(found),
*chainman.SnapshotBlockhash());
// Ensure that the genesis block was not marked assumed-valid.
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
}
const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx);
// To be checked against later when we try loading a subsequent snapshot.
uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
// Make some assertions about the both chainstates. These checks ensure the
// legacy chainstate hasn't changed and that the newly created chainstate
// reflects the expected content.
{
LOCK(::cs_main);
int chains_tested{0};
for (Chainstate* chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
// Both caches will be empty initially.
BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
size_t total_coins{0};
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
BOOST_CHECK(coinscache.HaveCoin(op));
total_coins++;
}
BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
chains_tested++;
}
BOOST_CHECK_EQUAL(chains_tested, 2);
}
// Mine some new blocks on top of the activated snapshot chainstate.
constexpr size_t new_coins{100};
mineBlocks(new_coins); // Defined in TestChain100Setup.
{
LOCK(::cs_main);
size_t coins_in_active{0};
size_t coins_in_background{0};
size_t coins_missing_from_background{0};
for (Chainstate* chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
bool is_background = chainstate != &chainman.ActiveChainstate();
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
if (coinscache.HaveCoin(op)) {
(is_background ? coins_in_background : coins_in_active)++;
} else if (is_background) {
coins_missing_from_background++;
}
}
}
BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
}
// Snapshot should refuse to load after one has already loaded.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
// Snapshot blockhash should be unchanged.
BOOST_CHECK_EQUAL(
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
loaded_snapshot_blockhash);
return std::make_tuple(&validation_chainstate, &snapshot_chainstate);
}
// Simulate a restart of the node by flushing all state to disk, clearing the
// existing ChainstateManager, and unloading the block index.
//
// @returns a reference to the "restarted" ChainstateManager
ChainstateManager& SimulateNodeRestart()
{
ChainstateManager& chainman = *Assert(m_node.chainman);
BOOST_TEST_MESSAGE("Simulating node restart");
{
LOCK(::cs_main);
for (Chainstate* cs : chainman.GetAll()) {
cs->ForceFlushStateToDisk();
}
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(),
.adjusted_time_callback = GetAdjustedTime,
};
// For robustness, ensure the old manager is destroyed before creating a
// new one.
m_node.chainman.reset();
m_node.chainman.reset(new ChainstateManager(chainman_opts));
}
return *Assert(m_node.chainman);
}
};
//! Test basic snapshot activation.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
{
this->SetupSnapshot();
}
//! Test LoadBlockIndex behavior when multiple chainstates are in use.
//!
//! - First, verify that setBlockIndexCandidates is as expected when using a single,
//! fully-validating chainstate.
//!
//! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
//! that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
//! chainstate only contains fully validated blocks and the other chainstate contains all blocks,
//! even those assumed-valid.
//!
BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
{
ChainstateManager& chainman = *Assert(m_node.chainman);
CTxMemPool& mempool = *m_node.mempool;
Chainstate& cs1 = chainman.ActiveChainstate();
int num_indexes{0};
int num_assumed_valid{0};
const int expected_assumed_valid{20};
const int last_assumed_valid_idx{40};
const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
CBlockIndex* validated_tip{nullptr};
CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
auto reload_all_block_indexes = [&]() {
for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->UnloadBlockIndex();
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
}
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
};
// Ensure that without any assumed-valid BlockIndex entries, all entries are considered
// tip candidates.
reload_all_block_indexes();
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
// Mark some region of the chain assumed-valid.
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
LOCK(::cs_main);
auto index = cs1.m_chain[i];
if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
}
++num_indexes;
if (index->IsAssumedValid()) ++num_assumed_valid;
// Note the last fully-validated block as the expected validated tip.
if (i == (assumed_valid_start_idx - 1)) {
validated_tip = index;
BOOST_CHECK(!index->IsAssumedValid());
}
}
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
Chainstate& cs2 = WITH_LOCK(::cs_main,
return chainman.ActivateExistingSnapshot(&mempool, GetRandHash()));
reload_all_block_indexes();
// The fully validated chain only has candidates up to the start of the assumed-valid
// blocks.
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_tip), 0);
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), assumed_valid_start_idx);
// The assumed-valid tolerant chain has all blocks as candidates.
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
}
//! Ensure that snapshot chainstates initialize properly when found on disk.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
{
this->SetupSnapshot();
ChainstateManager& chainman = *Assert(m_node.chainman);
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir();
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
BOOST_CHECK(chainman.IsSnapshotActive());
const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
return chainman.ActiveTip()->GetBlockHash());
auto all_chainstates = chainman.GetAll();
BOOST_CHECK_EQUAL(all_chainstates.size(), 2);
// Test that simulating a shutdown (resetting ChainstateManager) and then performing
// chainstate reinitializing successfully cleans up the background-validation
// chainstate data, and we end up with a single chainstate that is at tip.
ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
// This call reinitializes the chainstates.
this->LoadVerifyActivateChainstate();
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 2);
BOOST_CHECK(chainman_restarted.IsSnapshotActive());
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
}
BOOST_TEST_MESSAGE(
"Ensure we can mine blocks on top of the initialized snapshot chainstate");
mineBlocks(10);
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
// Background chainstate should be unaware of new blocks on the snapshot
// chainstate.
for (Chainstate* cs : chainman_restarted.GetAll()) {
if (cs != &chainman_restarted.ActiveChainstate()) {
BOOST_CHECK_EQUAL(cs->m_chain.Height(), 110);
}
}
}
}
BOOST_AUTO_TEST_SUITE_END()