22// Distributed under the MIT software license, see the accompanying
33// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44
5+ #include < addresstype.h>
6+ #include < common/args.h>
7+ #include < consensus/amount.h>
8+ #include < interfaces/chain.h>
59#include < kernel/chain.h>
10+ #include < outputtype.h>
11+ #include < policy/feerate.h>
12+ #include < policy/policy.h>
13+ #include < primitives/block.h>
14+ #include < primitives/transaction.h>
15+ #include < script/descriptor.h>
16+ #include < script/script.h>
17+ #include < script/signingprovider.h>
18+ #include < sync.h>
619#include < test/fuzz/FuzzedDataProvider.h>
720#include < test/fuzz/fuzz.h>
821#include < test/fuzz/util.h>
922#include < test/util/setup_common.h>
23+ #include < tinyformat.h>
24+ #include < uint256.h>
25+ #include < util/check.h>
26+ #include < util/result.h>
1027#include < util/translation.h>
28+ #include < wallet/coincontrol.h>
1129#include < wallet/context.h>
30+ #include < wallet/fees.h>
1231#include < wallet/receive.h>
32+ #include < wallet/spend.h>
33+ #include < wallet/test/util.h>
1334#include < wallet/wallet.h>
14- #include < wallet/walletdb.h>
1535#include < wallet/walletutil.h>
1636
17- #include < cassert >
37+ #include < cstddef >
1838#include < cstdint>
39+ #include < limits>
40+ #include < numeric>
41+ #include < set>
1942#include < string>
43+ #include < tuple>
44+ #include < utility>
2045#include < vector>
2146
2247namespace wallet {
@@ -29,45 +54,59 @@ void initialize_setup()
2954 g_setup = testing_setup.get ();
3055}
3156
57+ void ImportDescriptors (CWallet& wallet, const std::string& seed_insecure)
58+ {
59+ const std::vector<std::string> DESCS{
60+ " pkh(%s/%s/*)" ,
61+ " sh(wpkh(%s/%s/*))" ,
62+ " tr(%s/%s/*)" ,
63+ " wpkh(%s/%s/*)" ,
64+ };
65+
66+ for (const std::string& desc_fmt : DESCS) {
67+ for (bool internal : {true , false }) {
68+ const auto descriptor{(strprintf)(desc_fmt, " [5aa9973a/66h/4h/2h]" + seed_insecure, int {internal})};
69+
70+ FlatSigningProvider keys;
71+ std::string error;
72+ auto parsed_desc = Parse (descriptor, keys, error, /* require_checksum=*/ false );
73+ assert (parsed_desc);
74+ assert (error.empty ());
75+ assert (parsed_desc->IsRange ());
76+ assert (parsed_desc->IsSingleType ());
77+ assert (!keys.keys .empty ());
78+ WalletDescriptor w_desc{std::move (parsed_desc), /* creation_time=*/ 0 , /* range_start=*/ 0 , /* range_end=*/ 1 , /* next_index=*/ 0 };
79+ assert (!wallet.GetDescriptorScriptPubKeyMan (w_desc));
80+ LOCK (wallet.cs_wallet );
81+ auto spk_manager{wallet.AddWalletDescriptor (w_desc, keys, /* label=*/ " " , internal)};
82+ assert (spk_manager);
83+ wallet.AddActiveScriptPubKeyMan (spk_manager->GetID (), *Assert (w_desc.descriptor ->GetOutputType ()), internal);
84+ }
85+ }
86+ }
87+
3288/* *
33- * Wraps a descriptor wallet for fuzzing. The constructor writes the sqlite db
34- * to disk, the destructor deletes it.
89+ * Wraps a descriptor wallet for fuzzing.
3590 */
3691struct FuzzedWallet {
3792 ArgsManager args;
3893 WalletContext context;
3994 std::shared_ptr<CWallet> wallet;
40- FuzzedWallet (const std::string& name)
95+ FuzzedWallet (const std::string& name, const std::string& seed_insecure )
4196 {
42- context.args = &args;
43- context.chain = g_setup->m_node .chain .get ();
44-
45- DatabaseOptions options;
46- options.require_create = true ;
47- options.create_flags = WALLET_FLAG_DESCRIPTORS;
48- const std::optional<bool > load_on_start;
49- gArgs .ForceSetArg (" -keypool" , " 0" ); // Avoid timeout in TopUp()
50-
51- DatabaseStatus status;
52- bilingual_str error;
53- std::vector<bilingual_str> warnings;
54- wallet = CreateWallet (context, name, load_on_start, options, status, error, warnings);
55- assert (wallet);
56- assert (error.empty ());
57- assert (warnings.empty ());
97+ auto & chain{*Assert (g_setup->m_node .chain )};
98+ wallet = std::make_shared<CWallet>(&chain, name, CreateMockableWalletDatabase ());
99+ {
100+ LOCK (wallet->cs_wallet );
101+ wallet->SetWalletFlag (WALLET_FLAG_DESCRIPTORS);
102+ auto height{*Assert (chain.getHeight ())};
103+ wallet->SetLastBlockProcessed (height, chain.getBlockHash (height));
104+ }
105+ wallet->m_keypool_size = 1 ; // Avoid timeout in TopUp()
58106 assert (wallet->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS));
107+ ImportDescriptors (*wallet, seed_insecure);
59108 }
60- ~FuzzedWallet ()
61- {
62- const auto name{wallet->GetName ()};
63- std::vector<bilingual_str> warnings;
64- std::optional<bool > load_on_start;
65- assert (RemoveWallet (context, wallet, load_on_start, warnings));
66- assert (warnings.empty ());
67- UnloadWallet (std::move (wallet));
68- fs::remove_all (GetWalletDir () / fs::PathFromString (name));
69- }
70- CScript GetScriptPubKey (FuzzedDataProvider& fuzzed_data_provider)
109+ CTxDestination GetDestination (FuzzedDataProvider& fuzzed_data_provider)
71110 {
72111 auto type{fuzzed_data_provider.PickValueInArray (OUTPUT_TYPES)};
73112 util::Result<CTxDestination> op_dest{util::Error{}};
@@ -76,7 +115,51 @@ struct FuzzedWallet {
76115 } else {
77116 op_dest = wallet->GetNewChangeDestination (type);
78117 }
79- return GetScriptForDestination (*Assert (op_dest));
118+ return *Assert (op_dest);
119+ }
120+ CScript GetScriptPubKey (FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination (GetDestination (fuzzed_data_provider)); }
121+ void FundTx (FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx)
122+ {
123+ // The fee of "tx" is 0, so this is the total input and output amount
124+ const CAmount total_amt{
125+ std::accumulate (tx.vout .begin (), tx.vout .end (), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue ; })};
126+ const uint32_t tx_size (GetVirtualTransactionSize (CTransaction{tx}));
127+ std::set<int > subtract_fee_from_outputs;
128+ if (fuzzed_data_provider.ConsumeBool ()) {
129+ for (size_t i{}; i < tx.vout .size (); ++i) {
130+ if (fuzzed_data_provider.ConsumeBool ()) {
131+ subtract_fee_from_outputs.insert (i);
132+ }
133+ }
134+ }
135+ CCoinControl coin_control;
136+ coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool ();
137+ CallOneOf (
138+ fuzzed_data_provider, [&] { coin_control.destChange = GetDestination (fuzzed_data_provider); },
139+ [&] { coin_control.m_change_type .emplace (fuzzed_data_provider.PickValueInArray (OUTPUT_TYPES)); },
140+ [&] { /* no op (leave uninitialized) */ });
141+ coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool ();
142+ coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool ();
143+ {
144+ auto & r{coin_control.m_signal_bip125_rbf };
145+ CallOneOf (
146+ fuzzed_data_provider, [&] { r = true ; }, [&] { r = false ; }, [&] { r = std::nullopt ; });
147+ }
148+ coin_control.m_feerate = CFeeRate{
149+ // A fee of this range should cover all cases
150+ fuzzed_data_provider.ConsumeIntegralInRange <CAmount>(0 , 2 * total_amt),
151+ tx_size,
152+ };
153+ if (fuzzed_data_provider.ConsumeBool ()) {
154+ *coin_control.m_feerate += GetMinimumFeeRate (*wallet, coin_control, nullptr );
155+ }
156+ coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool ();
157+ // Add solving data (m_external_provider and SelectExternal)?
158+
159+ CAmount fee_out;
160+ int change_position{fuzzed_data_provider.ConsumeIntegralInRange <int >(-1 , tx.vout .size () - 1 )};
161+ bilingual_str error;
162+ (void )FundTransaction (*wallet, tx, fee_out, change_position, error, /* lockUnspents=*/ false , subtract_fee_from_outputs, coin_control);
80163 }
81164};
82165
@@ -87,8 +170,14 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
87170 // without fee. Thus, the balance of the wallets should always equal the
88171 // total amount.
89172 const auto total_amount{ConsumeMoney (fuzzed_data_provider)};
90- FuzzedWallet a{" fuzzed_wallet_a" };
91- FuzzedWallet b{" fuzzed_wallet_b" };
173+ FuzzedWallet a{
174+ " fuzzed_wallet_a" ,
175+ " tprv8ZgxMBicQKsPd1QwsGgzfu2pcPYbBosZhJknqreRHgsWx32nNEhMjGQX2cgFL8n6wz9xdDYwLcs78N4nsCo32cxEX8RBtwGsEGgybLiQJfk" ,
176+ };
177+ FuzzedWallet b{
178+ " fuzzed_wallet_b" ,
179+ " tprv8ZgxMBicQKsPfCunYTF18sEmEyjz8TfhGnZ3BoVAhkqLv7PLkQgmoG2Ecsp4JuqciWnkopuEwShit7st743fdmB9cMD4tznUkcs33vK51K9" ,
180+ };
92181
93182 // Keep track of all coins in this test.
94183 // Each tuple in the chain represents the coins and the block created with
@@ -123,7 +212,7 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
123212 coins.erase (coins.begin ());
124213 }
125214 // Create some outputs spending all inputs, without fee
126- LIMITED_WHILE (in > 0 && fuzzed_data_provider.ConsumeBool (), 100 )
215+ LIMITED_WHILE (in > 0 && fuzzed_data_provider.ConsumeBool (), 10 )
127216 {
128217 const auto out_value{ConsumeMoney (fuzzed_data_provider, in)};
129218 in -= out_value;
@@ -135,6 +224,9 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
135224 tx.vout .emplace_back (in, wallet.GetScriptPubKey (fuzzed_data_provider));
136225 // Add tx to block
137226 block.vtx .emplace_back (MakeTransactionRef (tx));
227+ // Check that funding the tx doesn't crash the wallet
228+ a.FundTx (fuzzed_data_provider, tx);
229+ b.FundTx (fuzzed_data_provider, tx);
138230 }
139231 // Mine block
140232 const uint256& hash = block.GetHash ();
0 commit comments