Skip to content

Commit 3368f84

Browse files
committed
Merge bitcoin/bitcoin#25083: Set effective_value when initializing a COutput
6fbb0ed Set effective_value when initializing a COutput (ishaanam) Pull request description: Previously in COutput, effective_value was initialized as the absolute value of the txout and the fee as 0. effective_value along with the fee was calculated outside of the COutput constructor and set after the object had been initialized. These changes will allow either the fee or the feerate to be passed in a COutput constructor and the fee and effective_value are calculated and set in the constructor. As a result, AvailableCoins also needs to be passed the feerate when utxos are being spent. When balance is calculated or the coins are being listed and feerate is neither available nor required, AvailableCoinsListUnspent is used instead, which runs AvailableCoins while providing the default value for `feerate`. Unit tests for the calculation of effective value have also been added. ACKs for top commit: achow101: re-ACK 6fbb0ed Xekyo: re-ACK 6fbb0ed w0xlt: Code Review ACK bitcoin/bitcoin@6fbb0ed furszy: Looks good, have been touching this area lately, code review ACK 6fbb0ed. Tree-SHA512: 5943ee4f4b0c1dcfe146f2fc22853e607259d6d53156b80a8a8f4baa70760a8b25ab822777b7f5d21ecb02dac08bdee704a9a918d5660276d6994b19a284b256
2 parents 5ebff43 + 6fbb0ed commit 3368f84

File tree

9 files changed

+137
-60
lines changed

9 files changed

+137
-60
lines changed

src/bench/coin_selection.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static void CoinSelection(benchmark::Bench& bench)
5858
// Create coins
5959
std::vector<COutput> coins;
6060
for (const auto& wtx : wtxs) {
61-
coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true);
61+
coins.emplace_back(COutPoint(wtx->GetHash(), 0), wtx->tx->vout.at(0), /*depth=*/6 * 24, GetTxSpendSize(wallet, *wtx, 0), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
6262
}
6363

6464
const CoinEligibilityFilter filter_standard(1, 6, 0);
@@ -88,7 +88,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
8888
CMutableTransaction tx;
8989
tx.vout.resize(nInput + 1);
9090
tx.vout[nInput].nValue = nValue;
91-
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);
91+
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, /*fees=*/ 0);
9292
set.emplace_back();
9393
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
9494
}

src/wallet/coinselection.cpp

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -328,24 +328,18 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
328328
******************************************************************************/
329329

330330
void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
331-
// Compute the effective value first
332-
const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
333-
const CAmount ev = output.txout.nValue - coin_fee;
334-
335331
// Filter for positive only here before adding the coin
336-
if (positive_only && ev <= 0) return;
332+
if (positive_only && output.GetEffectiveValue() <= 0) return;
337333

338334
m_outputs.push_back(output);
339335
COutput& coin = m_outputs.back();
340336

341-
coin.fee = coin_fee;
342-
fee += coin.fee;
337+
fee += coin.GetFee();
343338

344339
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
345340
long_term_fee += coin.long_term_fee;
346341

347-
coin.effective_value = ev;
348-
effective_value += coin.effective_value;
342+
effective_value += coin.GetEffectiveValue();
349343

350344
m_from_me &= coin.from_me;
351345
m_value += coin.txout.nValue;
@@ -380,8 +374,8 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost,
380374
CAmount waste = 0;
381375
CAmount selected_effective_value = 0;
382376
for (const COutput& coin : inputs) {
383-
waste += coin.fee - coin.long_term_fee;
384-
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
377+
waste += coin.GetFee() - coin.long_term_fee;
378+
selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
385379
}
386380

387381
if (change_cost) {

src/wallet/coinselection.h

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ static constexpr CAmount CHANGE_UPPER{1000000};
2020

2121
/** A UTXO under consideration for use in funding a new transaction. */
2222
struct COutput {
23+
private:
24+
/** The output's value minus fees required to spend it.*/
25+
std::optional<CAmount> effective_value;
26+
27+
/** The fee required to spend this output at the transaction's target feerate. */
28+
std::optional<CAmount> fee;
29+
30+
public:
2331
/** The outpoint identifying this UTXO */
2432
COutPoint outpoint;
2533

@@ -55,16 +63,10 @@ struct COutput {
5563
/** Whether the transaction containing this output is sent from the owning wallet */
5664
bool from_me;
5765

58-
/** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
59-
CAmount effective_value;
60-
61-
/** The fee required to spend this output at the transaction's target feerate. */
62-
CAmount fee{0};
63-
6466
/** The fee required to spend this output at the consolidation feerate. */
6567
CAmount long_term_fee{0};
6668

67-
COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
69+
COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const std::optional<CFeeRate> feerate = std::nullopt)
6870
: outpoint{outpoint},
6971
txout{txout},
7072
depth{depth},
@@ -73,16 +75,41 @@ struct COutput {
7375
solvable{solvable},
7476
safe{safe},
7577
time{time},
76-
from_me{from_me},
77-
effective_value{txout.nValue}
78-
{}
78+
from_me{from_me}
79+
{
80+
if (feerate) {
81+
fee = input_bytes < 0 ? 0 : feerate.value().GetFee(input_bytes);
82+
effective_value = txout.nValue - fee.value();
83+
}
84+
}
85+
86+
COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me, const CAmount fees)
87+
: COutput(outpoint, txout, depth, input_bytes, spendable, solvable, safe, time, from_me)
88+
{
89+
// if input_bytes is unknown, then fees should be 0, if input_bytes is known, then the fees should be a positive integer or 0 (input_bytes known and fees = 0 only happens in the tests)
90+
assert((input_bytes < 0 && fees == 0) || (input_bytes > 0 && fees >= 0));
91+
fee = fees;
92+
effective_value = txout.nValue - fee.value();
93+
}
7994

8095
std::string ToString() const;
8196

8297
bool operator<(const COutput& rhs) const
8398
{
8499
return outpoint < rhs.outpoint;
85100
}
101+
102+
CAmount GetFee() const
103+
{
104+
assert(fee.has_value());
105+
return fee.value();
106+
}
107+
108+
CAmount GetEffectiveValue() const
109+
{
110+
assert(effective_value.has_value());
111+
return effective_value.value();
112+
}
86113
};
87114

88115
/** Parameters for one iteration of Coin Selection. */

src/wallet/rpc/coins.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ RPCHelpMan listunspent()
638638
cctl.m_max_depth = nMaxDepth;
639639
cctl.m_include_unsafe_inputs = include_unsafe;
640640
LOCK(pwallet->cs_wallet);
641-
AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
641+
AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
642642
}
643643

644644
LOCK(pwallet->cs_wallet);

src/wallet/rpc/spend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1398,7 +1398,7 @@ RPCHelpMan sendall()
13981398
total_input_value += tx->tx->vout[input.prevout.n].nValue;
13991399
}
14001400
} else {
1401-
AvailableCoins(*pwallet, all_the_utxos, &coin_control, /*nMinimumAmount=*/0);
1401+
AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0);
14021402
for (const COutput& output : all_the_utxos) {
14031403
CHECK_NONFATAL(output.input_bytes > 0);
14041404
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {

src/wallet/spend.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
8484
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
8585
}
8686

87-
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
87+
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, std::optional<CFeeRate> feerate, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
8888
{
8989
AssertLockHeld(wallet.cs_wallet);
9090

@@ -192,7 +192,7 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
192192
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
193193
int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
194194

195-
vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me);
195+
vCoins.emplace_back(COutPoint(wtx.GetHash(), i), wtx.tx->vout.at(i), nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
196196

197197
// Checks the sum amount of all UTXO's.
198198
if (nMinimumSumAmount != MAX_MONEY) {
@@ -211,13 +211,18 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
211211
}
212212
}
213213

214+
void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
215+
{
216+
AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
217+
}
218+
214219
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
215220
{
216221
LOCK(wallet.cs_wallet);
217222

218223
CAmount balance = 0;
219224
std::vector<COutput> vCoins;
220-
AvailableCoins(wallet, vCoins, coinControl);
225+
AvailableCoinsListUnspent(wallet, vCoins, coinControl);
221226
for (const COutput& out : vCoins) {
222227
if (out.spendable) {
223228
balance += out.txout.nValue;
@@ -257,7 +262,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
257262
std::map<CTxDestination, std::vector<COutput>> result;
258263
std::vector<COutput> availableCoins;
259264

260-
AvailableCoins(wallet, availableCoins);
265+
AvailableCoinsListUnspent(wallet, availableCoins);
261266

262267
for (const COutput& coin : availableCoins) {
263268
CTxDestination address;
@@ -477,12 +482,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
477482
}
478483

479484
/* 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. */
480-
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false);
481-
output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
485+
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
482486
if (coin_selection_params.m_subtract_fee_outputs) {
483487
value_to_select -= output.txout.nValue;
484488
} else {
485-
value_to_select -= output.effective_value;
489+
value_to_select -= output.GetEffectiveValue();
486490
}
487491
preset_coins.insert(outpoint);
488492
/* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
@@ -788,7 +792,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
788792

789793
// Get available coins
790794
std::vector<COutput> vAvailableCoins;
791-
AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
795+
AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0);
792796

793797
// Choose coins to use
794798
std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);

src/wallet/spend.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
3737
/**
3838
* populate vCoins with vector of available COutputs.
3939
*/
40-
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
40+
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, std::optional<CFeeRate> feerate = std::nullopt, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
41+
42+
/**
43+
* Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function
44+
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction.
45+
*/
46+
void AvailableCoinsListUnspent(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
4147

4248
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
4349

0 commit comments

Comments
 (0)