Branch data Line data Source code
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 [ + - + - : 7470 : struct FuzzedWallet {
- - # # ]
[ + - + -
- - - - ]
24 : : std::shared_ptr<CWallet> wallet;
25 : 4440 : FuzzedWallet(interfaces::Chain& chain, const std::string& name, const std::string& seed_insecure)
26 [ + - ]: 4440 : {
27 [ + - + - : 4440 : wallet = std::make_shared<CWallet>(&chain, name, CreateMockableWalletDatabase());
- + ]
28 : 4440 : {
29 [ + - ]: 4440 : LOCK(wallet->cs_wallet);
30 [ + - ]: 4440 : wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
31 [ + - + - : 4440 : auto height{*Assert(chain.getHeight())};
+ - ]
32 [ + - + - ]: 4440 : wallet->SetLastBlockProcessed(height, chain.getBlockHash(height));
33 : 0 : }
34 [ + - ]: 4440 : wallet->m_keypool_size = 1; // Avoid timeout in TopUp()
35 [ + - - + ]: 4440 : assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
36 [ + - ]: 4440 : ImportDescriptors(seed_insecure);
37 [ - - ]: 4440 : }
38 : 4440 : void ImportDescriptors(const std::string& seed_insecure)
39 : : {
40 : 4440 : const std::vector<std::string> DESCS{
41 : : "pkh(%s/%s/*)",
42 : : "sh(wpkh(%s/%s/*))",
43 : : "tr(%s/%s/*)",
44 : : "wpkh(%s/%s/*)",
45 [ + - + + : 44400 : };
+ - + + -
- - - ]
46 : :
47 [ + + ]: 22200 : for (const std::string& desc_fmt : DESCS) {
48 [ + + ]: 53280 : for (bool internal : {true, false}) {
49 [ + - + - ]: 35520 : const auto descriptor{(strprintf)(desc_fmt, "[5aa9973a/66h/4h/2h]" + seed_insecure, int{internal})};
50 : :
51 : 35520 : FlatSigningProvider keys;
52 [ + - ]: 35520 : std::string error;
53 [ + - ]: 71040 : auto parsed_desc = std::move(Parse(descriptor, keys, error, /*require_checksum=*/false).at(0));
54 [ - + ]: 35520 : assert(parsed_desc);
55 [ - + ]: 35520 : assert(error.empty());
56 [ + - - + ]: 35520 : assert(parsed_desc->IsRange());
57 [ + - - + ]: 35520 : assert(parsed_desc->IsSingleType());
58 [ - + ]: 35520 : assert(!keys.keys.empty());
59 [ + - + - ]: 35520 : WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/0};
60 [ + - - + ]: 35520 : assert(!wallet->GetDescriptorScriptPubKeyMan(w_desc));
61 [ + - ]: 35520 : LOCK(wallet->cs_wallet);
62 [ + - + - ]: 35520 : auto spk_manager{wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", internal)};
63 [ - + ]: 35520 : assert(spk_manager);
64 [ + - + - : 35520 : wallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal);
+ - + - ]
65 : 35520 : }
66 : : }
67 : 4440 : }
68 : 259494 : CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider)
69 : : {
70 : 259494 : auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
71 [ + + ]: 259494 : if (fuzzed_data_provider.ConsumeBool()) {
72 [ + - + - : 342672 : return *Assert(wallet->GetNewDestination(type, ""));
+ - ]
73 : : } else {
74 [ + - + - ]: 176316 : return *Assert(wallet->GetNewChangeDestination(type));
75 : : }
76 : : }
77 [ + - ]: 340012 : CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); }
78 : 84640 : void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx)
79 : : {
80 : : // The fee of "tx" is 0, so this is the total input and output amount
81 : 84640 : const CAmount total_amt{
82 : 424652 : std::accumulate(tx.vout.begin(), tx.vout.end(), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue; })};
83 [ + - ]: 84640 : const uint32_t tx_size(GetVirtualTransactionSize(CTransaction{tx}));
84 : 84640 : std::set<int> subtract_fee_from_outputs;
85 [ + + ]: 84640 : if (fuzzed_data_provider.ConsumeBool()) {
86 [ + + ]: 272776 : for (size_t i{}; i < tx.vout.size(); ++i) {
87 [ + + ]: 236710 : if (fuzzed_data_provider.ConsumeBool()) {
88 [ + - ]: 197651 : subtract_fee_from_outputs.insert(i);
89 : : }
90 : : }
91 : : }
92 : 84640 : std::vector<CRecipient> recipients;
93 [ + + ]: 424652 : for (size_t idx = 0; idx < tx.vout.size(); idx++) {
94 [ + - ]: 340012 : const CTxOut& tx_out = tx.vout[idx];
95 : 340012 : CTxDestination dest;
96 [ + - ]: 340012 : ExtractDestination(tx_out.scriptPubKey, dest);
97 [ + - ]: 680024 : CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1};
98 [ + - ]: 340012 : recipients.push_back(recipient);
99 : 340012 : }
100 [ + - ]: 84640 : CCoinControl coin_control;
101 : 84640 : coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool();
102 [ + - ]: 84640 : CallOneOf(
103 : 57265 : fuzzed_data_provider, [&] { coin_control.destChange = GetDestination(fuzzed_data_provider); },
104 [ - + ]: 23112 : [&] { coin_control.m_change_type.emplace(fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)); },
105 : : [&] { /* no op (leave uninitialized) */ });
106 : 84640 : coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool();
107 : 84640 : coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool();
108 : 84640 : {
109 : 84640 : auto& r{coin_control.m_signal_bip125_rbf};
110 : 84640 : CallOneOf(
111 [ - + ]: 84640 : fuzzed_data_provider, [&] { r = true; }, [&] { r = false; }, [&] { r = std::nullopt; });
112 : : }
113 : 84640 : coin_control.m_feerate = CFeeRate{
114 : : // A fee of this range should cover all cases
115 : 84640 : fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, 2 * total_amt),
116 : : tx_size,
117 [ + - ]: 84640 : };
118 [ + + ]: 84640 : if (fuzzed_data_provider.ConsumeBool()) {
119 [ + - ]: 55458 : *coin_control.m_feerate += GetMinimumFeeRate(*wallet, coin_control, nullptr);
120 : : }
121 : 84640 : coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool();
122 : : // Add solving data (m_external_provider and SelectExternal)?
123 : :
124 : 84640 : int change_position{fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, tx.vout.size() - 1)};
125 : 84640 : 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 : 84640 : tx.vout.clear();
129 [ + - + - ]: 169280 : (void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control);
130 : 84640 : }
131 : : };
132 : : }
133 : :
134 : : #endif // BITCOIN_TEST_FUZZ_UTIL_WALLET_H
|