LCOV - code coverage report
Current view: top level - src/test - validation_chainstatemanager_tests.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 209 209 100.0 %
Date: 2026-06-25 07:23:51 Functions: 48 48 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2019-2021 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : //
       5             : #include <chainparams.h>
       6             : #include <consensus/validation.h>
       7             : #include <index/txindex.h>
       8             : #include <node/chainstate.h>
       9             : #include <node/utxo_snapshot.h>
      10             : #include <random.h>
      11             : #include <rpc/blockchain.h>
      12             : #include <sync.h>
      13             : #include <test/util/chainstate.h>
      14             : #include <test/util/random.h>
      15             : #include <test/util/setup_common.h>
      16             : #include <uint256.h>
      17             : #include <validation.h>
      18             : #include <validationinterface.h>
      19             : 
      20             : #include <chainlock/handler.h>
      21             : #include <evo/evodb.h>
      22             : #include <llmq/blockprocessor.h>
      23             : #include <llmq/signing.h>
      24             : 
      25             : #include <tinyformat.h>
      26             : 
      27             : #include <vector>
      28             : 
      29             : #include <boost/test/unit_test.hpp>
      30             : 
      31             : using node::SnapshotMetadata;
      32             : 
      33         146 : BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, ChainTestingSetup)
      34             : 
      35             : //! Basic tests for ChainstateManager.
      36             : //!
      37             : //! First create a legacy (IBD) chainstate, then create a snapshot chainstate.
      38         149 : BOOST_AUTO_TEST_CASE(chainstatemanager)
      39             : {
      40           1 :     const Consensus::Params& consensus_params = Params().GetConsensus();
      41           1 :     ChainstateManager& manager = *m_node.chainman;
      42           1 :     CTxMemPool& mempool = *m_node.mempool;
      43           1 :     CEvoDB& evodb = *m_node.evodb;
      44           1 :     std::vector<CChainState*> chainstates;
      45             : 
      46           1 :     BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
      47             : 
      48             :     // Create a legacy (IBD) chainstate.
      49             :     //
      50           2 :     CChainState& c1 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(&mempool, evodb, m_node.chain_helper));
      51           1 :     chainstates.push_back(&c1);
      52           1 :     c1.InitCoinsDB(
      53             :         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
      54           2 :     WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23));
      55             : 
      56           1 :     DashChainstateSetup(manager, m_node, /*llmq_dbs_in_memory=*/true, /*llmq_dbs_wipe=*/false, consensus_params);
      57             : 
      58           1 :     BOOST_CHECK(!manager.IsSnapshotActive());
      59           2 :     BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
      60           1 :     auto all = manager.GetAll();
      61           1 :     BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
      62             : 
      63           1 :     auto& active_chain = manager.ActiveChain();
      64           1 :     BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain);
      65             : 
      66           1 :     BOOST_CHECK_EQUAL(manager.ActiveHeight(), -1);
      67             : 
      68           1 :     auto active_tip = manager.ActiveTip();
      69           1 :     auto exp_tip = c1.m_chain.Tip();
      70           1 :     BOOST_CHECK_EQUAL(active_tip, exp_tip);
      71             : 
      72           1 :     BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
      73             : 
      74           1 :     if (m_node.clhandler) {
      75           1 :         m_node.clhandler->Stop();
      76           1 :     }
      77           1 :     DashChainstateSetupClose(m_node);
      78             : 
      79             :     // Create a snapshot-based chainstate.
      80             :     //
      81           1 :     const uint256 snapshot_blockhash = GetRandHash();
      82           2 :     CChainState& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(
      83             :         &mempool, evodb, m_node.chain_helper,
      84             :         snapshot_blockhash)
      85             :     );
      86           1 :     chainstates.push_back(&c2);
      87             : 
      88           1 :     DashChainstateSetup(manager, m_node, /*llmq_dbs_in_memory=*/true, /*llmq_dbs_wipe=*/false, consensus_params);
      89             : 
      90           1 :     BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash);
      91             : 
      92           1 :     c2.InitCoinsDB(
      93             :         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
      94           2 :     WITH_LOCK(::cs_main, c2.InitCoinsCache(1 << 23));
      95             :     // Unlike c1, which doesn't have any blocks. Gets us different tip, height.
      96           1 :     c2.LoadGenesisBlock();
      97           1 :     BlockValidationState dummy_state;
      98           1 :     BOOST_CHECK(c2.ActivateBestChain(dummy_state, nullptr));
      99             : 
     100           1 :     BOOST_CHECK(manager.IsSnapshotActive());
     101           2 :     BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated()));
     102           1 :     BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
     103           1 :     BOOST_CHECK(&c1 != &manager.ActiveChainstate());
     104           1 :     auto all2 = manager.GetAll();
     105           1 :     BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
     106             : 
     107           1 :     auto& active_chain2 = manager.ActiveChain();
     108           1 :     BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain);
     109             : 
     110           1 :     BOOST_CHECK_EQUAL(manager.ActiveHeight(), 0);
     111             : 
     112           1 :     auto active_tip2 = manager.ActiveTip();
     113           1 :     auto exp_tip2 = c2.m_chain.Tip();
     114           1 :     BOOST_CHECK_EQUAL(active_tip2, exp_tip2);
     115             : 
     116             :     // Ensure that these pointers actually correspond to different
     117             :     // CCoinsViewCache instances.
     118           1 :     BOOST_CHECK(exp_tip != exp_tip2);
     119             : 
     120             :     // Let scheduler events finish running to avoid accessing memory that is going to be unloaded
     121           1 :     SyncWithValidationInterfaceQueue();
     122             : 
     123           1 :     if (m_node.clhandler) {
     124           1 :         m_node.clhandler->Stop();
     125           1 :     }
     126           1 :     DashChainstateSetupClose(m_node);
     127           1 : }
     128             : 
     129             : //! Test rebalancing the caches associated with each chainstate.
     130         149 : BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
     131             : {
     132           1 :     ChainstateManager& manager = *m_node.chainman;
     133           1 :     CTxMemPool& mempool = *m_node.mempool;
     134           1 :     CEvoDB& evodb = *m_node.evodb;
     135           1 :     size_t max_cache = 10000;
     136           1 :     manager.m_total_coinsdb_cache = max_cache;
     137           1 :     manager.m_total_coinstip_cache = max_cache;
     138             : 
     139           1 :     std::vector<CChainState*> chainstates;
     140             : 
     141             :     // Create a legacy (IBD) chainstate.
     142             :     //
     143           2 :     CChainState& c1 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, evodb, m_node.chain_helper));
     144           1 :     chainstates.push_back(&c1);
     145           1 :     c1.InitCoinsDB(
     146             :         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
     147             : 
     148             :     {
     149           1 :         LOCK(::cs_main);
     150           1 :         c1.InitCoinsCache(1 << 23);
     151           1 :         BOOST_REQUIRE(c1.LoadGenesisBlock());
     152           1 :         c1.CoinsTip().SetBestBlock(InsecureRand256());
     153           1 :         manager.MaybeRebalanceCaches();
     154           1 :     }
     155             : 
     156           1 :     BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache);
     157           1 :     BOOST_CHECK_EQUAL(c1.m_coinsdb_cache_size_bytes, max_cache);
     158             : 
     159             :     // Create a snapshot-based chainstate.
     160             :     //
     161           2 :     CChainState& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, evodb, m_node.chain_helper, GetRandHash()));
     162           1 :     chainstates.push_back(&c2);
     163           1 :     c2.InitCoinsDB(
     164             :         /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
     165             : 
     166             :     {
     167           1 :         LOCK(::cs_main);
     168           1 :         c2.InitCoinsCache(1 << 23);
     169           1 :         BOOST_REQUIRE(c2.LoadGenesisBlock());
     170           1 :         c2.CoinsTip().SetBestBlock(InsecureRand256());
     171           1 :         manager.MaybeRebalanceCaches();
     172           1 :     }
     173             : 
     174             :     // Since both chainstates are considered to be in initial block download,
     175             :     // the snapshot chainstate should take priority.
     176           1 :     BOOST_CHECK_CLOSE(c1.m_coinstip_cache_size_bytes, max_cache * 0.05, 1);
     177           1 :     BOOST_CHECK_CLOSE(c1.m_coinsdb_cache_size_bytes, max_cache * 0.05, 1);
     178           1 :     BOOST_CHECK_CLOSE(c2.m_coinstip_cache_size_bytes, max_cache * 0.95, 1);
     179           1 :     BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
     180           1 : }
     181             : 
     182             : //! Test basic snapshot activation.
     183         149 : BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
     184             : {
     185           1 :     ChainstateManager& chainman = *Assert(m_node.chainman);
     186             : 
     187             :     size_t initial_size;
     188           1 :     size_t initial_total_coins{100};
     189             : 
     190             :     // Make some initial assertions about the contents of the chainstate.
     191             :     {
     192           1 :         LOCK(::cs_main);
     193           1 :         CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip();
     194           1 :         initial_size = ibd_coinscache.GetCacheSize();
     195           1 :         size_t total_coins{0};
     196             : 
     197         101 :         for (CTransactionRef& txn : m_coinbase_txns) {
     198         100 :             COutPoint op{txn->GetHash(), 0};
     199         100 :             BOOST_CHECK(ibd_coinscache.HaveCoin(op));
     200         100 :             total_coins++;
     201             :         }
     202             : 
     203           1 :         BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
     204           1 :         BOOST_CHECK_EQUAL(initial_size, initial_total_coins);
     205           1 :     }
     206             : 
     207             :     // Snapshot should refuse to load at this height.
     208           1 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
     209           1 :     BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
     210           1 :     BOOST_CHECK(!chainman.SnapshotBlockhash());
     211             : 
     212             :     // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
     213             :     // be found.
     214           1 :     constexpr int snapshot_height = 110;
     215           1 :     mineBlocks(10);
     216           1 :     initial_size += 10;
     217           1 :     initial_total_coins += 10;
     218             : 
     219             :     // Should not load malleated snapshots
     220           2 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
     221             :         m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
     222             :             // A UTXO is missing but count is correct
     223             :             metadata.m_coins_count -= 1;
     224             : 
     225             :             COutPoint outpoint;
     226             :             Coin coin;
     227             : 
     228             :             auto_infile >> outpoint;
     229             :             auto_infile >> coin;
     230             :     }));
     231           2 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
     232             :         m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
     233             :             // Coins count is larger than coins in file
     234             :             metadata.m_coins_count += 1;
     235             :     }));
     236           2 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
     237             :         m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
     238             :             // Coins count is smaller than coins in file
     239             :             metadata.m_coins_count -= 1;
     240             :     }));
     241           2 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
     242             :         m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
     243             :             // Wrong hash
     244             :             metadata.m_base_blockhash = uint256::ZERO;
     245             :     }));
     246           2 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(
     247             :         m_node, m_path_root, [](AutoFile& auto_infile, SnapshotMetadata& metadata) {
     248             :             // Wrong hash
     249             :             metadata.m_base_blockhash = uint256::ONE;
     250             :     }));
     251             : 
     252           1 :     BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
     253             : 
     254             :     // Ensure our active chain is the snapshot chainstate.
     255           1 :     BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
     256           1 :     BOOST_CHECK_EQUAL(
     257             :         *chainman.ActiveChainstate().m_from_snapshot_blockhash,
     258             :         *chainman.SnapshotBlockhash());
     259             : 
     260             :     // Ensure that the genesis block was not marked assumed-valid.
     261           2 :     BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid()));
     262             : 
     263           1 :     const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
     264           1 :     const CBlockIndex* tip = chainman.ActiveTip();
     265             : 
     266           1 :     BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx);
     267             : 
     268             :     // To be checked against later when we try loading a subsequent snapshot.
     269           1 :     uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()};
     270             : 
     271             :     // Make some assertions about the both chainstates. These checks ensure the
     272             :     // legacy chainstate hasn't changed and that the newly created chainstate
     273             :     // reflects the expected content.
     274             :     {
     275           1 :         LOCK(::cs_main);
     276           1 :         int chains_tested{0};
     277             : 
     278           3 :         for (CChainState* chainstate : chainman.GetAll()) {
     279           2 :             BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
     280           2 :             CCoinsViewCache& coinscache = chainstate->CoinsTip();
     281             : 
     282             :             // Both caches will be empty initially.
     283           2 :             BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize());
     284             : 
     285           2 :             size_t total_coins{0};
     286             : 
     287         222 :             for (CTransactionRef& txn : m_coinbase_txns) {
     288         220 :                 COutPoint op{txn->GetHash(), 0};
     289         220 :                 BOOST_CHECK(coinscache.HaveCoin(op));
     290         220 :                 total_coins++;
     291             :             }
     292             : 
     293           2 :             BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize());
     294           2 :             BOOST_CHECK_EQUAL(total_coins, initial_total_coins);
     295           2 :             chains_tested++;
     296             :         }
     297             : 
     298           1 :         BOOST_CHECK_EQUAL(chains_tested, 2);
     299           1 :     }
     300             : 
     301             :     // Mine some new blocks on top of the activated snapshot chainstate.
     302           1 :     constexpr size_t new_coins{100};
     303           1 :     mineBlocks(new_coins);  // Defined in TestChain100Setup.
     304             : 
     305             :     {
     306           1 :         LOCK(::cs_main);
     307           1 :         size_t coins_in_active{0};
     308           1 :         size_t coins_in_background{0};
     309           1 :         size_t coins_missing_from_background{0};
     310             : 
     311           3 :         for (CChainState* chainstate : chainman.GetAll()) {
     312           2 :             BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
     313           2 :             CCoinsViewCache& coinscache = chainstate->CoinsTip();
     314           2 :             bool is_background = chainstate != &chainman.ActiveChainstate();
     315             : 
     316         422 :             for (CTransactionRef& txn : m_coinbase_txns) {
     317         420 :                 COutPoint op{txn->GetHash(), 0};
     318         420 :                 if (coinscache.HaveCoin(op)) {
     319         320 :                     (is_background ? coins_in_background : coins_in_active)++;
     320         420 :                 } else if (is_background) {
     321         100 :                     coins_missing_from_background++;
     322         100 :                 }
     323             :             }
     324             :         }
     325             : 
     326           1 :         BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
     327           1 :         BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
     328           1 :         BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
     329           1 :     }
     330             : 
     331             :     // Snapshot should refuse to load after one has already loaded.
     332           1 :     BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root));
     333             : 
     334             :     // Snapshot blockhash should be unchanged.
     335           1 :     BOOST_CHECK_EQUAL(
     336             :         *chainman.ActiveChainstate().m_from_snapshot_blockhash,
     337             :         loaded_snapshot_blockhash);
     338           1 : }
     339             : 
     340             : //! Test LoadBlockIndex behavior when multiple chainstates are in use.
     341             : //!
     342             : //! - First, verify that setBlockIndexCandidates is as expected when using a single,
     343             : //!   fully-validating chainstate.
     344             : //!
     345             : //! - Then mark a region of the chain BLOCK_ASSUMED_VALID and introduce a second chainstate
     346             : //!   that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first
     347             : //!   chainstate only contains fully validated blocks and the other chainstate contains all blocks,
     348             : //!   even those assumed-valid.
     349             : //!
     350         149 : BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
     351             : {
     352           1 :     ChainstateManager& chainman = *Assert(m_node.chainman);
     353           1 :     CTxMemPool& mempool = *m_node.mempool;
     354           1 :     CChainState& cs1 = chainman.ActiveChainstate();
     355             : 
     356           1 :     int num_indexes{0};
     357           1 :     int num_assumed_valid{0};
     358           1 :     const int expected_assumed_valid{20};
     359           1 :     const int last_assumed_valid_idx{40};
     360           1 :     const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid;
     361             : 
     362           1 :     CBlockIndex* validated_tip{nullptr};
     363           1 :     CBlockIndex* assumed_tip{chainman.ActiveChain().Tip()};
     364             : 
     365           3 :     auto reload_all_block_indexes = [&]() {
     366           5 :         for (CChainState* cs : chainman.GetAll()) {
     367           3 :             LOCK(::cs_main);
     368           3 :             cs->UnloadBlockIndex();
     369           3 :             BOOST_CHECK(cs->setBlockIndexCandidates.empty());
     370           3 :         }
     371             : 
     372           4 :         WITH_LOCK(::cs_main, chainman.LoadBlockIndex());
     373           2 :     };
     374             : 
     375             :     // Ensure that without any assumed-valid BlockIndex entries, all entries are considered
     376             :     // tip candidates.
     377           1 :     reload_all_block_indexes();
     378           1 :     BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), cs1.m_chain.Height() + 1);
     379             : 
     380             :     // Mark some region of the chain assumed-valid.
     381         102 :     for (int i = 0; i <= cs1.m_chain.Height(); ++i) {
     382         101 :         LOCK(::cs_main);
     383         101 :         auto index = cs1.m_chain[i];
     384             : 
     385         101 :         if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) {
     386          20 :             index->nStatus = BlockStatus::BLOCK_VALID_TREE | BlockStatus::BLOCK_ASSUMED_VALID;
     387          20 :         }
     388             : 
     389         101 :         ++num_indexes;
     390         101 :         if (index->IsAssumedValid()) ++num_assumed_valid;
     391             : 
     392             :         // Note the last fully-validated block as the expected validated tip.
     393         101 :         if (i == (assumed_valid_start_idx - 1)) {
     394           1 :             validated_tip = index;
     395           1 :             BOOST_CHECK(!index->IsAssumedValid());
     396           1 :         }
     397         101 :     }
     398             : 
     399           1 :     BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
     400             : 
     401           2 :     CChainState& cs2 = WITH_LOCK(::cs_main,
     402             :         return chainman.InitializeChainstate(&mempool, *m_node.evodb, m_node.chain_helper, GetRandHash()));
     403             : 
     404           1 :     reload_all_block_indexes();
     405             : 
     406             :     // The fully validated chain only has candidates up to the start of the assumed-valid
     407             :     // blocks.
     408           1 :     BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1);
     409           1 :     BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_tip), 0);
     410           1 :     BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), assumed_valid_start_idx);
     411             : 
     412             :     // The assumed-valid tolerant chain has all blocks as candidates.
     413           1 :     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 1);
     414           1 :     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1);
     415           1 :     BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes);
     416           1 : }
     417             : 
     418         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16