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