Branch data Line data Source code
1 : : // Copyright (c) 2021-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 <kernel/chain.h>
6 : : #include <test/fuzz/FuzzedDataProvider.h>
7 : : #include <test/fuzz/fuzz.h>
8 : : #include <test/fuzz/util.h>
9 : : #include <test/util/setup_common.h>
10 : : #include <util/translation.h>
11 : : #include <wallet/context.h>
12 : : #include <wallet/receive.h>
13 : : #include <wallet/wallet.h>
14 : : #include <wallet/walletdb.h>
15 : : #include <wallet/walletutil.h>
16 : :
17 [ + - ]: 173 : #include <cassert>
18 [ + - ]: 173 : #include <cstdint>
19 : : #include <string>
20 : : #include <vector>
21 : :
22 : : namespace wallet {
23 : : namespace {
24 : : const TestingSetup* g_setup;
25 : :
26 : 1 : void initialize_setup()
27 : 173 : {
28 [ + - - + : 1 : static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
+ - ]
29 : 1 : g_setup = testing_setup.get();
30 : 1 : }
31 : :
32 : : /**
33 : : * Wraps a descriptor wallet for fuzzing. The constructor writes the sqlite db
34 : : * to disk, the destructor deletes it.
35 : : */
36 : : struct FuzzedWallet {
37 : : ArgsManager args;
38 : : WalletContext context;
39 : : std::shared_ptr<CWallet> wallet;
40 [ # # ]: 0 : FuzzedWallet(const std::string& name)
41 : : {
42 : 0 : context.args = &args;
43 : 0 : context.chain = g_setup->m_node.chain.get();
44 : :
45 : 0 : DatabaseOptions options;
46 : 0 : options.require_create = true;
47 : 0 : options.create_flags = WALLET_FLAG_DESCRIPTORS;
48 : 0 : const std::optional<bool> load_on_start;
49 [ # # # # : 0 : gArgs.ForceSetArg("-keypool", "0"); // Avoid timeout in TopUp()
# # ]
50 : :
51 : : DatabaseStatus status;
52 : 0 : bilingual_str error;
53 [ # # ]: 0 : std::vector<bilingual_str> warnings;
54 [ # # ]: 0 : wallet = CreateWallet(context, name, load_on_start, options, status, error, warnings);
55 [ # # # # ]: 0 : assert(wallet);
56 [ # # # # ]: 0 : assert(error.empty());
57 [ # # ]: 0 : assert(warnings.empty());
58 [ # # # # ]: 0 : assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
59 : 0 : }
60 : 0 : ~FuzzedWallet()
61 : : {
62 [ # # # # ]: 0 : const auto name{wallet->GetName()};
63 : 0 : std::vector<bilingual_str> warnings;
64 : 0 : std::optional<bool> load_on_start;
65 [ # # # # ]: 0 : assert(RemoveWallet(context, wallet, load_on_start, warnings));
66 [ # # ]: 0 : assert(warnings.empty());
67 [ # # ]: 0 : UnloadWallet(std::move(wallet));
68 [ # # # # : 0 : fs::remove_all(GetWalletDir() / fs::PathFromString(name));
# # # # ]
69 : 0 : }
70 : 0 : CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider)
71 : : {
72 : 0 : auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
73 [ # # ]: 0 : util::Result<CTxDestination> op_dest{util::Error{}};
74 [ # # # # ]: 173 : if (fuzzed_data_provider.ConsumeBool()) {
75 [ # # # # ]: 0 : op_dest = wallet->GetNewDestination(type, "");
76 : 0 : } else {
77 [ # # ]: 0 : op_dest = wallet->GetNewChangeDestination(type);
78 : : }
79 [ # # # # : 0 : return GetScriptForDestination(*Assert(op_dest));
# # ]
80 : 0 : }
81 : : };
82 : :
83 [ + - - + ]: 346 : FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
84 : : {
85 : 0 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
86 : : // The total amount, to be distributed to the wallets a and b in txs
87 : : // without fee. Thus, the balance of the wallets should always equal the
88 : : // total amount.
89 : 0 : const auto total_amount{ConsumeMoney(fuzzed_data_provider)};
90 [ # # # # ]: 0 : FuzzedWallet a{"fuzzed_wallet_a"};
91 [ # # # # ]: 173 : FuzzedWallet b{"fuzzed_wallet_b"};
92 : :
93 : : // Keep track of all coins in this test.
94 : : // Each tuple in the chain represents the coins and the block created with
95 : : // those coins. Once the block is mined, the next tuple will have an empty
96 : : // block and the freshly mined coins.
97 : : using Coins = std::set<std::tuple<CAmount, COutPoint>>;
98 : 0 : std::vector<std::tuple<Coins, CBlock>> chain;
99 : 173 : {
100 : : // Add the initial entry
101 [ # # ]: 0 : chain.emplace_back();
102 : 0 : auto& [coins, block]{chain.back()};
103 [ # # # # ]: 0 : coins.emplace(total_amount, COutPoint{uint256::ONE, 1});
104 : : }
105 [ # # # # : 0 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 200)
# # ]
106 : : {
107 [ # # ]: 0 : CallOneOf(
108 : : fuzzed_data_provider,
109 : 0 : [&] {
110 : 0 : auto& [coins_orig, block]{chain.back()};
111 : : // Copy the coins for this block and consume all of them
112 : 0 : Coins coins = coins_orig;
113 [ # # ]: 0 : while (!coins.empty()) {
114 : : // Create a new tx
115 [ # # ]: 0 : CMutableTransaction tx{};
116 : : // Add some coins as inputs to it
117 [ # # ]: 0 : auto num_inputs{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, coins.size())};
118 : 0 : CAmount in{0};
119 [ # # ]: 0 : while (num_inputs-- > 0) {
120 : 0 : const auto& [coin_amt, coin_outpoint]{*coins.begin()};
121 : 0 : in += coin_amt;
122 [ # # # # ]: 0 : tx.vin.emplace_back(coin_outpoint);
123 [ # # ]: 0 : coins.erase(coins.begin());
124 : : }
125 : : // Create some outputs spending all inputs, without fee
126 [ # # # # : 0 : LIMITED_WHILE(in > 0 && fuzzed_data_provider.ConsumeBool(), 100)
# # # # ]
127 : : {
128 [ # # ]: 0 : const auto out_value{ConsumeMoney(fuzzed_data_provider, in)};
129 : 0 : in -= out_value;
130 [ # # # # ]: 0 : auto& wallet{fuzzed_data_provider.ConsumeBool() ? a : b};
131 [ # # # # ]: 0 : tx.vout.emplace_back(out_value, wallet.GetScriptPubKey(fuzzed_data_provider));
132 : 0 : }
133 : : // Spend the remaining input value, if any
134 [ # # # # ]: 0 : auto& wallet{fuzzed_data_provider.ConsumeBool() ? a : b};
135 [ # # # # ]: 0 : tx.vout.emplace_back(in, wallet.GetScriptPubKey(fuzzed_data_provider));
136 : : // Add tx to block
137 [ # # # # ]: 0 : block.vtx.emplace_back(MakeTransactionRef(tx));
138 : 0 : }
139 : : // Mine block
140 [ # # ]: 0 : const uint256& hash = block.GetHash();
141 [ # # ]: 0 : interfaces::BlockInfo info{hash};
142 : 0 : info.prev_hash = &block.hashPrevBlock;
143 : 0 : info.height = chain.size();
144 : 0 : info.data = █
145 : : // Ensure that no blocks are skipped by the wallet by setting the chain's accumulated
146 : : // time to the maximum value. This ensures that the wallet's birth time is always
147 : : // earlier than this maximum time.
148 : 0 : info.chain_time_max = std::numeric_limits<unsigned int>::max();
149 [ # # ]: 0 : a.wallet->blockConnected(ChainstateRole::NORMAL, info);
150 [ # # ]: 0 : b.wallet->blockConnected(ChainstateRole::NORMAL, info);
151 : : // Store the coins for the next block
152 : 0 : Coins coins_new;
153 [ # # ]: 0 : for (const auto& tx : block.vtx) {
154 : 0 : uint32_t i{0};
155 [ # # ]: 0 : for (const auto& out : tx->vout) {
156 [ # # # # : 0 : coins_new.emplace(out.nValue, COutPoint{tx->GetHash(), i++});
# # ]
157 : : }
158 : : }
159 [ # # # # ]: 0 : chain.emplace_back(coins_new, CBlock{});
160 : 0 : },
161 : 0 : [&] {
162 [ # # ]: 0 : if (chain.size() <= 1) return; // The first entry can't be removed
163 [ + - ]: 173 : auto& [coins, block]{chain.back()};
164 [ + - # # ]: 173 : if (block.vtx.empty()) return; // Can only disconnect if the block was submitted first
165 [ + - ]: 173 : // Disconnect block
166 [ + - ]: 173 : const uint256& hash = block.GetHash();
167 [ + - ]: 173 : interfaces::BlockInfo info{hash};
168 [ + - ]: 173 : info.prev_hash = &block.hashPrevBlock;
169 [ + - ]: 173 : info.height = chain.size() - 1;
170 [ + - ]: 173 : info.data = █
171 : 0 : a.wallet->blockDisconnected(info);
172 : 0 : b.wallet->blockDisconnected(info);
173 : 0 : chain.pop_back();
174 : 0 : });
175 : 0 : auto& [coins, first_block]{chain.front()};
176 [ # # ]: 0 : if (!first_block.vtx.empty()) {
177 : : // Only check balance when at least one block was submitted
178 [ # # ]: 0 : const auto bal_a{GetBalance(*a.wallet).m_mine_trusted};
179 [ # # ]: 0 : const auto bal_b{GetBalance(*b.wallet).m_mine_trusted};
180 [ # # ]: 0 : assert(total_amount == bal_a + bal_b);
181 : 0 : }
182 : 0 : }
183 : 0 : }
184 : : } // namespace
185 : : } // namespace wallet
|