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