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