diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 3e415034b..9d1637e5b 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -1,376 +1,375 @@ // Copyright (c) 2019 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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 chainstates; const CChainParams &chainparams = Params(); BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); // Create a legacy (IBD) chainstate. // CChainState &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(!manager.IsSnapshotValidated()); BOOST_CHECK(!manager.IsBackgroundIBD(&c1)); auto all = manager.GetAll(); BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end()); auto &active_chain = manager.ActiveChain(); BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain); BOOST_CHECK_EQUAL(manager.ActiveHeight(), -1); auto active_tip = manager.ActiveTip(); auto exp_tip = c1.m_chain.Tip(); BOOST_CHECK_EQUAL(active_tip, exp_tip); auto &validated_cs = manager.ValidatedChainstate(); BOOST_CHECK_EQUAL(&validated_cs, &c1); BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); // Create a snapshot-based chainstate. // const BlockHash snapshot_blockhash{GetRandHash()}; CChainState &c2 = *WITH_LOCK( ::cs_main, return &manager.InitializeChainstate(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(chainparams); BlockValidationState _; BOOST_CHECK(c2.ActivateBestChain(GetConfig(), _, nullptr)); BOOST_CHECK(manager.IsSnapshotActive()); BOOST_CHECK(!manager.IsSnapshotValidated()); BOOST_CHECK(manager.IsBackgroundIBD(&c1)); BOOST_CHECK(!manager.IsBackgroundIBD(&c2)); auto all2 = manager.GetAll(); BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end()); auto &active_chain2 = manager.ActiveChain(); BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain); BOOST_CHECK_EQUAL(manager.ActiveHeight(), 0); auto active_tip2 = 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); auto &validated_cs2 = manager.ValidatedChainstate(); BOOST_CHECK_EQUAL(&validated_cs2, &c1); auto &validated_chain = manager.ValidatedChain(); BOOST_CHECK_EQUAL(&validated_chain, &c1.m_chain); auto validated_tip = manager.ValidatedTip(); exp_tip = c1.m_chain.Tip(); BOOST_CHECK_EQUAL(validated_tip, exp_tip); // Let scheduler events finish running to avoid accessing memory that is // going to be unloaded SyncWithValidationInterfaceQueue(); WITH_LOCK(::cs_main, manager.Unload()); } //! 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 chainstates; // Create a legacy (IBD) chainstate. // CChainState &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(Params())); c1.CoinsTip().SetBestBlock(BlockHash{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. // CChainState &c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate( mempool, BlockHash{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(Params())); c2.CoinsTip().SetBestBlock(BlockHash{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); } auto NoMalleation = [](CAutoFile &file, SnapshotMetadata &meta) {}; template static bool CreateAndActivateUTXOSnapshot(NodeContext &node, const fs::path root, F malleation = NoMalleation) { // 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; malleation(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->GetId(), 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(BlockHash{})); + 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( m_node, m_path_root, [](CAutoFile &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_REQUIRE(!CreateAndActivateUTXOSnapshot( m_node, m_path_root, [](CAutoFile &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, [](CAutoFile &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, [](CAutoFile &auto_infile, SnapshotMetadata &metadata) { // Wrong hash metadata.m_base_blockhash = BlockHash{uint256::ZERO}; })); BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( m_node, m_path_root, [](CAutoFile &auto_infile, SnapshotMetadata &metadata) { // Wrong hash metadata.m_base_blockhash = BlockHash{uint256::ONE}; })); 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.ActiveChainstate().m_from_snapshot_blockhash->IsNull()); + BOOST_CHECK_EQUAL(*chainman.ActiveChainstate().m_from_snapshot_blockhash, *chainman.SnapshotBlockhash()); const AssumeutxoData &au_data = *ExpectedAssumeutxo(snapshot_height, ::Params()); const CBlockIndex *tip = 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 (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->GetId(), 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->GetId(), 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, + BOOST_CHECK_EQUAL(*chainman.ActiveChainstate().m_from_snapshot_blockhash, loaded_snapshot_blockhash); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index 1ced129a8..d36c4eefa 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1,6489 +1,6487 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2018 The Bitcoin Core developers // Copyright (c) 2017-2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include