LCOV - code coverage report
Current view: top level - src/test/fuzz - utxo_snapshot.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 119 119
Test Date: 2026-10-29 05:39:06 Functions: 100.0 % 13 13
Branches: 45.2 % 378 171

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2021-present 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 <chain.h>
       6                 :             : #include <chainparams.h>
       7                 :             : #include <coins.h>
       8                 :             : #include <consensus/consensus.h>
       9                 :             : #include <consensus/validation.h>
      10                 :             : #include <kernel/coinstats.h>
      11                 :             : #include <node/block_template_manager.h>
      12                 :             : #include <node/blockstorage.h>
      13                 :             : #include <node/utxo_snapshot.h>
      14                 :             : #include <primitives/block.h>
      15                 :             : #include <primitives/transaction.h>
      16                 :             : #include <serialize.h>
      17                 :             : #include <span.h>
      18                 :             : #include <streams.h>
      19                 :             : #include <sync.h>
      20                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      21                 :             : #include <test/fuzz/fuzz.h>
      22                 :             : #include <test/fuzz/util.h>
      23                 :             : #include <test/fuzz/util/reachability.h>
      24                 :             : #include <test/util/mining.h>
      25                 :             : #include <test/util/setup_common.h>
      26                 :             : #include <test/util/time.h>
      27                 :             : #include <uint256.h>
      28                 :             : #include <util/check.h>
      29                 :             : #include <util/fs.h>
      30                 :             : #include <util/result.h>
      31                 :             : #include <util/time.h>
      32                 :             : #include <validation.h>
      33                 :             : 
      34                 :             : #include <cstdint>
      35                 :             : #include <functional>
      36                 :             : #include <ios>
      37                 :             : #include <memory>
      38                 :             : #include <optional>
      39                 :             : #include <vector>
      40                 :             : 
      41                 :             : using node::SnapshotMetadata;
      42                 :             : 
      43                 :             : namespace {
      44                 :             : 
      45                 :             : const std::vector<std::shared_ptr<CBlock>>* g_chain;
      46                 :             : TestingSetup* g_setup{nullptr};
      47                 :             : constexpr ReachabilityGoal UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS{"UTXO snapshot activation succeeds"};
      48                 :             : 
      49                 :             : /** Sanity check the assumeutxo values hardcoded in chainparams for the fuzz target. */
      50                 :           2 : void sanity_check_snapshot()
      51                 :             : {
      52   [ +  -  -  + ]:           2 :     Assert(g_chain && g_setup == nullptr);
      53                 :             : 
      54                 :             :     // Create a temporary chainstate manager to connect the chain to.
      55         [ +  - ]:           2 :     const auto tmp_setup{MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST, TestOpts{.setup_net = false})};
      56                 :           2 :     const auto& node{tmp_setup->m_node};
      57         [ +  + ]:         402 :     for (auto& block: *g_chain) {
      58         [ +  - ]:         400 :         ProcessBlock(node, block);
      59                 :             :     }
      60                 :             : 
      61                 :             :     // Connect the chain to the tmp chainman and sanity check the chainparams snapshot values.
      62         [ +  - ]:           2 :     LOCK(cs_main);
      63         [ +  - ]:           2 :     auto& cs{node.chainman->ActiveChainstate()};
      64         [ +  - ]:           2 :     cs.ForceFlushStateToDisk(/*wipe_cache=*/false);
      65   [ +  -  +  -  :           2 :     const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, cs.CoinsDB(), node.chainman->m_blockman))};
                   +  - ]
      66   [ +  -  +  - ]:           2 :     const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
      67         [ +  - ]:           2 :     Assert(stats.nHeight == cp_au_data.height);
      68         [ +  - ]:           2 :     Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
      69   [ -  +  +  - ]:           2 :     Assert(stats.hashBlock == cp_au_data.blockhash);
      70         [ +  - ]:           2 :     Assert(AssumeutxoHash{stats.hashSerialized} == cp_au_data.hash_serialized);
      71                 :           2 : }
      72                 :             : 
      73                 :             : template <bool INVALID>
      74                 :           2 : void initialize_chain()
      75                 :             : {
      76                 :             :     if constexpr (!INVALID) {
      77                 :           1 :         RegisterReachabilityGoal(UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS);
      78                 :             :     }
      79 [ +  - ][ +  - ]:           2 :     const auto params{CreateChainParams(ArgsManager{}, ChainType::REGTEST)};
      80   [ +  -  +  -  :           4 :     static const auto chain{CreateBlockChain(2 * COINBASE_MATURITY, *params)};
           +  - ][ +  -  
             +  -  +  - ]
      81                 :           2 :     g_chain = &chain;
      82 [ +  - ][ +  - ]:           2 :     FakeNodeClock node_clock{chain.back()->Time()};
      83                 :             : 
      84                 :             :     // Make sure we can generate a valid snapshot.
      85 [ +  - ][ +  - ]:           2 :     sanity_check_snapshot();
      86                 :             : 
      87   [ +  -  +  -  :           4 :     static const auto setup{
           +  - ][ +  -  
             +  -  +  - ]
      88                 :             :         MakeNoLogFileContext<TestingSetup>(ChainType::REGTEST,
      89                 :             :                                            TestOpts{
      90                 :             :                                                .setup_net = false,
      91                 :             :                                                .setup_validation_interface = false,
      92                 :             :                                                .min_validation_cache = true,
      93                 :             :                                            }),
      94                 :             :     };
      95                 :             :     if constexpr (INVALID) {
      96                 :           1 :         auto& chainman{*setup->m_node.chainman};
      97   [ +  -  +  + ]:         201 :         for (const auto& block : chain) {
      98         [ +  - ]:         200 :             BlockValidationState dummy;
      99         [ +  - ]:         200 :             bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
     100         [ +  - ]:         200 :             Assert(processed);
     101 [ +  - ][ +  -  :         600 :             const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
             +  -  +  - ]
     102         [ +  - ]:         200 :             Assert(index);
     103                 :             :         }
     104                 :             :     }
     105                 :           2 :     g_setup = setup.get();
     106                 :           2 : }
     107                 :             : 
     108                 :             : template <bool INVALID>
     109                 :        1584 : void utxo_snapshot_fuzz(FuzzBufferType buffer)
     110                 :             : {
     111                 :        1584 :     SeedRandomStateForTest(SeedRand::ZEROS);
     112                 :        1584 :     FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
     113                 :        1584 :     FakeNodeClock node_clock{ConsumeTime(fuzzed_data_provider, /*min=*/1296688602)}; // regtest genesis block timestamp
     114                 :        1584 :     auto& setup{*g_setup};
     115                 :        1584 :     bool dirty_chainman{false}; // Reuse the global chainman, but reset it when it is dirty
     116 [ +  - ][ +  - ]:        1584 :     auto& chainman{*setup.m_node.chainman};
     117                 :             : 
     118 [ +  - ][ +  - ]:        1584 :     const auto snapshot_path = gArgs.GetDataDirNet() / "fuzzed_snapshot.dat";
     119                 :             : 
     120   [ +  -  +  - ]:        1584 :     Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
           [ +  -  +  - ]
     121                 :             : 
     122                 :             :     {
     123   [ +  -  +  - ]:        3168 :         AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
           [ +  -  +  - ]
     124                 :             :         // Metadata
     125         [ +  + ]:        1584 :         if (fuzzed_data_provider.ConsumeBool()) {
     126         [ -  + ]:         305 :             std::vector<uint8_t> metadata{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
     127 [ +  - ][ +  - ]:         610 :             outfile << std::span{metadata};
     128                 :         305 :         } else {
     129                 :        1279 :             auto msg_start = chainman.GetParams().MessageStart();
     130                 :        1279 :             int base_blockheight{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 2 * COINBASE_MATURITY)};
     131   [ +  -  +  - ]:        1279 :             uint256 base_blockhash{g_chain->at(base_blockheight - 1)->GetHash()};
     132                 :        1279 :             uint64_t m_coins_count{fuzzed_data_provider.ConsumeIntegralInRange<uint64_t>(1, 3 * COINBASE_MATURITY)};
     133         [ +  - ]:        1279 :             SnapshotMetadata metadata{msg_start, base_blockhash, m_coins_count};
     134                 :        1279 :             outfile << metadata;
     135                 :        1279 :         }
     136                 :             :         // Coins
     137         [ +  + ]:        1584 :         if (fuzzed_data_provider.ConsumeBool()) {
     138         [ -  + ]:        1213 :             std::vector<uint8_t> file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
     139 [ +  - ][ +  - ]:        2426 :             outfile << std::span{file_data};
     140                 :        1213 :         } else {
     141                 :         371 :             int height{1};
     142   [ +  -  +  + ]:       74571 :             for (const auto& block : *g_chain) {
     143   [ +  -  +  -  :      148400 :                 auto coinbase{block->vtx.at(0)};
           +  - ][ +  -  
             +  -  +  - ]
     144 [ +  - ][ +  - ]:       74200 :                 outfile << coinbase->GetHash();
     145 [ +  - ][ +  - ]:       74200 :                 WriteCompactSize(outfile, 1); // number of coins for the hash
     146 [ +  - ][ +  - ]:       74200 :                 WriteCompactSize(outfile, 0); // index of coin
     147 [ +  - ][ +  - ]:      148400 :                 outfile << Coin(coinbase->vout[0], height, /*fCoinBaseIn=*/true);
     148         [ +  - ]:       74200 :                 height++;
     149                 :             :             }
     150                 :             :         }
     151                 :             :         if constexpr (INVALID) {
     152                 :             :             // Append an invalid coin to ensure invalidity. This error will be
     153                 :             :             // detected late in PopulateAndValidateSnapshot, and allows the
     154                 :             :             // INVALID fuzz target to reach more potential code coverage.
     155         [ +  - ]:         917 :             const auto& coinbase{g_chain->back()->vtx.back()};
     156         [ +  - ]:         917 :             outfile << coinbase->GetHash();
     157         [ +  - ]:         917 :             WriteCompactSize(outfile, 1);   // number of coins for the hash
     158         [ +  - ]:         917 :             WriteCompactSize(outfile, 999); // index of coin
     159   [ +  -  +  - ]:        1834 :             outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/false};
     160                 :             :         }
     161 [ -  + ][ -  + ]:        1584 :         assert(outfile.fclose() == 0);
     162                 :        1584 :     }
     163                 :             : 
     164                 :        4752 :     const auto ActivateFuzzedSnapshot{[&] {
     165                 :        6336 :         AutoFile infile{fsbridge::fopen(snapshot_path, "rb")};
     166   [ +  -  +  - ]:        3168 :         auto msg_start = chainman.GetParams().MessageStart();
     167   [ +  -  +  - ]:        3168 :         SnapshotMetadata metadata{msg_start};
     168                 :             :         try {
     169                 :        2916 :             infile >> metadata;
     170   [ -  +  -  + ]:         252 :         } catch (const std::ios_base::failure&) {
     171                 :             :             return false;
     172                 :             :         }
     173   [ +  -  +  - ]:        2916 :         return !!chainman.ActivateSnapshot(infile, metadata, /*in_memory=*/true);
     174                 :        3168 :     }};
     175                 :             : 
     176         [ +  + ]:        1584 :     if (fuzzed_data_provider.ConsumeBool()) {
     177                 :             :         // Consume the bool, but skip the code for the INVALID fuzz target
     178                 :             :         if constexpr (!INVALID) {
     179   [ +  -  +  + ]:      126429 :             for (const auto& block : *g_chain) {
     180         [ +  - ]:      125800 :                 BlockValidationState dummy;
     181         [ +  - ]:      125800 :                 bool processed{chainman.ProcessNewBlockHeaders({{*block}}, true, dummy)};
     182         [ +  - ]:      125800 :                 Assert(processed);
     183 [ +  - ][ +  -  :      377400 :                 const auto* index{WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(block->GetHash()))};
             +  -  +  - ]
     184         [ +  - ]:      125800 :                 Assert(index);
     185                 :             :             }
     186                 :             :             dirty_chainman = true;
     187                 :             :         }
     188                 :             :     }
     189                 :             : 
     190 [ +  - ][ +  - ]:        1584 :     const bool snapshot_activated{ActivateFuzzedSnapshot()};
     191 [ -  + ][ +  + ]:        1584 :     if (snapshot_activated) {
     192 [ #  # ][ +  - ]:          52 :         LOCK(::cs_main);
     193   [ #  #  #  # ]:         104 :         Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash->IsNull());
           [ +  -  +  - ]
     194   [ #  #  #  # ]:          52 :         const auto& coinscache{chainman.ActiveChainstate().CoinsTip()};
           [ +  -  +  - ]
     195 [ #  # ][ +  + ]:       10452 :         for (const auto& block : *g_chain) {
     196   [ #  #  #  #  :       10400 :             Assert(coinscache.HaveCoin(COutPoint{block->vtx.at(0)->GetHash(), 0}));
           #  # ][ +  -  
             +  -  +  - ]
     197   [ #  #  #  # ]:       10400 :             const auto* index{chainman.m_blockman.LookupBlockIndex(block->GetHash())};
           [ +  -  +  - ]
     198 [ #  # ][ +  - ]:       10400 :             Assert(index);
     199 [ #  # ][ +  - ]:       10400 :             Assert(index->nTx == 0);
     200   [ #  #  #  #  :       10400 :             if (index->nHeight == chainman.ActiveChainstate().SnapshotBase()->nHeight) {
           #  # ][ +  -  
             +  -  +  + ]
     201                 :          52 :                 auto params{chainman.GetParams().AssumeutxoForHeight(index->nHeight)};
     202 [ #  # ][ +  - ]:          52 :                 Assert(params.has_value());
     203   [ #  #  #  # ]:          52 :                 Assert(params.value().m_chain_tx_count == index->m_chain_tx_count);
           [ +  -  +  - ]
     204                 :             :             } else {
     205 [ #  # ][ +  - ]:       10348 :                 Assert(index->m_chain_tx_count == 0);
     206                 :             :             }
     207                 :             :         }
     208   [ #  #  #  #  :          52 :         Assert(g_chain->size() == coinscache.GetCacheSize());
           #  # ][ -  +  
             +  -  +  - ]
     209 [ #  # ][ +  - ]:          52 :         dirty_chainman = true;
     210                 :          52 :     } else {
     211   [ +  -  +  - ]:        1532 :         Assert(!chainman.ActiveChainstate().m_from_snapshot_blockhash);
           [ +  -  +  - ]
     212                 :             :     }
     213                 :             :     // Snapshot should refuse to load a second time regardless of validity
     214   [ +  -  +  - ]:        1584 :     Assert(!ActivateFuzzedSnapshot());
           [ +  -  +  - ]
     215                 :             :     if constexpr (INVALID) {
     216                 :             :         // Activating the snapshot, or any other action that makes the chainman
     217                 :             :         // "dirty" can and must not happen for the INVALID fuzz target
     218         [ +  - ]:         917 :         Assert(!dirty_chainman);
     219                 :             :     }
     220 [ -  + ][ +  + ]:        1584 :     if (dirty_chainman) {
     221 [ #  # ][ +  - ]:         629 :         setup.m_node.block_template_manager.reset();
     222 [ #  # ][ +  - ]:         629 :         setup.m_node.chainman.reset();
     223 [ #  # ][ +  - ]:         629 :         setup.m_make_chainman();
     224 [ #  # ][ +  - ]:         629 :         setup.LoadVerifyActivateChainstate();
     225 [ #  # ][ +  - ]:         629 :         setup.CreateBlockTemplateManager();
     226                 :             :     }
     227                 :             :     if constexpr (!INVALID) {
     228         [ +  - ]:         667 :         ObserveReachabilityGoal(snapshot_activated, UTXO_SNAPSHOT_ACTIVATION_SUCCEEDS);
     229                 :             :     }
     230                 :        1584 : }
     231                 :             : 
     232                 :             : // There are two fuzz targets:
     233                 :             : //
     234                 :             : // The target 'utxo_snapshot', which allows valid snapshots, but is slow,
     235                 :             : // because it has to reset the chainstate manager on almost all fuzz inputs.
     236                 :             : // Otherwise, a dirty header tree or dirty chainstate could leak from one fuzz
     237                 :             : // input execution into the next, which makes execution non-deterministic.
     238                 :             : //
     239                 :             : // The target 'utxo_snapshot_invalid', which is fast and does not require any
     240                 :             : // expensive state to be reset.
     241         [ +  - ]:        1143 : FUZZ_TARGET(utxo_snapshot /*valid*/, .init = initialize_chain<false>) { utxo_snapshot_fuzz<false>(buffer); }
     242         [ +  - ]:        1393 : FUZZ_TARGET(utxo_snapshot_invalid, .init = initialize_chain<true>) { utxo_snapshot_fuzz<true>(buffer); }
     243                 :             : 
     244                 :             : } // namespace
        

Generated by: LCOV version 2.5.0-full