Skip to content

Commit 70f31f1

Browse files
committed
coinselection: Use COutput instead of CInputCoin
Also rename setPresetCoins to preset_coins
1 parent 14fbb57 commit 70f31f1

File tree

5 files changed

+72
-71
lines changed

5 files changed

+72
-71
lines changed

src/bench/coin_selection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
8282
CMutableTransaction tx;
8383
tx.vout.resize(nInput + 1);
8484
tx.vout[nInput].nValue = nValue;
85-
CInputCoin coin(MakeTransactionRef(tx), nInput);
85+
COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 0, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ true);
8686
set.emplace_back();
87-
set.back().Insert(coin, 0, true, 0, 0, false);
87+
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
8888
}
8989
// Copied from src/wallet/test/coinselector_tests.cpp
9090
static CAmount make_hard_case(int utxos, std::vector<OutputGroup>& utxo_pool)

src/wallet/coinselection.cpp

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ struct {
5050
* The Branch and Bound algorithm is described in detail in Murch's Master Thesis:
5151
* https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
5252
*
53-
* @param const std::vector<CInputCoin>& utxo_pool The set of UTXOs that we are choosing from.
54-
* These UTXOs will be sorted in descending order by effective value and the CInputCoins'
53+
* @param const std::vector<OutputGroup>& utxo_pool The set of UTXO groups that we are choosing from.
54+
* These UTXO groups will be sorted in descending order by effective value and the OutputGroups'
5555
* values are their effective values.
5656
* @param const CAmount& selection_target This is the value that we want to select. It is the lower
5757
* bound of the range.
@@ -315,29 +315,29 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
315315
316316
******************************************************************************/
317317

318-
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
318+
void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
319319
// Compute the effective value first
320-
const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
320+
const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
321321
const CAmount ev = output.txout.nValue - coin_fee;
322322

323323
// Filter for positive only here before adding the coin
324324
if (positive_only && ev <= 0) return;
325325

326326
m_outputs.push_back(output);
327-
CInputCoin& coin = m_outputs.back();
327+
COutput& coin = m_outputs.back();
328328

329-
coin.m_fee = coin_fee;
330-
fee += coin.m_fee;
329+
coin.fee = coin_fee;
330+
fee += coin.fee;
331331

332-
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
333-
long_term_fee += coin.m_long_term_fee;
332+
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
333+
long_term_fee += coin.long_term_fee;
334334

335335
coin.effective_value = ev;
336336
effective_value += coin.effective_value;
337337

338-
m_from_me &= from_me;
339-
m_value += output.txout.nValue;
340-
m_depth = std::min(m_depth, depth);
338+
m_from_me &= coin.from_me;
339+
m_value += coin.txout.nValue;
340+
m_depth = std::min(m_depth, coin.depth);
341341
// ancestors here express the number of ancestors the new coin will end up having, which is
342342
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
343343
// have common ancestors
@@ -359,16 +359,16 @@ CAmount OutputGroup::GetSelectionAmount() const
359359
return m_subtract_fee_outputs ? m_value : effective_value;
360360
}
361361

362-
CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
362+
CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value)
363363
{
364364
// This function should not be called with empty inputs as that would mean the selection failed
365365
assert(!inputs.empty());
366366

367367
// Always consider the cost of spending an input now vs in the future.
368368
CAmount waste = 0;
369369
CAmount selected_effective_value = 0;
370-
for (const CInputCoin& coin : inputs) {
371-
waste += coin.m_fee - coin.m_long_term_fee;
370+
for (const COutput& coin : inputs) {
371+
waste += coin.fee - coin.long_term_fee;
372372
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
373373
}
374374

@@ -413,14 +413,14 @@ void SelectionResult::AddInput(const OutputGroup& group)
413413
m_use_effective = !group.m_subtract_fee_outputs;
414414
}
415415

416-
const std::set<CInputCoin>& SelectionResult::GetInputSet() const
416+
const std::set<COutput>& SelectionResult::GetInputSet() const
417417
{
418418
return m_selected_inputs;
419419
}
420420

421-
std::vector<CInputCoin> SelectionResult::GetShuffledInputVector() const
421+
std::vector<COutput> SelectionResult::GetShuffledInputVector() const
422422
{
423-
std::vector<CInputCoin> coins(m_selected_inputs.begin(), m_selected_inputs.end());
423+
std::vector<COutput> coins(m_selected_inputs.begin(), m_selected_inputs.end());
424424
Shuffle(coins.begin(), coins.end(), FastRandomContext());
425425
return coins;
426426
}

src/wallet/coinselection.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ class COutput
138138
{
139139
return CInputCoin(outpoint, txout, input_bytes);
140140
}
141+
142+
bool operator<(const COutput& rhs) const {
143+
return outpoint < rhs.outpoint;
144+
}
145+
146+
bool operator!=(const COutput& rhs) const {
147+
return outpoint != rhs.outpoint;
148+
}
149+
150+
bool operator==(const COutput& rhs) const {
151+
return outpoint == rhs.outpoint;
152+
}
141153
};
142154

143155
/** Parameters for one iteration of Coin Selection. */
@@ -207,7 +219,7 @@ struct CoinEligibilityFilter
207219
struct OutputGroup
208220
{
209221
/** The list of UTXOs contained in this output group. */
210-
std::vector<CInputCoin> m_outputs;
222+
std::vector<COutput> m_outputs;
211223
/** Whether the UTXOs were sent by the wallet to itself. This is relevant because we may want at
212224
* least a certain number of confirmations on UTXOs received from outside wallets while trusting
213225
* our own UTXOs more. */
@@ -244,7 +256,7 @@ struct OutputGroup
244256
m_subtract_fee_outputs(params.m_subtract_fee_outputs)
245257
{}
246258

247-
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
259+
void Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only);
248260
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
249261
CAmount GetSelectionAmount() const;
250262
};
@@ -266,13 +278,13 @@ struct OutputGroup
266278
* @param[in] use_effective_value Whether to use the input's effective value (when true) or the real value (when false).
267279
* @return The waste
268280
*/
269-
[[nodiscard]] CAmount GetSelectionWaste(const std::set<CInputCoin>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
281+
[[nodiscard]] CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost, CAmount target, bool use_effective_value = true);
270282

271283
struct SelectionResult
272284
{
273285
private:
274286
/** Set of inputs selected by the algorithm to use in the transaction */
275-
std::set<CInputCoin> m_selected_inputs;
287+
std::set<COutput> m_selected_inputs;
276288
/** The target the algorithm selected for. Note that this may not be equal to the recipient amount as it can include non-input fees */
277289
const CAmount m_target;
278290
/** Whether the input values for calculations should be the effective value (true) or normal value (false) */
@@ -298,9 +310,9 @@ struct SelectionResult
298310
[[nodiscard]] CAmount GetWaste() const;
299311

300312
/** Get m_selected_inputs */
301-
const std::set<CInputCoin>& GetInputSet() const;
302-
/** Get the vector of CInputCoins that will be used to fill in a CTransaction's vin */
303-
std::vector<CInputCoin> GetShuffledInputVector() const;
313+
const std::set<COutput>& GetInputSet() const;
314+
/** Get the vector of COutputs that will be used to fill in a CTransaction's vin */
315+
std::vector<COutput> GetShuffledInputVector() const;
304316

305317
bool operator<(SelectionResult other) const;
306318
};

src/wallet/spend.cpp

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -301,11 +301,10 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
301301

302302
size_t ancestors, descendants;
303303
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
304-
CInputCoin input_coin = output.GetInputCoin();
305304

306305
// Make an OutputGroup containing just this output
307306
OutputGroup group{coin_sel_params};
308-
group.Insert(input_coin, output.depth, output.from_me, ancestors, descendants, positive_only);
307+
group.Insert(output, ancestors, descendants, positive_only);
309308

310309
// Check the OutputGroup's eligibility. Only add the eligible ones.
311310
if (positive_only && group.GetSelectionAmount() <= 0) continue;
@@ -317,18 +316,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
317316
// We want to combine COutputs that have the same scriptPubKey into single OutputGroups
318317
// except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
319318
// To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
320-
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
319+
// For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added
321320
// to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
322-
// OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
321+
// OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector.
323322
std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
324323
for (const auto& output : outputs) {
325324
// Skip outputs we cannot spend
326325
if (!output.spendable) continue;
327326

328327
size_t ancestors, descendants;
329328
wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants);
330-
CInputCoin input_coin = output.GetInputCoin();
331-
CScript spk = input_coin.txout.scriptPubKey;
329+
CScript spk = output.txout.scriptPubKey;
332330

333331
std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
334332

@@ -337,7 +335,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
337335
groups.emplace_back(coin_sel_params);
338336
}
339337

340-
// Get the last OutputGroup in the vector so that we can add the CInputCoin to it
338+
// Get the last OutputGroup in the vector so that we can add the COutput to it
341339
// A pointer is used here so that group can be reassigned later if it is full.
342340
OutputGroup* group = &groups.back();
343341

@@ -349,8 +347,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
349347
group = &groups.back();
350348
}
351349

352-
// Add the input_coin to group
353-
group->Insert(input_coin, output.depth, output.from_me, ancestors, descendants, positive_only);
350+
// Add the output to group
351+
group->Insert(output, ancestors, descendants, positive_only);
354352
}
355353

356354
// Now we go through the entire map and pull out the OutputGroups
@@ -427,10 +425,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
427425
{
428426
for (const COutput& out : vCoins) {
429427
if (!out.spendable) continue;
430-
/* Set depth, from_me, ancestors, and descendants to 0 or false as these don't matter for preset inputs as no actual selection is being done.
428+
/* Set ancestors and descendants to 0 as these don't matter for preset inputs as no actual selection is being done.
431429
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
432430
*/
433-
preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false);
431+
preset_inputs.Insert(out, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
434432
}
435433
SelectionResult result(nTargetValue);
436434
result.AddInput(preset_inputs);
@@ -439,7 +437,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
439437
}
440438

441439
// calculate value from preset inputs and store them
442-
std::set<CInputCoin> setPresetCoins;
440+
std::set<COutPoint> preset_coins;
443441

444442
std::vector<COutPoint> vPresetInputs;
445443
coin_control.ListSelected(vPresetInputs);
@@ -467,27 +465,29 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
467465
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
468466
}
469467

470-
CInputCoin coin(outpoint, txout, input_bytes);
471-
if (coin.m_input_bytes == -1) {
468+
if (input_bytes == -1) {
472469
return std::nullopt; // Not solvable, can't estimate size for fee
473470
}
474-
coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
471+
472+
/* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
473+
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
474+
output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
475475
if (coin_selection_params.m_subtract_fee_outputs) {
476-
value_to_select -= coin.txout.nValue;
476+
value_to_select -= output.txout.nValue;
477477
} else {
478-
value_to_select -= coin.effective_value;
478+
value_to_select -= output.effective_value;
479479
}
480-
setPresetCoins.insert(coin);
481-
/* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done.
480+
preset_coins.insert(outpoint);
481+
/* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
482482
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
483483
*/
484-
preset_inputs.Insert(coin, 0, false, 0, 0, false);
484+
preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
485485
}
486486

487487
// remove preset inputs from vCoins so that Coin Selection doesn't pick them.
488488
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();)
489489
{
490-
if (setPresetCoins.count(it->GetInputCoin()))
490+
if (preset_coins.count(it->outpoint))
491491
it = vCoins.erase(it);
492492
else
493493
++it;
@@ -802,7 +802,7 @@ static bool CreateTransactionInternal(
802802
auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
803803

804804
// Shuffle selected coins and fill in final vin
805-
std::vector<CInputCoin> selected_coins = result->GetShuffledInputVector();
805+
std::vector<COutput> selected_coins = result->GetShuffledInputVector();
806806

807807
// The sequence number is set to non-maxint so that DiscourageFeeSniping
808808
// works.

src/wallet/test/coinselector_tests.cpp

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,20 @@ BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup)
2828
// we repeat those tests this many times and only complain if all iterations of the test fail
2929
#define RANDOM_REPEATS 5
3030

31-
typedef std::set<CInputCoin> CoinSet;
31+
typedef std::set<COutput> CoinSet;
3232

3333
static const CoinEligibilityFilter filter_standard(1, 6, 0);
3434
static const CoinEligibilityFilter filter_confirmed(1, 1, 0);
3535
static const CoinEligibilityFilter filter_standard_extra(6, 6, 0);
3636
static int nextLockTime = 0;
3737

38-
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
38+
static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& set)
3939
{
4040
CMutableTransaction tx;
4141
tx.vout.resize(nInput + 1);
4242
tx.vout[nInput].nValue = nValue;
4343
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
44-
set.emplace_back(MakeTransactionRef(tx), nInput);
44+
set.emplace_back(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
4545
}
4646

4747
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
@@ -50,9 +50,9 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
5050
tx.vout.resize(nInput + 1);
5151
tx.vout[nInput].nValue = nValue;
5252
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
53-
CInputCoin coin(MakeTransactionRef(tx), nInput);
53+
COutput output(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
5454
OutputGroup group;
55-
group.Insert(coin, 1, false, 0, 0, true);
55+
group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
5656
result.AddInput(group);
5757
}
5858

@@ -62,10 +62,10 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
6262
tx.vout.resize(nInput + 1);
6363
tx.vout[nInput].nValue = nValue;
6464
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
65-
CInputCoin coin(MakeTransactionRef(tx), nInput);
65+
COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
6666
coin.effective_value = nValue - fee;
67-
coin.m_fee = fee;
68-
coin.m_long_term_fee = long_term_fee;
67+
coin.fee = fee;
68+
coin.long_term_fee = long_term_fee;
6969
set.insert(coin);
7070
}
7171

@@ -117,7 +117,7 @@ static bool EqualResult(const SelectionResult& a, const SelectionResult& b)
117117
return ret.first == a.GetInputSet().end() && ret.second == b.GetInputSet().end();
118118
}
119119

120-
static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
120+
static CAmount make_hard_case(int utxos, std::vector<COutput>& utxo_pool)
121121
{
122122
utxo_pool.clear();
123123
CAmount target = 0;
@@ -129,24 +129,13 @@ static CAmount make_hard_case(int utxos, std::vector<CInputCoin>& utxo_pool)
129129
return target;
130130
}
131131

132-
inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins)
133-
{
134-
static std::vector<OutputGroup> static_groups;
135-
static_groups.clear();
136-
for (auto& coin : coins) {
137-
static_groups.emplace_back();
138-
static_groups.back().Insert(coin, 0, true, 0, 0, false);
139-
}
140-
return static_groups;
141-
}
142-
143132
inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
144133
{
145134
static std::vector<OutputGroup> static_groups;
146135
static_groups.clear();
147136
for (auto& coin : coins) {
148137
static_groups.emplace_back();
149-
static_groups.back().Insert(coin.GetInputCoin(), coin.depth, coin.from_me, 0, 0, false);
138+
static_groups.back().Insert(coin, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
150139
}
151140
return static_groups;
152141
}
@@ -166,7 +155,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const std::vector<COutput>
166155
BOOST_AUTO_TEST_CASE(bnb_search_test)
167156
{
168157
// Setup
169-
std::vector<CInputCoin> utxo_pool;
158+
std::vector<COutput> utxo_pool;
170159
SelectionResult expected_result(CAmount(0));
171160

172161
/////////////////////////

0 commit comments

Comments
 (0)