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 : 40670 : bool operator==(const Coin& a, const Coin& b)
37 : : {
38 [ + + - + ]: 40670 : if (a.IsSpent() && b.IsSpent()) return true;
39 [ + + + + : 36284 : 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 [ - - ]: 109266 : struct CacheCoinSnapshot {
53 : 27095 : COutPoint outpoint;
54 : 27095 : bool dirty{false};
55 : 27095 : bool fresh{false};
56 : 27095 : Coin coin;
57 [ + - + - : 27095 : bool operator==(const CacheCoinSnapshot&) const = default;
+ - - + ]
58 : : };
59 : :
60 : 59694 : std::vector<CacheCoinSnapshot> ComputeCacheCoinsSnapshot() const
61 : : {
62 : 59694 : std::vector<CacheCoinSnapshot> snapshot;
63 [ + - ]: 59694 : snapshot.reserve(cacheCoins.size());
64 : :
65 [ + + + - ]: 115415 : for (const auto& [outpoint, entry] : cacheCoins) {
66 [ + - ]: 55721 : snapshot.emplace_back(outpoint, entry.IsDirty(), entry.IsFresh(), entry.coin);
67 : : }
68 : :
69 : 59694 : std::ranges::sort(snapshot, std::less<>{}, &CacheCoinSnapshot::outpoint);
70 : 59694 : return snapshot;
71 : 0 : }
72 : :
73 : : mutable std::vector<CacheCoinSnapshot> m_expected_snapshot{ComputeCacheCoinsSnapshot()};
74 : :
75 : : public:
76 : 29024 : void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override
77 : : {
78 : : // Nothing must modify cacheCoins other than BatchWrite.
79 [ - + ]: 29024 : assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
80 : 29024 : CCoinsViewCache::BatchWrite(cursor, block_hash);
81 : 29024 : m_expected_snapshot = ComputeCacheCoinsSnapshot();
82 : 29024 : }
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 : 7985 : void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view)
94 : : {
95 [ + - ]: 7985 : const bool is_db{dynamic_cast<CCoinsViewDB*>(backend_coins_view) != nullptr};
96 : 7985 : bool good_data{true};
97 : 7985 : auto* original_backend{backend_coins_view};
98 : :
99 [ + + ]: 7985 : if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
100 : 7985 : COutPoint random_out_point;
101 : 7985 : Coin random_coin;
102 [ + - ]: 7985 : CMutableTransaction random_mutable_transaction;
103 [ + + + + : 8522773 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
+ + ]
104 : : {
105 [ + - ]: 4254029 : CallOneOf(
106 : : fuzzed_data_provider,
107 : 88543 : [&] {
108 [ + + ]: 88543 : if (random_coin.IsSpent()) {
109 : : return;
110 : : }
111 : 83206 : COutPoint outpoint{random_out_point};
112 : 83206 : Coin coin{random_coin};
113 [ + + ]: 83206 : if (fuzzed_data_provider.ConsumeBool()) {
114 : : // We can only skip the check if no unspent coin exists for this outpoint.
115 [ + - + + : 111695 : const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()};
+ + ]
116 [ + - ]: 63532 : coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite);
117 : : } else {
118 [ + - ]: 19674 : coins_view_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin));
119 : : }
120 : 83206 : },
121 : 3390093 : [&] {
122 : 3390093 : coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool());
123 : 3390093 : },
124 : 20011 : [&] {
125 : 20011 : coins_view_cache.Sync();
126 : 20011 : },
127 : 74101 : [&] {
128 : 74101 : uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
129 : : // `CCoinsViewDB::BatchWrite()` requires a non-null best block.
130 [ + + + + ]: 144539 : if (is_db && best_block.IsNull()) best_block = uint256::ONE;
131 : 74101 : coins_view_cache.SetBestBlock(best_block);
132 : 74101 : },
133 : 1970 : [&] {
134 : 1970 : (void)coins_view_cache.CreateResetGuard();
135 : : // Reset() clears the best block, so reseed db-backed caches.
136 [ + + ]: 1970 : if (is_db) {
137 : 361 : const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
138 [ + + ]: 722 : if (best_block.IsNull()) {
139 : 30 : good_data = false;
140 : 30 : return;
141 : : }
142 : 331 : coins_view_cache.SetBestBlock(best_block);
143 : : }
144 : : },
145 : 574570 : [&] {
146 : 574570 : Coin move_to;
147 [ + + + - ]: 595752 : (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
148 : 574570 : },
149 : 6608 : [&] {
150 : 6608 : coins_view_cache.Uncache(random_out_point);
151 : 6608 : },
152 : 38137 : [&] {
153 : 38137 : const bool use_original_backend{fuzzed_data_provider.ConsumeBool()};
154 [ + + + + ]: 38137 : 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 : 132 : (void)coins_view_cache.CreateResetGuard();
158 : : // Reset() clears the best block; db backends require a non-null hash.
159 [ + + ]: 132 : if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
160 : : }
161 : 38137 : backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get();
162 : 38137 : coins_view_cache.SetBackend(*backend_coins_view);
163 : 38137 : },
164 : 4898 : [&] {
165 : 4898 : const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
166 [ + + ]: 4898 : if (!opt_out_point) {
167 : 109 : good_data = false;
168 : 109 : return;
169 : : }
170 : 4789 : random_out_point = *opt_out_point;
171 : : },
172 : 24091 : [&] {
173 : 24091 : const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
174 [ + + ]: 24091 : if (!opt_coin) {
175 : 308 : good_data = false;
176 : 308 : return;
177 : : }
178 : 23783 : random_coin = *opt_coin;
179 : 24091 : },
180 : 13320 : [&] {
181 : 13320 : const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
182 [ + + ]: 13320 : if (!opt_mutable_transaction) {
183 : 681 : good_data = false;
184 [ - + ]: 681 : return;
185 : : }
186 [ + - ]: 12639 : random_mutable_transaction = *opt_mutable_transaction;
187 : 13320 : },
188 : 17687 : [&] {
189 : 17687 : CoinsCachePair sentinel{};
190 : 17687 : sentinel.second.SelfRef(sentinel);
191 : 17687 : size_t dirty_count{0};
192 [ + - ]: 17687 : CCoinsMapMemoryResource resource;
193 [ + - + - ]: 17687 : CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
194 [ + - + + : 958408 : LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
+ + ]
195 : : {
196 : 940848 : CCoinsCacheEntry coins_cache_entry;
197 [ + + ]: 940848 : if (fuzzed_data_provider.ConsumeBool()) {
198 : 937338 : coins_cache_entry.coin = random_coin;
199 : : } else {
200 : 3510 : const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
201 [ + + ]: 3510 : if (!opt_coin) {
202 : 127 : good_data = false;
203 : 127 : return;
204 : : }
205 : 3383 : coins_cache_entry.coin = *opt_coin;
206 : 3383 : }
207 : : // Avoid setting FRESH for an outpoint that already exists unspent in the parent view.
208 [ + - + + : 944937 : bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()};
+ + ]
209 [ + + + + ]: 940721 : bool dirty{fresh || fuzzed_data_provider.ConsumeBool()};
210 [ + - ]: 940721 : auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
211 [ + + ]: 940721 : if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
212 [ + + ]: 940721 : if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
213 : 940721 : dirty_count += dirty;
214 : 940848 : }
215 [ + - ]: 17560 : auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
216 [ + - ]: 17560 : uint256 best_block{coins_view_cache.GetBestBlock()};
217 [ + + ]: 17560 : 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 [ + + + + ]: 20349 : if (is_db && best_block.IsNull()) best_block = uint256::ONE;
220 [ + - ]: 17560 : coins_view_cache.BatchWrite(cursor, best_block);
221 : 17687 : });
222 : : }
223 : :
224 : 7985 : {
225 : 7985 : bool expected_code_path = false;
226 : 7985 : try {
227 [ - + ]: 7985 : (void)coins_view_cache.Cursor();
228 [ - + ]: 7985 : } catch (const std::logic_error&) {
229 : 7985 : expected_code_path = true;
230 : 7985 : }
231 : 0 : assert(expected_code_path);
232 [ + - ]: 7985 : (void)coins_view_cache.DynamicMemoryUsage();
233 [ + - ]: 7985 : (void)coins_view_cache.EstimateSize();
234 [ + - ]: 7985 : (void)coins_view_cache.GetBestBlock();
235 [ + - ]: 7985 : (void)coins_view_cache.GetCacheSize();
236 [ + - ]: 7985 : (void)coins_view_cache.GetHeadBlocks();
237 [ + - + - ]: 7985 : (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
238 : : }
239 : :
240 : 7985 : {
241 [ + + + + ]: 7985 : if (is_db && backend_coins_view == original_backend) {
242 [ + - - + ]: 2657 : assert(backend_coins_view->Cursor());
243 : : }
244 [ + - ]: 7985 : (void)backend_coins_view->EstimateSize();
245 [ + - ]: 7985 : (void)backend_coins_view->GetBestBlock();
246 [ + - ]: 7985 : (void)backend_coins_view->GetHeadBlocks();
247 : : }
248 : :
249 [ + + ]: 7985 : if (fuzzed_data_provider.ConsumeBool()) {
250 [ + - ]: 5900 : CallOneOf(
251 : : fuzzed_data_provider,
252 : 454 : [&] {
253 : 454 : const CTransaction transaction{random_mutable_transaction};
254 : 454 : bool is_spent = false;
255 [ + + ]: 503790 : for (const CTxOut& tx_out : transaction.vout) {
256 [ + + ]: 503336 : if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
257 : 40 : is_spent = true;
258 : : }
259 : : }
260 [ + + ]: 454 : if (is_spent) {
261 : : // Avoid:
262 : : // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
263 : 20 : return;
264 : : }
265 : 434 : const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
266 [ + + + + ]: 848 : const bool check_for_overwrite{transaction.IsCoinBase() || [&] {
267 [ - + + + ]: 372380 : for (uint32_t i{0}; i < transaction.vout.size(); ++i) {
268 [ + + ]: 371972 : if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true;
269 : : }
270 : 408 : return fuzzed_data_provider.ConsumeBool();
271 [ + - ]: 414 : }()}; // We can only skip the check if the current txid has no unspent outputs
272 [ + - ]: 434 : AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
273 : 454 : },
274 : 4003 : [&] {
275 [ + - ]: 8006 : (void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
276 : 4003 : },
277 : 280 : [&] {
278 [ + - ]: 280 : TxValidationState state;
279 : 280 : CAmount tx_fee_out;
280 [ + - ]: 280 : const CTransaction transaction{random_mutable_transaction};
281 [ + + ]: 280 : 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 [ + - ]: 257 : TxValidationState dummy;
287 [ + - + + ]: 257 : if (!CheckTransaction(transaction, dummy)) {
288 : : // It is not allowed to call CheckTxInputs if CheckTransaction failed
289 : 164 : return;
290 : : }
291 [ + - + + ]: 93 : if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
292 [ - + ]: 9 : assert(MoneyRange(tx_fee_out));
293 : : }
294 : 817 : },
295 : 200 : [&] {
296 : 200 : const CTransaction transaction{random_mutable_transaction};
297 [ + + ]: 200 : 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 : 84 : return;
301 : : }
302 [ + - ]: 116 : (void)GetP2SHSigOpCount(transaction, coins_view_cache);
303 : 200 : },
304 : 542 : [&] {
305 : 542 : const CTransaction transaction{random_mutable_transaction};
306 [ + + ]: 542 : 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 [ + + ]: 483 : const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>());
312 [ + + + + : 483 : 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 [ + - ]: 478 : (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
318 : 542 : },
319 : 421 : [&] {
320 [ + - ]: 421 : (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
321 : 421 : });
322 : : }
323 : :
324 : 7985 : {
325 [ + - ]: 7985 : const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
326 : 7985 : const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
327 [ + - ]: 7985 : const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
328 [ + - ]: 7985 : const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
329 [ + - + + ]: 7985 : if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
330 [ - + ]: 5590 : assert(*coin == coin_using_access_coin);
331 [ + - - + ]: 5590 : assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
332 : : } else {
333 [ + - - + ]: 2395 : assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
334 : 7985 : }
335 : : // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
336 [ + - ]: 7985 : const bool exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point);
337 [ + + + + ]: 7985 : if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
338 [ - + ]: 820 : assert(exists_using_have_coin);
339 : : }
340 [ + - + + ]: 7985 : if (auto coin{backend_coins_view->GetCoin(random_out_point)}) {
341 [ - + ]: 861 : 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 [ - + ]: 7124 : assert(!exists_using_have_coin_in_backend);
346 : 7985 : }
347 : : }
348 : 7985 : }
349 : :
350 [ + - ]: 3224 : FUZZ_TARGET(coins_view, .init = initialize_coins_view)
351 : : {
352 : 2766 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
353 : 2766 : CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
354 [ + - + - ]: 2766 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get());
355 : 2766 : }
356 : :
357 [ + - ]: 4031 : FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
358 : : {
359 : 3573 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
360 : 3573 : auto db_params = DBParams{
361 : : .path = "",
362 : : .cache_bytes = 1_MiB,
363 : : .memory_only = true,
364 : 3573 : };
365 [ + - ]: 3573 : CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
366 [ + - ]: 3573 : CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
367 [ + - ]: 3573 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view);
368 : 7146 : }
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 [ + - ]: 2104 : FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
375 : : {
376 : 1646 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
377 : 1646 : MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
378 [ + - ]: 1646 : CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
379 [ + - ]: 1646 : TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
380 : 1646 : }
|