|
4 | 4 | //
|
5 | 5 | #include <chainparams.h>
|
6 | 6 | #include <consensus/validation.h>
|
| 7 | +#include <node/utxo_snapshot.h> |
7 | 8 | #include <random.h>
|
| 9 | +#include <rpc/blockchain.h> |
8 | 10 | #include <sync.h>
|
9 | 11 | #include <test/util/setup_common.h>
|
10 | 12 | #include <uint256.h>
|
11 | 13 | #include <validation.h>
|
12 | 14 | #include <validationinterface.h>
|
13 | 15 |
|
| 16 | +#include <tinyformat.h> |
| 17 | +#include <univalue.h> |
| 18 | + |
14 | 19 | #include <vector>
|
15 | 20 |
|
16 | 21 | #include <boost/test/unit_test.hpp>
|
@@ -164,4 +169,147 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
|
164 | 169 | BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
|
165 | 170 | }
|
166 | 171 |
|
| 172 | +static bool |
| 173 | +CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root) |
| 174 | +{ |
| 175 | + // Write out a snapshot to the test's tempdir. |
| 176 | + // |
| 177 | + int height; |
| 178 | + WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight()); |
| 179 | + fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height); |
| 180 | + FILE* outfile{fsbridge::fopen(snapshot_path, "wb")}; |
| 181 | + CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION}; |
| 182 | + |
| 183 | + UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile); |
| 184 | + BOOST_TEST_MESSAGE( |
| 185 | + "Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write()); |
| 186 | + |
| 187 | + // Read the written snapshot in and then activate it. |
| 188 | + // |
| 189 | + FILE* infile{fsbridge::fopen(snapshot_path, "rb")}; |
| 190 | + CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION}; |
| 191 | + SnapshotMetadata metadata; |
| 192 | + auto_infile >> metadata; |
| 193 | + |
| 194 | + return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true); |
| 195 | +} |
| 196 | + |
| 197 | +//! Test basic snapshot activation. |
| 198 | +BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100DeterministicSetup) |
| 199 | +{ |
| 200 | + ChainstateManager& chainman = *Assert(m_node.chainman); |
| 201 | + |
| 202 | + size_t initial_size; |
| 203 | + size_t initial_total_coins{100}; |
| 204 | + |
| 205 | + // Make some initial assertions about the contents of the chainstate. |
| 206 | + { |
| 207 | + LOCK(::cs_main); |
| 208 | + CCoinsViewCache& ibd_coinscache = chainman.ActiveChainstate().CoinsTip(); |
| 209 | + initial_size = ibd_coinscache.GetCacheSize(); |
| 210 | + size_t total_coins{0}; |
| 211 | + |
| 212 | + for (CTransactionRef& txn : m_coinbase_txns) { |
| 213 | + COutPoint op{txn->GetHash(), 0}; |
| 214 | + BOOST_CHECK(ibd_coinscache.HaveCoin(op)); |
| 215 | + total_coins++; |
| 216 | + } |
| 217 | + |
| 218 | + BOOST_CHECK_EQUAL(total_coins, initial_total_coins); |
| 219 | + BOOST_CHECK_EQUAL(initial_size, initial_total_coins); |
| 220 | + } |
| 221 | + |
| 222 | + // Snapshot should refuse to load at this height. |
| 223 | + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root)); |
| 224 | + BOOST_CHECK(chainman.ActiveChainstate().m_from_snapshot_blockhash.IsNull()); |
| 225 | + BOOST_CHECK_EQUAL( |
| 226 | + chainman.ActiveChainstate().m_from_snapshot_blockhash, |
| 227 | + chainman.SnapshotBlockhash().value_or(uint256())); |
| 228 | + |
| 229 | + // Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can |
| 230 | + // be found. |
| 231 | + mineBlocks(10); |
| 232 | + initial_size += 10; |
| 233 | + initial_total_coins += 10; |
| 234 | + |
| 235 | + BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root)); |
| 236 | + |
| 237 | + // Ensure our active chain is the snapshot chainstate. |
| 238 | + BOOST_CHECK(!chainman.ActiveChainstate().m_from_snapshot_blockhash.IsNull()); |
| 239 | + BOOST_CHECK_EQUAL( |
| 240 | + chainman.ActiveChainstate().m_from_snapshot_blockhash, |
| 241 | + *chainman.SnapshotBlockhash()); |
| 242 | + |
| 243 | + // To be checked against later when we try loading a subsequent snapshot. |
| 244 | + 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 | + { |
| 250 | + LOCK(::cs_main); |
| 251 | + int chains_tested{0}; |
| 252 | + |
| 253 | + for (CChainState* chainstate : chainman.GetAll()) { |
| 254 | + BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); |
| 255 | + CCoinsViewCache& coinscache = chainstate->CoinsTip(); |
| 256 | + |
| 257 | + // Both caches will be empty initially. |
| 258 | + BOOST_CHECK_EQUAL((unsigned int)0, coinscache.GetCacheSize()); |
| 259 | + |
| 260 | + size_t total_coins{0}; |
| 261 | + |
| 262 | + for (CTransactionRef& txn : m_coinbase_txns) { |
| 263 | + COutPoint op{txn->GetHash(), 0}; |
| 264 | + BOOST_CHECK(coinscache.HaveCoin(op)); |
| 265 | + total_coins++; |
| 266 | + } |
| 267 | + |
| 268 | + BOOST_CHECK_EQUAL(initial_size , coinscache.GetCacheSize()); |
| 269 | + BOOST_CHECK_EQUAL(total_coins, initial_total_coins); |
| 270 | + chains_tested++; |
| 271 | + } |
| 272 | + |
| 273 | + BOOST_CHECK_EQUAL(chains_tested, 2); |
| 274 | + } |
| 275 | + |
| 276 | + // Mine some new blocks on top of the activated snapshot chainstate. |
| 277 | + constexpr size_t new_coins{100}; |
| 278 | + mineBlocks(new_coins); // Defined in TestChain100Setup. |
| 279 | + |
| 280 | + { |
| 281 | + LOCK(::cs_main); |
| 282 | + size_t coins_in_active{0}; |
| 283 | + size_t coins_in_ibd{0}; |
| 284 | + size_t coins_missing_ibd{0}; |
| 285 | + |
| 286 | + for (CChainState* chainstate : chainman.GetAll()) { |
| 287 | + BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString()); |
| 288 | + CCoinsViewCache& coinscache = chainstate->CoinsTip(); |
| 289 | + bool is_ibd = chainman.IsBackgroundIBD(chainstate); |
| 290 | + |
| 291 | + for (CTransactionRef& txn : m_coinbase_txns) { |
| 292 | + COutPoint op{txn->GetHash(), 0}; |
| 293 | + if (coinscache.HaveCoin(op)) { |
| 294 | + (is_ibd ? coins_in_ibd : coins_in_active)++; |
| 295 | + } else if (is_ibd) { |
| 296 | + coins_missing_ibd++; |
| 297 | + } |
| 298 | + } |
| 299 | + } |
| 300 | + |
| 301 | + BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins); |
| 302 | + BOOST_CHECK_EQUAL(coins_in_ibd, initial_total_coins); |
| 303 | + BOOST_CHECK_EQUAL(coins_missing_ibd, new_coins); |
| 304 | + } |
| 305 | + |
| 306 | + // Snapshot should refuse to load after one has already loaded. |
| 307 | + BOOST_REQUIRE(!CreateAndActivateUTXOSnapshot(m_node, m_path_root)); |
| 308 | + |
| 309 | + // Snapshot blockhash should be unchanged. |
| 310 | + BOOST_CHECK_EQUAL( |
| 311 | + chainman.ActiveChainstate().m_from_snapshot_blockhash, |
| 312 | + loaded_snapshot_blockhash); |
| 313 | +} |
| 314 | + |
167 | 315 | BOOST_AUTO_TEST_SUITE_END()
|
0 commit comments