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