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