Skip to content

Commit 60d2ca7

Browse files
committed
Return SelectionResult from SelectCoinsBnB
Removes coins_out and value_ret has SelectCoinsBnB return a std::optional<SelectionResult>
1 parent a339add commit 60d2ca7

File tree

5 files changed

+60
-70
lines changed

5 files changed

+60
-70
lines changed

src/bench/coin_selection.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,17 +92,14 @@ static void BnBExhaustion(benchmark::Bench& bench)
9292
{
9393
// Setup
9494
std::vector<OutputGroup> utxo_pool;
95-
CoinSet selection;
96-
CAmount value_ret = 0;
9795

9896
bench.run([&] {
9997
// Benchmark
10098
CAmount target = make_hard_case(17, utxo_pool);
101-
SelectCoinsBnB(utxo_pool, target, 0, selection, value_ret); // Should exhaust
99+
SelectCoinsBnB(utxo_pool, target, 0); // Should exhaust
102100

103101
// Cleanup
104102
utxo_pool.clear();
105-
selection.clear();
106103
});
107104
}
108105

src/wallet/coinselection.cpp

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,14 @@ struct {
5656
* bound of the range.
5757
* @param const CAmount& cost_of_change This is the cost of creating and spending a change output.
5858
* This plus selection_target is the upper bound of the range.
59-
* @param std::set<CInputCoin>& out_set -> This is an output parameter for the set of CInputCoins
60-
* that have been selected.
61-
* @param CAmount& value_ret -> This is an output parameter for the total value of the CInputCoins
62-
* that were selected.
59+
* @returns The result of this coin selection algorithm, or std::nullopt
6360
*/
6461

6562
static const size_t TOTAL_TRIES = 100000;
6663

67-
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret)
64+
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change)
6865
{
69-
out_set.clear();
66+
SelectionResult result(selection_target);
7067
CAmount curr_value = 0;
7168

7269
std::vector<bool> curr_selection; // select the utxo at this index
@@ -80,7 +77,7 @@ bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selectio
8077
curr_available_value += utxo.GetSelectionAmount();
8178
}
8279
if (curr_available_value < selection_target) {
83-
return false;
80+
return std::nullopt;
8481
}
8582

8683
// Sort the utxo_pool
@@ -156,19 +153,17 @@ bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selectio
156153

157154
// Check for solution
158155
if (best_selection.empty()) {
159-
return false;
156+
return std::nullopt;
160157
}
161158

162159
// Set output set
163-
value_ret = 0;
164160
for (size_t i = 0; i < best_selection.size(); ++i) {
165161
if (best_selection.at(i)) {
166-
util::insert(out_set, utxo_pool.at(i).m_outputs);
167-
value_ret += utxo_pool.at(i).m_value;
162+
result.AddInput(utxo_pool.at(i));
168163
}
169164
}
170165

171-
return true;
166+
return result;
172167
}
173168

174169
std::optional<std::pair<std::set<CInputCoin>, CAmount>> SelectCoinsSRD(const std::vector<OutputGroup>& utxo_pool, CAmount target_value)
@@ -421,6 +416,7 @@ void SelectionResult::Clear()
421416
void SelectionResult::AddInput(const OutputGroup& group)
422417
{
423418
util::insert(m_selected_inputs, group.m_outputs);
419+
m_use_effective = !group.m_subtract_fee_outputs;
424420
}
425421

426422
const std::set<CInputCoin>& SelectionResult::GetInputSet() const

src/wallet/coinselection.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ struct SelectionResult
234234
bool operator<(SelectionResult other) const;
235235
};
236236

237-
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret);
237+
std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, const CAmount& cost_of_change);
238238

239239
/** Select coins by Single Random Draw. OutputGroups are selected randomly from the eligible
240240
* outputs until the target is satisfied

src/wallet/spend.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,9 @@ bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const
385385

386386
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
387387
std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
388-
std::set<CInputCoin> bnb_coins;
389-
CAmount bnb_value;
390-
if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, bnb_coins, bnb_value)) {
391-
const auto waste = GetSelectionWaste(bnb_coins, /* cost of change */ CAmount(0), nTargetValue, !coin_selection_params.m_subtract_fee_outputs);
392-
results.emplace_back(std::make_tuple(waste, std::move(bnb_coins), bnb_value));
388+
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
389+
bnb_result->ComputeAndSetWaste(CAmount(0));
390+
results.emplace_back(std::make_tuple(bnb_result->GetWaste(), bnb_result->GetInputSet(), bnb_result->GetSelectedValue()));
393391
}
394392

395393
// 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.

src/wallet/test/coinselector_tests.cpp

Lines changed: 47 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <wallet/test/wallet_test_fixture.h>
1515
#include <wallet/wallet.h>
1616

17+
#include <algorithm>
1718
#include <boost/test/unit_test.hpp>
1819
#include <random>
1920

@@ -95,20 +96,22 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
9596
coins.push_back(output);
9697
}
9798

98-
static bool equivalent_sets(CoinSet a, CoinSet b)
99+
/** Check if SelectionResult a is equivalent to SelectionResult b.
100+
* Equivalent means same input values, but maybe different inputs (i.e. same value, different prevout) */
101+
static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
99102
{
100103
std::vector<CAmount> a_amts;
101104
std::vector<CAmount> b_amts;
102-
for (const auto& coin : a) {
105+
for (const auto& coin : a.GetInputSet()) {
103106
a_amts.push_back(coin.txout.nValue);
104107
}
105-
for (const auto& coin : b) {
108+
for (const auto& coin : b.GetInputSet()) {
106109
b_amts.push_back(coin.txout.nValue);
107110
}
108111
std::sort(a_amts.begin(), a_amts.end());
109112
std::sort(b_amts.begin(), b_amts.end());
110113

111-
std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator> ret = mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
114+
std::pair<std::vector<CAmount>::iterator, std::vector<CAmount>::iterator> ret = std::mismatch(a_amts.begin(), a_amts.end(), b_amts.begin());
112115
return ret.first == a_amts.end() && ret.second == b_amts.end();
113116
}
114117

@@ -168,17 +171,14 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
168171
{
169172
// Setup
170173
std::vector<CInputCoin> utxo_pool;
171-
CoinSet selection;
172174
SelectionResult expected_result(CAmount(0));
173-
CAmount value_ret = 0;
174175

175176
/////////////////////////
176177
// Known Outcome tests //
177178
/////////////////////////
178179

179180
// Empty utxo pool
180-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret));
181-
selection.clear();
181+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT));
182182

183183
// Add utxos
184184
add_coin(1 * CENT, 1, utxo_pool);
@@ -188,78 +188,77 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
188188

189189
// Select 1 Cent
190190
add_coin(1 * CENT, 1, expected_result);
191-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret));
192-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
193-
BOOST_CHECK_EQUAL(value_ret, 1 * CENT);
191+
const auto result1 = SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT);
192+
BOOST_CHECK(result1);
193+
BOOST_CHECK(EquivalentResult(expected_result, *result1));
194+
BOOST_CHECK_EQUAL(result1->GetSelectedValue(), 1 * CENT);
194195
expected_result.Clear();
195-
selection.clear();
196196

197197
// Select 2 Cent
198198
add_coin(2 * CENT, 2, expected_result);
199-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, 0.5 * CENT, selection, value_ret));
200-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
201-
BOOST_CHECK_EQUAL(value_ret, 2 * CENT);
199+
const auto result2 = SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, 0.5 * CENT);
200+
BOOST_CHECK(result2);
201+
BOOST_CHECK(EquivalentResult(expected_result, *result2));
202+
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 2 * CENT);
202203
expected_result.Clear();
203-
selection.clear();
204204

205205
// Select 5 Cent
206206
add_coin(4 * CENT, 4, expected_result);
207207
add_coin(1 * CENT, 1, expected_result);
208-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT, selection, value_ret));
209-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
210-
BOOST_CHECK_EQUAL(value_ret, 5 * CENT);
208+
const auto result3 = SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT);
209+
BOOST_CHECK(result3);
210+
BOOST_CHECK(EquivalentResult(expected_result, *result3));
211+
BOOST_CHECK_EQUAL(result3->GetSelectedValue(), 5 * CENT);
211212
expected_result.Clear();
212-
selection.clear();
213213

214214
// Select 11 Cent, not possible
215-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, 0.5 * CENT, selection, value_ret));
215+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, 0.5 * CENT));
216216
expected_result.Clear();
217-
selection.clear();
218217

219218
// Cost of change is greater than the difference between target value and utxo sum
220219
add_coin(1 * CENT, 1, expected_result);
221-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0.5 * CENT, selection, value_ret));
222-
BOOST_CHECK_EQUAL(value_ret, 1 * CENT);
223-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
220+
const auto result4 = SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0.5 * CENT);
221+
BOOST_CHECK(result4);
222+
BOOST_CHECK_EQUAL(result4->GetSelectedValue(), 1 * CENT);
223+
BOOST_CHECK(EquivalentResult(expected_result, *result4));
224224
expected_result.Clear();
225-
selection.clear();
226225

227226
// Cost of change is less than the difference between target value and utxo sum
228-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0, selection, value_ret));
227+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.9 * CENT, 0));
229228
expected_result.Clear();
230-
selection.clear();
231229

232230
// Select 10 Cent
233231
add_coin(5 * CENT, 5, utxo_pool);
234232
add_coin(5 * CENT, 5, expected_result);
235233
add_coin(4 * CENT, 4, expected_result);
236234
add_coin(1 * CENT, 1, expected_result);
237-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT, selection, value_ret));
238-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
239-
BOOST_CHECK_EQUAL(value_ret, 10 * CENT);
235+
const auto result5 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT);
236+
BOOST_CHECK(result5);
237+
BOOST_CHECK(EquivalentResult(expected_result, *result5));
238+
BOOST_CHECK_EQUAL(result5->GetSelectedValue(), 10 * CENT);
240239
expected_result.Clear();
241-
selection.clear();
242240

243241
// Negative effective value
244242
// Select 10 Cent but have 1 Cent not be possible because too small
245243
add_coin(5 * CENT, 5, expected_result);
246244
add_coin(3 * CENT, 3, expected_result);
247245
add_coin(2 * CENT, 2, expected_result);
248-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000, selection, value_ret));
249-
BOOST_CHECK_EQUAL(value_ret, 10 * CENT);
246+
const auto result6 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000);
247+
BOOST_CHECK(result6);
248+
BOOST_CHECK_EQUAL(result6->GetSelectedValue(), 10 * CENT);
250249
// FIXME: this test is redundant with the above, because 1 Cent is selected, not "too small"
251-
// BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
250+
// BOOST_CHECK(EquivalentResult(expected_result, *result));
252251

253252
// Select 0.25 Cent, not possible
254-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.25 * CENT, 0.5 * CENT, selection, value_ret));
253+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 0.25 * CENT, 0.5 * CENT));
255254
expected_result.Clear();
256-
selection.clear();
257255

258256
// Iteration exhaustion test
259257
CAmount target = make_hard_case(17, utxo_pool);
260-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret)); // Should exhaust
258+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, 0)); // Should exhaust
261259
target = make_hard_case(14, utxo_pool);
262-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, 0, selection, value_ret)); // Should not exhaust
260+
const auto result7 = SelectCoinsBnB(GroupCoins(utxo_pool), target, 0); // Should not exhaust
261+
BOOST_CHECK(result7);
263262

264263
// Test same value early bailout optimization
265264
utxo_pool.clear();
@@ -276,9 +275,10 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
276275
for (int i = 0; i < 50000; ++i) {
277276
add_coin(5 * CENT, 7, utxo_pool);
278277
}
279-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000, selection, value_ret));
280-
BOOST_CHECK_EQUAL(value_ret, 30 * CENT);
281-
BOOST_CHECK(equivalent_sets(selection, expected_result.GetInputSet()));
278+
const auto result8 = SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000);
279+
BOOST_CHECK(result8);
280+
BOOST_CHECK_EQUAL(result8->GetSelectedValue(), 30 * CENT);
281+
BOOST_CHECK(EquivalentResult(expected_result, *result8));
282282

283283
////////////////////
284284
// Behavior tests //
@@ -290,7 +290,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
290290
}
291291
// Run 100 times, to make sure it is never finding a solution
292292
for (int i = 0; i < 100; ++i) {
293-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret));
293+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT));
294294
}
295295

296296
// Make sure that effective value is working in AttemptSelection when BnB is used
@@ -306,20 +306,19 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
306306
wallet->SetupDescriptorScriptPubKeyMans();
307307

308308
std::vector<COutput> coins;
309-
CoinSet setCoinsRet;
310-
CAmount nValueRet;
311309

312310
add_coin(coins, *wallet, 1);
313311
coins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
314-
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change, setCoinsRet, nValueRet));
312+
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
315313

316314
// Test fees subtracted from output:
317315
coins.clear();
318316
add_coin(coins, *wallet, 1 * CENT);
319317
coins.at(0).nInputBytes = 40;
320318
coin_selection_params_bnb.m_subtract_fee_outputs = true;
321-
BOOST_CHECK(SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change, setCoinsRet, nValueRet));
322-
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
319+
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
320+
BOOST_CHECK(result9);
321+
BOOST_CHECK_EQUAL(result9->GetSelectedValue(), 1 * CENT);
323322
}
324323

325324
{

0 commit comments

Comments
 (0)