/root/bitcoin/src/test/fuzz/util/wallet.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2024-present 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 | | #ifndef BITCOIN_TEST_FUZZ_UTIL_WALLET_H |
6 | | #define BITCOIN_TEST_FUZZ_UTIL_WALLET_H |
7 | | |
8 | | #include <test/fuzz/FuzzedDataProvider.h> |
9 | | #include <test/fuzz/fuzz.h> |
10 | | #include <test/fuzz/util.h> |
11 | | #include <policy/policy.h> |
12 | | #include <wallet/coincontrol.h> |
13 | | #include <wallet/fees.h> |
14 | | #include <wallet/spend.h> |
15 | | #include <wallet/test/util.h> |
16 | | #include <wallet/wallet.h> |
17 | | |
18 | | namespace wallet { |
19 | | |
20 | | /** |
21 | | * Wraps a descriptor wallet for fuzzing. |
22 | | */ |
23 | | struct FuzzedWallet { |
24 | | std::shared_ptr<CWallet> wallet; |
25 | | FuzzedWallet(interfaces::Chain& chain, const std::string& name, const std::string& seed_insecure) |
26 | 0 | { |
27 | 0 | wallet = std::make_shared<CWallet>(&chain, name, CreateMockableWalletDatabase()); |
28 | 0 | { |
29 | 0 | LOCK(wallet->cs_wallet); |
30 | 0 | wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); |
31 | 0 | auto height{*Assert(chain.getHeight())}; |
32 | 0 | wallet->SetLastBlockProcessed(height, chain.getBlockHash(height)); |
33 | 0 | } |
34 | 0 | wallet->m_keypool_size = 1; // Avoid timeout in TopUp() |
35 | 0 | assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); |
36 | 0 | ImportDescriptors(seed_insecure); |
37 | 0 | } |
38 | | void ImportDescriptors(const std::string& seed_insecure) |
39 | 0 | { |
40 | 0 | const std::vector<std::string> DESCS{ |
41 | 0 | "pkh(%s/%s/*)", |
42 | 0 | "sh(wpkh(%s/%s/*))", |
43 | 0 | "tr(%s/%s/*)", |
44 | 0 | "wpkh(%s/%s/*)", |
45 | 0 | }; |
46 | |
|
47 | 0 | for (const std::string& desc_fmt : DESCS) { |
48 | 0 | for (bool internal : {true, false}) { |
49 | 0 | const auto descriptor{strprintf(tfm::RuntimeFormat{desc_fmt}, "[5aa9973a/66h/4h/2h]" + seed_insecure, int{internal})}; |
50 | |
|
51 | 0 | FlatSigningProvider keys; |
52 | 0 | std::string error; |
53 | 0 | auto parsed_desc = std::move(Parse(descriptor, keys, error, /*require_checksum=*/false).at(0)); |
54 | 0 | assert(parsed_desc); |
55 | 0 | assert(error.empty()); |
56 | 0 | assert(parsed_desc->IsRange()); |
57 | 0 | assert(parsed_desc->IsSingleType()); |
58 | 0 | assert(!keys.keys.empty()); |
59 | 0 | WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/0}; |
60 | 0 | assert(!wallet->GetDescriptorScriptPubKeyMan(w_desc)); |
61 | 0 | LOCK(wallet->cs_wallet); |
62 | 0 | auto spk_manager{wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", internal)}; |
63 | 0 | assert(spk_manager); |
64 | 0 | wallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal); |
65 | 0 | } |
66 | 0 | } |
67 | 0 | } |
68 | | CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) |
69 | 0 | { |
70 | 0 | auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; |
71 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
72 | 0 | return *Assert(wallet->GetNewDestination(type, "")); |
73 | 0 | } else { |
74 | 0 | return *Assert(wallet->GetNewChangeDestination(type)); |
75 | 0 | } |
76 | 0 | } |
77 | 0 | CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } |
78 | | void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) |
79 | 0 | { |
80 | | // The fee of "tx" is 0, so this is the total input and output amount |
81 | 0 | const CAmount total_amt{ |
82 | 0 | std::accumulate(tx.vout.begin(), tx.vout.end(), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue; })}; |
83 | 0 | const uint32_t tx_size(GetVirtualTransactionSize(CTransaction{tx})); |
84 | 0 | std::set<int> subtract_fee_from_outputs; |
85 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
86 | 0 | for (size_t i{}; i < tx.vout.size(); ++i) { |
87 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
88 | 0 | subtract_fee_from_outputs.insert(i); |
89 | 0 | } |
90 | 0 | } |
91 | 0 | } |
92 | 0 | std::vector<CRecipient> recipients; |
93 | 0 | for (size_t idx = 0; idx < tx.vout.size(); idx++) { |
94 | 0 | const CTxOut& tx_out = tx.vout[idx]; |
95 | 0 | CTxDestination dest; |
96 | 0 | ExtractDestination(tx_out.scriptPubKey, dest); |
97 | 0 | CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1}; |
98 | 0 | recipients.push_back(recipient); |
99 | 0 | } |
100 | 0 | CCoinControl coin_control; |
101 | 0 | coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); |
102 | 0 | CallOneOf( |
103 | 0 | fuzzed_data_provider, [&] { coin_control.destChange = GetDestination(fuzzed_data_provider); }, |
104 | 0 | [&] { coin_control.m_change_type.emplace(fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)); }, |
105 | 0 | [&] { /* no op (leave uninitialized) */ }); |
106 | 0 | coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool(); |
107 | 0 | coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); |
108 | 0 | { |
109 | 0 | auto& r{coin_control.m_signal_bip125_rbf}; |
110 | 0 | CallOneOf( |
111 | 0 | fuzzed_data_provider, [&] { r = true; }, [&] { r = false; }, [&] { r = std::nullopt; }); |
112 | 0 | } |
113 | 0 | coin_control.m_feerate = CFeeRate{ |
114 | | // A fee of this range should cover all cases |
115 | 0 | fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, 2 * total_amt), |
116 | 0 | tx_size, |
117 | 0 | }; |
118 | 0 | if (fuzzed_data_provider.ConsumeBool()) { |
119 | 0 | *coin_control.m_feerate += GetMinimumFeeRate(*wallet, coin_control, nullptr); |
120 | 0 | } |
121 | 0 | coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); |
122 | | // Add solving data (m_external_provider and SelectExternal)? |
123 | |
|
124 | 0 | int change_position{fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, tx.vout.size() - 1)}; |
125 | 0 | bilingual_str error; |
126 | | // Clear tx.vout since it is not meant to be used now that we are passing outputs directly. |
127 | | // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly |
128 | 0 | tx.vout.clear(); |
129 | 0 | (void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control); |
130 | 0 | } |
131 | | }; |
132 | | } |
133 | | |
134 | | #endif // BITCOIN_TEST_FUZZ_UTIL_WALLET_H |