Skip to content

Commit

Permalink
test: add reset_chainstate parameter for snapshot unittests
Browse files Browse the repository at this point in the history
Summary:
> validation: add ResetChainstates()
>
> Necessary for the following test commit.
bitcoin/bitcoin@00b357c

> test: add reset_chainstate parameter for snapshot unittests
>
> This CreateAndActivateUTXOSnapshot parameter is necessary once we
> perform snapshot completion within ABC, since the existing UpdateTip
> test will fail because the IBD chain that has generated the snapshot
> will exceed the base of the snapshot.
>
> Being able to test snapshots being loaded into a mostly-uninitialized
> datadir allows for more realistic unittest scenarios.
bitcoin/bitcoin@3c36139

Backport note: this process of resetting the chainstate without also resetting the block index would cause `CheckBlockIndex` to fail on `assert(setBlockIndexCandidates.count(pindex)));` after D4717 made `CheckBlockIndex` run on intermediate steps in `ActiveBestChain`. I disabled `CheckBlockIndex` until after the snapshot chain is activated.

This is a partial backport of [[bitcoin/bitcoin#25667 | core#25667]]
Depends on D14652

Test Plan: `ninja all check-all`

Reviewers: #bitcoin_abc, Fabien

Reviewed By: #bitcoin_abc, Fabien

Subscribers: Fabien

Differential Revision: https://reviews.bitcoinabc.org/D14653
  • Loading branch information
jamesob authored and PiRK committed Oct 26, 2023
1 parent 88394e9 commit 26b3b6b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 18 deletions.
75 changes: 71 additions & 4 deletions src/test/util/chainstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
#define BITCOIN_TEST_UTIL_CHAINSTATE_H

#include <clientversion.h>
#include <config.h>
#include <fs.h>
#include <node/context.h>
#include <node/utxo_snapshot.h>
#include <rpc/blockchain.h>
#include <test/util/setup_common.h>
#include <validation.h>

#include <univalue.h>
Expand All @@ -18,11 +20,24 @@

const auto NoMalleation = [](AutoFile &file, node::SnapshotMetadata &meta) {};

/**
* Create and activate a UTXO snapshot, optionally providing a function to
* malleate the snapshot.
*
* If `reset_chainstate` is true, reset the original chainstate back to the
* genesis block. This allows us to simulate more realistic conditions in which
* a snapshot is loaded into an otherwise mostly-uninitialized datadir. It also
* allows us to test conditions that would otherwise cause shutdowns based on
* the IBD chainstate going past the snapshot it generated.
*/
template <typename F = decltype(NoMalleation)>
static bool CreateAndActivateUTXOSnapshot(node::NodeContext &node,
const fs::path root,
static bool CreateAndActivateUTXOSnapshot(TestingSetup *fixture,
F malleation = NoMalleation,
bool reset_chainstate = false,
bool in_memory_chainstate = false) {
node::NodeContext &node = fixture->m_node;
fs::path root = fixture->m_path_root;

// Write out a snapshot to the test's tempdir.
//
int height;
Expand All @@ -46,8 +61,60 @@ static bool CreateAndActivateUTXOSnapshot(node::NodeContext &node,

malleation(auto_infile, metadata);

return node.chainman->ActivateSnapshot(auto_infile, metadata,
in_memory_chainstate);
if (reset_chainstate) {
// Disable CheckBlockIndex until we are finished resetting the
// chainstate. It is enabled again and CheckBlockIndex is
// called manually after the snapshot is activated.
// This is necessary for Bitcoin ABC because, as of D4717,
// CheckBlockIndex is called more aggressively in ActivateBestChain and
// would not support resetting the chainstate while preserving the
// block index.
bool prev_check_block_index = fCheckBlockIndex;
fCheckBlockIndex = false;

{
// What follows is code to selectively reset chainstate data without
// disturbing the existing BlockManager instance, which is needed to
// recognize the headers chain previously generated by the
// chainstate we're removing. Without those headers, we can't
// activate the snapshot below.
//
// This is a stripped-down version of node::LoadChainstate which
// preserves the block index.
LOCK(::cs_main);
BlockHash gen_hash{
node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash()};
node.chainman->ResetChainstates();
node.chainman->InitializeChainstate(node.mempool.get());
Chainstate &chain = node.chainman->ActiveChainstate();
Assert(chain.LoadGenesisBlock());
// These cache values will be corrected shortly in
// `MaybeRebalanceCaches`.
chain.InitCoinsDB(1 << 20, true, false, "");
chain.InitCoinsCache(1 << 20);
chain.CoinsTip().SetBestBlock(gen_hash);
chain.setBlockIndexCandidates.insert(
node.chainman->m_blockman.LookupBlockIndex(gen_hash));
chain.LoadChainTip();
node.chainman->MaybeRebalanceCaches();
}
BlockValidationState state;
if (!node.chainman->ActiveChainstate().ActivateBestChain(::GetConfig(),
state)) {
throw std::runtime_error(
strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
fCheckBlockIndex = prev_check_block_index;
Assert(0 == WITH_LOCK(node.chainman->GetMutex(),
return node.chainman->ActiveHeight()));
}

bool ret = node.chainman->ActivateSnapshot(auto_infile, metadata,
in_memory_chainstate);
if (fCheckBlockIndex) {
node.chainman->ActiveChainstate().CheckBlockIndex();
}
return ret;
}

#endif // BITCOIN_TEST_UTIL_CHAINSTATE_H
3 changes: 2 additions & 1 deletion src/test/validation_chainstate_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup) {
// After adding some blocks to the tip, best block should have changed.
BOOST_CHECK(::g_best_block != curr_tip);

BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this, NoMalleation,
/*reset_chainstate=*/true));

// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(WITH_LOCK(::cs_main, return chainman.IsSnapshotActive()));
Expand Down
21 changes: 8 additions & 13 deletions src/test/validation_chainstatemanager_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ struct SnapshotTestSetup : TestChain100Setup {
Chainstate &validation_chainstate = chainman.ActiveChainstate();

// Snapshot should refuse to load at this height.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));
BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
BOOST_CHECK(!chainman.SnapshotBlockhash());

Expand All @@ -213,8 +213,7 @@ struct SnapshotTestSetup : TestChain100Setup {

// Should not load malleated snapshots
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
m_node, m_path_root,
[](AutoFile &auto_infile, SnapshotMetadata &metadata) {
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// A UTXO is missing but count is correct
metadata.m_coins_count -= 1;

Expand All @@ -225,31 +224,27 @@ struct SnapshotTestSetup : TestChain100Setup {
auto_infile >> coin;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
m_node, m_path_root,
[](AutoFile &auto_infile, SnapshotMetadata &metadata) {
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Coins count is larger than coins in file
metadata.m_coins_count += 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
m_node, m_path_root,
[](AutoFile &auto_infile, SnapshotMetadata &metadata) {
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Coins count is smaller than coins in file
metadata.m_coins_count -= 1;
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
m_node, m_path_root,
[](AutoFile &auto_infile, SnapshotMetadata &metadata) {
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Wrong hash
metadata.m_base_blockhash = BlockHash{uint256::ZERO};
}));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
m_node, m_path_root,
[](AutoFile &auto_infile, SnapshotMetadata &metadata) {
this, [](AutoFile &auto_infile, SnapshotMetadata &metadata) {
// Wrong hash
metadata.m_base_blockhash = BlockHash{uint256::ONE};
}));

BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this));

// Ensure our active chain is the snapshot chainstate.
BOOST_CHECK(
Expand Down Expand Up @@ -348,7 +343,7 @@ struct SnapshotTestSetup : TestChain100Setup {
}

// Snapshot should refuse to load after one has already loaded.
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this));

// Snapshot blockhash should be unchanged.
BOOST_CHECK_EQUAL(
Expand Down
6 changes: 6 additions & 0 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6162,6 +6162,12 @@ void ChainstateManager::MaybeRebalanceCaches() {
}
}

void ChainstateManager::ResetChainstates() {
m_ibd_chainstate.reset();
m_snapshot_chainstate.reset();
m_active_chainstate = nullptr;
}

bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool *mempool) {
assert(!m_snapshot_chainstate);
std::optional<fs::path> path = node::FindSnapshotChainstateDir();
Expand Down
2 changes: 2 additions & 0 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,8 @@ class ChainstateManager {
bool DetectSnapshotChainstate(CTxMemPool *mempool)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

void ResetChainstates() EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

//! Switch the active chainstate to one based on a UTXO snapshot that was
//! loaded previously.
Chainstate &ActivateExistingSnapshot(CTxMemPool *mempool,
Expand Down

0 comments on commit 26b3b6b

Please sign in to comment.