Skip to content

Commit fa15861

Browse files
author
MarcoFalke
committed
fuzz: Faster wallet_notifications target
1 parent fa971c0 commit fa15861

File tree

1 file changed

+127
-35
lines changed

1 file changed

+127
-35
lines changed

src/wallet/test/fuzz/notifications.cpp

Lines changed: 127 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,46 @@
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>
1025
#include <util/check.h>
26+
#include <util/result.h>
1127
#include <util/translation.h>
28+
#include <wallet/coincontrol.h>
1229
#include <wallet/context.h>
30+
#include <wallet/fees.h>
1331
#include <wallet/receive.h>
32+
#include <wallet/spend.h>
33+
#include <wallet/test/util.h>
1434
#include <wallet/wallet.h>
15-
#include <wallet/walletdb.h>
1635
#include <wallet/walletutil.h>
1736

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

2247
namespace 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
*/
3691
struct 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

Comments
 (0)