wallet: Pass FastRandomContext& to coin selection

This commit is contained in:
MarcoFalke
2022-03-14 15:22:42 +01:00
parent 77773b061c
commit fa7deaa046
5 changed files with 78 additions and 45 deletions

View File

@@ -62,10 +62,17 @@ static void CoinSelection(benchmark::Bench& bench)
} }
const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinEligibilityFilter filter_standard(1, 6, 0);
const CoinSelectionParams coin_selection_params(/* change_output_size= */ 34, FastRandomContext rand{};
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), const CoinSelectionParams coin_selection_params{
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), rand,
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false); /* 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([&] { bench.run([&] {
auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params); auto result = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, coin_selection_params);
assert(result); assert(result);

View File

@@ -169,14 +169,14 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
return result; 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); SelectionResult result(target_value);
std::vector<size_t> indexes; std::vector<size_t> indexes;
indexes.resize(utxo_pool.size()); indexes.resize(utxo_pool.size());
std::iota(indexes.begin(), indexes.end(), 0); std::iota(indexes.begin(), indexes.end(), 0);
Shuffle(indexes.begin(), indexes.end(), FastRandomContext()); Shuffle(indexes.begin(), indexes.end(), rng);
CAmount selected_eff_value = 0; CAmount selected_eff_value = 0;
for (const size_t i : indexes) { for (const size_t i : indexes) {
@@ -191,7 +191,7 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
return std::nullopt; 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>& vfBest, CAmount& nBest, int iterations = 1000)
{ {
std::vector<char> vfIncluded; std::vector<char> vfIncluded;
@@ -199,8 +199,6 @@ static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const
vfBest.assign(groups.size(), true); vfBest.assign(groups.size(), true);
nBest = nTotalLower; nBest = nTotalLower;
FastRandomContext insecure_rand;
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{ {
vfIncluded.assign(groups.size(), false); 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); SelectionResult result(nTargetValue);
@@ -246,7 +244,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
std::vector<OutputGroup> applicable_groups; std::vector<OutputGroup> applicable_groups;
CAmount nTotalLower = 0; CAmount nTotalLower = 0;
Shuffle(groups.begin(), groups.end(), FastRandomContext()); Shuffle(groups.begin(), groups.end(), rng);
for (const OutputGroup& group : groups) { for (const OutputGroup& group : groups) {
if (group.GetSelectionAmount() == nTargetValue) { if (group.GetSelectionAmount() == nTargetValue) {
@@ -278,9 +276,9 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
std::vector<char> vfBest; std::vector<char> vfBest;
CAmount nBest; CAmount nBest;
ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest); ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) { 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, // If we have a bigger coin and (either the stochastic approximation didn't find a good solution,

View File

@@ -73,8 +73,9 @@ public:
}; };
/** Parameters for one iteration of Coin Selection. */ /** 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 of a change output in bytes, determined by the output type. */
size_t change_output_size = 0; size_t change_output_size = 0;
/** Size of the input to spend a change output in virtual bytes. */ /** 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. */ * reuse. Dust outputs are not eligible to be added to output groups and thus not considered. */
bool m_avoid_partial_spends = false; bool m_avoid_partial_spends = false;
CoinSelectionParams(size_t change_output_size, size_t change_spend_size, CFeeRate effective_feerate, 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) : CFeeRate long_term_feerate, CFeeRate discard_feerate, size_t tx_noinputs_size, bool avoid_partial)
change_output_size(change_output_size), : rng_fast{rng_fast},
change_spend_size(change_spend_size), change_output_size(change_output_size),
m_effective_feerate(effective_feerate), change_spend_size(change_spend_size),
m_long_term_feerate(long_term_feerate), m_effective_feerate(effective_feerate),
m_discard_feerate(discard_feerate), m_long_term_feerate(long_term_feerate),
tx_noinputs_size(tx_noinputs_size), m_discard_feerate(discard_feerate),
m_avoid_partial_spends(avoid_partial) tx_noinputs_size(tx_noinputs_size),
{} m_avoid_partial_spends(avoid_partial)
CoinSelectionParams() {} {
}
CoinSelectionParams(FastRandomContext& rng_fast)
: rng_fast{rng_fast} {}
}; };
/** Parameters for filtering which OutputGroups we may use in coin selection. /** 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 * @param[in] target_value The target value to select for
* @returns If successful, a SelectionResult, otherwise, std::nullopt * @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 // 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 } // namespace wallet
#endif // BITCOIN_WALLET_COINSELECTION_H #endif // BITCOIN_WALLET_COINSELECTION_H

View File

@@ -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 */); 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. // 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. // 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); knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*knapsack_result); 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. // 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. // 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; 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); srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*srd_result); 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 // 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 // privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing // 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 // 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; FastRandomContext rng_fast;
CMutableTransaction txNew; // The resulting transaction that we make 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; 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 // Set the long term feerate estimate to the wallet's consolidate feerate

View File

@@ -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) inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>& coins, CWallet& wallet, const CoinEligibilityFilter& filter)
{ {
CoinSelectionParams coin_selection_params(/* change_output_size= */ 0, FastRandomContext rand{};
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(0), CoinSelectionParams coin_selection_params{
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), rand,
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false); /* 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 std::vector<OutputGroup> static_groups;
static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false); static_groups = GroupOutputs(wallet, coins, coin_selection_params, filter, /*positive_only=*/false);
return static_groups; return static_groups;
@@ -176,6 +183,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
// Branch and bound coin selection tests // Branch and bound coin selection tests
BOOST_AUTO_TEST_CASE(bnb_search_test) BOOST_AUTO_TEST_CASE(bnb_search_test)
{ {
FastRandomContext rand{};
// Setup // Setup
std::vector<CInputCoin> utxo_pool; std::vector<CInputCoin> utxo_pool;
SelectionResult expected_result(CAmount(0)); 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 // Make sure that effective value is working in AttemptSelection when BnB is used
CoinSelectionParams coin_selection_params_bnb(/* change_output_size= */ 0, CoinSelectionParams coin_selection_params_bnb{
/* change_spend_size= */ 0, /* effective_feerate= */ CFeeRate(3000), rand,
/* long_term_feerate= */ CFeeRate(1000), /* discard_feerate= */ CFeeRate(1000), /* change_output_size= */ 0,
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false); /* 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()); std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet(); wallet->LoadWallet();
@@ -351,6 +365,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
BOOST_AUTO_TEST_CASE(knapsack_solver_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()); std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet(); wallet->LoadWallet();
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
@@ -660,6 +677,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
BOOST_AUTO_TEST_CASE(ApproximateBestSubset) BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
{ {
FastRandomContext rand{};
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase()); std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
wallet->LoadWallet(); wallet->LoadWallet();
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
@@ -673,7 +691,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(coins, *wallet, 1000 * COIN); add_coin(coins, *wallet, 1000 * COIN);
add_coin(coins, *wallet, 3 * 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(result);
BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN); BOOST_CHECK_EQUAL(result->GetSelectedValue(), 1003 * COIN);
BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U); BOOST_CHECK_EQUAL(result->GetInputSet().size(), 2U);
@@ -714,10 +732,16 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CAmount target = rand.randrange(balance - 1000) + 1000; CAmount target = rand.randrange(balance - 1000) + 1000;
// Perform selection // Perform selection
CoinSelectionParams cs_params(/* change_output_size= */ 34, CoinSelectionParams cs_params{
/* change_spend_size= */ 148, /* effective_feerate= */ CFeeRate(0), rand,
/* long_term_feerate= */ CFeeRate(0), /* discard_feerate= */ CFeeRate(0), /* change_output_size= */ 34,
/* tx_noinputs_size= */ 0, /* avoid_partial= */ false); /* 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; CCoinControl cc;
const auto result = SelectCoins(*wallet, coins, target, cc, cs_params); const auto result = SelectCoins(*wallet, coins, target, cc, cs_params);
BOOST_CHECK(result); BOOST_CHECK(result);