2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include < addresstype.h>
6
+ #include < common/args.h>
7
+ #include < consensus/amount.h>
8
+ #include < interfaces/chain.h>
5
9
#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>
6
19
#include < test/fuzz/FuzzedDataProvider.h>
7
20
#include < test/fuzz/fuzz.h>
8
21
#include < test/fuzz/util.h>
9
22
#include < test/util/setup_common.h>
23
+ #include < tinyformat.h>
24
+ #include < uint256.h>
10
25
#include < util/check.h>
26
+ #include < util/result.h>
11
27
#include < util/translation.h>
28
+ #include < wallet/coincontrol.h>
12
29
#include < wallet/context.h>
30
+ #include < wallet/fees.h>
13
31
#include < wallet/receive.h>
32
+ #include < wallet/spend.h>
33
+ #include < wallet/test/util.h>
14
34
#include < wallet/wallet.h>
15
- #include < wallet/walletdb.h>
16
35
#include < wallet/walletutil.h>
17
36
37
+ #include < cstddef>
18
38
#include < cstdint>
39
+ #include < limits>
40
+ #include < numeric>
41
+ #include < set>
19
42
#include < string>
43
+ #include < tuple>
44
+ #include < utility>
20
45
#include < vector>
21
46
22
47
namespace wallet {
@@ -29,45 +54,59 @@ void initialize_setup()
29
54
g_setup = testing_setup.get ();
30
55
}
31
56
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
+
32
88
/* *
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.
35
90
*/
36
91
struct FuzzedWallet {
37
92
ArgsManager args;
38
93
WalletContext context;
39
94
std::shared_ptr<CWallet> wallet;
40
- FuzzedWallet (const std::string& name)
95
+ FuzzedWallet (const std::string& name, const std::string& seed_insecure )
41
96
{
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()
58
106
assert (wallet->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS));
107
+ ImportDescriptors (*wallet, seed_insecure);
59
108
}
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)
71
110
{
72
111
auto type{fuzzed_data_provider.PickValueInArray (OUTPUT_TYPES)};
73
112
util::Result<CTxDestination> op_dest{util::Error{}};
@@ -76,7 +115,51 @@ struct FuzzedWallet {
76
115
} else {
77
116
op_dest = wallet->GetNewChangeDestination (type);
78
117
}
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);
80
163
}
81
164
};
82
165
@@ -87,8 +170,14 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
87
170
// without fee. Thus, the balance of the wallets should always equal the
88
171
// total amount.
89
172
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
+ };
92
181
93
182
// Keep track of all coins in this test.
94
183
// Each tuple in the chain represents the coins and the block created with
@@ -123,7 +212,7 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
123
212
coins.erase (coins.begin ());
124
213
}
125
214
// 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 )
127
216
{
128
217
const auto out_value{ConsumeMoney (fuzzed_data_provider, in)};
129
218
in -= out_value;
@@ -135,6 +224,9 @@ FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
135
224
tx.vout .emplace_back (in, wallet.GetScriptPubKey (fuzzed_data_provider));
136
225
// Add tx to block
137
226
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);
138
230
}
139
231
// Mine block
140
232
const uint256& hash = block.GetHash ();
0 commit comments