mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
wallet: Pass FastRandomContext& to coin selection
This commit is contained in:
parent
77773b061c
commit
fa7deaa046
5 changed files with 78 additions and 45 deletions
|
@ -62,10 +62,17 @@ static void CoinSelection(benchmark::Bench& bench)
|
|||
}
|
||||
|
||||
const CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||
const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34,
|
||||
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
|
||||
FastRandomContext rand{};
|
||||
const CoinSelectionParams coin_selection_params{
|
||||
rand,
|
||||
/* change_output_size= */ 34,
|
||||
/* change_spend_size= */ 148,
|
||||
/* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0),
|
||||
/* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0,
|
||||
/* avoid_partial= */ false,
|
||||
};
|
||||
bench.run([&] {
|
||||
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
|
||||
assert(result);
|
||||
|
|
|
@ -169,14 +169,14 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
return result;
|
||||
}
|
||||
|
||||
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value)
|
||||
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng)
|
||||
{
|
||||
SelectionResult result(target_value);
|
||||
|
||||
std::vector<size_t> indexes;
|
||||
indexes.resize(utxo_pool.size());
|
||||
std::iota(indexes.begin(), indexes.end(), 0);
|
||||
Shuffle(indexes.begin(), indexes.end(), FastRandomContext());
|
||||
Shuffle(indexes.begin(), indexes.end(), rng);
|
||||
|
||||
CAmount selected_eff_value = 0;
|
||||
for (const size_t i : indexes) {
|
||||
|
@ -191,7 +191,7 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
||||
static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
||||
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
|
||||
{
|
||||
std::vector<char> vfIncluded;
|
||||
|
@ -199,8 +199,6 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
|
|||
vfBest.assign(groups.size(), true);
|
||||
nBest = nTotalLower;
|
||||
|
||||
FastRandomContext insecure_rand;
|
||||
|
||||
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
|
||||
{
|
||||
vfIncluded.assign(groups.size(), false);
|
||||
|
@ -237,7 +235,7 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue)
|
||||
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, FastRandomContext& rng)
|
||||
{
|
||||
SelectionResult result(nTargetValue);
|
||||
|
||||
|
@ -246,7 +244,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
|
|||
std::vector<OutputGroup> applicable_groups;
|
||||
CAmount nTotalLower = 0;
|
||||
|
||||
Shuffle(groups.begin(), groups.end(), FastRandomContext());
|
||||
Shuffle(groups.begin(), groups.end(), rng);
|
||||
|
||||
for (const OutputGroup& group : groups) {
|
||||
if (group.GetSelectionAmount() == nTargetValue) {
|
||||
|
@ -278,9 +276,9 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
|
|||
std::vector<char> vfBest;
|
||||
CAmount nBest;
|
||||
|
||||
ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
|
||||
ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
|
||||
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
|
||||
ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
|
||||
ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
|
||||
}
|
||||
|
||||
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
|
||||
|
|
|
@ -73,8 +73,9 @@ public:
|
|||
};
|
||||
|
||||
/** Parameters for one iteration of Coin Selection. */
|
||||
struct CoinSelectionParams
|
||||
{
|
||||
struct CoinSelectionParams {
|
||||
/** Randomness to use in the context of coin selection. */
|
||||
FastRandomContext& rng_fast;
|
||||
/** Size of a change output in bytes, determined by the output type. */
|
||||
size_t change_output_size = 0;
|
||||
/** Size of the input to spend a change output in virtual bytes. */
|
||||
|
@ -100,17 +101,20 @@ struct CoinSelectionParams
|
|||
* reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */
|
||||
bool m_avoid_partial_spends = false;
|
||||
|
||||
CoinSelectionParams(size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate,
|
||||
CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial) :
|
||||
change_output_size(change_output_size),
|
||||
change_spend_size(change_spend_size),
|
||||
m_effective_feerate(effective_feerate),
|
||||
m_long_term_feerate(long_term_feerate),
|
||||
m_discard_feerate(discard_feerate),
|
||||
tx_noinputs_size(tx_noinputs_size),
|
||||
m_avoid_partial_spends(avoid_partial)
|
||||
{}
|
||||
CoinSelectionParams() {}
|
||||
CoinSelectionParams(FastRandomContext& rng_fast, size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate,
|
||||
CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial)
|
||||
: rng_fast{rng_fast},
|
||||
change_output_size(change_output_size),
|
||||
change_spend_size(change_spend_size),
|
||||
m_effective_feerate(effective_feerate),
|
||||
m_long_term_feerate(long_term_feerate),
|
||||
m_discard_feerate(discard_feerate),
|
||||
tx_noinputs_size(tx_noinputs_size),
|
||||
m_avoid_partial_spends(avoid_partial)
|
||||
{
|
||||
}
|
||||
CoinSelectionParams(FastRandomContext& rng_fast)
|
||||
: rng_fast{rng_fast} {}
|
||||
};
|
||||
|
||||
/** Parameters for filtering which OutputGroups we may use in coin selection.
|
||||
|
@ -246,10 +250,10 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
|
|||
* @param[in] target_value The target value to select for
|
||||
* @returns If successful, a SelectionResult, otherwise, std::nullopt
|
||||
*/
|
||||
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value);
|
||||
std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value, FastRandomContext& rng);
|
||||
|
||||
// Original coin selection algorithm as a fallback
|
||||
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue);
|
||||
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue, FastRandomContext& rng);
|
||||
} // namespace wallet
|
||||
|
||||
#endif // BITCOIN_WALLET_COINSELECTION_H
|
||||
|
|
|
@ -386,7 +386,7 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
|
|||
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
|
||||
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
|
||||
// So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
|
||||
if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee)}) {
|
||||
if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee, coin_selection_params.rng_fast)}) {
|
||||
knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
|
||||
results.push_back(*knapsack_result);
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
|
|||
// We include the minimum final change for SRD as we do want to avoid making really small change.
|
||||
// KnapsackSolver does not need this because it includes MIN_CHANGE internally.
|
||||
const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE;
|
||||
if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target)}) {
|
||||
if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
|
||||
srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
|
||||
results.push_back(*srd_result);
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
|
|||
// Cases where we have 101+ outputs all pointing to the same destination may result in
|
||||
// privacy leaks as they will potentially be deterministically sorted. We solve that by
|
||||
// explicitly shuffling the outputs before processing
|
||||
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
|
||||
Shuffle(vCoins.begin(), vCoins.end(), coin_selection_params.rng_fast);
|
||||
}
|
||||
|
||||
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
|
||||
|
@ -657,7 +657,7 @@ static bool CreateTransactionInternal(
|
|||
FastRandomContext rng_fast;
|
||||
CMutableTransaction txNew; // The resulting transaction that we make
|
||||
|
||||
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
|
||||
CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy
|
||||
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
|
||||
|
||||
// Set the long term feerate estimate to the wallet's consolidate feerate
|
||||
|
|
|
@ -164,10 +164,17 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
|
|||
|
||||
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
|
||||
{
|
||||
CoinSelectionParams coin_selection_params(/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
|
||||
FastRandomContext rand{};
|
||||
CoinSelectionParams coin_selection_params{
|
||||
rand,
|
||||
/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0,
|
||||
/* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0),
|
||||
/* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0,
|
||||
/* avoid_partial= */ false,
|
||||
};
|
||||
static std::vector<OutputGroup> static_groups;
|
||||
static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
|
||||
return static_groups;
|
||||
|
@ -176,6 +183,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
|
|||
// Branch and bound coin selection tests
|
||||
BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||
{
|
||||
FastRandomContext rand{};
|
||||
// Setup
|
||||
std::vector<CInputCoin> utxo_pool;
|
||||
SelectionResult expected_result(CAmount(0));
|
||||
|
@ -301,10 +309,16 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||
}
|
||||
|
||||
// Make sure that effective value is working in AttemptSelection when BnB is used
|
||||
CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000),
|
||||
/* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000),
|
||||
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
|
||||
CoinSelectionParams coin_selection_params_bnb{
|
||||
rand,
|
||||
/* change_output_size= */ 0,
|
||||
/* change_spend_size= */ 0,
|
||||
/* effective_feerate= */ CFeeRate(3000),
|
||||
/* long_term_feerate= */ CFeeRate(1000),
|
||||
/* discard_feerate= */ CFeeRate(1000),
|
||||
/* tx_noinputs_size= */ 0,
|
||||
/* avoid_partial= */ false,
|
||||
};
|
||||
{
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
|
@ -351,6 +365,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
|||
|
||||
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||
{
|
||||
FastRandomContext rand{};
|
||||
const auto temp1{[&rand](std::vector<OutputGroup>& g, const CAmount& v) { return KnapsackSolver(g, v, rand); }};
|
||||
const auto KnapsackSolver{temp1};
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
|
@ -660,6 +677,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
|||
|
||||
BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
||||
{
|
||||
FastRandomContext rand{};
|
||||
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
|
||||
wallet->LoadWallet();
|
||||
LOCK(wallet->cs_wallet);
|
||||
|
@ -673,7 +691,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
|
|||
add_coin(coins, *wallet, 1000 * COIN);
|
||||
add_coin(coins, *wallet, 3 * COIN);
|
||||
|
||||
const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN);
|
||||
const auto result = KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1003 * COIN, rand);
|
||||
BOOST_CHECK(result);
|
||||
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
|
||||
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
|
||||
|
@ -714,10 +732,16 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
|
|||
CAmount target = rand.randrange(balance - 1000) + 1000;
|
||||
|
||||
// Perform selection
|
||||
CoinSelectionParams cs_params(/* change_output_size= */ 34,
|
||||
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false);
|
||||
CoinSelectionParams cs_params{
|
||||
rand,
|
||||
/* change_output_size= */ 34,
|
||||
/* change_spend_size= */ 148,
|
||||
/* effective_feerate= */ CFeeRate(0),
|
||||
/* long_term_feerate= */ CFeeRate(0),
|
||||
/* discard_feerate= */ CFeeRate(0),
|
||||
/* tx_noinputs_size= */ 0,
|
||||
/* avoid_partial= */ false,
|
||||
};
|
||||
CCoinControl cc;
|
||||
const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
|
||||
BOOST_CHECK(result);
|
||||
|
|
Loading…
Add table
Reference in a new issue