Skip to content

Commit 7dc4807

Browse files
committed
Merge #20040: wallet: Refactor OutputGroups to handle fees and spending eligibility on grouping
5d45976 Rewrite OutputGroups to be clearer and to use scriptPubKeys (Andrew Chow) f6b3052 Explicitly filter out partial groups when we don't want them (Andrew Chow) 416d74f Move OutputGroup positive only filtering into Insert (Andrew Chow) d895e98 Move EligibleForSpending into GroupOutputs (Andrew Chow) 99b399a Move fee setting of OutputGroup to Insert (Andrew Chow) 6148a8a Move GroupOutputs into SelectCoinsMinConf (Andrew Chow) 2acad03 Remove OutputGroup non-default constructors (Andrew Chow) Pull request description: Even after #17458, we still deal with setting fees of an `OutputGroup` and filtering the `OutputGroup` outside of the struct. We currently make all of the `OutputGroup`s in `SelectCoins` and then copy and modify them within each `SelectCoinsMinConf` scenario. This PR changes this to constructing the `OutputGroup`s within the `SelectCoinsMinConf` so that the scenario can be taken into account during the group construction. Furthermore, setting of fees and filtering for effective value is moved into `OutputGroup::Insert` itself so that we don't add undesirable outputs to an `OutputGroup` rather than deleting them afterwards. To facilitate fee calculation and effective value filtering during `OutputGroup::Insert`, `OutputGroup` now takes the feerates in its constructor and computes the fees and effective value for each output during `Insert`. While removing `OutputGroup`s in accordance with the `CoinEligibilityFilter` still requires creating the `OutputGroup`s first, we can do that within the function that makes them - `GroupOutput`s. ACKs for top commit: Xekyo: Code review ACK: bitcoin/bitcoin@5d45976 fjahr: Code review ACK 5d45976 meshcollider: Light utACK 5d45976 Tree-SHA512: 35965b6d49a87f4ebb366ec4f00aafaaf78e9282481ae2c9682b515a3a9f2cbcd3cd6e202fee29489d48fe7f3a7cede4270796f5e72bbaff76da647138fb3059
2 parents 4c55f92 + 5d45976 commit 7dc4807

File tree

6 files changed

+199
-192
lines changed

6 files changed

+199
-192
lines changed

src/bench/coin_selection.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,19 @@ static void CoinSelection(benchmark::Bench& bench)
4242
}
4343
addCoin(3 * COIN, wallet, wtxs);
4444

45-
// Create groups
46-
std::vector<OutputGroup> groups;
45+
// Create coins
46+
std::vector<COutput> coins;
4747
for (const auto& wtx : wtxs) {
48-
COutput output(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
49-
groups.emplace_back(output.GetInputCoin(), 6, false, 0, 0);
48+
coins.emplace_back(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
5049
}
5150

5251
const CoinEligibilityFilter filter_standard(1, 6, 0);
53-
const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0);
52+
const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0, false);
5453
bench.run([&] {
5554
std::set<CInputCoin> setCoinsRet;
5655
CAmount nValueRet;
5756
bool bnb_used;
58-
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
57+
bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
5958
assert(success);
6059
assert(nValueRet == 1003 * COIN);
6160
assert(setCoinsRet.size() == 2);
@@ -75,7 +74,8 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
7574
tx.vout.resize(nInput + 1);
7675
tx.vout[nInput].nValue = nValue;
7776
std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx)));
78-
set.emplace_back(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0);
77+
set.emplace_back();
78+
set.back().Insert(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false);
7979
wtxn.emplace_back(std::move(wtx));
8080
}
8181
// Copied from src/wallet/test/coinselector_tests.cpp

src/wallet/coinselection.cpp

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,26 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
300300
301301
******************************************************************************/
302302

303-
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
303+
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
304+
// Compute the effective value first
305+
const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
306+
const CAmount ev = output.txout.nValue - coin_fee;
307+
308+
// Filter for positive only here before adding the coin
309+
if (positive_only && ev <= 0) return;
310+
304311
m_outputs.push_back(output);
312+
CInputCoin& coin = m_outputs.back();
313+
314+
coin.m_fee = coin_fee;
315+
fee += coin.m_fee;
316+
317+
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
318+
long_term_fee += coin.m_long_term_fee;
319+
320+
coin.effective_value = ev;
321+
effective_value += coin.effective_value;
322+
305323
m_from_me &= from_me;
306324
m_value += output.txout.nValue;
307325
m_depth = std::min(m_depth, depth);
@@ -312,20 +330,6 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
312330
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
313331
// coin itself; thus, this value is counted as the max, not the sum
314332
m_descendants = std::max(m_descendants, descendants);
315-
effective_value += output.effective_value;
316-
fee += output.m_fee;
317-
long_term_fee += output.m_long_term_fee;
318-
}
319-
320-
std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
321-
auto it = m_outputs.begin();
322-
while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
323-
if (it == m_outputs.end()) return it;
324-
m_value -= output.txout.nValue;
325-
effective_value -= output.effective_value;
326-
fee -= output.m_fee;
327-
long_term_fee -= output.m_long_term_fee;
328-
return m_outputs.erase(it);
329333
}
330334

331335
bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const
@@ -334,35 +338,3 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
334338
&& m_ancestors <= eligibility_filter.max_ancestors
335339
&& m_descendants <= eligibility_filter.max_descendants;
336340
}
337-
338-
void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
339-
{
340-
fee = 0;
341-
long_term_fee = 0;
342-
effective_value = 0;
343-
for (CInputCoin& coin : m_outputs) {
344-
coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
345-
fee += coin.m_fee;
346-
347-
coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
348-
long_term_fee += coin.m_long_term_fee;
349-
350-
coin.effective_value = coin.txout.nValue - coin.m_fee;
351-
effective_value += coin.effective_value;
352-
}
353-
}
354-
355-
OutputGroup OutputGroup::GetPositiveOnlyGroup()
356-
{
357-
OutputGroup group(*this);
358-
for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
359-
const CInputCoin& coin = *it;
360-
// Only include outputs that are positive effective value (i.e. not dust)
361-
if (coin.effective_value <= 0) {
362-
it = group.Discard(coin);
363-
} else {
364-
++it;
365-
}
366-
}
367-
return group;
368-
}

src/wallet/coinselection.h

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
#define BITCOIN_WALLET_COINSELECTION_H
77

88
#include <amount.h>
9+
#include <policy/feerate.h>
910
#include <primitives/transaction.h>
1011
#include <random.h>
1112

12-
class CFeeRate;
13-
1413
//! target minimum change amount
1514
static constexpr CAmount MIN_CHANGE{COIN / 100};
1615
//! final minimum change amount after paying for fees
@@ -63,9 +62,11 @@ struct CoinEligibilityFilter
6362
const int conf_theirs;
6463
const uint64_t max_ancestors;
6564
const uint64_t max_descendants;
65+
const bool m_include_partial_groups{false}; //! Include partial destination groups when avoid_reuse and there are full groups
6666

6767
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
6868
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
69+
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {}
6970
};
7071

7172
struct OutputGroup
@@ -78,27 +79,18 @@ struct OutputGroup
7879
size_t m_descendants{0};
7980
CAmount effective_value{0};
8081
CAmount fee{0};
82+
CFeeRate m_effective_feerate{0};
8183
CAmount long_term_fee{0};
84+
CFeeRate m_long_term_feerate{0};
8285

8386
OutputGroup() {}
84-
OutputGroup(std::vector<CInputCoin>&& outputs, bool from_me, CAmount value, int depth, size_t ancestors, size_t descendants)
85-
: m_outputs(std::move(outputs))
86-
, m_from_me(from_me)
87-
, m_value(value)
88-
, m_depth(depth)
89-
, m_ancestors(ancestors)
90-
, m_descendants(descendants)
87+
OutputGroup(const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate) :
88+
m_effective_feerate(effective_feerate),
89+
m_long_term_feerate(long_term_feerate)
9190
{}
92-
OutputGroup(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) : OutputGroup() {
93-
Insert(output, depth, from_me, ancestors, descendants);
94-
}
95-
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
96-
std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
97-
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
9891

99-
//! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
100-
void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
101-
OutputGroup GetPositiveOnlyGroup();
92+
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
93+
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
10294
};
10395

10496
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);

0 commit comments

Comments
 (0)