-
Notifications
You must be signed in to change notification settings - Fork 266
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge bitcoin/bitcoin#29936: fuzz: wallet: add target for `CreateTran…
…saction` c495731 fuzz: wallet: add target for `CreateTransaction` (brunoerg) 3db68e2 wallet: move `ImportDescriptors`/`FuzzedWallet` to util (brunoerg) Pull request description: This PR adds a fuzz target for the `CreateTransaction` function. It is a regression target for bitcoin/bitcoin#27271 and can be testing by applying: ```diff @@ -1110,7 +1110,7 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal( // This can only happen if feerate is 0, and requested destinations are value of 0 (e.g. OP_RETURN) // and no pre-selected inputs. This will result in 0-input transaction, which is consensus-invalid anyways if (selection_target == 0 && !coin_control.HasSelected()) { - return util::Error{_("Transaction requires one destination of non-0 value, a non-0 feerate, or a pre-selected input")}; + // return util::Error{_("Transaction requires one destination of non-0 value, a non-0 feerate, or a pre-selected input")}; } ``` Also, it moves `ImportDescriptors` function to `src/wallet/test/util.h` to avoid to duplicate same code. ACKs for top commit: marcofleon: ACK c495731 maflcko: ACK c495731 🏻 Tree-SHA512: a439f947b91b01e327e18cd18e63d5ce49f2cb9ca16ca9d56fe337b8cff239b3af4db18fe89478fe5faa5549d37ca935bd321913db7646fbf6818f825cb5d878
- Loading branch information
Showing
4 changed files
with
240 additions
and
115 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// Copyright (c) 2024-present The Bitcoin Core developers | ||
// Distributed under the MIT software license, see the accompanying | ||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#ifndef BITCOIN_TEST_FUZZ_UTIL_WALLET_H | ||
#define BITCOIN_TEST_FUZZ_UTIL_WALLET_H | ||
|
||
#include <test/fuzz/FuzzedDataProvider.h> | ||
#include <test/fuzz/fuzz.h> | ||
#include <test/fuzz/util.h> | ||
#include <policy/policy.h> | ||
#include <wallet/coincontrol.h> | ||
#include <wallet/fees.h> | ||
#include <wallet/spend.h> | ||
#include <wallet/test/util.h> | ||
#include <wallet/wallet.h> | ||
|
||
namespace wallet { | ||
|
||
/** | ||
* Wraps a descriptor wallet for fuzzing. | ||
*/ | ||
struct FuzzedWallet { | ||
std::shared_ptr<CWallet> wallet; | ||
FuzzedWallet(interfaces::Chain& chain, const std::string& name, const std::string& seed_insecure) | ||
{ | ||
wallet = std::make_shared<CWallet>(&chain, name, CreateMockableWalletDatabase()); | ||
{ | ||
LOCK(wallet->cs_wallet); | ||
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); | ||
auto height{*Assert(chain.getHeight())}; | ||
wallet->SetLastBlockProcessed(height, chain.getBlockHash(height)); | ||
} | ||
wallet->m_keypool_size = 1; // Avoid timeout in TopUp() | ||
assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); | ||
ImportDescriptors(seed_insecure); | ||
} | ||
void ImportDescriptors(const std::string& seed_insecure) | ||
{ | ||
const std::vector<std::string> DESCS{ | ||
"pkh(%s/%s/*)", | ||
"sh(wpkh(%s/%s/*))", | ||
"tr(%s/%s/*)", | ||
"wpkh(%s/%s/*)", | ||
}; | ||
|
||
for (const std::string& desc_fmt : DESCS) { | ||
for (bool internal : {true, false}) { | ||
const auto descriptor{(strprintf)(desc_fmt, "[5aa9973a/66h/4h/2h]" + seed_insecure, int{internal})}; | ||
|
||
FlatSigningProvider keys; | ||
std::string error; | ||
auto parsed_desc = std::move(Parse(descriptor, keys, error, /*require_checksum=*/false).at(0)); | ||
assert(parsed_desc); | ||
assert(error.empty()); | ||
assert(parsed_desc->IsRange()); | ||
assert(parsed_desc->IsSingleType()); | ||
assert(!keys.keys.empty()); | ||
WalletDescriptor w_desc{std::move(parsed_desc), /*creation_time=*/0, /*range_start=*/0, /*range_end=*/1, /*next_index=*/0}; | ||
assert(!wallet->GetDescriptorScriptPubKeyMan(w_desc)); | ||
LOCK(wallet->cs_wallet); | ||
auto spk_manager{wallet->AddWalletDescriptor(w_desc, keys, /*label=*/"", internal)}; | ||
assert(spk_manager); | ||
wallet->AddActiveScriptPubKeyMan(spk_manager->GetID(), *Assert(w_desc.descriptor->GetOutputType()), internal); | ||
} | ||
} | ||
} | ||
CTxDestination GetDestination(FuzzedDataProvider& fuzzed_data_provider) | ||
{ | ||
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)}; | ||
if (fuzzed_data_provider.ConsumeBool()) { | ||
return *Assert(wallet->GetNewDestination(type, "")); | ||
} else { | ||
return *Assert(wallet->GetNewChangeDestination(type)); | ||
} | ||
} | ||
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider) { return GetScriptForDestination(GetDestination(fuzzed_data_provider)); } | ||
void FundTx(FuzzedDataProvider& fuzzed_data_provider, CMutableTransaction tx) | ||
{ | ||
// The fee of "tx" is 0, so this is the total input and output amount | ||
const CAmount total_amt{ | ||
std::accumulate(tx.vout.begin(), tx.vout.end(), CAmount{}, [](CAmount t, const CTxOut& out) { return t + out.nValue; })}; | ||
const uint32_t tx_size(GetVirtualTransactionSize(CTransaction{tx})); | ||
std::set<int> subtract_fee_from_outputs; | ||
if (fuzzed_data_provider.ConsumeBool()) { | ||
for (size_t i{}; i < tx.vout.size(); ++i) { | ||
if (fuzzed_data_provider.ConsumeBool()) { | ||
subtract_fee_from_outputs.insert(i); | ||
} | ||
} | ||
} | ||
std::vector<CRecipient> recipients; | ||
for (size_t idx = 0; idx < tx.vout.size(); idx++) { | ||
const CTxOut& tx_out = tx.vout[idx]; | ||
CTxDestination dest; | ||
ExtractDestination(tx_out.scriptPubKey, dest); | ||
CRecipient recipient = {dest, tx_out.nValue, subtract_fee_from_outputs.count(idx) == 1}; | ||
recipients.push_back(recipient); | ||
} | ||
CCoinControl coin_control; | ||
coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); | ||
CallOneOf( | ||
fuzzed_data_provider, [&] { coin_control.destChange = GetDestination(fuzzed_data_provider); }, | ||
[&] { coin_control.m_change_type.emplace(fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)); }, | ||
[&] { /* no op (leave uninitialized) */ }); | ||
coin_control.fAllowWatchOnly = fuzzed_data_provider.ConsumeBool(); | ||
coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); | ||
{ | ||
auto& r{coin_control.m_signal_bip125_rbf}; | ||
CallOneOf( | ||
fuzzed_data_provider, [&] { r = true; }, [&] { r = false; }, [&] { r = std::nullopt; }); | ||
} | ||
coin_control.m_feerate = CFeeRate{ | ||
// A fee of this range should cover all cases | ||
fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, 2 * total_amt), | ||
tx_size, | ||
}; | ||
if (fuzzed_data_provider.ConsumeBool()) { | ||
*coin_control.m_feerate += GetMinimumFeeRate(*wallet, coin_control, nullptr); | ||
} | ||
coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); | ||
// Add solving data (m_external_provider and SelectExternal)? | ||
|
||
int change_position{fuzzed_data_provider.ConsumeIntegralInRange<int>(-1, tx.vout.size() - 1)}; | ||
bilingual_str error; | ||
// Clear tx.vout since it is not meant to be used now that we are passing outputs directly. | ||
// This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly | ||
tx.vout.clear(); | ||
(void)FundTransaction(*wallet, tx, recipients, change_position, /*lockUnspents=*/false, coin_control); | ||
} | ||
}; | ||
} | ||
|
||
#endif // BITCOIN_TEST_FUZZ_UTIL_WALLET_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// Copyright (c) 2024-present The Bitcoin Core developers | ||
// Distributed under the MIT software license, see the accompanying | ||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#include <test/fuzz/FuzzedDataProvider.h> | ||
#include <test/fuzz/fuzz.h> | ||
#include <test/fuzz/util.h> | ||
#include <test/fuzz/util/wallet.h> | ||
#include <test/util/random.h> | ||
#include <test/util/setup_common.h> | ||
#include <wallet/coincontrol.h> | ||
#include <wallet/context.h> | ||
#include <wallet/spend.h> | ||
#include <wallet/test/util.h> | ||
#include <wallet/wallet.h> | ||
#include <validation.h> | ||
#include <addresstype.h> | ||
|
||
using util::ToString; | ||
|
||
namespace wallet { | ||
namespace { | ||
const TestingSetup* g_setup; | ||
|
||
void initialize_setup() | ||
{ | ||
static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); | ||
g_setup = testing_setup.get(); | ||
} | ||
|
||
FUZZ_TARGET(wallet_create_transaction, .init = initialize_setup) | ||
{ | ||
SeedRandomStateForTest(SeedRand::ZEROS); | ||
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; | ||
const auto& node = g_setup->m_node; | ||
Chainstate& chainstate{node.chainman->ActiveChainstate()}; | ||
ArgsManager& args = *node.args; | ||
args.ForceSetArg("-dustrelayfee", ToString(fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(0, MAX_MONEY))); | ||
FuzzedWallet fuzzed_wallet{ | ||
*g_setup->m_node.chain, | ||
"fuzzed_wallet_a", | ||
"tprv8ZgxMBicQKsPd1QwsGgzfu2pcPYbBosZhJknqreRHgsWx32nNEhMjGQX2cgFL8n6wz9xdDYwLcs78N4nsCo32cxEX8RBtwGsEGgybLiQJfk", | ||
}; | ||
|
||
CCoinControl coin_control; | ||
if (fuzzed_data_provider.ConsumeBool()) coin_control.m_version = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); | ||
coin_control.m_avoid_partial_spends = fuzzed_data_provider.ConsumeBool(); | ||
coin_control.m_include_unsafe_inputs = fuzzed_data_provider.ConsumeBool(); | ||
if (fuzzed_data_provider.ConsumeBool()) coin_control.m_confirm_target = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); | ||
coin_control.destChange = fuzzed_data_provider.ConsumeBool() ? fuzzed_wallet.GetDestination(fuzzed_data_provider) : ConsumeTxDestination(fuzzed_data_provider); | ||
if (fuzzed_data_provider.ConsumeBool()) coin_control.m_change_type = fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES); | ||
if (fuzzed_data_provider.ConsumeBool()) coin_control.m_feerate = CFeeRate(ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)); | ||
coin_control.m_allow_other_inputs = fuzzed_data_provider.ConsumeBool(); | ||
coin_control.m_locktime = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); | ||
coin_control.fOverrideFeeRate = fuzzed_data_provider.ConsumeBool(); | ||
|
||
int next_locktime{0}; | ||
CAmount all_values{0}; | ||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) | ||
{ | ||
CMutableTransaction tx; | ||
tx.nLockTime = next_locktime++; | ||
tx.vout.resize(1); | ||
CAmount n_value{ConsumeMoney(fuzzed_data_provider)}; | ||
all_values += n_value; | ||
if (all_values > MAX_MONEY) return; | ||
tx.vout[0].nValue = n_value; | ||
tx.vout[0].scriptPubKey = GetScriptForDestination(fuzzed_wallet.GetDestination(fuzzed_data_provider)); | ||
LOCK(fuzzed_wallet.wallet->cs_wallet); | ||
auto txid{tx.GetHash()}; | ||
auto ret{fuzzed_wallet.wallet->mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateConfirmed{chainstate.m_chain.Tip()->GetBlockHash(), chainstate.m_chain.Height(), /*index=*/0}))}; | ||
assert(ret.second); | ||
} | ||
|
||
std::vector<CRecipient> recipients; | ||
LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 100) { | ||
CTxDestination destination; | ||
CallOneOf( | ||
fuzzed_data_provider, | ||
[&] { | ||
destination = fuzzed_wallet.GetDestination(fuzzed_data_provider); | ||
}, | ||
[&] { | ||
CScript script; | ||
script << OP_RETURN; | ||
destination = CNoDestination{script}; | ||
}, | ||
[&] { | ||
destination = ConsumeTxDestination(fuzzed_data_provider); | ||
} | ||
); | ||
recipients.push_back({destination, | ||
/*nAmount=*/ConsumeMoney(fuzzed_data_provider), | ||
/*fSubtractFeeFromAmount=*/fuzzed_data_provider.ConsumeBool()}); | ||
} | ||
|
||
std::optional<unsigned int> change_pos; | ||
if (fuzzed_data_provider.ConsumeBool()) change_pos = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); | ||
(void)CreateTransaction(*fuzzed_wallet.wallet, recipients, change_pos, coin_control); | ||
} | ||
} // namespace | ||
} // namespace wallet |