Skip to content

Commit

Permalink
Merge bitcoin#17458: Refactor OutputGroup effective value calculation…
Browse files Browse the repository at this point in the history
…s and filtering to occur within the struct

9adc2f8 Refactor OutputGroups to handle effective values, fees, and filtering (Andrew Chow)
7d07e86 Use real value when calculating OutputGroup value (Andrew Chow)

Pull request description:

  Currently, the effective values and filtering for positive effective values is done outside of the OutputGroup. We should instead have functions in Outputgroup to do this and call those for each OutputGroup. So this PR does that.

  This makes future changes for effective values in coin selection much easier.

ACKs for top commit:
  instagibbs:
    reACK 9adc2f8
  fjahr:
    re-ACK 9adc2f8
  meshcollider:
    Light code review ACK 9adc2f8

Tree-SHA512: 7445c94b7295b45bcd83a6f8a5c8f6961a89453fcc856335192d4b4a66aec7724513616b04e5111588ab208c89b311055399d6279cd9c4ce452aefb85f04b64a
  • Loading branch information
meshcollider authored and vijaydasmp committed Nov 20, 2023
1 parent bd1437e commit dbd0ca9
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 23 deletions.
43 changes: 40 additions & 3 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <wallet/coinselection.h>

#include <policy/feerate.h>
#include <util/system.h>
#include <util/moneystr.h>

Expand Down Expand Up @@ -365,7 +366,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
m_outputs.push_back(output);
m_from_me &= from_me;
m_value += output.effective_value;
m_value += output.txout.nValue;
m_depth = std::min(m_depth, depth);
// ancestors here express the number of ancestors the new coin will end up having, which is
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
Expand All @@ -374,15 +375,19 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
// coin itself; thus, this value is counted as the max, not the sum
m_descendants = std::max(m_descendants, descendants);
effective_value = m_value;
effective_value += output.effective_value;
fee += output.m_fee;
long_term_fee += output.m_long_term_fee;
}

std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
auto it = m_outputs.begin();
while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
if (it == m_outputs.end()) return it;
m_value -= output.effective_value;
m_value -= output.txout.nValue;
effective_value -= output.effective_value;
fee -= output.m_fee;
long_term_fee -= output.m_long_term_fee;
return m_outputs.erase(it);
}

Expand All @@ -401,3 +406,35 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
&& m_ancestors <= eligibility_filter.max_ancestors
&& m_descendants <= eligibility_filter.max_descendants;
}

void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
{
fee = 0;
long_term_fee = 0;
effective_value = 0;
for (CInputCoin& coin : m_outputs) {
coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
fee += coin.m_fee;

coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
long_term_fee += coin.m_long_term_fee;

coin.effective_value = coin.txout.nValue - coin.m_fee;
effective_value += coin.effective_value;
}
}

OutputGroup OutputGroup::GetPositiveOnlyGroup()
{
OutputGroup group(*this);
for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
const CInputCoin& coin = *it;
// Only include outputs that are positive effective value (i.e. not dust)
if (coin.effective_value <= 0) {
it = group.Discard(coin);
} else {
++it;
}
}
return group;
}
8 changes: 8 additions & 0 deletions src/wallet/coinselection.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <primitives/transaction.h>
#include <random.h>

class CFeeRate;

//! target minimum change amount
static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees
Expand Down Expand Up @@ -36,6 +38,8 @@ class CInputCoin {
COutPoint outpoint;
CTxOut txout;
CAmount effective_value;
CAmount m_fee{0};
CAmount m_long_term_fee{0};

/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
int m_input_bytes{-1};
Expand Down Expand Up @@ -92,6 +96,10 @@ struct OutputGroup
std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
bool IsLockedByInstantSend() const;
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;

//! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
OutputGroup GetPositiveOnlyGroup();
};

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);
Expand Down
28 changes: 8 additions & 20 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2745,27 +2745,15 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
for (OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;

group.fee = 0;
group.long_term_fee = 0;
group.effective_value = 0;
for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
const CInputCoin& coin = *it;
CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes));
// Only include outputs that are positive effective value (i.e. not dust)
if (effective_value > 0) {
group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
if (coin_selection_params.m_subtract_fee_outputs) {
group.effective_value += coin.txout.nValue;
} else {
group.effective_value += effective_value;
}
++it;
} else {
it = group.Discard(coin);
}
if (coin_selection_params.m_subtract_fee_outputs) {
// Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
} else {
group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
}
if (group.effective_value > 0) utxo_pool.push_back(group);

OutputGroup pos_group = group.GetPositiveOnlyGroup();
if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
}
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
Expand Down

0 comments on commit dbd0ca9

Please sign in to comment.