validation_chainstatemanager_tests.cpp
1 // Copyright (c) 2019-2022 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 <kernel/disconnected_transactions.h> 8 #include <node/kernel_notifications.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/logging.h> 15 #include <test/util/random.h> 16 #include <test/util/setup_common.h> 17 #include <test/util/validation.h> 18 #include <uint256.h> 19 #include <validation.h> 20 #include <validationinterface.h> 21 22 #include <tinyformat.h> 23 24 #include <vector> 25 26 #include <boost/test/unit_test.hpp> 27 28 using node::BlockManager; 29 using node::KernelNotifications; 30 using node::SnapshotMetadata; 31 32 BOOST_FIXTURE_TEST_SUITE(validation_chainstatemanager_tests, TestingSetup) 33 34 //! Basic tests for ChainstateManager. 35 //! 36 //! First create a legacy (IBD) chainstate, then create a snapshot chainstate. 37 BOOST_FIXTURE_TEST_CASE(chainstatemanager, TestChain100Setup) 38 { 39 ChainstateManager& manager = *m_node.chainman; 40 std::vector<Chainstate*> chainstates; 41 42 BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); 43 44 // Create a legacy (IBD) chainstate. 45 // 46 Chainstate& c1 = manager.ActiveChainstate(); 47 chainstates.push_back(&c1); 48 49 BOOST_CHECK(!manager.IsSnapshotActive()); 50 BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); 51 auto all = manager.GetAll(); 52 BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end()); 53 54 auto& active_chain = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); 55 BOOST_CHECK_EQUAL(&active_chain, &c1.m_chain); 56 57 // Get to a valid assumeutxo tip (per chainparams); 58 mineBlocks(10); 59 BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110); 60 auto active_tip = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); 61 auto exp_tip = c1.m_chain.Tip(); 62 BOOST_CHECK_EQUAL(active_tip, exp_tip); 63 64 BOOST_CHECK(!manager.SnapshotBlockhash().has_value()); 65 66 // Create a snapshot-based chainstate. 67 // 68 const uint256 snapshot_blockhash = active_tip->GetBlockHash(); 69 Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(snapshot_blockhash)); 70 chainstates.push_back(&c2); 71 c2.InitCoinsDB( 72 /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); 73 { 74 LOCK(::cs_main); 75 c2.InitCoinsCache(1 << 23); 76 c2.CoinsTip().SetBestBlock(active_tip->GetBlockHash()); 77 c2.setBlockIndexCandidates.insert(manager.m_blockman.LookupBlockIndex(active_tip->GetBlockHash())); 78 c2.LoadChainTip(); 79 } 80 BlockValidationState _; 81 BOOST_CHECK(c2.ActivateBestChain(_, nullptr)); 82 83 BOOST_CHECK_EQUAL(manager.SnapshotBlockhash().value(), snapshot_blockhash); 84 BOOST_CHECK(manager.IsSnapshotActive()); 85 BOOST_CHECK(WITH_LOCK(::cs_main, return !manager.IsSnapshotValidated())); 86 BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate()); 87 BOOST_CHECK(&c1 != &manager.ActiveChainstate()); 88 auto all2 = manager.GetAll(); 89 BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end()); 90 91 auto& active_chain2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()); 92 BOOST_CHECK_EQUAL(&active_chain2, &c2.m_chain); 93 94 BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 110); 95 mineBlocks(1); 96 BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return manager.ActiveHeight()), 111); 97 BOOST_CHECK_EQUAL(WITH_LOCK(manager.GetMutex(), return c1.m_chain.Height()), 110); 98 99 auto active_tip2 = WITH_LOCK(manager.GetMutex(), return manager.ActiveTip()); 100 BOOST_CHECK_EQUAL(active_tip, active_tip2->pprev); 101 BOOST_CHECK_EQUAL(active_tip, c1.m_chain.Tip()); 102 BOOST_CHECK_EQUAL(active_tip2, c2.m_chain.Tip()); 103 104 // Let scheduler events finish running to avoid accessing memory that is going to be unloaded 105 m_node.validation_signals->SyncWithValidationInterfaceQueue(); 106 } 107 108 //! Test rebalancing the caches associated with each chainstate. 109 BOOST_FIXTURE_TEST_CASE(chainstatemanager_rebalance_caches, TestChain100Setup) 110 { 111 ChainstateManager& manager = *m_node.chainman; 112 113 size_t max_cache = 10000; 114 manager.m_total_coinsdb_cache = max_cache; 115 manager.m_total_coinstip_cache = max_cache; 116 117 std::vector<Chainstate*> chainstates; 118 119 // Create a legacy (IBD) chainstate. 120 // 121 Chainstate& c1 = manager.ActiveChainstate(); 122 chainstates.push_back(&c1); 123 { 124 LOCK(::cs_main); 125 c1.InitCoinsCache(1 << 23); 126 manager.MaybeRebalanceCaches(); 127 } 128 129 BOOST_CHECK_EQUAL(c1.m_coinstip_cache_size_bytes, max_cache); 130 BOOST_CHECK_EQUAL(c1.m_coinsdb_cache_size_bytes, max_cache); 131 132 // Create a snapshot-based chainstate. 133 // 134 CBlockIndex* snapshot_base{WITH_LOCK(manager.GetMutex(), return manager.ActiveChain()[manager.ActiveChain().Height() / 2])}; 135 Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(*snapshot_base->phashBlock)); 136 chainstates.push_back(&c2); 137 c2.InitCoinsDB( 138 /*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false); 139 140 // Reset IBD state so IsInitialBlockDownload() returns true and causes 141 // MaybeRebalancesCaches() to prioritize the snapshot chainstate, giving it 142 // more cache space than the snapshot chainstate. Calling ResetIbd() is 143 // necessary because m_cached_finished_ibd is already latched to true before 144 // the test starts due to the test setup. After ResetIbd() is called. 145 // IsInitialBlockDownload will return true because at this point the active 146 // chainstate has a null chain tip. 147 static_cast<TestChainstateManager&>(manager).ResetIbd(); 148 149 { 150 LOCK(::cs_main); 151 c2.InitCoinsCache(1 << 23); 152 manager.MaybeRebalanceCaches(); 153 } 154 155 BOOST_CHECK_CLOSE(c1.m_coinstip_cache_size_bytes, max_cache * 0.05, 1); 156 BOOST_CHECK_CLOSE(c1.m_coinsdb_cache_size_bytes, max_cache * 0.05, 1); 157 BOOST_CHECK_CLOSE(c2.m_coinstip_cache_size_bytes, max_cache * 0.95, 1); 158 BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1); 159 } 160 161 struct SnapshotTestSetup : TestChain100Setup { 162 // Run with coinsdb on the filesystem to support, e.g., moving invalidated 163 // chainstate dirs to "*_invalid". 164 // 165 // Note that this means the tests run considerably slower than in-memory DB 166 // tests, but we can't otherwise test this functionality since it relies on 167 // destructive filesystem operations. 168 SnapshotTestSetup() : TestChain100Setup{ 169 {}, 170 {}, 171 /*coins_db_in_memory=*/false, 172 /*block_tree_db_in_memory=*/false, 173 } 174 { 175 } 176 177 std::tuple<Chainstate*, Chainstate*> SetupSnapshot() 178 { 179 ChainstateManager& chainman = *Assert(m_node.chainman); 180 181 BOOST_CHECK(!chainman.IsSnapshotActive()); 182 183 { 184 LOCK(::cs_main); 185 BOOST_CHECK(!chainman.IsSnapshotValidated()); 186 BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir)); 187 } 188 189 size_t initial_size; 190 size_t initial_total_coins{100}; 191 192 // Make some initial assertions about the contents of the chainstate. 193 { 194 LOCK(::cs_main); 195 CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip(); 196 initial_size = ibd_coinscache.GetCacheSize(); 197 size_t total_coins{0}; 198 199 for (CTransactionRef& txn : m_coinbase_txns) { 200 COutPoint op{txn->GetHash(), 0}; 201 BOOST_CHECK(ibd_coinscache.HaveCoin(op)); 202 total_coins++; 203 } 204 205 BOOST_CHECK_EQUAL(total_coins, initial_total_coins); 206 BOOST_CHECK_EQUAL(initial_size, initial_total_coins); 207 } 208 209 Chainstate& validation_chainstate = chainman.ActiveChainstate(); 210 211 // Snapshot should refuse to load at this height. 212 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this)); 213 BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash); 214 BOOST_CHECK(!chainman.SnapshotBlockhash()); 215 216 // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can 217 // be found. 218 constexpr int snapshot_height = 110; 219 mineBlocks(10); 220 initial_size += 10; 221 initial_total_coins += 10; 222 223 // Should not load malleated snapshots 224 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( 225 this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { 226 // A UTXO is missing but count is correct 227 metadata.m_coins_count -= 1; 228 229 COutPoint outpoint; 230 Coin coin; 231 232 auto_infile >> outpoint; 233 auto_infile >> coin; 234 })); 235 236 BOOST_CHECK(!node::FindSnapshotChainstateDir(chainman.m_options.datadir)); 237 238 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( 239 this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { 240 // Coins count is larger than coins in file 241 metadata.m_coins_count += 1; 242 })); 243 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( 244 this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { 245 // Coins count is smaller than coins in file 246 metadata.m_coins_count -= 1; 247 })); 248 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( 249 this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { 250 // Wrong hash 251 metadata.m_base_blockhash = uint256::ZERO; 252 })); 253 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot( 254 this, [](AutoFile& auto_infile, SnapshotMetadata& metadata) { 255 // Wrong hash 256 metadata.m_base_blockhash = uint256::ONE; 257 })); 258 259 BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(this)); 260 BOOST_CHECK(fs::exists(*node::FindSnapshotChainstateDir(chainman.m_options.datadir))); 261 262 // Ensure our active chain is the snapshot chainstate. 263 BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull()); 264 BOOST_CHECK_EQUAL( 265 *chainman.ActiveChainstate().m_from_snapshot_blockhash, 266 *chainman.SnapshotBlockhash()); 267 268 Chainstate& snapshot_chainstate = chainman.ActiveChainstate(); 269 270 { 271 LOCK(::cs_main); 272 273 fs::path found = *node::FindSnapshotChainstateDir(chainman.m_options.datadir); 274 275 // Note: WriteSnapshotBaseBlockhash() is implicitly tested above. 276 BOOST_CHECK_EQUAL( 277 *node::ReadSnapshotBaseBlockhash(found), 278 *chainman.SnapshotBlockhash()); 279 } 280 281 const auto& au_data = ::Params().AssumeutxoForHeight(snapshot_height); 282 const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip()); 283 284 BOOST_CHECK_EQUAL(tip->nChainTx, au_data->nChainTx); 285 286 // To be checked against later when we try loading a subsequent snapshot. 287 uint256 loaded_snapshot_blockhash{*chainman.SnapshotBlockhash()}; 288 289 // Make some assertions about the both chainstates. These checks ensure the 290 // legacy chainstate hasn't changed and that the newly created chainstate 291 // reflects the expected content. 292 { 293 LOCK(::cs_main); 294 int chains_tested{0}; 295 296 for (Chainstate* chainstate : chainman.GetAll()) { 297 BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); 298 CCoinsViewCache& coinscache = chainstate->CoinsTip(); 299 300 // Both caches will be empty initially. 301 BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize()); 302 303 size_t total_coins{0}; 304 305 for (CTransactionRef& txn : m_coinbase_txns) { 306 COutPoint op{txn->GetHash(), 0}; 307 BOOST_CHECK(coinscache.HaveCoin(op)); 308 total_coins++; 309 } 310 311 BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize()); 312 BOOST_CHECK_EQUAL(total_coins, initial_total_coins); 313 chains_tested++; 314 } 315 316 BOOST_CHECK_EQUAL(chains_tested, 2); 317 } 318 319 // Mine some new blocks on top of the activated snapshot chainstate. 320 constexpr size_t new_coins{100}; 321 mineBlocks(new_coins); // Defined in TestChain100Setup. 322 323 { 324 LOCK(::cs_main); 325 size_t coins_in_active{0}; 326 size_t coins_in_background{0}; 327 size_t coins_missing_from_background{0}; 328 329 for (Chainstate* chainstate : chainman.GetAll()) { 330 BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); 331 CCoinsViewCache& coinscache = chainstate->CoinsTip(); 332 bool is_background = chainstate != &chainman.ActiveChainstate(); 333 334 for (CTransactionRef& txn : m_coinbase_txns) { 335 COutPoint op{txn->GetHash(), 0}; 336 if (coinscache.HaveCoin(op)) { 337 (is_background ? coins_in_background : coins_in_active)++; 338 } else if (is_background) { 339 coins_missing_from_background++; 340 } 341 } 342 } 343 344 BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins); 345 BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins); 346 BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins); 347 } 348 349 // Snapshot should refuse to load after one has already loaded. 350 BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(this)); 351 352 // Snapshot blockhash should be unchanged. 353 BOOST_CHECK_EQUAL( 354 *chainman.ActiveChainstate().m_from_snapshot_blockhash, 355 loaded_snapshot_blockhash); 356 return std::make_tuple(&validation_chainstate, &snapshot_chainstate); 357 } 358 359 // Simulate a restart of the node by flushing all state to disk, clearing the 360 // existing ChainstateManager, and unloading the block index. 361 // 362 // @returns a reference to the "restarted" ChainstateManager 363 ChainstateManager& SimulateNodeRestart() 364 { 365 ChainstateManager& chainman = *Assert(m_node.chainman); 366 367 BOOST_TEST_MESSAGE("Simulating node restart"); 368 { 369 for (Chainstate* cs : chainman.GetAll()) { 370 LOCK(::cs_main); 371 cs->ForceFlushStateToDisk(); 372 } 373 // Process all callbacks referring to the old manager before wiping it. 374 m_node.validation_signals->SyncWithValidationInterfaceQueue(); 375 LOCK(::cs_main); 376 chainman.ResetChainstates(); 377 BOOST_CHECK_EQUAL(chainman.GetAll().size(), 0); 378 m_node.notifications = std::make_unique<KernelNotifications>(*Assert(m_node.shutdown), m_node.exit_status); 379 const ChainstateManager::Options chainman_opts{ 380 .chainparams = ::Params(), 381 .datadir = chainman.m_options.datadir, 382 .notifications = *m_node.notifications, 383 .signals = m_node.validation_signals.get(), 384 }; 385 const BlockManager::Options blockman_opts{ 386 .chainparams = chainman_opts.chainparams, 387 .blocks_dir = m_args.GetBlocksDirPath(), 388 .notifications = chainman_opts.notifications, 389 }; 390 // For robustness, ensure the old manager is destroyed before creating a 391 // new one. 392 m_node.chainman.reset(); 393 m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown), chainman_opts, blockman_opts); 394 } 395 return *Assert(m_node.chainman); 396 } 397 }; 398 399 //! Test basic snapshot activation. 400 BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, SnapshotTestSetup) 401 { 402 this->SetupSnapshot(); 403 } 404 405 //! Test LoadBlockIndex behavior when multiple chainstates are in use. 406 //! 407 //! - First, verify that setBlockIndexCandidates is as expected when using a single, 408 //! fully-validating chainstate. 409 //! 410 //! - Then mark a region of the chain as missing data and introduce a second chainstate 411 //! that will tolerate assumed-valid blocks. Run LoadBlockIndex() and ensure that the first 412 //! chainstate only contains fully validated blocks and the other chainstate contains all blocks, 413 //! except those marked assume-valid, because those entries don't HAVE_DATA. 414 //! 415 BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup) 416 { 417 ChainstateManager& chainman = *Assert(m_node.chainman); 418 Chainstate& cs1 = chainman.ActiveChainstate(); 419 420 int num_indexes{0}; 421 // Blocks in range [assumed_valid_start_idx, last_assumed_valid_idx) will be 422 // marked as assumed-valid and not having data. 423 const int expected_assumed_valid{20}; 424 const int last_assumed_valid_idx{111}; 425 const int assumed_valid_start_idx = last_assumed_valid_idx - expected_assumed_valid; 426 427 // Mine to height 120, past the hardcoded regtest assumeutxo snapshot at 428 // height 110 429 mineBlocks(20); 430 431 CBlockIndex* validated_tip{nullptr}; 432 CBlockIndex* assumed_base{nullptr}; 433 CBlockIndex* assumed_tip{WITH_LOCK(chainman.GetMutex(), return chainman.ActiveChain().Tip())}; 434 BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120); 435 436 auto reload_all_block_indexes = [&]() { 437 // For completeness, we also reset the block sequence counters to 438 // ensure that no state which affects the ranking of tip-candidates is 439 // retained (even though this isn't strictly necessary). 440 WITH_LOCK(::cs_main, return chainman.ResetBlockSequenceCounters()); 441 for (Chainstate* cs : chainman.GetAll()) { 442 LOCK(::cs_main); 443 cs->ClearBlockIndexCandidates(); 444 BOOST_CHECK(cs->setBlockIndexCandidates.empty()); 445 } 446 447 WITH_LOCK(::cs_main, chainman.LoadBlockIndex()); 448 }; 449 450 // Ensure that without any assumed-valid BlockIndex entries, only the current tip is 451 // considered as a candidate. 452 reload_all_block_indexes(); 453 BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 1); 454 455 // Reset some region of the chain's nStatus, removing the HAVE_DATA flag. 456 for (int i = 0; i <= cs1.m_chain.Height(); ++i) { 457 LOCK(::cs_main); 458 auto index = cs1.m_chain[i]; 459 460 // Blocks with heights in range [91, 110] are marked as missing data. 461 if (i < last_assumed_valid_idx && i >= assumed_valid_start_idx) { 462 index->nStatus = BlockStatus::BLOCK_VALID_TREE; 463 index->nTx = 0; 464 index->nChainTx = 0; 465 } 466 467 ++num_indexes; 468 469 // Note the last fully-validated block as the expected validated tip. 470 if (i == (assumed_valid_start_idx - 1)) { 471 validated_tip = index; 472 } 473 // Note the last assumed valid block as the snapshot base 474 if (i == last_assumed_valid_idx - 1) { 475 assumed_base = index; 476 } 477 } 478 479 // Note: cs2's tip is not set when ActivateExistingSnapshot is called. 480 Chainstate& cs2 = WITH_LOCK(::cs_main, 481 return chainman.ActivateExistingSnapshot(*assumed_base->phashBlock)); 482 483 // Set tip of the fully validated chain to be the validated tip 484 cs1.m_chain.SetTip(*validated_tip); 485 486 // Set tip of the assume-valid-based chain to the assume-valid block 487 cs2.m_chain.SetTip(*assumed_base); 488 489 // Sanity check test variables. 490 BOOST_CHECK_EQUAL(num_indexes, 121); // 121 total blocks, including genesis 491 BOOST_CHECK_EQUAL(assumed_tip->nHeight, 120); // original chain has height 120 492 BOOST_CHECK_EQUAL(validated_tip->nHeight, 90); // current cs1 chain has height 90 493 BOOST_CHECK_EQUAL(assumed_base->nHeight, 110); // current cs2 chain has height 110 494 495 // Regenerate cs1.setBlockIndexCandidates and cs2.setBlockIndexCandidate and 496 // check contents below. 497 reload_all_block_indexes(); 498 499 // The fully validated chain should only have the current validated tip and 500 // the assumed valid base as candidates, blocks 90 and 110. Specifically: 501 // 502 // - It does not have blocks 0-89 because they contain less work than the 503 // chain tip. 504 // 505 // - It has block 90 because it has data and equal work to the chain tip, 506 // (since it is the chain tip). 507 // 508 // - It does not have blocks 91-109 because they do not contain data. 509 // 510 // - It has block 110 even though it does not have data, because 511 // LoadBlockIndex has a special case to always add the snapshot block as a 512 // candidate. The special case is only actually intended to apply to the 513 // snapshot chainstate cs2, not the background chainstate cs1, but it is 514 // written broadly and applies to both. 515 // 516 // - It does not have any blocks after height 110 because cs1 is a background 517 // chainstate, and only blocks where are ancestors of the snapshot block 518 // are added as candidates for the background chainstate. 519 BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.size(), 2); 520 BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(validated_tip), 1); 521 BOOST_CHECK_EQUAL(cs1.setBlockIndexCandidates.count(assumed_base), 1); 522 523 // The assumed-valid tolerant chain has the assumed valid base as a 524 // candidate, but otherwise has none of the assumed-valid (which do not 525 // HAVE_DATA) blocks as candidates. 526 // 527 // Specifically: 528 // - All blocks below height 110 are not candidates, because cs2 chain tip 529 // has height 110 and they have less work than it does. 530 // 531 // - Block 110 is a candidate even though it does not have data, because it 532 // is the snapshot block, which is assumed valid. 533 // 534 // - Blocks 111-120 are added because they have data. 535 536 // Check that block 90 is absent 537 BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(validated_tip), 0); 538 // Check that block 109 is absent 539 BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base->pprev), 0); 540 // Check that block 110 is present 541 BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_base), 1); 542 // Check that block 120 is present 543 BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.count(assumed_tip), 1); 544 // Check that 11 blocks total are present. 545 BOOST_CHECK_EQUAL(cs2.setBlockIndexCandidates.size(), num_indexes - last_assumed_valid_idx + 1); 546 } 547 548 //! Ensure that snapshot chainstates initialize properly when found on disk. 549 BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup) 550 { 551 ChainstateManager& chainman = *Assert(m_node.chainman); 552 Chainstate& bg_chainstate = chainman.ActiveChainstate(); 553 554 this->SetupSnapshot(); 555 556 fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir); 557 BOOST_CHECK(fs::exists(snapshot_chainstate_dir)); 558 BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot"); 559 560 BOOST_CHECK(chainman.IsSnapshotActive()); 561 const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(), 562 return chainman.ActiveTip()->GetBlockHash()); 563 564 auto all_chainstates = chainman.GetAll(); 565 BOOST_CHECK_EQUAL(all_chainstates.size(), 2); 566 567 // "Rewind" the background chainstate so that its tip is not at the 568 // base block of the snapshot - this is so after simulating a node restart, 569 // it will initialize instead of attempting to complete validation. 570 // 571 // Note that this is not a realistic use of DisconnectTip(). 572 DisconnectedBlockTransactions unused_pool{MAX_DISCONNECTED_TX_POOL_BYTES}; 573 BlockValidationState unused_state; 574 { 575 LOCK2(::cs_main, bg_chainstate.MempoolMutex()); 576 BOOST_CHECK(bg_chainstate.DisconnectTip(unused_state, &unused_pool)); 577 unused_pool.clear(); // to avoid queuedTx assertion errors on teardown 578 } 579 BOOST_CHECK_EQUAL(bg_chainstate.m_chain.Height(), 109); 580 581 // Test that simulating a shutdown (resetting ChainstateManager) and then performing 582 // chainstate reinitializing successfully cleans up the background-validation 583 // chainstate data, and we end up with a single chainstate that is at tip. 584 ChainstateManager& chainman_restarted = this->SimulateNodeRestart(); 585 586 BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate"); 587 588 // This call reinitializes the chainstates. 589 this->LoadVerifyActivateChainstate(); 590 591 { 592 LOCK(chainman_restarted.GetMutex()); 593 BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 2); 594 BOOST_CHECK(chainman_restarted.IsSnapshotActive()); 595 BOOST_CHECK(!chainman_restarted.IsSnapshotValidated()); 596 597 BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash); 598 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210); 599 } 600 601 BOOST_TEST_MESSAGE( 602 "Ensure we can mine blocks on top of the initialized snapshot chainstate"); 603 mineBlocks(10); 604 { 605 LOCK(chainman_restarted.GetMutex()); 606 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220); 607 608 // Background chainstate should be unaware of new blocks on the snapshot 609 // chainstate. 610 for (Chainstate* cs : chainman_restarted.GetAll()) { 611 if (cs != &chainman_restarted.ActiveChainstate()) { 612 BOOST_CHECK_EQUAL(cs->m_chain.Height(), 109); 613 } 614 } 615 } 616 } 617 618 BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion, SnapshotTestSetup) 619 { 620 this->SetupSnapshot(); 621 622 ChainstateManager& chainman = *Assert(m_node.chainman); 623 Chainstate& active_cs = chainman.ActiveChainstate(); 624 auto tip_cache_before_complete = active_cs.m_coinstip_cache_size_bytes; 625 auto db_cache_before_complete = active_cs.m_coinsdb_cache_size_bytes; 626 627 SnapshotCompletionResult res; 628 m_node.notifications->m_shutdown_on_fatal_error = false; 629 630 fs::path snapshot_chainstate_dir = *node::FindSnapshotChainstateDir(chainman.m_options.datadir); 631 BOOST_CHECK(fs::exists(snapshot_chainstate_dir)); 632 BOOST_CHECK_EQUAL(snapshot_chainstate_dir, gArgs.GetDataDirNet() / "chainstate_snapshot"); 633 634 BOOST_CHECK(chainman.IsSnapshotActive()); 635 const uint256 snapshot_tip_hash = WITH_LOCK(chainman.GetMutex(), 636 return chainman.ActiveTip()->GetBlockHash()); 637 638 res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation()); 639 BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SUCCESS); 640 641 WITH_LOCK(::cs_main, BOOST_CHECK(chainman.IsSnapshotValidated())); 642 BOOST_CHECK(chainman.IsSnapshotActive()); 643 644 // Cache should have been rebalanced and reallocated to the "only" remaining 645 // chainstate. 646 BOOST_CHECK(active_cs.m_coinstip_cache_size_bytes > tip_cache_before_complete); 647 BOOST_CHECK(active_cs.m_coinsdb_cache_size_bytes > db_cache_before_complete); 648 649 auto all_chainstates = chainman.GetAll(); 650 BOOST_CHECK_EQUAL(all_chainstates.size(), 1); 651 BOOST_CHECK_EQUAL(all_chainstates[0], &active_cs); 652 653 // Trying completion again should return false. 654 res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation()); 655 BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::SKIPPED); 656 657 // The invalid snapshot path should not have been used. 658 fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID"; 659 BOOST_CHECK(!fs::exists(snapshot_invalid_dir)); 660 // chainstate_snapshot should still exist. 661 BOOST_CHECK(fs::exists(snapshot_chainstate_dir)); 662 663 // Test that simulating a shutdown (resetting ChainstateManager) and then performing 664 // chainstate reinitializing successfully cleans up the background-validation 665 // chainstate data, and we end up with a single chainstate that is at tip. 666 ChainstateManager& chainman_restarted = this->SimulateNodeRestart(); 667 668 BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate"); 669 670 // This call reinitializes the chainstates, and should clean up the now unnecessary 671 // background-validation leveldb contents. 672 this->LoadVerifyActivateChainstate(); 673 674 BOOST_CHECK(!fs::exists(snapshot_invalid_dir)); 675 // chainstate_snapshot should now *not* exist. 676 BOOST_CHECK(!fs::exists(snapshot_chainstate_dir)); 677 678 const Chainstate& active_cs2 = chainman_restarted.ActiveChainstate(); 679 680 { 681 LOCK(chainman_restarted.GetMutex()); 682 BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1); 683 BOOST_CHECK(!chainman_restarted.IsSnapshotActive()); 684 BOOST_CHECK(!chainman_restarted.IsSnapshotValidated()); 685 BOOST_CHECK(active_cs2.m_coinstip_cache_size_bytes > tip_cache_before_complete); 686 BOOST_CHECK(active_cs2.m_coinsdb_cache_size_bytes > db_cache_before_complete); 687 688 BOOST_CHECK_EQUAL(chainman_restarted.ActiveTip()->GetBlockHash(), snapshot_tip_hash); 689 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210); 690 } 691 692 BOOST_TEST_MESSAGE( 693 "Ensure we can mine blocks on top of the \"new\" IBD chainstate"); 694 mineBlocks(10); 695 { 696 LOCK(chainman_restarted.GetMutex()); 697 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220); 698 } 699 } 700 701 BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_completion_hash_mismatch, SnapshotTestSetup) 702 { 703 auto chainstates = this->SetupSnapshot(); 704 Chainstate& validation_chainstate = *std::get<0>(chainstates); 705 ChainstateManager& chainman = *Assert(m_node.chainman); 706 SnapshotCompletionResult res; 707 m_node.notifications->m_shutdown_on_fatal_error = false; 708 709 // Test tampering with the IBD UTXO set with an extra coin to ensure it causes 710 // snapshot completion to fail. 711 CCoinsViewCache& ibd_coins = WITH_LOCK(::cs_main, 712 return validation_chainstate.CoinsTip()); 713 Coin badcoin; 714 badcoin.out.nValue = InsecureRand32(); 715 badcoin.nHeight = 1; 716 badcoin.out.scriptPubKey.assign(InsecureRandBits(6), 0); 717 Txid txid = Txid::FromUint256(InsecureRand256()); 718 ibd_coins.AddCoin(COutPoint(txid, 0), std::move(badcoin), false); 719 720 fs::path snapshot_chainstate_dir = gArgs.GetDataDirNet() / "chainstate_snapshot"; 721 BOOST_CHECK(fs::exists(snapshot_chainstate_dir)); 722 723 { 724 ASSERT_DEBUG_LOG("failed to validate the -assumeutxo snapshot state"); 725 res = WITH_LOCK(::cs_main, return chainman.MaybeCompleteSnapshotValidation()); 726 BOOST_CHECK_EQUAL(res, SnapshotCompletionResult::HASH_MISMATCH); 727 } 728 729 auto all_chainstates = chainman.GetAll(); 730 BOOST_CHECK_EQUAL(all_chainstates.size(), 1); 731 BOOST_CHECK_EQUAL(all_chainstates[0], &validation_chainstate); 732 BOOST_CHECK_EQUAL(&chainman.ActiveChainstate(), &validation_chainstate); 733 734 fs::path snapshot_invalid_dir = gArgs.GetDataDirNet() / "chainstate_snapshot_INVALID"; 735 BOOST_CHECK(fs::exists(snapshot_invalid_dir)); 736 737 // Test that simulating a shutdown (resetting ChainstateManager) and then performing 738 // chainstate reinitializing successfully loads only the fully-validated 739 // chainstate data, and we end up with a single chainstate that is at tip. 740 ChainstateManager& chainman_restarted = this->SimulateNodeRestart(); 741 742 BOOST_TEST_MESSAGE("Performing Load/Verify/Activate of chainstate"); 743 744 // This call reinitializes the chainstates, and should clean up the now unnecessary 745 // background-validation leveldb contents. 746 this->LoadVerifyActivateChainstate(); 747 748 BOOST_CHECK(fs::exists(snapshot_invalid_dir)); 749 BOOST_CHECK(!fs::exists(snapshot_chainstate_dir)); 750 751 { 752 LOCK(::cs_main); 753 BOOST_CHECK_EQUAL(chainman_restarted.GetAll().size(), 1); 754 BOOST_CHECK(!chainman_restarted.IsSnapshotActive()); 755 BOOST_CHECK(!chainman_restarted.IsSnapshotValidated()); 756 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 210); 757 } 758 759 BOOST_TEST_MESSAGE( 760 "Ensure we can mine blocks on top of the \"new\" IBD chainstate"); 761 mineBlocks(10); 762 { 763 LOCK(::cs_main); 764 BOOST_CHECK_EQUAL(chainman_restarted.ActiveHeight(), 220); 765 } 766 } 767 768 BOOST_AUTO_TEST_SUITE_END()