Skip to content

Commit 4fef534

Browse files
committed
wallet: use GetChange() when computing waste
1 parent 87e0ef9 commit 4fef534

File tree

5 files changed

+25
-14
lines changed

5 files changed

+25
-14
lines changed

src/wallet/coinselection.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
156156
for (const size_t& i : best_selection) {
157157
result.AddInput(utxo_pool.at(i));
158158
}
159-
result.ComputeAndSetWaste(CAmount{0});
159+
result.ComputeAndSetWaste(cost_of_change, cost_of_change, CAmount{0});
160160
assert(best_waste == result.GetWaste());
161161

162162
return result;
@@ -406,9 +406,15 @@ CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_f
406406
}
407407
}
408408

409-
void SelectionResult::ComputeAndSetWaste(CAmount change_cost)
409+
void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
410410
{
411-
m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
411+
const CAmount change = GetChange(min_viable_change, change_fee);
412+
413+
if (change > 0) {
414+
m_waste = GetSelectionWaste(m_selected_inputs, change_cost, m_target, m_use_effective);
415+
} else {
416+
m_waste = GetSelectionWaste(m_selected_inputs, 0, m_target, m_use_effective);
417+
}
412418
}
413419

414420
CAmount SelectionResult::GetWaste() const

src/wallet/coinselection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ struct SelectionResult
310310
void AddInput(const OutputGroup& group);
311311

312312
/** Calculates and stores the waste for this selection via GetSelectionWaste */
313-
void ComputeAndSetWaste(CAmount change_cost);
313+
void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
314314
[[nodiscard]] CAmount GetWaste() const;
315315

316316
void Merge(const SelectionResult& other);

src/wallet/spend.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -497,12 +497,12 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
497497
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
498498
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/false);
499499
if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
500-
knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
500+
knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
501501
results.push_back(*knapsack_result);
502502
}
503503

504504
if (auto srd_result{SelectCoinsSRD(positive_groups, nTargetValue, coin_selection_params.rng_fast)}) {
505-
srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
505+
srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
506506
results.push_back(*srd_result);
507507
}
508508

@@ -574,7 +574,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
574574
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
575575
result.AddInput(preset_inputs);
576576
if (result.GetSelectedValue() < nTargetValue) return std::nullopt;
577-
result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
577+
result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
578578
return result;
579579
}
580580

@@ -671,7 +671,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
671671
// Add preset inputs to result
672672
res->Merge(preselected);
673673
if (res->GetAlgo() == SelectionAlgorithm::MANUAL) {
674-
res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
674+
res->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
675675
}
676676

677677
return res;

src/wallet/test/coinselector_tests.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
248248

249249
// Iteration exhaustion test
250250
CAmount target = make_hard_case(17, utxo_pool);
251-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0)); // Should exhaust
251+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 1)); // Should exhaust
252252
target = make_hard_case(14, utxo_pool);
253-
const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 0); // Should not exhaust
253+
const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 1); // Should not exhaust
254254
BOOST_CHECK(result7);
255255

256256
// Test same value early bailout optimization
@@ -289,15 +289,18 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
289289
// Make sure that effective value is working in AttemptSelection when BnB is used
290290
CoinSelectionParams coin_selection_params_bnb{
291291
rand,
292-
/*change_output_size=*/ 0,
293-
/*change_spend_size=*/ 0,
292+
/*change_output_size=*/ 31,
293+
/*change_spend_size=*/ 68,
294294
/*min_change_target=*/ 0,
295295
/*effective_feerate=*/ CFeeRate(3000),
296296
/*long_term_feerate=*/ CFeeRate(1000),
297297
/*discard_feerate=*/ CFeeRate(1000),
298298
/*tx_noinputs_size=*/ 0,
299299
/*avoid_partial=*/ false,
300300
};
301+
coin_selection_params_bnb.m_change_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_output_size);
302+
coin_selection_params_bnb.m_cost_of_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size) + coin_selection_params_bnb.m_change_fee;
303+
coin_selection_params_bnb.min_viable_change = coin_selection_params_bnb.m_effective_feerate.GetFee(coin_selection_params_bnb.change_spend_size);
301304
{
302305
std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
303306
wallet->LoadWallet();
@@ -777,6 +780,8 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
777780
/*tx_noinputs_size=*/ 0,
778781
/*avoid_partial=*/ false,
779782
};
783+
cs_params.m_cost_of_change = 1;
784+
cs_params.min_viable_change = 1;
780785
CCoinControl cc;
781786
const auto result = SelectCoins(*wallet, available_coins, target, cc, cs_params);
782787
BOOST_CHECK(result);

src/wallet/test/fuzz/coinselection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ FUZZ_TARGET(coinselection)
8585
const auto result_bnb = SelectCoinsBnB(group_pos, target, cost_of_change);
8686

8787
auto result_srd = SelectCoinsSRD(group_pos, target, fast_random_context);
88-
if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change);
88+
if (result_srd) result_srd->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
8989

9090
CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
9191
auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context);
92-
if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change);
92+
if (result_knapsack) result_knapsack->ComputeAndSetWaste(cost_of_change, cost_of_change, 0);
9393

9494
// If the total balance is sufficient for the target and we are not using
9595
// effective values, Knapsack should always find a solution.

0 commit comments

Comments
 (0)