Branch data Line data Source code
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/cs_main.h>
11 : : #include <policy/policy.h>
12 : : #include <primitives/transaction.h>
13 : : #include <script/interpreter.h>
14 : : #include <test/fuzz/FuzzedDataProvider.h>
15 : : #include <test/fuzz/fuzz.h>
16 : : #include <test/fuzz/util.h>
17 : : #include <test/util/setup_common.h>
18 : : #include <txdb.h>
19 : : #include <util/hasher.h>
20 : :
21 : : #include <cassert>
22 : : #include <algorithm>
23 : : #include <cstdint>
24 : : #include <functional>
25 : : #include <limits>
26 : : #include <memory>
27 : : #include <optional>
28 : : #include <ranges>
29 : : #include <stdexcept>
30 : : #include <string>
31 : : #include <utility>
32 : : #include <vector>
33 : :
34 : : namespace {
35 : : const Coin EMPTY_COIN{};
36 : :
37 : 11946 : bool operator==(const Coin& a, const Coin& b)
38 : : {
39 [ + + - + ]: 11946 : if (a.IsSpent() && b.IsSpent()) return true;
40 [ + + + + : 1662 : return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
+ + ]
41 : : }
42 : :
43 : : /**
44 : : * MutationGuardCoinsViewCache asserts that nothing mutates cacheCoins until
45 : : * BatchWrite is called. It keeps a snapshot of the cacheCoins state, which it
46 : : * uses for the assertion in BatchWrite. After the call to the superclass
47 : : * CCoinsViewCache::BatchWrite returns, it recomputes the snapshot at that
48 : : * moment.
49 : : */
50 : : class MutationGuardCoinsViewCache final : public CCoinsViewCache
51 : : {
52 : : private:
53 [ - - ]: 9 : struct CacheCoinSnapshot {
54 : 0 : COutPoint outpoint;
55 : 0 : bool dirty{false};
56 : 0 : bool fresh{false};
57 : 0 : Coin coin;
58 [ # # # # : 0 : bool operator==(const CacheCoinSnapshot&) const = default;
# # # # ]
59 : : };
60 : :
61 : 7191 : std::vector<CacheCoinSnapshot> ComputeCacheCoinsSnapshot() const
62 : : {
63 : 7191 : std::vector<CacheCoinSnapshot> snapshot;
64 [ + - ]: 7191 : snapshot.reserve(cacheCoins.size());
65 : :
66 [ + + + - ]: 7200 : for (const auto& [outpoint, entry] : cacheCoins) {
67 [ + - ]: 9 : snapshot.emplace_back(outpoint, entry.IsDirty(), entry.IsFresh(), entry.coin);
68 : : }
69 : :
70 : 7191 : std::ranges::sort(snapshot, std::less<>{}, &CacheCoinSnapshot::outpoint);
71 : 7191 : return snapshot;
72 : 0 : }
73 : :
74 : : mutable std::vector<CacheCoinSnapshot> m_expected_snapshot{ComputeCacheCoinsSnapshot()};
75 : :
76 : : public:
77 : 2087 : void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override
78 : : {
79 : : // Nothing must modify cacheCoins other than BatchWrite.
80 [ - + ]: 2087 : assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
81 : 2087 : CCoinsViewCache::BatchWrite(cursor, block_hash);
82 : 2087 : m_expected_snapshot = ComputeCacheCoinsSnapshot();
83 : 2087 : }
84 : :
85 : : using CCoinsViewCache::CCoinsViewCache;
86 : : };
87 : : } // namespace
88 : :
89 : 3 : void initialize_coins_view()
90 : : {
91 [ + - + - : 3 : static const auto testing_setup = MakeNoLogFileContext<>();
+ - ]
92 : 3 : }
93 : :
94 : 11115 : void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view)
95 : : {
96 [ + - ]: 11115 : auto* const db{dynamic_cast<CCoinsViewDB*>(backend_coins_view)};
97 : 11115 : const bool is_db{db != nullptr};
98 : 11115 : bool good_data{true};
99 : 11115 : auto* original_backend{backend_coins_view};
100 : :
101 [ + + ]: 11115 : if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
102 : 11115 : COutPoint random_out_point;
103 : 11115 : Coin random_coin;
104 [ + - ]: 11115 : CMutableTransaction random_mutable_transaction;
105 [ + + + + : 775861 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
+ + ]
106 : : {
107 [ + - ]: 377547 : CallOneOf(
108 : : fuzzed_data_provider,
109 : 13924 : [&] {
110 [ + + ]: 13924 : if (random_coin.IsSpent()) {
111 : : return;
112 : : }
113 : 977 : COutPoint outpoint{random_out_point};
114 : 977 : Coin coin{random_coin};
115 [ + + ]: 977 : if (fuzzed_data_provider.ConsumeBool()) {
116 : : // We can only skip the check if no unspent coin exists for this outpoint.
117 [ + - + + : 1030 : const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()};
+ + ]
118 [ + - ]: 857 : coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite);
119 : : } else {
120 [ + - ]: 120 : coins_view_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin));
121 : : }
122 : 977 : },
123 : 186805 : [&] {
124 : 186805 : coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool());
125 : 186805 : },
126 : 14454 : [&] {
127 : 14454 : coins_view_cache.Sync();
128 : 14454 : },
129 : 9959 : [&] {
130 [ + + + - : 34814 : if (db) WITH_LOCK(::cs_main, (void)db->CompactFullAsync());
+ - ]
131 : 9959 : },
132 : 18359 : [&] {
133 : 18359 : uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
134 : : // `CCoinsViewDB::BatchWrite()` requires a non-null best block.
135 [ + + + + ]: 33321 : if (is_db && best_block.IsNull()) best_block = uint256::ONE;
136 : 18359 : coins_view_cache.SetBestBlock(best_block);
137 : 18359 : },
138 : 2005 : [&] {
139 : 2005 : (void)coins_view_cache.CreateResetGuard();
140 : : // Reset() clears the best block, so reseed db-backed caches.
141 [ + + ]: 2005 : if (is_db) {
142 : 1132 : const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
143 [ + + ]: 2264 : if (best_block.IsNull()) {
144 : 34 : good_data = false;
145 : 34 : return;
146 : : }
147 : 1098 : coins_view_cache.SetBestBlock(best_block);
148 : : }
149 : : },
150 : 98733 : [&] {
151 : 98733 : Coin move_to;
152 [ + + + - ]: 99376 : (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
153 : 98733 : },
154 : 2270 : [&] {
155 : 2270 : coins_view_cache.Uncache(random_out_point);
156 : 2270 : },
157 : 16492 : [&] {
158 : 16492 : const bool use_original_backend{fuzzed_data_provider.ConsumeBool()};
159 [ + + + + ]: 16492 : if (use_original_backend && backend_coins_view != original_backend) {
160 : : // FRESH flags valid against the empty backend may be invalid
161 : : // against the original backend, so reset before restoring it.
162 : 24 : (void)coins_view_cache.CreateResetGuard();
163 : : // Reset() clears the best block; db backends require a non-null hash.
164 [ + + ]: 24 : if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
165 : : }
166 : 16492 : backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get();
167 : 16492 : coins_view_cache.SetBackend(*backend_coins_view);
168 : 16492 : },
169 : 4977 : [&] {
170 : 4977 : const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
171 [ + + ]: 4977 : if (!opt_out_point) {
172 : 328 : good_data = false;
173 : 328 : return;
174 : : }
175 : 4649 : random_out_point = *opt_out_point;
176 : : },
177 : 6063 : [&] {
178 : 6063 : const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
179 [ + + ]: 6063 : if (!opt_coin) {
180 : 546 : good_data = false;
181 : 546 : return;
182 : : }
183 : 5517 : random_coin = *opt_coin;
184 : 6063 : },
185 : 700 : [&] {
186 : 700 : const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
187 [ + + ]: 700 : if (!opt_mutable_transaction) {
188 : 439 : good_data = false;
189 [ - + ]: 439 : return;
190 : : }
191 [ + - ]: 261 : random_mutable_transaction = *opt_mutable_transaction;
192 : 700 : },
193 : 2806 : [&] {
194 : 2806 : CoinsCachePair sentinel{};
195 : 2806 : sentinel.second.SelfRef(sentinel);
196 : 2806 : size_t dirty_count{0};
197 [ + - ]: 2806 : CCoinsMapMemoryResource resource;
198 [ + - + - ]: 2806 : CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
199 [ + - + + : 413553 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
+ + ]
200 : : {
201 : 410863 : CCoinsCacheEntry coins_cache_entry;
202 [ + + ]: 410863 : if (fuzzed_data_provider.ConsumeBool()) {
203 : 410052 : coins_cache_entry.coin = random_coin;
204 : : } else {
205 : 811 : const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
206 [ + + ]: 811 : if (!opt_coin) {
207 : 116 : good_data = false;
208 : 116 : return;
209 : : }
210 : 695 : coins_cache_entry.coin = *opt_coin;
211 : 695 : }
212 : : // Avoid setting FRESH for an outpoint that already exists unspent in the parent view.
213 [ + - + + : 411137 : bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()};
+ + ]
214 [ + + + + ]: 410747 : bool dirty{fresh || fuzzed_data_provider.ConsumeBool()};
215 [ + - ]: 410747 : auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
216 [ + + ]: 410747 : if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
217 [ + + ]: 410747 : if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
218 : 410747 : dirty_count += dirty;
219 : 410863 : }
220 [ + - ]: 2690 : auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
221 [ + - ]: 2690 : uint256 best_block{coins_view_cache.GetBestBlock()};
222 [ + + ]: 2690 : if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
223 : : // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
224 [ + + + + ]: 3078 : if (is_db && best_block.IsNull()) best_block = uint256::ONE;
225 [ + - ]: 2690 : coins_view_cache.BatchWrite(cursor, best_block);
226 : 2806 : });
227 : : }
228 : :
229 : 11115 : {
230 : 11115 : bool expected_code_path = false;
231 : 11115 : try {
232 [ - + ]: 11115 : (void)coins_view_cache.Cursor();
233 [ - + ]: 11115 : } catch (const std::logic_error&) {
234 : 11115 : expected_code_path = true;
235 : 11115 : }
236 : 0 : assert(expected_code_path);
237 [ + - ]: 11115 : (void)coins_view_cache.DynamicMemoryUsage();
238 [ + - ]: 11115 : (void)coins_view_cache.EstimateSize();
239 [ + - ]: 11115 : (void)coins_view_cache.GetBestBlock();
240 [ + - ]: 11115 : (void)coins_view_cache.GetCacheSize();
241 [ + - ]: 11115 : (void)coins_view_cache.GetHeadBlocks();
242 [ + - + - ]: 11115 : (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
243 : : }
244 : :
245 : 11115 : {
246 [ + + + + ]: 11115 : if (is_db && backend_coins_view == original_backend) {
247 [ + - - + ]: 4446 : assert(backend_coins_view->Cursor());
248 : : }
249 [ + - ]: 11115 : (void)backend_coins_view->EstimateSize();
250 [ + - ]: 11115 : (void)backend_coins_view->GetBestBlock();
251 [ + - ]: 11115 : (void)backend_coins_view->GetHeadBlocks();
252 : : }
253 : :
254 [ + + ]: 11115 : if (fuzzed_data_provider.ConsumeBool()) {
255 [ + - ]: 6942 : CallOneOf(
256 : : fuzzed_data_provider,
257 : 848 : [&] {
258 : 848 : const CTransaction transaction{random_mutable_transaction};
259 : 848 : bool is_spent = false;
260 [ + + ]: 1165 : for (const CTxOut& tx_out : transaction.vout) {
261 [ - + ]: 317 : if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
262 : 0 : is_spent = true;
263 : : }
264 : : }
265 [ - + ]: 848 : if (is_spent) {
266 : : // Avoid:
267 : : // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
268 : 0 : return;
269 : : }
270 : 848 : const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
271 [ + - + + ]: 1696 : const bool check_for_overwrite{transaction.IsCoinBase() || [&] {
272 [ - + + + ]: 1165 : for (uint32_t i{0}; i < transaction.vout.size(); ++i) {
273 [ + - ]: 317 : if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true;
274 : : }
275 : 848 : return fuzzed_data_provider.ConsumeBool();
276 [ + - ]: 848 : }()}; // We can only skip the check if the current txid has no unspent outputs
277 [ + - ]: 848 : AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
278 : 848 : },
279 : 2298 : [&] {
280 [ + - ]: 6894 : (void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
281 : 2298 : },
282 : 720 : [&] {
283 [ + - ]: 720 : TxValidationState state;
284 : 720 : CAmount tx_fee_out;
285 [ + - ]: 720 : const CTransaction transaction{random_mutable_transaction};
286 [ + + ]: 720 : if (ContainsSpentInput(transaction, coins_view_cache)) {
287 : : // Avoid:
288 : : // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
289 : : return;
290 : : }
291 [ + - ]: 719 : TxValidationState dummy;
292 [ + - + - ]: 719 : if (!CheckTransaction(transaction, dummy)) {
293 : : // It is not allowed to call CheckTxInputs if CheckTransaction failed
294 : 719 : return;
295 : : }
296 [ # # # # ]: 0 : if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
297 [ # # ]: 0 : assert(MoneyRange(tx_fee_out));
298 : : }
299 : 2159 : },
300 : 1505 : [&] {
301 : 1505 : const CTransaction transaction{random_mutable_transaction};
302 [ + + ]: 1505 : if (ContainsSpentInput(transaction, coins_view_cache)) {
303 : : // Avoid:
304 : : // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
305 : 17 : return;
306 : : }
307 [ + - ]: 1488 : (void)GetP2SHSigOpCount(transaction, coins_view_cache);
308 : 1505 : },
309 : 635 : [&] {
310 : 635 : const CTransaction transaction{random_mutable_transaction};
311 [ + + ]: 635 : if (ContainsSpentInput(transaction, coins_view_cache)) {
312 : : // Avoid:
313 : : // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
314 : : return;
315 : : }
316 [ - + ]: 600 : const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>());
317 [ - + - - : 600 : if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
- - ]
318 : : // Avoid:
319 : : // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness &, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
320 : : return;
321 : : }
322 [ + - ]: 600 : (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
323 : 635 : },
324 : 936 : [&] {
325 [ + - ]: 936 : (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
326 : 936 : });
327 : : }
328 : :
329 : 11115 : {
330 [ + - ]: 11115 : const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
331 : 11115 : const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
332 [ + - ]: 11115 : const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
333 [ + - ]: 11115 : const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
334 [ + - + + ]: 11115 : if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
335 [ - + ]: 831 : assert(*coin == coin_using_access_coin);
336 [ + - - + ]: 831 : assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
337 : : } else {
338 [ + - - + ]: 10284 : assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
339 : 11115 : }
340 : : // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
341 [ + - ]: 11115 : const bool exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point);
342 [ + + + + ]: 11115 : if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
343 [ - + ]: 28 : assert(exists_using_have_coin);
344 : : }
345 [ + - + + ]: 11115 : if (auto coin{backend_coins_view->GetCoin(random_out_point)}) {
346 [ - + ]: 29 : assert(exists_using_have_coin_in_backend);
347 : : // Note we can't assert that `coin_using_get_coin == *coin` because the coin in
348 : : // the cache may have been modified but not yet flushed.
349 : : } else {
350 [ - + ]: 11086 : assert(!exists_using_have_coin_in_backend);
351 : 11115 : }
352 : : }
353 : 11115 : }
354 : :
355 [ + - ]: 4017 : FUZZ_TARGET(coins_view, .init = initialize_coins_view)
356 : : {
357 : 3547 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
358 : 3547 : CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
359 [ + - + - ]: 3547 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get());
360 : 3547 : }
361 : :
362 [ + - ]: 5021 : FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
363 : : {
364 : 4551 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
365 : 4551 : auto db_params = DBParams{
366 : : .path = "",
367 : : .cache_bytes = 1_MiB,
368 : : .memory_only = true,
369 : 4551 : };
370 [ + - ]: 4551 : CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
371 [ + - ]: 4551 : CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
372 [ + - ]: 4551 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view);
373 : 9102 : }
374 : :
375 : : // Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base.
376 : : // This allows us to exercise all methods on a CoinsViewOverlay, while also
377 : : // ensuring that nothing can mutate the underlying cache until Flush or Sync is
378 : : // called.
379 [ + - ]: 3487 : FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
380 : : {
381 : 3017 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
382 : 3017 : MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
383 [ + - ]: 3017 : CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
384 [ + - ]: 3017 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
385 : 3017 : }
|