Skip to content

Commit

Permalink
tests: add snapshot activation test
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesob committed Feb 12, 2021
1 parent 31d2252 commit 4d8de04
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 0 deletions.
148 changes: 148 additions & 0 deletions src/test/validation_chainstatemanager_tests.cpp
Expand Up @@ -4,13 +4,18 @@
//
#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/setup_common.h>
#include <uint256.h>
#include <validation.h>
#include <validationinterface.h>

#include <tinyformat.h>
#include <univalue.h>

#include <vector>

#include <boost/test/unit_test.hpp>
Expand Down Expand Up @@ -164,4 +169,147 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
}

static bool
CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root)
{
// Write out a snapshot to the test's tempdir.
//
int height;
WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height);
FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};

UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile);
BOOST_TEST_MESSAGE(
"Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());

// Read the written snapshot in and then activate it.
//
FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION};
SnapshotMetadata metadata;
auto_infile >> metadata;

return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true);
}

//! Test basic snapshot activation.
BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100DeterministicSetup)
{
ChainstateManager& chainman = *Assert(m_node.chainman);

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);
}

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

// Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
// be found.
mineBlocks(10);
initial_size += 10;
initial_total_coins += 10;

BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));

// 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());

// 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 (CChainState* 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_ibd{0};
size_t coins_missing_ibd{0};

for (CChainState* chainstate : chainman.GetAll()) {
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
CCoinsViewCache& coinscache = chainstate->CoinsTip();
bool is_ibd = chainman.IsBackgroundIBD(chainstate);

for (CTransactionRef& txn : m_coinbase_txns) {
COutPoint op{txn->GetHash(), 0};
if (coinscache.HaveCoin(op)) {
(is_ibd ? coins_in_ibd : coins_in_active)++;
} else if (is_ibd) {
coins_missing_ibd++;
}
}
}

BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
BOOST_CHECK_EQUAL(coins_in_ibd, initial_total_coins);
BOOST_CHECK_EQUAL(coins_missing_ibd, new_coins);
}

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

// Snapshot blockhash should be unchanged.
BOOST_CHECK_EQUAL(
chainman.ActiveChainstate().m_from_snapshot_blockhash,
loaded_snapshot_blockhash);
}

BOOST_AUTO_TEST_SUITE_END()
1 change: 1 addition & 0 deletions test/sanitizer_suppressions/tsan
Expand Up @@ -28,6 +28,7 @@ race:BerkeleyBatch
race:BerkeleyDatabase
race:DatabaseBatch
race:leveldb::DBImpl::DeleteObsoleteFiles
race:validation_chainstatemanager_tests
race:zmq::*
race:bitcoin-qt

Expand Down

0 comments on commit 4d8de04

Please sign in to comment.