Skip to content

Commit f9b22e3

Browse files
tests: Add fuzzing harness for CCoinsViewCache
1 parent 24f7029 commit f9b22e3

File tree

3 files changed

+313
-0
lines changed

3 files changed

+313
-0
lines changed

src/Makefile.test.include

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ FUZZ_TARGETS = \
3131
test/fuzz/chain \
3232
test/fuzz/checkqueue \
3333
test/fuzz/coins_deserialize \
34+
test/fuzz/coins_view \
3435
test/fuzz/cuckoocache \
3536
test/fuzz/decode_tx \
3637
test/fuzz/descriptor_parse \
@@ -466,6 +467,12 @@ test_fuzz_coins_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON)
466467
test_fuzz_coins_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
467468
test_fuzz_coins_deserialize_SOURCES = test/fuzz/deserialize.cpp
468469

470+
test_fuzz_coins_view_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
471+
test_fuzz_coins_view_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
472+
test_fuzz_coins_view_LDADD = $(FUZZ_SUITE_LD_COMMON)
473+
test_fuzz_coins_view_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
474+
test_fuzz_coins_view_SOURCES = test/fuzz/coins_view.cpp
475+
469476
test_fuzz_cuckoocache_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
470477
test_fuzz_cuckoocache_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
471478
test_fuzz_cuckoocache_LDADD = $(FUZZ_SUITE_LD_COMMON)

src/test/fuzz/coins_view.cpp

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
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+
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+
37+
void initialize()
38+
{
39+
static const ECCVerifyHandle ecc_verify_handle;
40+
ECC_Start();
41+
SelectParams(CBaseChainParams::REGTEST);
42+
}
43+
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);
233+
break;
234+
}
235+
case 2: {
236+
TxValidationState state;
237+
CAmount tx_fee_out;
238+
const CTransaction transaction{random_mutable_transaction};
239+
if (ContainsSpentInput(transaction, coins_view_cache)) {
240+
// Avoid:
241+
// consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
242+
break;
243+
}
244+
try {
245+
(void)Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out);
246+
assert(MoneyRange(tx_fee_out));
247+
} catch (const std::runtime_error&) {
248+
}
249+
break;
250+
}
251+
case 3: {
252+
const CTransaction transaction{random_mutable_transaction};
253+
if (ContainsSpentInput(transaction, coins_view_cache)) {
254+
// Avoid:
255+
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
256+
break;
257+
}
258+
(void)GetP2SHSigOpCount(transaction, coins_view_cache);
259+
break;
260+
}
261+
case 4: {
262+
const CTransaction transaction{random_mutable_transaction};
263+
if (ContainsSpentInput(transaction, coins_view_cache)) {
264+
// Avoid:
265+
// consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
266+
break;
267+
}
268+
const int flags = fuzzed_data_provider.ConsumeIntegral<int>();
269+
if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
270+
// Avoid:
271+
// script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
272+
break;
273+
}
274+
(void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
275+
break;
276+
}
277+
case 5: {
278+
CCoinsStats stats;
279+
bool expected_code_path = false;
280+
try {
281+
(void)GetUTXOStats(&coins_view_cache, stats);
282+
} catch (const std::logic_error&) {
283+
expected_code_path = true;
284+
}
285+
assert(expected_code_path);
286+
break;
287+
}
288+
case 6: {
289+
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
290+
break;
291+
}
292+
}
293+
}
294+
}

src/test/fuzz/util.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <amount.h>
99
#include <arith_uint256.h>
1010
#include <attributes.h>
11+
#include <coins.h>
1112
#include <consensus/consensus.h>
1213
#include <primitives/transaction.h>
1314
#include <script/script.h>
@@ -149,4 +150,15 @@ NODISCARD bool AdditionOverflow(const T i, const T j) noexcept
149150
return std::numeric_limits<T>::max() - i < j;
150151
}
151152

153+
NODISCARD inline bool ContainsSpentInput(const CTransaction& tx, const CCoinsViewCache& inputs) noexcept
154+
{
155+
for (const CTxIn& tx_in : tx.vin) {
156+
const Coin& coin = inputs.AccessCoin(tx_in.prevout);
157+
if (coin.IsSpent()) {
158+
return true;
159+
}
160+
}
161+
return false;
162+
}
163+
152164
#endif // BITCOIN_TEST_FUZZ_UTIL_H

0 commit comments

Comments
 (0)