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