2022-12-24 23:49:50 +00:00
|
|
|
// Copyright (c) 2019-2022 The Bitcoin Core developers
|
2019-12-12 14:11:03 -05:00
|
|
|
// 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>
|
2023-09-05 13:45:09 +01:00
|
|
|
#include <kernel/disconnected_transactions.h>
|
2023-05-10 22:29:17 +02:00
|
|
|
#include <node/kernel_notifications.h>
|
2020-08-25 13:52:51 -04:00
|
|
|
#include <node/utxo_snapshot.h>
|
2020-04-12 20:22:38 -04:00
|
|
|
#include <random.h>
|
2020-08-25 13:52:51 -04:00
|
|
|
#include <rpc/blockchain.h>
|
2019-12-12 14:11:03 -05:00
|
|
|
#include <sync.h>
|
2021-04-07 11:48:02 -04:00
|
|
|
#include <test/util/chainstate.h>
|
2023-07-07 17:45:30 -03:00
|
|
|
#include <test/util/logging.h>
|
2023-01-22 09:57:19 -08:00
|
|
|
#include <test/util/random.h>
|
2021-04-08 10:06:10 -04:00
|
|
|
#include <test/util/setup_common.h>
|
2023-08-04 16:43:39 -04:00
|
|
|
#include <test/util/validation.h>
|
2022-02-07 19:56:31 -05:00
|
|
|
#include <timedata.h>
|
2020-04-12 20:22:38 -04:00
|
|
|
#include <uint256.h>
|
2019-12-12 14:11:03 -05:00
|
|
|
#include <validation.h>
|
2020-04-12 20:22:38 -04:00
|
|
|
#include <validationinterface.h>
|
2019-12-12 14:11:03 -05:00
|
|
|
|
2020-08-25 13:52:51 -04:00
|
|
|
#include <tinyformat.h>
|
|
|
|
|
2019-12-12 14:11:03 -05:00
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
|
2023-02-18 18:17:33 +01:00
|
|
|
using node::BlockManager;
|
2023-05-10 22:29:17 +02:00
|
|
|
using node::KernelNotifications;
|
2021-11-12 10:06:00 -05:00
|
|
|
using node::SnapshotMetadata;
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
|
2019-12-12 14:11:03 -05:00
|
|
|
|
|
|
|
//! Basic tests for ChainstateManager.
|
|
|
|
//!
|
|
|
|
//! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
|
2023-08-25 14:05:07 -04:00
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
|
2019-12-12 14:11:03 -05:00
|
|
|
{
|
test: Add new ChainTestingSetup and use it
Previously, the validation_chainstatemanager_tests test suite
instantiated its own duplicate ChainstateManager on which tests were
performed.
This wasn't a problem for the specific actions performed in
that suite. However, the existence of this duplicate ChainstateManager
and the fact that many of our validation static functions reach for
g_chainman, ::Chain(state|)Active means we may end up acting on two
different CChainStates should we write more extensive tests in the
future.
This change adds a new ChainTestingSetup which performs all
initialization previously done by TestingSetup except:
1. RPC command registration
2. ChainState initialization
3. Genesis Activation
4. {Ban,Conn,Peer}Man initialization
Means that we will no longer need to initialize a duplicate
ChainstateManger in order to test the initialization codepaths of
CChainState and ChainstateManager.
Lastly, this change has the additional benefit of allowing for
review-only assertions meant to show correctness to work in future work
de-globalizing g_chainman.
In the test chainstatemanager_rebalance_caches, an additional
LoadGenesisBlock call is added as MaybeReblanaceCaches eventually calls
FlushBlockFile, which tries to access vinfoBlockFile[nLastBlockFile],
which is out of bounds when LoadGenesisBlock hasn't been called yet.
-----
Note for the future:
The class con/destructor inheritance structure we have for these
TestingSetup classes is probably not the most suitable abstraction. In
particular, for both TestingSetup and ChainTestingSetup, we need to stop
the scheduler first before anything else. Otherwise classes depending on
the scheduler may be referenced by the scheduler after said classes are
freed. This means that there's no clear parallel between our teardown
code and C++'s destructuring order for class hierarchies.
Future work should strive to coalesce (as much as possible) test and
non-test init codepaths and perhaps structure it in a more fail-proof
way.
2020-10-13 16:55:20 -04:00
|
|
|
ChainstateManager& manager = *m_node.chainman;
|
2022-03-09 12:37:19 -05:00
|
|
|
std::vector<Chainstate*> chainstates;
|
2019-12-12 14:11:03 -05:00
|
|
|
|
2020-08-25 13:50:23 -04:00
|
|
|
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
|
|
|
|
|
2019-12-12 14:11:03 -05:00
|
|
|
// Create a legacy (IBD) chainstate.
|
|
|
|
//
|
2023-08-25 14:05:07 -04:00
|
|
|
Chainstate& c1 = manager.ActiveChainstate();
|
2019-12-12 14:11:03 -05:00
|
|
|
chainstates.push_back(&c1);
|
|
|
|
|
|
|
|
BOOST_CHECK(!manager.IsSnapshotActive());
|
2022-04-15 07:47:20 +10:00
|
|
|
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
|
2019-12-12 14:11:03 -05:00
|
|
|
auto all = manager.GetAll();
|
|
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
|
|
|
|
|
2022-05-06 16:58:53 +02:00
|
|
|
auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
|
2019-12-12 14:11:03 -05:00
|
|
|
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
// Get to a valid assumeutxo tip (per chainparams);
|
|
|
|
mineBlocks(10);
|
|
|
|
BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110);
|
2022-05-06 16:58:53 +02:00
|
|
|
auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
|
2019-12-12 14:11:03 -05:00
|
|
|
auto exp_tip = c1.m_chain.Tip();
|
|
|
|
BOOST_CHECK_EQUAL(active_tip, exp_tip);
|
|
|
|
|
2020-08-25 13:50:23 -04:00
|
|
|
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
|
|
|
|
|
2019-12-12 14:11:03 -05:00
|
|
|
// Create a snapshot-based chainstate.
|
|
|
|
//
|
2023-05-24 13:38:32 -04:00
|
|
|
const uint256 snapshot_blockhash = active_tip->GetBlockHash();
|
2023-05-05 18:27:56 -04:00
|
|
|
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(snapshot_blockhash));
|
2019-12-12 14:11:03 -05:00
|
|
|
chainstates.push_back(&c2);
|
|
|
|
c2.InitCoinsDB(
|
2021-09-14 16:56:34 +02:00
|
|
|
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
|
2023-08-25 14:05:07 -04:00
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
2019-12-12 14:11:03 -05:00
|
|
|
BlockValidationState _;
|
2021-04-27 22:54:53 +02:00
|
|
|
BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
|
2019-12-12 14:11:03 -05:00
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
|
2019-12-12 14:11:03 -05:00
|
|
|
BOOST_CHECK(manager.IsSnapshotActive());
|
2022-04-15 07:47:20 +10:00
|
|
|
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
|
2021-07-15 10:40:28 -04:00
|
|
|
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
|
|
|
|
BOOST_CHECK(&c1 != &manager.ActiveChainstate());
|
2019-12-12 14:11:03 -05:00
|
|
|
auto all2 = manager.GetAll();
|
|
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
|
|
|
|
|
2022-05-06 16:58:53 +02:00
|
|
|
auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
|
2019-12-12 14:11:03 -05:00
|
|
|
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
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);
|
2019-12-12 14:11:03 -05:00
|
|
|
|
2022-05-06 16:58:53 +02:00
|
|
|
auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip());
|
2023-08-25 14:05:07 -04:00
|
|
|
BOOST_CHECK_EQUAL(active_tip, active_tip2->pprev);
|
|
|
|
BOOST_CHECK_EQUAL(active_tip, c1.m_chain.Tip());
|
|
|
|
BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip());
|
2019-12-12 14:11:03 -05:00
|
|
|
|
2020-04-12 20:22:38 -04:00
|
|
|
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded
|
|
|
|
SyncWithValidationInterfaceQueue();
|
2019-12-12 14:11:03 -05:00
|
|
|
}
|
|
|
|
|
2019-09-16 13:37:29 -04:00
|
|
|
//! Test rebalancing the caches associated with each chainstate.
|
2023-05-24 13:38:32 -04:00
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
|
2019-09-16 13:37:29 -04:00
|
|
|
{
|
test: Add new ChainTestingSetup and use it
Previously, the validation_chainstatemanager_tests test suite
instantiated its own duplicate ChainstateManager on which tests were
performed.
This wasn't a problem for the specific actions performed in
that suite. However, the existence of this duplicate ChainstateManager
and the fact that many of our validation static functions reach for
g_chainman, ::Chain(state|)Active means we may end up acting on two
different CChainStates should we write more extensive tests in the
future.
This change adds a new ChainTestingSetup which performs all
initialization previously done by TestingSetup except:
1. RPC command registration
2. ChainState initialization
3. Genesis Activation
4. {Ban,Conn,Peer}Man initialization
Means that we will no longer need to initialize a duplicate
ChainstateManger in order to test the initialization codepaths of
CChainState and ChainstateManager.
Lastly, this change has the additional benefit of allowing for
review-only assertions meant to show correctness to work in future work
de-globalizing g_chainman.
In the test chainstatemanager_rebalance_caches, an additional
LoadGenesisBlock call is added as MaybeReblanaceCaches eventually calls
FlushBlockFile, which tries to access vinfoBlockFile[nLastBlockFile],
which is out of bounds when LoadGenesisBlock hasn't been called yet.
-----
Note for the future:
The class con/destructor inheritance structure we have for these
TestingSetup classes is probably not the most suitable abstraction. In
particular, for both TestingSetup and ChainTestingSetup, we need to stop
the scheduler first before anything else. Otherwise classes depending on
the scheduler may be referenced by the scheduler after said classes are
freed. This means that there's no clear parallel between our teardown
code and C++'s destructuring order for class hierarchies.
Future work should strive to coalesce (as much as possible) test and
non-test init codepaths and perhaps structure it in a more fail-proof
way.
2020-10-13 16:55:20 -04:00
|
|
|
ChainstateManager& manager = *m_node.chainman;
|
|
|
|
|
2019-09-16 13:37:29 -04:00
|
|
|
size_t max_cache = 10000;
|
|
|
|
manager.m_total_coinsdb_cache = max_cache;
|
|
|
|
manager.m_total_coinstip_cache = max_cache;
|
|
|
|
|
2022-03-09 12:37:19 -05:00
|
|
|
std::vector<Chainstate*> chainstates;
|
2019-09-16 13:37:29 -04:00
|
|
|
|
|
|
|
// Create a legacy (IBD) chainstate.
|
|
|
|
//
|
2023-05-24 13:38:32 -04:00
|
|
|
Chainstate& c1 = manager.ActiveChainstate();
|
2019-09-16 13:37:29 -04:00
|
|
|
chainstates.push_back(&c1);
|
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
c1.InitCoinsCache(1 << 23);
|
|
|
|
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.
|
|
|
|
//
|
2023-05-24 13:38:32 -04:00
|
|
|
CBlockIndex* snapshot_base{WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()[manager.ActiveChain().Height() / 2])};
|
2023-05-05 18:27:56 -04:00
|
|
|
Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(*snapshot_base->phashBlock));
|
2019-09-16 13:37:29 -04:00
|
|
|
chainstates.push_back(&c2);
|
|
|
|
c2.InitCoinsDB(
|
2021-09-14 16:56:34 +02:00
|
|
|
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
|
2019-09-16 13:37:29 -04:00
|
|
|
|
2023-08-04 16:43:39 -04:00
|
|
|
// Reset IBD state so IsInitialBlockDownload() returns true and causes
|
|
|
|
// MaybeRebalancesCaches() to prioritize the snapshot chainstate, giving it
|
|
|
|
// more cache space than the snapshot chainstate. Calling ResetIbd() is
|
|
|
|
// necessary because m_cached_finished_ibd is already latched to true before
|
|
|
|
// the test starts due to the test setup. After ResetIbd() is called.
|
|
|
|
// IsInitialBlockDownload will return true because at this point the active
|
|
|
|
// chainstate has a null chain tip.
|
|
|
|
static_cast<TestChainstateManager&>(manager).ResetIbd();
|
|
|
|
|
2019-09-16 13:37:29 -04:00
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
c2.InitCoinsCache(1 << 23);
|
|
|
|
manager.MaybeRebalanceCaches();
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
struct SnapshotTestSetup : TestChain100Setup {
|
2022-02-07 19:56:31 -05:00
|
|
|
// 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,
|
|
|
|
}
|
2020-08-25 13:52:51 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
std::tuple<Chainstate*, Chainstate*> SetupSnapshot()
|
2020-08-25 13:52:51 -04:00
|
|
|
{
|
2022-02-07 19:29:52 -05:00
|
|
|
ChainstateManager& chainman = *Assert(m_node.chainman);
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_CHECK(!chainman.IsSnapshotActive());
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:56:31 -05:00
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
BOOST_CHECK(!chainman.IsSnapshotValidated());
|
2023-06-13 13:52:26 +02:00
|
|
|
BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir));
|
2022-02-07 19:56:31 -05:00
|
|
|
}
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
size_t initial_size;
|
|
|
|
size_t initial_total_coins{100};
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// Make some initial assertions about the contents of the chainstate.
|
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
|
|
|
|
initial_size = ibd_coinscache.GetCacheSize();
|
2020-08-25 13:52:51 -04:00
|
|
|
size_t total_coins{0};
|
|
|
|
|
|
|
|
for (CTransactionRef& txn : m_coinbase_txns) {
|
|
|
|
COutPoint op{txn->GetHash(), 0};
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_CHECK(ibd_coinscache.HaveCoin(op));
|
2020-08-25 13:52:51 -04:00
|
|
|
total_coins++;
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
Chainstate& validation_chainstate = chainman.ActiveChainstate();
|
|
|
|
|
|
|
|
// Snapshot should refuse to load at this height.
|
2022-02-08 15:56:16 -05:00
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
|
2022-02-07 19:29:52 -05:00
|
|
|
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(
|
2022-02-08 15:56:16 -05:00
|
|
|
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
|
2022-02-07 19:29:52 -05:00
|
|
|
// A UTXO is missing but count is correct
|
|
|
|
metadata.m_coins_count -= 1;
|
|
|
|
|
|
|
|
COutPoint outpoint;
|
|
|
|
Coin coin;
|
|
|
|
|
|
|
|
auto_infile >> outpoint;
|
|
|
|
auto_infile >> coin;
|
|
|
|
}));
|
2022-02-07 19:56:31 -05:00
|
|
|
|
2023-06-13 13:52:26 +02:00
|
|
|
BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir));
|
2022-02-07 19:56:31 -05:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
|
2022-02-08 15:56:16 -05:00
|
|
|
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
|
2022-02-07 19:29:52 -05:00
|
|
|
// Coins count is larger than coins in file
|
|
|
|
metadata.m_coins_count += 1;
|
|
|
|
}));
|
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
|
2022-02-08 15:56:16 -05:00
|
|
|
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
|
2022-02-07 19:29:52 -05:00
|
|
|
// Coins count is smaller than coins in file
|
|
|
|
metadata.m_coins_count -= 1;
|
|
|
|
}));
|
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
|
2022-02-08 15:56:16 -05:00
|
|
|
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
|
2022-02-07 19:29:52 -05:00
|
|
|
// Wrong hash
|
|
|
|
metadata.m_base_blockhash = uint256::ZERO;
|
|
|
|
}));
|
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
|
2022-02-08 15:56:16 -05:00
|
|
|
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
|
2022-02-07 19:29:52 -05:00
|
|
|
// Wrong hash
|
|
|
|
metadata.m_base_blockhash = uint256::ONE;
|
|
|
|
}));
|
|
|
|
|
2022-02-08 15:56:16 -05:00
|
|
|
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
|
2023-06-13 13:52:26 +02:00
|
|
|
BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir(chainman.m_options.datadir)));
|
2022-02-07 19:29:52 -05:00
|
|
|
|
|
|
|
// Ensure our active chain is the snapshot chainstate.
|
|
|
|
BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
|
2022-04-20 14:59:02 -04:00
|
|
|
BOOST_CHECK_EQUAL(
|
2022-02-07 19:29:52 -05:00
|
|
|
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
|
2022-04-20 14:59:02 -04:00
|
|
|
*chainman.SnapshotBlockhash());
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
Chainstate& snapshot_chainstate = chainman.ActiveChainstate();
|
2021-10-28 15:15:10 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2023-06-13 13:52:26 +02:00
|
|
|
fs::path found = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
|
|
|
|
BOOST_CHECK_EQUAL(
|
2022-02-07 19:56:31 -05:00
|
|
|
*node::ReadSnapshotBaseBlockhash(found),
|
2022-02-07 19:29:52 -05:00
|
|
|
*chainman.SnapshotBlockhash());
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// Ensure that the genesis block was not marked assumed-valid.
|
|
|
|
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
|
|
|
|
}
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2023-05-25 13:05:27 -04:00
|
|
|
const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height);
|
2022-02-07 19:29:52 -05:00
|
|
|
const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2023-05-25 13:05:27 -04:00
|
|
|
BOOST_CHECK_EQUAL(tip->nChainTx, au_data->nChainTx);
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// To be checked against later when we try loading a subsequent snapshot.
|
|
|
|
uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// 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};
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
for (Chainstate* chainstate : chainman.GetAll()) {
|
|
|
|
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
|
|
|
|
CCoinsViewCache& coinscache = chainstate->CoinsTip();
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// Both caches will be empty initially.
|
|
|
|
BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
size_t total_coins{0};
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
for (CTransactionRef& txn : m_coinbase_txns) {
|
|
|
|
COutPoint op{txn->GetHash(), 0};
|
|
|
|
BOOST_CHECK(coinscache.HaveCoin(op));
|
|
|
|
total_coins++;
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
|
|
|
|
BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
|
|
|
|
chains_tested++;
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
BOOST_CHECK_EQUAL(chains_tested, 2);
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// 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++;
|
|
|
|
}
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
}
|
2022-02-07 19:29:52 -05:00
|
|
|
|
|
|
|
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);
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// Snapshot should refuse to load after one has already loaded.
|
2022-02-08 15:56:16 -05:00
|
|
|
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
// 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);
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:56:31 -05:00
|
|
|
// 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");
|
|
|
|
{
|
|
|
|
for (Chainstate* cs : chainman.GetAll()) {
|
2023-03-27 15:09:23 -04:00
|
|
|
LOCK(::cs_main);
|
2022-02-07 19:56:31 -05:00
|
|
|
cs->ForceFlushStateToDisk();
|
|
|
|
}
|
2023-03-27 15:09:23 -04:00
|
|
|
// Process all callbacks referring to the old manager before wiping it.
|
|
|
|
SyncWithValidationInterfaceQueue();
|
|
|
|
LOCK(::cs_main);
|
2022-02-07 19:56:31 -05:00
|
|
|
chainman.ResetChainstates();
|
|
|
|
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
|
2023-05-09 11:15:46 +02:00
|
|
|
m_node.notifications = std::make_unique<KernelNotifications>(m_node.exit_status);
|
2022-02-07 19:56:31 -05:00
|
|
|
const ChainstateManager::Options chainman_opts{
|
|
|
|
.chainparams = ::Params(),
|
2023-06-13 13:52:26 +02:00
|
|
|
.datadir = chainman.m_options.datadir,
|
2022-02-07 19:56:31 -05:00
|
|
|
.adjusted_time_callback = GetAdjustedTime,
|
2023-05-10 22:29:17 +02:00
|
|
|
.notifications = *m_node.notifications,
|
2022-02-07 19:56:31 -05:00
|
|
|
};
|
2023-02-18 18:17:33 +01:00
|
|
|
const BlockManager::Options blockman_opts{
|
2023-05-04 12:19:35 +02:00
|
|
|
.chainparams = chainman_opts.chainparams,
|
2023-02-18 18:17:33 +01:00
|
|
|
.blocks_dir = m_args.GetBlocksDirPath(),
|
2023-06-15 23:09:37 +02:00
|
|
|
.notifications = chainman_opts.notifications,
|
2023-05-04 12:19:35 +02:00
|
|
|
};
|
2022-02-07 19:56:31 -05:00
|
|
|
// For robustness, ensure the old manager is destroyed before creating a
|
|
|
|
// new one.
|
|
|
|
m_node.chainman.reset();
|
2023-05-17 12:43:23 +02:00
|
|
|
m_node.chainman = std::make_unique<ChainstateManager>(m_node.kernel->interrupt, chainman_opts, blockman_opts);
|
2022-02-07 19:56:31 -05:00
|
|
|
}
|
|
|
|
return *Assert(m_node.chainman);
|
|
|
|
}
|
2022-02-07 19:29:52 -05:00
|
|
|
};
|
2020-08-25 13:52:51 -04:00
|
|
|
|
2022-02-07 19:29:52 -05:00
|
|
|
//! Test basic snapshot activation.
|
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup)
|
|
|
|
{
|
|
|
|
this->SetupSnapshot();
|
2020-08-25 13:52:51 -04:00
|
|
|
}
|
|
|
|
|
2021-11-10 16:35:12 -05:00
|
|
|
//! Test LoadBlockIndex behavior when multiple chainstates are in use.
|
|
|
|
//!
|
2022-09-14 20:11:45 +02:00
|
|
|
//! - First, verify that setBlockIndexCandidates is as expected when using a single,
|
2021-11-10 16:35:12 -05:00
|
|
|
//! 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,
|
2023-05-24 13:56:37 -04:00
|
|
|
//! except those marked assume-valid, because those entries don't HAVE_DATA.
|
2021-11-10 16:35:12 -05:00
|
|
|
//!
|
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
|
|
|
|
{
|
|
|
|
ChainstateManager& chainman = *Assert(m_node.chainman);
|
2022-03-09 12:37:19 -05:00
|
|
|
Chainstate& cs1 = chainman.ActiveChainstate();
|
2021-11-10 16:35:12 -05:00
|
|
|
|
|
|
|
int num_indexes{0};
|
|
|
|
int num_assumed_valid{0};
|
2023-08-25 14:05:07 -04:00
|
|
|
// Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be
|
|
|
|
// marked as assumed-valid and not having data.
|
2021-11-10 16:35:12 -05:00
|
|
|
const int expected_assumed_valid{20};
|
2023-08-25 14:05:07 -04:00
|
|
|
const int last_assumed_valid_idx{111};
|
2021-11-10 16:35:12 -05:00
|
|
|
const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
// Mine to height 120, past the hardcoded regtest assumeutxo snapshot at
|
|
|
|
// height 110
|
|
|
|
mineBlocks(20);
|
|
|
|
|
2021-11-10 16:35:12 -05:00
|
|
|
CBlockIndex* validated_tip{nullptr};
|
2023-05-24 13:38:32 -04:00
|
|
|
CBlockIndex* assumed_base{nullptr};
|
2022-05-06 16:58:53 +02:00
|
|
|
CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())};
|
2023-08-25 14:05:07 -04:00
|
|
|
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
|
2021-11-10 16:35:12 -05:00
|
|
|
|
|
|
|
auto reload_all_block_indexes = [&]() {
|
2023-06-06 07:50:51 -04:00
|
|
|
// For completeness, we also reset the block sequence counters to
|
|
|
|
// ensure that no state which affects the ranking of tip-candidates is
|
|
|
|
// retained (even though this isn't strictly necessary).
|
|
|
|
WITH_LOCK(::cs_main, return chainman.ResetBlockSequenceCounters());
|
2022-03-09 12:37:19 -05:00
|
|
|
for (Chainstate* cs : chainman.GetAll()) {
|
2021-11-10 16:35:12 -05:00
|
|
|
LOCK(::cs_main);
|
2023-06-06 07:50:51 -04:00
|
|
|
cs->ClearBlockIndexCandidates();
|
2021-11-10 16:35:12 -05:00
|
|
|
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
|
|
|
|
};
|
|
|
|
|
2023-05-24 13:56:37 -04:00
|
|
|
// Ensure that without any assumed-valid BlockIndex entries, only the current tip is
|
|
|
|
// considered as a candidate.
|
2021-11-10 16:35:12 -05:00
|
|
|
reload_all_block_indexes();
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1);
|
2021-11-10 16:35:12 -05:00
|
|
|
|
2023-05-24 13:56:37 -04:00
|
|
|
// Mark some region of the chain assumed-valid, and remove the HAVE_DATA flag.
|
2021-11-10 16:35:12 -05:00
|
|
|
for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
|
2022-01-19 14:03:23 +01:00
|
|
|
LOCK(::cs_main);
|
2021-11-10 16:35:12 -05:00
|
|
|
auto index = cs1.m_chain[i];
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
// Blocks with heights in range [91, 110] are marked ASSUMED_VALID
|
2021-11-10 16:35:12 -05:00
|
|
|
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());
|
|
|
|
}
|
2023-05-24 13:56:37 -04:00
|
|
|
// Note the last assumed valid block as the snapshot base
|
|
|
|
if (i == last_assumed_valid_idx - 1) {
|
2023-05-24 13:38:32 -04:00
|
|
|
assumed_base = index;
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK(index->IsAssumedValid());
|
|
|
|
} else if (i == last_assumed_valid_idx) {
|
2023-05-24 13:38:32 -04:00
|
|
|
BOOST_CHECK(!index->IsAssumedValid());
|
|
|
|
}
|
2021-11-10 16:35:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
|
|
|
|
|
2023-05-24 13:56:37 -04:00
|
|
|
// Note: cs2's tip is not set when ActivateExistingSnapshot is called.
|
2022-03-09 12:37:19 -05:00
|
|
|
Chainstate& cs2 = WITH_LOCK(::cs_main,
|
2023-05-05 18:27:56 -04:00
|
|
|
return chainman.ActivateExistingSnapshot(*assumed_base->phashBlock));
|
2023-05-24 13:38:32 -04:00
|
|
|
|
|
|
|
// Set tip of the fully validated chain to be the validated tip
|
|
|
|
cs1.m_chain.SetTip(*validated_tip);
|
2021-11-10 16:35:12 -05:00
|
|
|
|
2023-05-24 13:56:37 -04:00
|
|
|
// Set tip of the assume-valid-based chain to the assume-valid block
|
|
|
|
cs2.m_chain.SetTip(*assumed_base);
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
// 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.
|
2021-11-10 16:35:12 -05:00
|
|
|
reload_all_block_indexes();
|
|
|
|
|
2023-08-25 14:05:07 -04:00
|
|
|
// The fully validated chain should only have the current validated tip and
|
|
|
|
// 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.
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 2);
|
2021-11-10 16:35:12 -05:00
|
|
|
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1);
|
2021-11-10 16:35:12 -05:00
|
|
|
|
2023-05-24 13:56:37 -04:00
|
|
|
// The assumed-valid tolerant chain has the assumed valid base as a
|
|
|
|
// candidate, but otherwise has none of the assumed-valid (which do not
|
|
|
|
// HAVE_DATA) blocks as candidates.
|
2023-08-25 14:05:07 -04:00
|
|
|
//
|
|
|
|
// 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
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0);
|
2023-08-25 14:05:07 -04:00
|
|
|
// 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
|
2021-11-10 16:35:12 -05:00
|
|
|
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
|
2023-08-25 14:05:07 -04:00
|
|
|
// Check that 11 blocks total are present.
|
2023-05-24 13:56:37 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1);
|
2021-11-10 16:35:12 -05:00
|
|
|
}
|
|
|
|
|
2022-02-07 19:56:31 -05:00
|
|
|
//! Ensure that snapshot chainstates initialize properly when found on disk.
|
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
|
|
|
|
{
|
|
|
|
ChainstateManager& chainman = *Assert(m_node.chainman);
|
2022-07-22 11:26:55 -04:00
|
|
|
Chainstate& bg_chainstate = chainman.ActiveChainstate();
|
|
|
|
|
|
|
|
this->SetupSnapshot();
|
2022-02-07 19:56:31 -05:00
|
|
|
|
2023-06-13 13:52:26 +02:00
|
|
|
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
|
2022-02-07 19:56:31 -05:00
|
|
|
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);
|
|
|
|
|
2022-07-22 11:26:55 -04:00
|
|
|
// "Rewind" the background chainstate so that its tip is not at the
|
|
|
|
// base block of the snapshot - this is so after simulating a node restart,
|
|
|
|
// it will initialize instead of attempting to complete validation.
|
|
|
|
//
|
|
|
|
// Note that this is not a realistic use of DisconnectTip().
|
2023-09-25 13:00:52 +01:00
|
|
|
DisconnectedBlockTransactions unused_pool{MAX_DISCONNECTED_TX_POOL_BYTES};
|
2022-07-22 11:26:55 -04:00
|
|
|
BlockValidationState unused_state;
|
|
|
|
{
|
|
|
|
LOCK2(::cs_main, bg_chainstate.MempoolMutex());
|
|
|
|
BOOST_CHECK(bg_chainstate.DisconnectTip(unused_state, &unused_pool));
|
|
|
|
unused_pool.clear(); // to avoid queuedTx assertion errors on teardown
|
|
|
|
}
|
|
|
|
BOOST_CHECK_EQUAL(bg_chainstate.m_chain.Height(), 109);
|
|
|
|
|
2022-02-07 19:56:31 -05:00
|
|
|
// 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()) {
|
2022-07-22 11:26:55 -04:00
|
|
|
BOOST_CHECK_EQUAL(cs->m_chain.Height(), 109);
|
2022-02-07 19:56:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-22 11:26:55 -04:00
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup)
|
|
|
|
{
|
|
|
|
this->SetupSnapshot();
|
|
|
|
|
|
|
|
ChainstateManager& chainman = *Assert(m_node.chainman);
|
|
|
|
Chainstate& active_cs = chainman.ActiveChainstate();
|
|
|
|
auto tip_cache_before_complete = active_cs.m_coinstip_cache_size_bytes;
|
|
|
|
auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes;
|
|
|
|
|
|
|
|
SnapshotCompletionResult res;
|
2023-05-09 11:15:46 +02:00
|
|
|
m_node.notifications->m_shutdown_on_fatal_error = false;
|
2022-07-22 11:26:55 -04:00
|
|
|
|
2023-06-13 13:52:26 +02:00
|
|
|
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
|
2022-07-22 11:26:55 -04:00
|
|
|
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());
|
|
|
|
|
2023-05-09 11:15:46 +02:00
|
|
|
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
|
2022-07-22 11:26:55 -04:00
|
|
|
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
|
|
|
|
|
|
|
|
WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated()));
|
|
|
|
BOOST_CHECK(chainman.IsSnapshotActive());
|
|
|
|
|
|
|
|
// Cache should have been rebalanced and reallocated to the "only" remaining
|
|
|
|
// chainstate.
|
|
|
|
BOOST_CHECK(active_cs.m_coinstip_cache_size_bytes > tip_cache_before_complete);
|
|
|
|
BOOST_CHECK(active_cs.m_coinsdb_cache_size_bytes > db_cache_before_complete);
|
|
|
|
|
|
|
|
auto all_chainstates = chainman.GetAll();
|
|
|
|
BOOST_CHECK_EQUAL(all_chainstates.size(), 1);
|
|
|
|
BOOST_CHECK_EQUAL(all_chainstates[0], &active_cs);
|
|
|
|
|
|
|
|
// Trying completion again should return false.
|
2023-05-09 11:15:46 +02:00
|
|
|
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
|
2022-07-22 11:26:55 -04:00
|
|
|
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
|
|
|
|
|
|
|
|
// The invalid snapshot path should not have been used.
|
|
|
|
fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
|
|
|
|
BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
|
|
|
|
// chainstate_snapshot should still exist.
|
|
|
|
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
|
|
|
|
|
2023-01-03 13:12:45 +01:00
|
|
|
// Test that simulating a shutdown (resetting ChainstateManager) and then performing
|
2022-07-22 11:26:55 -04:00
|
|
|
// 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, and should clean up the now unnecessary
|
|
|
|
// background-validation leveldb contents.
|
|
|
|
this->LoadVerifyActivateChainstate();
|
|
|
|
|
|
|
|
BOOST_CHECK(!fs::exists(snapshot_invalid_dir));
|
|
|
|
// chainstate_snapshot should now *not* exist.
|
|
|
|
BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
|
|
|
|
|
|
|
|
const Chainstate& active_cs2 = chainman_restarted.ActiveChainstate();
|
|
|
|
|
|
|
|
{
|
|
|
|
LOCK(chainman_restarted.GetMutex());
|
|
|
|
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1);
|
|
|
|
BOOST_CHECK(!chainman_restarted.IsSnapshotActive());
|
|
|
|
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
|
|
|
|
BOOST_CHECK(active_cs2.m_coinstip_cache_size_bytes > tip_cache_before_complete);
|
|
|
|
BOOST_CHECK(active_cs2.m_coinsdb_cache_size_bytes > db_cache_before_complete);
|
|
|
|
|
|
|
|
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 \"new\" IBD chainstate");
|
|
|
|
mineBlocks(10);
|
|
|
|
{
|
|
|
|
LOCK(chainman_restarted.GetMutex());
|
|
|
|
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, SnapshotTestSetup)
|
|
|
|
{
|
|
|
|
auto chainstates = this->SetupSnapshot();
|
|
|
|
Chainstate& validation_chainstate = *std::get<0>(chainstates);
|
|
|
|
ChainstateManager& chainman = *Assert(m_node.chainman);
|
|
|
|
SnapshotCompletionResult res;
|
2023-05-09 11:15:46 +02:00
|
|
|
m_node.notifications->m_shutdown_on_fatal_error = false;
|
2022-07-22 11:26:55 -04:00
|
|
|
|
|
|
|
// Test tampering with the IBD UTXO set with an extra coin to ensure it causes
|
|
|
|
// snapshot completion to fail.
|
|
|
|
CCoinsViewCache& ibd_coins = WITH_LOCK(::cs_main,
|
|
|
|
return validation_chainstate.CoinsTip());
|
|
|
|
Coin badcoin;
|
|
|
|
badcoin.out.nValue = InsecureRand32();
|
|
|
|
badcoin.nHeight = 1;
|
|
|
|
badcoin.out.scriptPubKey.assign(InsecureRandBits(6), 0);
|
|
|
|
uint256 txid = InsecureRand256();
|
|
|
|
ibd_coins.AddCoin(COutPoint(txid, 0), std::move(badcoin), false);
|
|
|
|
|
|
|
|
fs::path snapshot_chainstate_dir = gArgs.GetDataDirNet() / "chainstate_snapshot";
|
|
|
|
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
|
|
|
|
|
2023-07-07 17:45:30 -03:00
|
|
|
{
|
|
|
|
ASSERT_DEBUG_LOG("failed to validate the -assumeutxo snapshot state");
|
|
|
|
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
|
|
|
|
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
|
|
|
|
}
|
2022-07-22 11:26:55 -04:00
|
|
|
|
|
|
|
auto all_chainstates = chainman.GetAll();
|
|
|
|
BOOST_CHECK_EQUAL(all_chainstates.size(), 1);
|
|
|
|
BOOST_CHECK_EQUAL(all_chainstates[0], &validation_chainstate);
|
|
|
|
BOOST_CHECK_EQUAL(&chainman.ActiveChainstate(), &validation_chainstate);
|
|
|
|
|
|
|
|
fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
|
|
|
|
BOOST_CHECK(fs::exists(snapshot_invalid_dir));
|
|
|
|
|
2023-01-03 13:12:45 +01:00
|
|
|
// Test that simulating a shutdown (resetting ChainstateManager) and then performing
|
2022-07-22 11:26:55 -04:00
|
|
|
// chainstate reinitializing successfully loads only the fully-validated
|
|
|
|
// 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, and should clean up the now unnecessary
|
|
|
|
// background-validation leveldb contents.
|
|
|
|
this->LoadVerifyActivateChainstate();
|
|
|
|
|
|
|
|
BOOST_CHECK(fs::exists(snapshot_invalid_dir));
|
|
|
|
BOOST_CHECK(!fs::exists(snapshot_chainstate_dir));
|
|
|
|
|
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1);
|
|
|
|
BOOST_CHECK(!chainman_restarted.IsSnapshotActive());
|
|
|
|
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
|
|
|
|
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_TEST_MESSAGE(
|
|
|
|
"Ensure we can mine blocks on top of the \"new\" IBD chainstate");
|
|
|
|
mineBlocks(10);
|
|
|
|
{
|
|
|
|
LOCK(::cs_main);
|
|
|
|
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-12 14:11:03 -05:00
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|