Coverage Report

Created: 2026-10-07 20:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/bitcoin/src/test/fuzz/coins_view.cpp
Line
Count
Source
1
// Copyright (c) 2020-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 <coins.h>
6
#include <consensus/amount.h>
7
#include <consensus/tx_check.h>
8
#include <consensus/tx_verify.h>
9
#include <consensus/validation.h>
10
#include <kernel/chainstatemanager_opts.h>
11
#include <kernel/cs_main.h>
12
#include <policy/policy.h>
13
#include <primitives/block.h>
14
#include <primitives/transaction.h>
15
#include <script/interpreter.h>
16
#include <test/fuzz/FuzzedDataProvider.h>
17
#include <test/fuzz/fuzz.h>
18
#include <test/fuzz/util.h>
19
#include <test/util/coins.h>
20
#include <test/util/setup_common.h>
21
#include <txdb.h>
22
#include <util/hasher.h>
23
#include <util/threadpool.h>
24
25
#include <cassert>
26
#include <algorithm>
27
#include <cstdint>
28
#include <functional>
29
#include <limits>
30
#include <memory>
31
#include <optional>
32
#include <ranges>
33
#include <stdexcept>
34
#include <string>
35
#include <utility>
36
#include <vector>
37
38
namespace {
39
/**
40
 * MutationGuardCoinsViewCache asserts that nothing mutates cacheCoins until
41
 * BatchWrite is called. It keeps a snapshot of the cacheCoins state, which it
42
 * uses for the assertion in BatchWrite. After the call to the superclass
43
 * CCoinsViewCache::BatchWrite returns, it recomputes the snapshot at that
44
 * moment.
45
 */
46
class MutationGuardCoinsViewCache final : public CCoinsViewCache
47
{
48
private:
49
    struct CacheCoinSnapshot {
50
        COutPoint outpoint;
51
        bool dirty{false};
52
        bool fresh{false};
53
        Coin coin;
54
0
        bool operator==(const CacheCoinSnapshot&) const = default;
55
    };
56
57
    std::vector<CacheCoinSnapshot> ComputeCacheCoinsSnapshot() const
58
0
    {
59
0
        std::vector<CacheCoinSnapshot> snapshot;
60
0
        snapshot.reserve(cacheCoins.size());
61
62
0
        for (const auto& [outpoint, entry] : cacheCoins) {
  Branch (62:44): [True: 0, False: 0]
63
0
            snapshot.emplace_back(outpoint, entry.IsDirty(), entry.IsFresh(), entry.coin);
64
0
        }
65
66
0
        std::ranges::sort(snapshot, std::less<>{}, &CacheCoinSnapshot::outpoint);
67
0
        return snapshot;
68
0
    }
69
70
    mutable std::vector<CacheCoinSnapshot> m_expected_snapshot{ComputeCacheCoinsSnapshot()};
71
72
public:
73
    void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override
74
0
    {
75
        // Nothing must modify cacheCoins other than BatchWrite.
76
0
        assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
  Branch (76:9): [True: 0, False: 0]
77
0
        CCoinsViewCache::BatchWrite(cursor, block_hash);
78
0
        m_expected_snapshot = ComputeCacheCoinsSnapshot();
79
0
    }
80
81
    using CCoinsViewCache::CCoinsViewCache;
82
};
83
84
// Reuse a single global thread pool across fuzz iterations. Creating and destroying a pool every
85
// iteration leaks memory, since iterations can run faster than the OS can tear down the threads.
86
std::shared_ptr<ThreadPool> g_thread_pool{std::make_shared<ThreadPool>("view_fuzz")};
87
88
void StartPoolIfNeeded()
89
0
{
90
0
    if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
  Branch (90:9): [True: 0, False: 0]
91
0
}
92
93
//! Build a random block and seed a view with utxos for its inputs.
94
CBlock BuildRandomBlock(FuzzedDataProvider& fuzzed_data_provider, CCoinsView& view)
95
0
{
96
0
    CBlock block;
97
0
    CMutableTransaction coinbase;
98
0
    coinbase.vin.emplace_back();
99
0
    block.vtx.push_back(MakeTransactionRef(coinbase));
100
101
0
    CCoinsViewCache seed_cache{&view, /*deterministic=*/true};
102
0
    seed_cache.SetBestBlock(uint256::ONE);
103
104
0
    Txid prevhash{Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider))};
105
0
    LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
106
0
    {
107
0
        CMutableTransaction tx;
108
0
        LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100)
109
0
        {
110
0
            const Txid txid{fuzzed_data_provider.ConsumeBool()
  Branch (110:29): [True: 0, False: 0]
111
0
                                ? Txid::FromUint256(ConsumeUInt256(fuzzed_data_provider))
112
0
                                : prevhash};
113
0
            const COutPoint outpoint{txid, fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
114
0
            if (auto coin{ConsumeDeserializable<Coin>(fuzzed_data_provider)}; coin && !coin->IsSpent()) {
  Branch (114:79): [True: 0, False: 0]
  Branch (114:87): [True: 0, False: 0]
115
0
                seed_cache.AddCoin(outpoint, std::move(*coin), /*possible_overwrite=*/true);
116
0
            }
117
0
            tx.vin.emplace_back(outpoint);
118
0
        }
119
0
        prevhash = tx.GetHash();
120
0
        block.vtx.push_back(MakeTransactionRef(tx));
121
0
    }
122
123
0
    seed_cache.Flush();
124
0
    return block;
125
0
}
126
127
} // namespace
128
129
void initialize_coins_view()
130
0
{
131
0
    static const auto testing_setup = MakeNoLogFileContext<>();
132
0
}
133
134
void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view)
135
0
{
136
0
    auto* const db{dynamic_cast<CCoinsViewDB*>(backend_coins_view)};
137
0
    auto* const overlay{dynamic_cast<CoinsViewOverlay*>(&coins_view_cache)};
138
0
    const bool is_db{db != nullptr};
139
0
    bool good_data{true};
140
0
    auto* original_backend{backend_coins_view};
141
142
0
    if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
  Branch (142:9): [True: 0, False: 0]
143
0
    COutPoint random_out_point;
144
0
    Coin random_coin;
145
0
    CMutableTransaction random_mutable_transaction;
146
0
    LIMITED_WHILE (good_data && fuzzed_data_provider.ConsumeBool(), 10'000) {
147
0
        CallOneOf(
148
0
            fuzzed_data_provider,
149
0
            [&] {
150
0
                if (random_coin.IsSpent()) {
  Branch (150:21): [True: 0, False: 0]
151
0
                    return;
152
0
                }
153
0
                COutPoint outpoint{random_out_point};
154
0
                Coin coin{random_coin};
155
0
                if (fuzzed_data_provider.ConsumeBool()) {
  Branch (155:21): [True: 0, False: 0]
156
                    // We can only skip the check if no unspent coin exists for this outpoint.
157
0
                    const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()};
  Branch (157:51): [True: 0, False: 0]
  Branch (157:90): [True: 0, False: 0]
158
0
                    coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite);
159
0
                } else {
160
0
                    coins_view_cache.EmplaceCoinInternalDANGER(outpoint, std::move(coin));
161
0
                }
162
0
            },
163
0
            [&] {
164
0
                if (overlay && !overlay->AllInputsConsumed()) return; // CoinsViewOverlay::Flush() must have all inputs consumed before being called
  Branch (164:21): [True: 0, False: 0]
  Branch (164:32): [True: 0, False: 0]
165
0
                coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool());
166
0
            },
167
0
            [&] {
168
0
                if (overlay) return; // CoinsViewOverlay::Sync() is never called in production code
  Branch (168:21): [True: 0, False: 0]
169
0
                coins_view_cache.Sync();
170
0
            },
171
0
            [&] {
172
0
                if (db) WITH_LOCK(::cs_main, (void)db->CompactFullAsync());
  Branch (172:21): [True: 0, False: 0]
173
0
            },
174
0
            [&] {
175
0
                uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
176
                // `CCoinsViewDB::BatchWrite()` requires a non-null best block.
177
0
                if (is_db && best_block.IsNull()) best_block = uint256::ONE;
  Branch (177:21): [True: 0, False: 0]
  Branch (177:30): [True: 0, False: 0]
178
0
                coins_view_cache.SetBestBlock(best_block);
179
0
            },
180
0
            [&] {
181
0
                (void)coins_view_cache.CreateResetGuard();
182
                // Reset() clears the best block, so reseed db-backed caches.
183
0
                if (is_db) {
  Branch (183:21): [True: 0, False: 0]
184
0
                    const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
185
0
                    if (best_block.IsNull()) {
  Branch (185:25): [True: 0, False: 0]
186
0
                        good_data = false;
187
0
                        return;
188
0
                    }
189
0
                    coins_view_cache.SetBestBlock(best_block);
190
0
                }
191
0
            },
192
0
            [&] {
193
0
                Coin move_to;
194
0
                (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
  Branch (194:68): [True: 0, False: 0]
195
0
            },
196
0
            [&] {
197
0
                coins_view_cache.Uncache(random_out_point);
198
0
            },
199
0
            [&] {
200
0
                if (overlay) return; // CoinsViewOverlay::SetBackend() is never called in production code
  Branch (200:21): [True: 0, False: 0]
201
0
                const bool use_original_backend{fuzzed_data_provider.ConsumeBool()};
202
0
                if (use_original_backend && backend_coins_view != original_backend) {
  Branch (202:21): [True: 0, False: 0]
  Branch (202:45): [True: 0, False: 0]
203
                    // FRESH flags valid against the empty backend may be invalid
204
                    // against the original backend, so reset before restoring it.
205
0
                    (void)coins_view_cache.CreateResetGuard();
206
                    // Reset() clears the best block; db backends require a non-null hash.
207
0
                    if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
  Branch (207:25): [True: 0, False: 0]
208
0
                }
209
0
                backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get();
  Branch (209:38): [True: 0, False: 0]
210
0
                coins_view_cache.SetBackend(*backend_coins_view);
211
0
            },
212
0
            [&] {
213
0
                const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
214
0
                if (!opt_out_point) {
  Branch (214:21): [True: 0, False: 0]
215
0
                    good_data = false;
216
0
                    return;
217
0
                }
218
0
                random_out_point = *opt_out_point;
219
0
            },
220
0
            [&] {
221
0
                const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
222
0
                if (!opt_coin) {
  Branch (222:21): [True: 0, False: 0]
223
0
                    good_data = false;
224
0
                    return;
225
0
                }
226
0
                random_coin = *opt_coin;
227
0
            },
228
0
            [&] {
229
0
                const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
230
0
                if (!opt_mutable_transaction) {
  Branch (230:21): [True: 0, False: 0]
231
0
                    good_data = false;
232
0
                    return;
233
0
                }
234
0
                random_mutable_transaction = *opt_mutable_transaction;
235
0
            },
236
0
            [&] {
237
0
                CoinsCachePair sentinel{};
238
0
                sentinel.second.SelfRef(sentinel);
239
0
                size_t dirty_count{0};
240
0
                CCoinsMapMemoryResource resource;
241
0
                CCoinsMap coins_map{0, SaltedCoinsCacheHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
242
0
                LIMITED_WHILE (good_data && fuzzed_data_provider.ConsumeBool(), 10'000) {
243
0
                    CCoinsCacheEntry coins_cache_entry;
244
0
                    if (fuzzed_data_provider.ConsumeBool()) {
  Branch (244:25): [True: 0, False: 0]
245
0
                        coins_cache_entry.coin = random_coin;
246
0
                    } else {
247
0
                        const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
248
0
                        if (!opt_coin) {
  Branch (248:29): [True: 0, False: 0]
249
0
                            good_data = false;
250
0
                            return;
251
0
                        }
252
0
                        coins_cache_entry.coin = *opt_coin;
253
0
                    }
254
                    // Avoid setting FRESH for an outpoint that already exists unspent in the parent view.
255
0
                    bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()};
  Branch (255:32): [True: 0, False: 0]
  Branch (255:80): [True: 0, False: 0]
256
0
                    bool dirty{fresh || fuzzed_data_provider.ConsumeBool()};
  Branch (256:32): [True: 0, False: 0]
  Branch (256:41): [True: 0, False: 0]
257
0
                    auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
258
0
                    if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
  Branch (258:25): [True: 0, False: 0]
259
0
                    if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
  Branch (259:25): [True: 0, False: 0]
260
0
                    dirty_count += dirty;
261
0
                }
262
0
                auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
263
0
                uint256 best_block{coins_view_cache.GetBestBlock()};
264
0
                if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
  Branch (264:21): [True: 0, False: 0]
265
                // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
266
0
                if (is_db && best_block.IsNull()) best_block = uint256::ONE;
  Branch (266:21): [True: 0, False: 0]
  Branch (266:30): [True: 0, False: 0]
267
0
                coins_view_cache.BatchWrite(cursor, best_block);
268
0
            });
269
0
    }
270
271
0
    {
272
0
        (void)coins_view_cache.DynamicMemoryUsage();
273
0
        (void)coins_view_cache.EstimateSize();
274
0
        (void)coins_view_cache.GetBestBlock();
275
0
        (void)coins_view_cache.GetCacheSize();
276
0
        (void)coins_view_cache.GetHeadBlocks();
277
0
        (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
278
0
    }
279
280
0
    {
281
0
        if (is_db && backend_coins_view == original_backend) {
  Branch (281:13): [True: 0, False: 0]
  Branch (281:22): [True: 0, False: 0]
282
0
            assert(db->Cursor());
  Branch (282:13): [True: 0, False: 0]
283
0
        }
284
0
        (void)backend_coins_view->EstimateSize();
285
0
        (void)backend_coins_view->GetBestBlock();
286
0
        (void)backend_coins_view->GetHeadBlocks();
287
0
    }
288
289
0
    if (fuzzed_data_provider.ConsumeBool()) {
  Branch (289:9): [True: 0, False: 0]
290
0
        CallOneOf(
291
0
            fuzzed_data_provider,
292
0
            [&] {
293
0
                const CTransaction transaction{random_mutable_transaction};
294
0
                bool is_spent = false;
295
0
                for (const CTxOut& tx_out : transaction.vout) {
  Branch (295:43): [True: 0, False: 0]
296
0
                    if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
  Branch (296:25): [True: 0, False: 0]
297
0
                        is_spent = true;
298
0
                    }
299
0
                }
300
0
                if (is_spent) {
  Branch (300:21): [True: 0, False: 0]
301
                    // Avoid:
302
                    // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
303
0
                    return;
304
0
                }
305
0
                const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
306
0
                const bool check_for_overwrite{transaction.IsCoinBase() || [&] {
  Branch (306:48): [True: 0, False: 0]
  Branch (306:76): [True: 0, False: 0]
307
0
                    for (uint32_t i{0}; i < transaction.vout.size(); ++i) {
  Branch (307:41): [True: 0, False: 0]
308
0
                        if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true;
  Branch (308:29): [True: 0, False: 0]
309
0
                    }
310
0
                    return fuzzed_data_provider.ConsumeBool();
311
0
                }()}; // We can only skip the check if the current txid has no unspent outputs
312
0
                AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
313
0
            },
314
0
            [&] {
315
0
                (void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
316
0
            },
317
0
            [&] {
318
0
                TxValidationState state;
319
0
                CAmount tx_fee_out;
320
0
                const CTransaction transaction{random_mutable_transaction};
321
0
                if (ContainsSpentInput(transaction, coins_view_cache)) {
  Branch (321:21): [True: 0, False: 0]
322
                    // Avoid:
323
                    // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
324
0
                    return;
325
0
                }
326
0
                TxValidationState dummy;
327
0
                if (!CheckTransaction(transaction, dummy)) {
  Branch (327:21): [True: 0, False: 0]
328
                    // It is not allowed to call CheckTxInputs if CheckTransaction failed
329
0
                    return;
330
0
                }
331
0
                if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
  Branch (331:21): [True: 0, False: 0]
332
0
                    assert(MoneyRange(tx_fee_out));
  Branch (332:21): [True: 0, False: 0]
333
0
                }
334
0
            },
335
0
            [&] {
336
0
                const CTransaction transaction{random_mutable_transaction};
337
0
                if (ContainsSpentInput(transaction, coins_view_cache)) {
  Branch (337:21): [True: 0, False: 0]
338
                    // Avoid:
339
                    // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
340
0
                    return;
341
0
                }
342
0
                (void)GetP2SHSigOpCount(transaction, coins_view_cache);
343
0
            },
344
0
            [&] {
345
0
                const CTransaction transaction{random_mutable_transaction};
346
0
                if (ContainsSpentInput(transaction, coins_view_cache)) {
  Branch (346:21): [True: 0, False: 0]
347
                    // Avoid:
348
                    // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
349
0
                    return;
350
0
                }
351
0
                const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>());
352
0
                if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
  Branch (352:21): [True: 0, False: 0]
  Branch (352:21): [True: 0, False: 0]
  Branch (352:49): [True: 0, False: 0]
  Branch (352:89): [True: 0, False: 0]
353
                    // Avoid:
354
                    // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness &, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
355
0
                    return;
356
0
                }
357
0
                (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
358
0
            },
359
0
            [&] {
360
0
                (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
361
0
            });
362
0
    }
363
364
0
    {
365
0
        const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
366
0
        const bool exists_using_access_coin = !coin_using_access_coin.IsSpent();
367
0
        const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
368
0
        const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
369
0
        if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
  Branch (369:18): [True: 0, False: 0]
370
0
            assert(*coin == coin_using_access_coin);
  Branch (370:13): [True: 0, False: 0]
371
0
            assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
  Branch (371:13): [True: 0, False: 0]
  Branch (371:13): [True: 0, False: 0]
  Branch (371:13): [True: 0, False: 0]
  Branch (371:13): [True: 0, False: 0]
372
0
        } else {
373
0
            assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
  Branch (373:13): [True: 0, False: 0]
  Branch (373:13): [True: 0, False: 0]
  Branch (373:13): [True: 0, False: 0]
  Branch (373:13): [True: 0, False: 0]
374
0
        }
375
        // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
376
0
        std::optional<Coin> coin_in_backend;
377
0
        bool exists_using_have_coin_in_backend;
378
0
        if (overlay) {
  Branch (378:13): [True: 0, False: 0]
379
            // PeekCoin does not mutate cacheCoins, so async workers can keep running.
380
0
            coin_in_backend = backend_coins_view->PeekCoin(random_out_point);
381
0
            exists_using_have_coin_in_backend = coin_in_backend.has_value();
382
0
        } else {
383
0
            exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point);
384
0
            coin_in_backend = backend_coins_view->GetCoin(random_out_point);
385
0
        }
386
0
        if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
  Branch (386:13): [True: 0, False: 0]
  Branch (386:50): [True: 0, False: 0]
387
0
            assert(exists_using_have_coin);
  Branch (387:13): [True: 0, False: 0]
388
0
        }
389
0
        if (coin_in_backend) {
  Branch (389:13): [True: 0, False: 0]
390
0
            assert(exists_using_have_coin_in_backend);
  Branch (390:13): [True: 0, False: 0]
391
            // Note we can't assert that `coin_using_get_coin == *coin` because the coin in
392
            // the cache may have been modified but not yet flushed.
393
0
        } else {
394
0
            assert(!exists_using_have_coin_in_backend);
  Branch (394:13): [True: 0, False: 0]
395
0
        }
396
0
    }
397
0
}
398
399
FUZZ_TARGET(coins_view, .init = initialize_coins_view)
400
0
{
401
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
402
0
    CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
403
0
    TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get());
404
0
}
405
406
FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
407
0
{
408
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
409
0
    auto db_params = DBParams{
410
0
        .path = "",
411
0
        .cache_bytes = 1_MiB,
412
0
        .memory_only = true,
413
0
    };
414
0
    CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
415
0
    CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
416
0
    TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view);
417
0
}
418
419
// Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base.
420
// This allows us to exercise all methods on a CoinsViewOverlay, while also
421
// ensuring that nothing can mutate the underlying cache until Flush or Sync is
422
// called.
423
FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
424
0
{
425
0
    SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedCoinsCacheHasher
426
0
    StartPoolIfNeeded();
427
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
428
0
    MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
429
0
    CoinsViewOverlay coins_view_cache{&backend_cache, g_thread_pool, /*deterministic=*/true};
430
0
    CBlock block{BuildRandomBlock(fuzzed_data_provider, backend_cache)};
431
0
    const auto reset_guard{coins_view_cache.StartFetching(block)};
432
0
    TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
433
0
}
434
435
FUZZ_TARGET(coins_view_stacked, .init = initialize_coins_view)
436
0
{
437
0
    SeedRandomStateForTest(SeedRand::ZEROS); // for SaltedCoinsCacheHasher
438
0
    StartPoolIfNeeded();
439
0
    FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
440
0
    auto db_params = DBParams{
441
0
        .path = "",
442
0
        .cache_bytes = 1_MiB,
443
0
        .memory_only = true,
444
0
    };
445
0
    CCoinsViewDB backend_base_coins_view{std::move(db_params), CoinsViewOptions{}};
446
0
    CCoinsViewCache backend_cache{&backend_base_coins_view, /*deterministic=*/true};
447
0
    TestCoinsView(fuzzed_data_provider, backend_cache, &backend_base_coins_view);
448
0
    CoinsViewOverlay coins_view_cache{&backend_cache, g_thread_pool, /*deterministic=*/true};
449
0
    CBlock block{BuildRandomBlock(fuzzed_data_provider, backend_base_coins_view)};
450
0
    {
451
0
        const auto reset_guard{coins_view_cache.StartFetching(block)};
452
0
        TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
453
0
    }
454
0
    TestCoinsView(fuzzed_data_provider, backend_cache, &backend_base_coins_view);
455
0
}