/root/bitcoin/src/test/fuzz/coins_view.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2020-2022 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 <util/hasher.h> |
18 | | |
19 | | #include <cassert> |
20 | | #include <cstdint> |
21 | | #include <limits> |
22 | | #include <memory> |
23 | | #include <optional> |
24 | | #include <stdexcept> |
25 | | #include <string> |
26 | | #include <utility> |
27 | | #include <vector> |
28 | | |
29 | | namespace { |
30 | | const Coin EMPTY_COIN{}; |
31 | | |
32 | | bool operator==(const Coin& a, const Coin& b) |
33 | 0 | { |
34 | 0 | if (a.IsSpent() && b.IsSpent()) return true; |
35 | 0 | return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out; |
36 | 0 | } |
37 | | } // namespace |
38 | | |
39 | | void initialize_coins_view() |
40 | 0 | { |
41 | 0 | static const auto testing_setup = MakeNoLogFileContext<>(); |
42 | 0 | } |
43 | | |
44 | | FUZZ_TARGET(coins_view, .init = initialize_coins_view) |
45 | 0 | { |
46 | 0 | FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; |
47 | 0 | bool good_data{true}; |
48 | |
|
49 | 0 | CCoinsView backend_coins_view; |
50 | 0 | CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true}; |
51 | 0 | COutPoint random_out_point; |
52 | 0 | Coin random_coin; |
53 | 0 | CMutableTransaction random_mutable_transaction; |
54 | 0 | LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) |
55 | 0 | { |
56 | 0 | CallOneOf( |
57 | 0 | fuzzed_data_provider, |
58 | 0 | [&] { |
59 | 0 | if (random_coin.IsSpent()) { |
60 | 0 | return; |
61 | 0 | } |
62 | 0 | Coin coin = random_coin; |
63 | 0 | bool expected_code_path = false; |
64 | 0 | const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); |
65 | 0 | try { |
66 | 0 | coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite); |
67 | 0 | expected_code_path = true; |
68 | 0 | } catch (const std::logic_error& e) { |
69 | 0 | if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { |
70 | 0 | assert(!possible_overwrite); |
71 | 0 | expected_code_path = true; |
72 | 0 | } |
73 | 0 | } |
74 | 0 | assert(expected_code_path); |
75 | 0 | }, |
76 | 0 | [&] { |
77 | 0 | (void)coins_view_cache.Flush(); |
78 | 0 | }, |
79 | 0 | [&] { |
80 | 0 | (void)coins_view_cache.Sync(); |
81 | 0 | }, |
82 | 0 | [&] { |
83 | 0 | coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider)); |
84 | 0 | }, |
85 | 0 | [&] { |
86 | 0 | Coin move_to; |
87 | 0 | (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr); |
88 | 0 | }, |
89 | 0 | [&] { |
90 | 0 | coins_view_cache.Uncache(random_out_point); |
91 | 0 | }, |
92 | 0 | [&] { |
93 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
94 | 0 | backend_coins_view = CCoinsView{}; |
95 | 0 | } |
96 | 0 | coins_view_cache.SetBackend(backend_coins_view); |
97 | 0 | }, |
98 | 0 | [&] { |
99 | 0 | const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); |
100 | 0 | if (!opt_out_point) { |
101 | 0 | good_data = false; |
102 | 0 | return; |
103 | 0 | } |
104 | 0 | random_out_point = *opt_out_point; |
105 | 0 | }, |
106 | 0 | [&] { |
107 | 0 | const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); |
108 | 0 | if (!opt_coin) { |
109 | 0 | good_data = false; |
110 | 0 | return; |
111 | 0 | } |
112 | 0 | random_coin = *opt_coin; |
113 | 0 | }, |
114 | 0 | [&] { |
115 | 0 | const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); |
116 | 0 | if (!opt_mutable_transaction) { |
117 | 0 | good_data = false; |
118 | 0 | return; |
119 | 0 | } |
120 | 0 | random_mutable_transaction = *opt_mutable_transaction; |
121 | 0 | }, |
122 | 0 | [&] { |
123 | 0 | CoinsCachePair sentinel{}; |
124 | 0 | sentinel.second.SelfRef(sentinel); |
125 | 0 | size_t usage{0}; |
126 | 0 | CCoinsMapMemoryResource resource; |
127 | 0 | CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource}; |
128 | 0 | LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) |
129 | 0 | { |
130 | 0 | CCoinsCacheEntry coins_cache_entry; |
131 | 0 | const auto dirty{fuzzed_data_provider.ConsumeBool()}; |
132 | 0 | const auto fresh{fuzzed_data_provider.ConsumeBool()}; |
133 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
134 | 0 | coins_cache_entry.coin = random_coin; |
135 | 0 | } else { |
136 | 0 | const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); |
137 | 0 | if (!opt_coin) { |
138 | 0 | good_data = false; |
139 | 0 | return; |
140 | 0 | } |
141 | 0 | coins_cache_entry.coin = *opt_coin; |
142 | 0 | } |
143 | 0 | auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first}; |
144 | 0 | if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel); |
145 | 0 | if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel); |
146 | 0 | usage += it->second.coin.DynamicMemoryUsage(); |
147 | 0 | } |
148 | 0 | bool expected_code_path = false; |
149 | 0 | try { |
150 | 0 | auto cursor{CoinsViewCacheCursor(usage, sentinel, coins_map, /*will_erase=*/true)}; |
151 | 0 | coins_view_cache.BatchWrite(cursor, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock()); |
152 | 0 | expected_code_path = true; |
153 | 0 | } catch (const std::logic_error& e) { |
154 | 0 | if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) { |
155 | 0 | expected_code_path = true; |
156 | 0 | } |
157 | 0 | } |
158 | 0 | assert(expected_code_path); |
159 | 0 | }); |
160 | 0 | } |
161 | |
|
162 | 0 | { |
163 | 0 | const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point); |
164 | 0 | const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN); |
165 | 0 | const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); |
166 | 0 | const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); |
167 | 0 | if (auto coin{coins_view_cache.GetCoin(random_out_point)}) { |
168 | 0 | assert(*coin == coin_using_access_coin); |
169 | 0 | assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin); |
170 | 0 | } else { |
171 | 0 | assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin); |
172 | 0 | } |
173 | | // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. |
174 | 0 | const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point); |
175 | 0 | if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { |
176 | 0 | assert(exists_using_have_coin); |
177 | 0 | } |
178 | 0 | if (auto coin{backend_coins_view.GetCoin(random_out_point)}) { |
179 | 0 | assert(exists_using_have_coin_in_backend); |
180 | | // Note we can't assert that `coin_using_get_coin == *coin` because the coin in |
181 | | // the cache may have been modified but not yet flushed. |
182 | 0 | } else { |
183 | 0 | assert(!exists_using_have_coin_in_backend); |
184 | 0 | } |
185 | 0 | } |
186 | | |
187 | 0 | { |
188 | 0 | bool expected_code_path = false; |
189 | 0 | try { |
190 | 0 | (void)coins_view_cache.Cursor(); |
191 | 0 | } catch (const std::logic_error&) { |
192 | 0 | expected_code_path = true; |
193 | 0 | } |
194 | 0 | assert(expected_code_path); |
195 | 0 | (void)coins_view_cache.DynamicMemoryUsage(); |
196 | 0 | (void)coins_view_cache.EstimateSize(); |
197 | 0 | (void)coins_view_cache.GetBestBlock(); |
198 | 0 | (void)coins_view_cache.GetCacheSize(); |
199 | 0 | (void)coins_view_cache.GetHeadBlocks(); |
200 | 0 | (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction}); |
201 | 0 | } |
202 | | |
203 | 0 | { |
204 | 0 | std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor(); |
205 | 0 | assert(!coins_view_cursor); |
206 | 0 | (void)backend_coins_view.EstimateSize(); |
207 | 0 | (void)backend_coins_view.GetBestBlock(); |
208 | 0 | (void)backend_coins_view.GetHeadBlocks(); |
209 | 0 | } |
210 | | |
211 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
212 | 0 | CallOneOf( |
213 | 0 | fuzzed_data_provider, |
214 | 0 | [&] { |
215 | 0 | const CTransaction transaction{random_mutable_transaction}; |
216 | 0 | bool is_spent = false; |
217 | 0 | for (const CTxOut& tx_out : transaction.vout) { |
218 | 0 | if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { |
219 | 0 | is_spent = true; |
220 | 0 | } |
221 | 0 | } |
222 | 0 | if (is_spent) { |
223 | | // Avoid: |
224 | | // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed. |
225 | 0 | return; |
226 | 0 | } |
227 | 0 | bool expected_code_path = false; |
228 | 0 | const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)}; |
229 | 0 | const bool possible_overwrite = fuzzed_data_provider.ConsumeBool(); |
230 | 0 | try { |
231 | 0 | AddCoins(coins_view_cache, transaction, height, possible_overwrite); |
232 | 0 | expected_code_path = true; |
233 | 0 | } catch (const std::logic_error& e) { |
234 | 0 | if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) { |
235 | 0 | assert(!possible_overwrite); |
236 | 0 | expected_code_path = true; |
237 | 0 | } |
238 | 0 | } |
239 | 0 | assert(expected_code_path); |
240 | 0 | }, |
241 | 0 | [&] { |
242 | 0 | (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache); |
243 | 0 | }, |
244 | 0 | [&] { |
245 | 0 | TxValidationState state; |
246 | 0 | CAmount tx_fee_out; |
247 | 0 | const CTransaction transaction{random_mutable_transaction}; |
248 | 0 | if (ContainsSpentInput(transaction, coins_view_cache)) { |
249 | | // Avoid: |
250 | | // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. |
251 | 0 | return; |
252 | 0 | } |
253 | 0 | TxValidationState dummy; |
254 | 0 | if (!CheckTransaction(transaction, dummy)) { |
255 | | // It is not allowed to call CheckTxInputs if CheckTransaction failed |
256 | 0 | return; |
257 | 0 | } |
258 | 0 | if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) { |
259 | 0 | assert(MoneyRange(tx_fee_out)); |
260 | 0 | } |
261 | 0 | }, |
262 | 0 | [&] { |
263 | 0 | const CTransaction transaction{random_mutable_transaction}; |
264 | 0 | if (ContainsSpentInput(transaction, coins_view_cache)) { |
265 | | // Avoid: |
266 | | // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. |
267 | 0 | return; |
268 | 0 | } |
269 | 0 | (void)GetP2SHSigOpCount(transaction, coins_view_cache); |
270 | 0 | }, |
271 | 0 | [&] { |
272 | 0 | const CTransaction transaction{random_mutable_transaction}; |
273 | 0 | if (ContainsSpentInput(transaction, coins_view_cache)) { |
274 | | // Avoid: |
275 | | // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. |
276 | 0 | return; |
277 | 0 | } |
278 | 0 | const auto flags{fuzzed_data_provider.ConsumeIntegral<uint32_t>()}; |
279 | 0 | if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { |
280 | | // Avoid: |
281 | | // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed. |
282 | 0 | return; |
283 | 0 | } |
284 | 0 | (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); |
285 | 0 | }, |
286 | 0 | [&] { |
287 | 0 | (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); |
288 | 0 | }); |
289 | 0 | } |
290 | 0 | } |