Merge bitcoin/bitcoin#30214: refactor: Improve assumeutxo state representation

82be652e40 doc: Improve ChainstateManager documentation, use consistent terms (Ryan Ofsky)
af455dcb39 refactor: Simplify pruning functions (TheCharlatan)
ae85c495f1 refactor: Delete ChainstateManager::GetAll() method (Ryan Ofsky)
6a572dbda9 refactor: Add ChainstateManager::ActivateBestChains() method (Ryan Ofsky)
491d827d52 refactor: Add ChainstateManager::m_chainstates member (Ryan Ofsky)
e514fe6116 refactor: Delete ChainstateManager::SnapshotBlockhash() method (Ryan Ofsky)
ee35250683 refactor: Delete ChainstateManager::IsSnapshotValidated() method (Ryan Ofsky)
d9e82299fc refactor: Delete ChainstateManager::IsSnapshotActive() method (Ryan Ofsky)
4dfe383912 refactor: Convert ChainstateRole enum to struct (Ryan Ofsky)
352ad27fc1 refactor: Add ChainstateManager::ValidatedChainstate() method (Ryan Ofsky)
a229cb9477 refactor: Add ChainstateManager::CurrentChainstate() method (Ryan Ofsky)
a9b7f5614c refactor: Add Chainstate::StoragePath() method (Ryan Ofsky)
840bd2ef23 refactor: Pass chainstate parameters to MaybeCompleteSnapshotValidation (Ryan Ofsky)
1598a15aed refactor: Deduplicate Chainstate activation code (Ryan Ofsky)
9fe927b6d6 refactor: Add Chainstate m_assumeutxo and m_target_utxohash members (Ryan Ofsky)
6082c84713 refactor: Add Chainstate::m_target_blockhash member (Ryan Ofsky)
de00e87548 test: Fix broken chainstatemanager_snapshot_init check (Ryan Ofsky)

Pull request description:

  This PR contains the first part of #28608, which tries to make assumeutxo code more maintainable, and improve it by not locking `cs_main` for a long time when the snapshot block is connected, and by deleting the snapshot validation chainstate when it is no longer used, instead of waiting until the next restart.

  The changes in this PR are just refactoring. They make `Chainstate` objects self-contained, so for example, it is possible to determine what blocks to connect to a chainstate without querying `ChainstateManager`, and to determine whether a Chainstate is validated without basing it on inferences like `&cs != &ActiveChainstate()` or `GetAll().size() == 1`.

  The PR also tries to make assumeutxo terminology less confusing, using "current chainstate" to refer to the chainstate targeting the current network tip, and "historical chainstate" to refer to the chainstate downloading old blocks and validating the assumeutxo snapshot. It removes uses of the terms "active chainstate," "usable chainstate," "disabled chainstate," "ibd chainstate," and "snapshot chainstate" which are confusing for various reasons.

ACKs for top commit:
  maflcko:
    re-review ACK 82be652e40 🕍
  fjahr:
    re-ACK 82be652e40
  sedited:
    Re-ACK 82be652e40

Tree-SHA512: 81c67abba9fc5bb170e32b7bf8a1e4f7b5592315b4ef720be916d5f1f5a7088c0c59cfb697744dd385552f58aa31ee36176bae6a6e465723e65861089a1252e5
This commit is contained in:
merge-script
2025-12-16 14:03:34 +00:00
40 changed files with 724 additions and 767 deletions

View File

@@ -8,6 +8,8 @@
#include <boost/test/unit_test.hpp>
using kernel::ChainstateRole;
// Taken from validation.cpp
static constexpr auto DATABASE_WRITE_INTERVAL_MIN{50min};
static constexpr auto DATABASE_WRITE_INTERVAL_MAX{70min};
@@ -18,7 +20,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup)
{
struct TestSubscriber final : CValidationInterface {
bool m_did_flush{false};
void ChainStateFlushed(ChainstateRole, const CBlockLocator&) override
void ChainStateFlushed(const ChainstateRole&, const CBlockLocator&) override
{
m_did_flush = true;
}
@@ -55,7 +57,7 @@ BOOST_FIXTURE_TEST_CASE(write_during_multiblock_activation, TestChain100Setup)
{
const CBlockIndex* m_tip{nullptr};
const CBlockIndex* m_flushed_at_block{nullptr};
void ChainStateFlushed(ChainstateRole, const CBlockLocator&) override
void ChainStateFlushed(const ChainstateRole&, const CBlockLocator&) override
{
m_flushed_at_block = m_tip;
}

View File

@@ -6,12 +6,15 @@
#include <index/coinstatsindex.h>
#include <interfaces/chain.h>
#include <kernel/coinstats.h>
#include <kernel/types.h>
#include <test/util/setup_common.h>
#include <test/util/validation.h>
#include <validation.h>
#include <boost/test/unit_test.hpp>
using kernel::ChainstateRole;
BOOST_AUTO_TEST_SUITE(coinstatsindex_tests)
BOOST_FIXTURE_TEST_CASE(coinstatsindex_initial_sync, TestChain100Setup)
@@ -101,7 +104,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
// Send block connected notification, then stop the index without
// sending a chainstate flushed notification. Prior to #24138, this
// would cause the index to be corrupted and fail to reload.
ValidationInterfaceTest::BlockConnected(ChainstateRole::NORMAL, index, new_block, new_block_index);
ValidationInterfaceTest::BlockConnected(ChainstateRole{}, index, new_block, new_block_index);
index.Stop();
}

View File

@@ -110,7 +110,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
Assert(!chainman.SnapshotBlockhash());
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
{
AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
@@ -183,15 +183,13 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
if (ActivateFuzzedSnapshot()) {
LOCK(::cs_main);
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
Assert(*chainman.ActiveChainstate().m_from_snapshot_blockhash ==
*chainman.SnapshotBlockhash());
const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
for (const auto& block : *g_chain) {
Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
Assert(index);
Assert(index->nTx == 0);
if (index->nHeight == chainman.GetSnapshotBaseHeight()) {
if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {
auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
Assert(params.has_value());
Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
@@ -202,7 +200,6 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
Assert(g_chain->size() == coinscache.GetCacheSize());
dirty_chainman = true;
} else {
Assert(!chainman.SnapshotBlockhash());
Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
}
// Snapshot should refuse to load a second time regardless of validity

View File

@@ -81,7 +81,7 @@ CreateAndActivateUTXOSnapshot(
Chainstate& chain = node.chainman->ActiveChainstate();
Assert(chain.LoadGenesisBlock());
// These cache values will be corrected shortly in `MaybeRebalanceCaches`.
chain.InitCoinsDB(1 << 20, true, false, "");
chain.InitCoinsDB(1 << 20, /*in_memory=*/true, /*should_wipe=*/false);
chain.InitCoinsCache(1 << 20);
chain.CoinsTip().SetBestBlock(gen_hash);
chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));

View File

@@ -9,15 +9,19 @@
#include <validation.h>
#include <validationinterface.h>
using kernel::ChainstateRole;
void TestChainstateManager::DisableNextWrite()
{
struct TestChainstate : public Chainstate {
void ResetNextWrite() { m_next_write = NodeClock::time_point::max() - 1s; }
};
for (auto* cs : GetAll()) {
static_cast<TestChainstate*>(cs)->ResetNextWrite();
LOCK(::cs_main);
for (const auto& cs : m_chainstates) {
static_cast<TestChainstate&>(*cs).ResetNextWrite();
}
}
void TestChainstateManager::ResetIbd()
{
m_cached_finished_ibd = false;
@@ -32,10 +36,10 @@ void TestChainstateManager::JumpOutOfIbd()
}
void ValidationInterfaceTest::BlockConnected(
ChainstateRole role,
CValidationInterface& obj,
const std::shared_ptr<const CBlock>& block,
const CBlockIndex* pindex)
const ChainstateRole& role,
CValidationInterface& obj,
const std::shared_ptr<const CBlock>& block,
const CBlockIndex* pindex)
{
obj.BlockConnected(role, block, pindex);
}

View File

@@ -22,7 +22,7 @@ class ValidationInterfaceTest
{
public:
static void BlockConnected(
ChainstateRole role,
const kernel::ChainstateRole& role,
CValidationInterface& obj,
const std::shared_ptr<const CBlock>& block,
const CBlockIndex* pindex);

View File

@@ -19,6 +19,7 @@
#include <thread>
using kernel::ChainstateRole;
using node::BlockAssembler;
namespace validation_block_tests {
@@ -43,7 +44,7 @@ struct TestSubscriber final : public CValidationInterface {
BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash());
}
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
void BlockConnected(const ChainstateRole& role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
{
BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock);
BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash());

View File

@@ -95,7 +95,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
this, NoMalleation, /*reset_chainstate=*/ true));
// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive()));
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
curr_tip = get_notify_tip();
@@ -107,16 +107,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
curr_tip = get_notify_tip();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
Chainstate& background_cs{*Assert([&]() -> Chainstate* {
for (Chainstate* cs : chainman.GetAll()) {
if (cs != &chainman.ActiveChainstate()) {
return cs;
}
}
return nullptr;
}())};
Chainstate& background_cs{*Assert(WITH_LOCK(::cs_main, return chainman.HistoricalChainstate()))};
// Append the first block to the background chain.
BlockValidationState state;

View File

@@ -40,19 +40,19 @@ BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup)
BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
{
ChainstateManager& manager = *m_node.chainman;
std::vector<Chainstate*> chainstates;
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
// Create a legacy (IBD) chainstate.
//
Chainstate& c1 = manager.ActiveChainstate();
chainstates.push_back(&c1);
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());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
{
LOCK(manager.GetMutex());
BOOST_CHECK_EQUAL(manager.m_chainstates.size(), 1);
BOOST_CHECK_EQUAL(manager.m_chainstates[0].get(), &c1);
}
auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
@@ -64,13 +64,12 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
auto exp_tip = c1.m_chain.Tip();
BOOST_CHECK_EQUAL(active_tip, exp_tip);
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.CurrentChainstate().m_from_snapshot_blockhash));
// Create a snapshot-based chainstate.
//
const uint256 snapshot_blockhash = active_tip->GetBlockHash();
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(snapshot_blockhash));
chainstates.push_back(&c2);
Chainstate& c2{WITH_LOCK(::cs_main, return manager.AddChainstate(std::make_unique<Chainstate>(nullptr, manager.m_blockman, manager, snapshot_blockhash)))};
c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
{
@@ -83,13 +82,16 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup)
BlockValidationState _;
BOOST_CHECK(c2.ActivateBestChain(_, nullptr));
BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
BOOST_CHECK(manager.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
BOOST_CHECK_EQUAL(WITH_LOCK(::cs_main, return *manager.CurrentChainstate().m_from_snapshot_blockhash), snapshot_blockhash);
BOOST_CHECK(WITH_LOCK(::cs_main, return manager.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED));
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());
{
LOCK(manager.GetMutex());
BOOST_CHECK_EQUAL(manager.m_chainstates.size(), 2);
BOOST_CHECK_EQUAL(manager.m_chainstates[0].get(), &c1);
BOOST_CHECK_EQUAL(manager.m_chainstates[1].get(), &c2);
}
auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain());
BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
@@ -135,7 +137,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup)
// Create a snapshot-based chainstate.
//
CBlockIndex* snapshot_base{WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()[manager.ActiveChain().Height() / 2])};
Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(*snapshot_base->phashBlock));
Chainstate& c2{WITH_LOCK(::cs_main, return manager.AddChainstate(std::make_unique<Chainstate>(nullptr, manager.m_blockman, manager, *snapshot_base->phashBlock)))};
chainstates.push_back(&c2);
c2.InitCoinsDB(
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -182,12 +184,10 @@ struct SnapshotTestSetup : TestChain100Setup {
{
ChainstateManager& chainman = *Assert(m_node.chainman);
BOOST_CHECK(!chainman.IsSnapshotActive());
{
LOCK(::cs_main);
BOOST_CHECK(!chainman.IsSnapshotValidated());
BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir));
BOOST_CHECK(!chainman.CurrentChainstate().m_from_snapshot_blockhash);
BOOST_CHECK(!node::FindAssumeutxoChainstateDir(chainman.m_options.datadir));
}
size_t initial_size;
@@ -215,7 +215,6 @@ struct SnapshotTestSetup : TestChain100Setup {
// 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.
@@ -240,7 +239,7 @@ struct SnapshotTestSetup : TestChain100Setup {
auto_infile >> coin;
}));
BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir));
BOOST_CHECK(!node::FindAssumeutxoChainstateDir(chainman.m_options.datadir));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
@@ -264,25 +263,22 @@ struct SnapshotTestSetup : TestChain100Setup {
}));
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir(chainman.m_options.datadir)));
BOOST_CHECK(fs::exists(*node::FindAssumeutxoChainstateDir(chainman.m_options.datadir)));
// 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(chainman.m_options.datadir);
fs::path found = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
// Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
BOOST_CHECK_EQUAL(
*node::ReadSnapshotBaseBlockhash(found),
*chainman.SnapshotBlockhash());
*Assert(chainman.CurrentChainstate().m_from_snapshot_blockhash));
}
const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height);
@@ -291,7 +287,7 @@ struct SnapshotTestSetup : TestChain100Setup {
BOOST_CHECK_EQUAL(tip->m_chain_tx_count, au_data->m_chain_tx_count);
// To be checked against later when we try loading a subsequent snapshot.
uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
uint256 loaded_snapshot_blockhash{*Assert(WITH_LOCK(chainman.GetMutex(), return chainman.CurrentChainstate().m_from_snapshot_blockhash))};
// Make some assertions about the both chainstates. These checks ensure the
// legacy chainstate hasn't changed and that the newly created chainstate
@@ -300,7 +296,7 @@ struct SnapshotTestSetup : TestChain100Setup {
LOCK(::cs_main);
int chains_tested{0};
for (Chainstate* chainstate : chainman.GetAll()) {
for (const auto& chainstate : chainman.m_chainstates) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
@@ -333,10 +329,10 @@ struct SnapshotTestSetup : TestChain100Setup {
size_t coins_in_background{0};
size_t coins_missing_from_background{0};
for (Chainstate* chainstate : chainman.GetAll()) {
for (const auto& chainstate : chainman.m_chainstates) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
bool is_background = chainstate != &chainman.ActiveChainstate();
bool is_background = chainstate.get() != &chainman.ActiveChainstate();
for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
@@ -373,15 +369,17 @@ struct SnapshotTestSetup : TestChain100Setup {
BOOST_TEST_MESSAGE("Simulating node restart");
{
for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
cs->ForceFlushStateToDisk();
LOCK(chainman.GetMutex());
for (const auto& cs : chainman.m_chainstates) {
if (cs->CanFlushToDisk()) cs->ForceFlushStateToDisk();
}
}
{
// Process all callbacks referring to the old manager before wiping it.
m_node.validation_signals->SyncWithValidationInterfaceQueue();
LOCK(::cs_main);
chainman.ResetChainstates();
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0);
BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 0);
m_node.notifications = std::make_unique<KernelNotifications>(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings));
const ChainstateManager::Options chainman_opts{
.chainparams = ::Params(),
@@ -446,17 +444,16 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120);
auto reload_all_block_indexes = [&]() {
LOCK(chainman.GetMutex());
// 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());
for (Chainstate* cs : chainman.GetAll()) {
LOCK(::cs_main);
chainman.ResetBlockSequenceCounters();
for (const auto& cs : chainman.m_chainstates) {
cs->ClearBlockIndexCandidates();
BOOST_CHECK(cs->setBlockIndexCandidates.empty());
}
WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
chainman.LoadBlockIndex();
};
// Ensure that without any assumed-valid BlockIndex entries, only the current tip is
@@ -489,8 +486,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
}
// Note: cs2's tip is not set when ActivateExistingSnapshot is called.
Chainstate& cs2 = WITH_LOCK(::cs_main,
return chainman.ActivateExistingSnapshot(*assumed_base->phashBlock));
Chainstate& cs2{WITH_LOCK(::cs_main, return chainman.AddChainstate(std::make_unique<Chainstate>(nullptr, chainman.m_blockman, chainman, *assumed_base->phashBlock)))};
// Set tip of the fully validated chain to be the validated tip
cs1.m_chain.SetTip(*validated_tip);
@@ -557,7 +553,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1);
}
//! Ensure that snapshot chainstates initialize properly when found on disk.
//! Ensure that snapshot chainstate can be loaded when found on disk after a
//! restart, and that new blocks can be connected to both chainstates.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
{
ChainstateManager& chainman = *Assert(m_node.chainman);
@@ -565,16 +562,15 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
this->SetupSnapshot();
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
fs::path snapshot_chainstate_dir = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
BOOST_CHECK(chainman.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
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);
BOOST_CHECK_EQUAL(WITH_LOCK(chainman.GetMutex(), return chainman.m_chainstates.size()), 2);
// "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,
@@ -591,8 +587,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
BOOST_CHECK_EQUAL(bg_chainstate.m_chain.Height(), 109);
// 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.
// chainstate reinitializing successfully reloads both chainstates.
ChainstateManager& chainman_restarted = this->SimulateNodeRestart();
BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate");
@@ -602,12 +597,20 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
{
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.m_chainstates.size(), 2);
// Background chainstate has height of 109 not 110 here due to a quirk
// of the LoadVerifyActivate only calling ActivateBestChain on one
// chainstate. The height would be 110 after a real restart, but it's
// fine for this test which is focused on the snapshot chainstate.
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[0]->m_chain.Height(), 109);
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[1]->m_chain.Height(), 210);
BOOST_CHECK(chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
BOOST_CHECK(chainman_restarted.CurrentChainstate().m_assumeutxo == Assumeutxo::UNVALIDATED);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
BOOST_CHECK_EQUAL(chainman_restarted.HistoricalChainstate()->m_chain.Height(), 109);
}
BOOST_TEST_MESSAGE(
@@ -618,12 +621,11 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
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(), 109);
}
}
// chainstate, but the block disconnected above is now reattached.
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 2);
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[0]->m_chain.Height(), 110);
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates[1]->m_chain.Height(), 220);
BOOST_CHECK_EQUAL(chainman_restarted.HistoricalChainstate(), nullptr);
}
}
@@ -633,37 +635,35 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup
ChainstateManager& chainman = *Assert(m_node.chainman);
Chainstate& active_cs = chainman.ActiveChainstate();
Chainstate& validated_cs{*Assert(WITH_LOCK(cs_main, return chainman.HistoricalChainstate()))};
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;
m_node.notifications->m_shutdown_on_fatal_error = false;
fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir);
fs::path snapshot_chainstate_dir = *node::FindAssumeutxoChainstateDir(chainman.m_options.datadir);
BOOST_CHECK(fs::exists(snapshot_chainstate_dir));
BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot");
BOOST_CHECK(chainman.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(),
return chainman.ActiveTip()->GetBlockHash());
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validated_cs, active_cs));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS);
WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated()));
BOOST_CHECK(chainman.IsSnapshotActive());
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_assumeutxo == Assumeutxo::VALIDATED));
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.CurrentChainstate().m_from_snapshot_blockhash));
BOOST_CHECK_EQUAL(WITH_LOCK(chainman.GetMutex(), return chainman.HistoricalChainstate()), nullptr);
// 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.
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validated_cs, active_cs));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED);
// The invalid snapshot path should not have been used.
@@ -691,9 +691,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup
{
LOCK(chainman_restarted.GetMutex());
BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1);
BOOST_CHECK(!chainman_restarted.IsSnapshotActive());
BOOST_CHECK(!chainman_restarted.IsSnapshotValidated());
BOOST_CHECK_EQUAL(chainman_restarted.m_chainstates.size(), 1);
BOOST_CHECK(!chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
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);
@@ -714,6 +713,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
{
auto chainstates = this->SetupSnapshot();
Chainstate& validation_chainstate = *std::get<0>(chainstates);
Chainstate& unvalidated_cs = *std::get<1>(chainstates);
ChainstateManager& chainman = *Assert(m_node.chainman);
SnapshotCompletionResult res;
m_node.notifications->m_shutdown_on_fatal_error = false;
@@ -734,14 +734,18 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
{
ASSERT_DEBUG_LOG("failed to validate the -assumeutxo snapshot state");
res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation());
res = WITH_LOCK(::cs_main, return chainman.MaybeValidateSnapshot(validation_chainstate, unvalidated_cs));
BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH);
}
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);
{
LOCK(chainman.GetMutex());
BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 2);
BOOST_CHECK(chainman.m_chainstates[0]->m_assumeutxo == Assumeutxo::VALIDATED);
BOOST_CHECK(!chainman.m_chainstates[0]->SnapshotBase());
BOOST_CHECK(chainman.m_chainstates[1]->m_assumeutxo == Assumeutxo::INVALID);
BOOST_CHECK(chainman.m_chainstates[1]->SnapshotBase());
}
fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID";
BOOST_CHECK(fs::exists(snapshot_invalid_dir));
@@ -762,9 +766,8 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, Sna
{
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.m_chainstates.size(), 1);
BOOST_CHECK(!chainman_restarted.CurrentChainstate().m_from_snapshot_blockhash);
BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210);
}

View File

@@ -8,7 +8,6 @@
#include <scheduler.h>
#include <test/util/setup_common.h>
#include <util/check.h>
#include <kernel/chain.h>
#include <validationinterface.h>
#include <atomic>