1 // Copyright (c) 2020 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 <amount.h>
6 #include <chainparams.h>
7 #include <chainparamsbase.h>
8 #include <coins.h>
9 #include <consensus/tx_verify.h>
10 #include <consensus/validation.h>
11 #include <key.h>
12 #include <node/coinstats.h>
13 #include <policy/policy.h>
14 #include <primitives/transaction.h>
15 #include <pubkey.h>
16 #include <test/fuzz/FuzzedDataProvider.h>
17 #include <test/fuzz/fuzz.h>
18 #include <test/fuzz/util.h>
19 #include <validation.h>
20 
21 #include <cstdint>
22 #include <limits>
23 #include <optional>
24 #include <string>
25 #include <vector>
26 
27 namespace {
28 const Coin EMPTY_COIN{};
29 
operator ==(const Coin & a,const Coin & b)30 bool operator==(const Coin& a, const Coin& b)
31 {
32     if (a.IsSpent() && b.IsSpent()) return true;
33     return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
34 }
35 } // namespace
36 
initialize()37 void initialize()
38 {
39     static const ECCVerifyHandle ecc_verify_handle;
40     ECC_Start();
41     SelectParams(CBaseChainParams::REGTEST);
42 }
43 
test_one_input(const std::vector<uint8_t> & buffer)44 void test_one_input(const std::vector<uint8_t>& buffer)
45 {
46     FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
47     CCoinsView backend_coins_view;
48     CCoinsViewCache coins_view_cache{&backend_coins_view};
49     COutPoint random_out_point;
50     Coin random_coin;
51     CMutableTransaction random_mutable_transaction;
52     while (fuzzed_data_provider.ConsumeBool()) {
53         switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 9)) {
54         case 0: {
55             if (random_coin.IsSpent()) {
56                 break;
57             }
58             Coin coin = random_coin;
59             bool expected_code_path = false;
60             const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
61             try {
62                 coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite);
63                 expected_code_path = true;
64             } catch (const std::logic_error& e) {
65                 if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
66                     assert(!possible_overwrite);
67                     expected_code_path = true;
68                 }
69             }
70             assert(expected_code_path);
71             break;
72         }
73         case 1: {
74             (void)coins_view_cache.Flush();
75             break;
76         }
77         case 2: {
78             coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider));
79             break;
80         }
81         case 3: {
82             Coin move_to;
83             (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
84             break;
85         }
86         case 4: {
87             coins_view_cache.Uncache(random_out_point);
88             break;
89         }
90         case 5: {
91             if (fuzzed_data_provider.ConsumeBool()) {
92                 backend_coins_view = CCoinsView{};
93             }
94             coins_view_cache.SetBackend(backend_coins_view);
95             break;
96         }
97         case 6: {
98             const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
99             if (!opt_out_point) {
100                 break;
101             }
102             random_out_point = *opt_out_point;
103             break;
104         }
105         case 7: {
106             const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
107             if (!opt_coin) {
108                 break;
109             }
110             random_coin = *opt_coin;
111             break;
112         }
113         case 8: {
114             const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
115             if (!opt_mutable_transaction) {
116                 break;
117             }
118             random_mutable_transaction = *opt_mutable_transaction;
119             break;
120         }
121         case 9: {
122             CCoinsMap coins_map;
123             while (fuzzed_data_provider.ConsumeBool()) {
124                 CCoinsCacheEntry coins_cache_entry;
125                 coins_cache_entry.flags = fuzzed_data_provider.ConsumeIntegral<unsigned char>();
126                 if (fuzzed_data_provider.ConsumeBool()) {
127                     coins_cache_entry.coin = random_coin;
128                 } else {
129                     const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
130                     if (!opt_coin) {
131                         break;
132                     }
133                     coins_cache_entry.coin = *opt_coin;
134                 }
135                 coins_map.emplace(random_out_point, std::move(coins_cache_entry));
136             }
137             bool expected_code_path = false;
138             try {
139                 coins_view_cache.BatchWrite(coins_map, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
140                 expected_code_path = true;
141             } catch (const std::logic_error& e) {
142                 if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) {
143                     expected_code_path = true;
144                 }
145             }
146             assert(expected_code_path);
147             break;
148         }
149         }
150     }
151 
152     {
153         const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
154         const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
155         const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
156         const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
157         Coin coin_using_get_coin;
158         const bool exists_using_get_coin = coins_view_cache.GetCoin(random_out_point, coin_using_get_coin);
159         if (exists_using_get_coin) {
160             assert(coin_using_get_coin == coin_using_access_coin);
161         }
162         assert((exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin && exists_using_get_coin) ||
163                (!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin && !exists_using_get_coin));
164         const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
165         if (exists_using_have_coin_in_backend) {
166             assert(exists_using_have_coin);
167         }
168         Coin coin_using_backend_get_coin;
169         if (backend_coins_view.GetCoin(random_out_point, coin_using_backend_get_coin)) {
170             assert(exists_using_have_coin_in_backend);
171             assert(coin_using_get_coin == coin_using_backend_get_coin);
172         } else {
173             assert(!exists_using_have_coin_in_backend);
174         }
175     }
176 
177     {
178         bool expected_code_path = false;
179         try {
180             (void)coins_view_cache.Cursor();
181         } catch (const std::logic_error&) {
182             expected_code_path = true;
183         }
184         assert(expected_code_path);
185         (void)coins_view_cache.DynamicMemoryUsage();
186         (void)coins_view_cache.EstimateSize();
187         (void)coins_view_cache.GetBestBlock();
188         (void)coins_view_cache.GetCacheSize();
189         (void)coins_view_cache.GetHeadBlocks();
190         (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
191     }
192 
193     {
194         const CCoinsViewCursor* coins_view_cursor = backend_coins_view.Cursor();
195         assert(coins_view_cursor == nullptr);
196         (void)backend_coins_view.EstimateSize();
197         (void)backend_coins_view.GetBestBlock();
198         (void)backend_coins_view.GetHeadBlocks();
199     }
200 
201     if (fuzzed_data_provider.ConsumeBool()) {
202         switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 6)) {
203         case 0: {
204             const CTransaction transaction{random_mutable_transaction};
205             bool is_spent = false;
206             for (const CTxOut& tx_out : transaction.vout) {
207                 if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
208                     is_spent = true;
209                 }
210             }
211             if (is_spent) {
212                 // Avoid:
213                 // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
214                 break;
215             }
216             bool expected_code_path = false;
217             const int height = fuzzed_data_provider.ConsumeIntegral<int>();
218             const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
219             try {
220                 AddCoins(coins_view_cache, transaction, height, possible_overwrite);
221                 expected_code_path = true;
222             } catch (const std::logic_error& e) {
223                 if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
224                     assert(!possible_overwrite);
225                     expected_code_path = true;
226                 }
227             }
228             assert(expected_code_path);
229             break;
230         }
231         case 1: {
232             (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, false);
233             (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache, true);
234             break;
235         }
236         case 2: {
237             TxValidationState state;
238             CAmount tx_fee_out;
239             const CTransaction transaction{random_mutable_transaction};
240             if (ContainsSpentInput(transaction, coins_view_cache)) {
241                 // Avoid:
242                 // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
243                 break;
244             }
245             try {
246                 (void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out);
247                 assert(MoneyRange(tx_fee_out));
248             } catch (const std::runtime_error&) {
249             }
250             break;
251         }
252         case 3: {
253             const CTransaction transaction{random_mutable_transaction};
254             if (ContainsSpentInput(transaction, coins_view_cache)) {
255                 // Avoid:
256                 // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
257                 break;
258             }
259             (void)GetP2SHSigOpCount(transaction, coins_view_cache);
260             break;
261         }
262         case 4: {
263             const CTransaction transaction{random_mutable_transaction};
264             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                 break;
268             }
269             const int flags = fuzzed_data_provider.ConsumeIntegral<int>();
270             if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
271                 // Avoid:
272                 // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
273                 break;
274             }
275             (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
276             break;
277         }
278         case 5: {
279             CCoinsStats stats;
280             bool expected_code_path = false;
281             try {
282                 (void)GetUTXOStats(&coins_view_cache, stats, CoinStatsHashType::HASH_SERIALIZED);
283             } catch (const std::logic_error&) {
284                 expected_code_path = true;
285             }
286             assert(expected_code_path);
287             break;
288         }
289         case 6: {
290             (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
291             break;
292         }
293         }
294     }
295 }
296