Branch data Line data Source code
1 : : // Copyright (c) 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 <key_io.h>
6 : : #include <outputtype.h>
7 : : #include <script/script.h>
8 : : #include <test/fuzz/FuzzedDataProvider.h>
9 : : #include <test/fuzz/fuzz.h>
10 : : #include <test/fuzz/util.h>
11 : : #include <test/util/setup_common.h>
12 : : #include <validation.h>
13 : : #include <wallet/coincontrol.h>
14 : : #include <wallet/spend.h>
15 : : #include <wallet/test/util.h>
16 : : #include <wallet/wallet.h>
17 [ + - ]: 2 :
18 [ + - ]: 2 : namespace wallet {
19 : : namespace {
20 : :
21 : : TestChain100Setup* g_setup;
22 : :
23 : : /**
24 : : * Singleton wallet class ensures that only one
25 : : * instance of CWallet is created and reused as required.
26 : : * This increases Fuzzing efficiency by reducing the expense
27 : 2 : * of creating a new `Descriptor Wallet` each time and deletes
28 : : * the pointer safely using the destructor.
29 : : */
30 : : class WalletSingleton
31 : : {
32 : : public:
33 : 2046 : static WalletSingleton& GetInstance()
34 : : {
35 [ + - ][ + + ]: 2048 : static WalletSingleton instance;
[ - + ][ + - ]
36 : 2046 : return instance;
37 : 0 : }
38 : :
39 : 1023 : CWallet& GetWallet()
40 : : {
41 [ + - ]: 1023 : if (!wallet) {
42 : 0 : InitializeWallet();
43 : 0 : }
44 : 1023 : return *wallet;
45 : : }
46 : 1023 : CoinsResult& GetCoins()
47 : : {
48 [ + - ]: 1023 : if (!wallet) {
49 : 0 : InitializeWallet();
50 : 0 : }
51 : 1023 : return available_coins;
52 : : }
53 [ # # ]: 0 :
54 : : private:
55 : 1 : WalletSingleton()
56 : : {
57 [ + - ]: 1 : InitializeWallet();
58 : 1 : }
59 : :
60 : 1 : ~WalletSingleton()
61 : : {
62 : 1 : wallet.reset();
63 : 1 : available_coins.coins.clear();
64 : 1 : }
65 : :
66 : : WalletSingleton(const WalletSingleton&) = delete;
67 : : WalletSingleton& operator=(const WalletSingleton&) = delete;
68 : :
69 : 1 : void InitializeWallet()
70 : : {
71 : 1 : auto& node{g_setup->m_node};
72 [ + - ]: 2 : wallet = CreateSyncedWallet(*node.chain, WITH_LOCK(Assert(node.chainman)->GetMutex(), return node.chainman->ActiveChain()), g_setup->coinbaseKey);
73 : :
74 : : // This is placed here because Available Coins will never be updated while fuzzing.
75 : 1 : LOCK(wallet->cs_wallet);
76 [ + - ]: 1 : available_coins = AvailableCoins(*wallet);
77 : 1 : }
78 : :
79 : : std::unique_ptr<CWallet> wallet;
80 : : CoinsResult available_coins;
81 : : };
82 : :
83 : 505 : CCoinControl GetNewCoinControl(FuzzedDataProvider& fuzzed_data_provider, CWallet& wallet, std::vector<COutput>& coins)
84 : : {
85 : 505 : CCoinControl new_coin_control;
86 [ + - ][ + + ]: 505 : if (fuzzed_data_provider.ConsumeBool()) {
87 : 329 : const CTxDestination tx_destination{ConsumeTxDestination(fuzzed_data_provider)};
88 [ + - ]: 329 : new_coin_control.destChange = tx_destination;
89 : 329 : }
90 [ + - ][ + + ]: 505 : if (fuzzed_data_provider.ConsumeBool()) {
91 [ + - ]: 270 : new_coin_control.m_change_type = fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES);
92 : 268 : }
93 [ + - ]: 505 : new_coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool();
94 [ + - ]: 505 : new_coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool();
95 [ + - ][ + + ]: 505 : if (fuzzed_data_provider.ConsumeBool()) {
96 [ + - ]: 387 : new_coin_control.m_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
97 : 387 : }
98 [ + - ]: 505 : new_coin_control.m_signal_bip125_rbf = fuzzed_data_provider.ConsumeBool();
99 [ + - ]: 507 : new_coin_control.m_avoid_partial_spends = fuzzed_data_provider.ConsumeBool();
100 : :
101 : : // Taking a random sub-sequence from available coins.
102 [ + + ]: 25755 : for (const COutput& coin : coins) {
103 [ + - ][ + + ]: 25250 : if (fuzzed_data_provider.ConsumeBool()) {
104 [ + - ]: 10434 : new_coin_control.Select(coin.outpoint);
105 : 10434 : }
106 : : }
107 : :
108 : : // Occasionally, some random `CCoutPoint` are also selected.
109 [ + - ][ + + ]: 505 : if (fuzzed_data_provider.ConsumeBool()) {
110 [ + + ]: 770 : for (int i = 0; i < 10; ++i) {
111 : 700 : std::optional<COutPoint> optional_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
112 [ + + ]: 700 : if (!optional_out_point) {
113 : 519 : continue;
114 : : }
115 [ + - ]: 181 : new_coin_control.Select(*optional_out_point);
116 : 181 : }
117 : 70 : }
118 : 505 : return new_coin_control;
119 [ + - ]: 505 : }
120 : :
121 : 1 : void initialize_spend()
122 : : {
123 [ + - ][ - + ]: 1 : static auto testing_setup = MakeNoLogFileContext<TestChain100Setup>();
[ + - ]
124 : 1 : g_setup = testing_setup.get();
125 : :
126 : : // Add 50 spendable UTXO, 50 BTC each, to the wallet (total balance 5000 BTC)
127 : : // Because none of the UTXO will ever be used (marked as spent), mining this many should be sufficient.
128 [ + + ]: 51 : for (int i = 0; i < 50; ++i) {
129 [ + - ][ + - ]: 50 : g_setup->CreateAndProcessBlock({}, GetScriptForRawPubKey(g_setup->coinbaseKey.GetPubKey()));
[ + - ]
130 : 50 : }
131 : 1 : }
132 : :
133 [ + - ]: 1027 : FUZZ_TARGET(spend, .init = initialize_spend)
134 : : {
135 : 1023 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
136 : 1023 : CWallet& wallet = WalletSingleton::GetInstance().GetWallet();
137 : 1023 : CoinsResult& available_coins = WalletSingleton::GetInstance().GetCoins();
138 : :
139 : : // Asserting if the `wallet` has no funds.
140 : 1023 : std::vector<COutput> coins = available_coins.All();
141 [ + - ]: 1023 : assert(!coins.empty());
142 : :
143 [ + - ]: 1023 : CCoinControl coin_control;
144 [ - + ]: 1023 : CMutableTransaction mtx;
145 : :
146 [ + - ][ + + ]: 29793 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 505)
[ + + ]
147 : : {
148 [ + - ]: 28770 : CallOneOf(
149 : : fuzzed_data_provider,
150 : 29275 : [&] {
151 : : // Creates a new `CoinControl`.
152 : 505 : coin_control = GetNewCoinControl(fuzzed_data_provider, wallet, coins);
153 : 505 : },
154 : 31448 : [&] {
155 : : // Creates a new Transaction using `CreateTransaction`.
156 : 2678 : std::vector<CRecipient> recipients;
157 [ + - ]: 2678 : unsigned int recipients_size = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, 100);
158 [ + + ]: 16560 : for (unsigned int i = 0; i < recipients_size; ++i) {
159 [ + - ]: 27764 : recipients.push_back({/*dest=*/ConsumeTxDestination(fuzzed_data_provider),
160 : 13882 : /*nAmount=*/ConsumeMoney(fuzzed_data_provider),
161 [ + - ]: 13882 : /*fSubtractFeeFromAmount=*/fuzzed_data_provider.ConsumeBool()});
162 : 13882 : }
163 [ + - ]: 2 : // Equally likely to choose one of the three values.
164 [ + - ][ + - ]: 2680 : std::vector<int> random_positions = {-1, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, recipients_size), fuzzed_data_provider.ConsumeIntegral<int>()};
[ + - ][ + - ]
165 [ + - ][ + - ]: 2680 : int change_pos = PickValue(fuzzed_data_provider, random_positions);
166 [ + - ]: 2 :
167 [ + - ][ + - ]: 2680 : auto res = CreateTransaction(wallet, recipients, change_pos, coin_control, /*sign=*/fuzzed_data_provider.ConsumeBool());
[ + - ]
168 [ + - ][ + + ]: 2680 : if (!res) return;
169 [ + - ][ + - ]: 427 : mtx = CMutableTransaction(*(res->tx));
[ + - ]
170 [ + - ][ - + ]: 2680 : },
171 : 29559 : [&] {
172 : : // Occasionally, creating a random Transaction for Fuzzing.
173 : 789 : auto new_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
174 [ + + ]: 789 : if (!new_mtx) return;
175 [ + - ]: 391 : mtx = *new_mtx;
176 [ - + ]: 789 : },
177 : 31298 : [&] {
178 : 2528 : LOCK(wallet.cs_wallet);
179 [ + - ][ + + ]: 2528 : if (fuzzed_data_provider.ConsumeBool()) {
180 [ + - ][ + - ]: 2406 : (void)CalculateMaximumSignedTxSize(CTransaction(mtx), &wallet);
181 : 2406 : } else {
182 [ + - ][ + - ]: 122 : (void)CalculateMaximumSignedTxSize(CTransaction(mtx), &wallet, &coin_control);
183 : : }
184 : 2528 : },
185 : 28909 : [&] {
186 [ + - ]: 139 : const CTxOut tx_out{ConsumeMoney(fuzzed_data_provider), ConsumeScript(fuzzed_data_provider)};
187 [ - + ]: 139 : CalculateMaximumSignedInputSize(tx_out, &wallet, &coin_control);
188 : 139 : },
189 : 50901 : [&] {
190 : : CAmount fee;
191 [ + - ]: 22131 : std::vector<int> random_positions = {-1, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, mtx.vout.size()), fuzzed_data_provider.ConsumeIntegral<int>()};
192 [ + - ]: 22131 : int change_pos = PickValue(fuzzed_data_provider, random_positions);
193 : 22131 : bilingual_str error;
194 : 22131 : std::set<int> setSubtractFeeFromOutputs;
195 [ + + ]: 58154 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
196 [ + - ][ + + ]: 36023 : if (fuzzed_data_provider.ConsumeBool()) {
197 [ + - ]: 34795 : setSubtractFeeFromOutputs.insert(i);
198 : 34795 : }
199 : 36023 : }
200 [ + - ][ - + ]: 22131 : FundTransaction(wallet, mtx, fee, change_pos, error, false, setSubtractFeeFromOutputs, coin_control);
201 : 22131 : });
202 : 28770 : }
203 : :
204 : : // Plays with the `CoinsResult`
205 : 1023 : CoinsResult wallet_coins;
206 : :
207 [ + - ][ + + ]: 3607 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 50)
[ + + ]
208 : : {
209 [ + - ]: 2584 : CallOneOf(
210 : : fuzzed_data_provider,
211 : 3202 : [&] {
212 : : // Re-initializing the wallet_coins.
213 : 618 : wallet_coins = available_coins;
214 : 618 : },
215 : 2936 : [&] {
216 : 352 : std::optional<COutPoint> optional_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
217 [ + + ]: 352 : if (!optional_out_point) {
218 : 137 : return;
219 : : }
220 [ + - ]: 430 : COutput out_put = COutput{/*outpoint=*/*optional_out_point,
221 [ + - ]: 215 : /*txout=*/CTxOut{ConsumeMoney(fuzzed_data_provider), ConsumeScript(fuzzed_data_provider)},
222 : 215 : /*depth=*/fuzzed_data_provider.ConsumeIntegral<int>(),
223 : 215 : /*input_bytes=*/fuzzed_data_provider.ConsumeIntegral<int>(),
224 [ + - ]: 215 : /*spendable=*/fuzzed_data_provider.ConsumeBool(),
225 [ + - ]: 215 : /*solvable=*/fuzzed_data_provider.ConsumeBool(),
226 [ + - ]: 215 : /*safe=*/fuzzed_data_provider.ConsumeBool(),
227 [ + - ]: 215 : /*time=*/fuzzed_data_provider.ConsumeIntegral<int64_t>(),
228 [ + - ]: 215 : /*from_me=*/fuzzed_data_provider.ConsumeBool()};
229 : 215 : OutputType type = fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES);
230 [ - + ]: 215 : wallet_coins.Add(type, out_put);
231 : 352 : },
232 : 3628 : [&] {
233 : 1044 : std::unordered_set<COutPoint, SaltedOutpointHasher> outs_to_remove;
234 [ + - ]: 1044 : std::vector<COutput> coins = wallet_coins.All();
235 : : // Taking a random sub-sequence from available coins
236 [ + + ]: 5565 : for (const COutput& coin : coins) {
237 [ + - ][ + + ]: 4521 : if (fuzzed_data_provider.ConsumeBool()) {
238 [ + - ]: 2880 : outs_to_remove.emplace(coin.outpoint);
239 : 2880 : }
240 : : }
241 [ + - ]: 1044 : wallet_coins.Erase(outs_to_remove);
242 : 1044 : },
243 : 2840 : [&] {
244 : 256 : FastRandomContext random_context{ConsumeUInt256(fuzzed_data_provider)};
245 [ + - ]: 256 : wallet_coins.Shuffle(random_context);
246 : 256 : },
247 : 2674 : [&] {
248 : 90 : wallet_coins.Clear();
249 : 90 : },
250 : 2669 : [&] {
251 : 85 : (void)wallet_coins.GetTotalAmount();
252 : 85 : },
253 : 2723 : [&] {
254 : 139 : (void)wallet_coins.GetEffectiveTotalAmount();
255 : 139 : });
256 : 2584 : }
257 : 1023 : }
258 : : } // namespace
259 : : } // namespace wallet
|