Skip to content

Commit

Permalink
Set effective_value when initializing a COutput
Browse files Browse the repository at this point in the history
Previously in COutput, effective_value was initialized as the absolute
value of the txout, and fee as 0. effective_value along with fee were
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. If either are
provided, 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.
  • Loading branch information
ishaanam committed May 21, 2022
1 parent 640eb77 commit 6fbb0ed
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 60 deletions.
4 changes: 2 additions & 2 deletions src/bench/coin_selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins
std::vector<COutput> coins;
for (const auto& wtx : wtxs) {
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);
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);
}

const CoinEligibilityFilter filter_standard(1, 6, 0);
Expand Down Expand Up @@ -88,7 +88,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
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);
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);
set.emplace_back();
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
}
Expand Down
16 changes: 5 additions & 11 deletions src/wallet/coinselection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,24 +328,18 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
******************************************************************************/

void OutputGroup::Insert(const COutput& output, size_t ancestors, size_t descendants, bool positive_only) {
// Compute the effective value first
const CAmount coin_fee = output.input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.input_bytes);
const CAmount ev = output.txout.nValue - coin_fee;

// Filter for positive only here before adding the coin
if (positive_only && ev <= 0) return;
if (positive_only && output.GetEffectiveValue() <= 0) return;

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

coin.fee = coin_fee;
fee += coin.fee;
fee += coin.GetFee();

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

coin.effective_value = ev;
effective_value += coin.effective_value;
effective_value += coin.GetEffectiveValue();

m_from_me &= coin.from_me;
m_value += coin.txout.nValue;
Expand Down Expand Up @@ -380,8 +374,8 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost,
CAmount waste = 0;
CAmount selected_effective_value = 0;
for (const COutput& coin : inputs) {
waste += coin.fee - coin.long_term_fee;
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
waste += coin.GetFee() - coin.long_term_fee;
selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
}

if (change_cost) {
Expand Down
47 changes: 37 additions & 10 deletions src/wallet/coinselection.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ static constexpr CAmount CHANGE_UPPER{1000000};

/** A UTXO under consideration for use in funding a new transaction. */
struct COutput {
private:
/** The output's value minus fees required to spend it.*/
std::optional<CAmount> effective_value;

/** The fee required to spend this output at the transaction's target feerate. */
std::optional<CAmount> fee;

public:
/** The outpoint identifying this UTXO */
COutPoint outpoint;

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

/** The output's value minus fees required to spend it. Initialized as the output's absolute value. */
CAmount effective_value;

/** The fee required to spend this output at the transaction's target feerate. */
CAmount fee{0};

/** The fee required to spend this output at the consolidation feerate. */
CAmount long_term_fee{0};

COutput(const COutPoint& outpoint, const CTxOut& txout, int depth, int input_bytes, bool spendable, bool solvable, bool safe, int64_t time, bool from_me)
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)
: outpoint{outpoint},
txout{txout},
depth{depth},
Expand All @@ -73,16 +75,41 @@ struct COutput {
solvable{solvable},
safe{safe},
time{time},
from_me{from_me},
effective_value{txout.nValue}
{}
from_me{from_me}
{
if (feerate) {
fee = input_bytes < 0 ? 0 : feerate.value().GetFee(input_bytes);
effective_value = txout.nValue - fee.value();
}
}

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)
: COutput(outpoint, txout, depth, input_bytes, spendable, solvable, safe, time, from_me)
{
// 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)
assert((input_bytes < 0 && fees == 0) || (input_bytes > 0 && fees >= 0));
fee = fees;
effective_value = txout.nValue - fee.value();
}

std::string ToString() const;

bool operator<(const COutput& rhs) const
{
return outpoint < rhs.outpoint;
}

CAmount GetFee() const
{
assert(fee.has_value());
return fee.value();
}

CAmount GetEffectiveValue() const
{
assert(effective_value.has_value());
return effective_value.value();
}
};

/** Parameters for one iteration of Coin Selection. */
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpc/coins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
}

LOCK(pwallet->cs_wallet);
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/rpc/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ RPCHelpMan sendall()
total_input_value += tx->tx->vout[input.prevout.n].nValue;
}
} else {
AvailableCoins(*pwallet, all_the_utxos, &coin_control, /*nMinimumAmount=*/0);
AvailableCoins(*pwallet, all_the_utxos, &coin_control, fee_rate, /*nMinimumAmount=*/0);
for (const COutput& output : all_the_utxos) {
CHECK_NONFATAL(output.input_bytes > 0);
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
Expand Down
20 changes: 12 additions & 8 deletions src/wallet/spend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}

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)
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)
{
AssertLockHeld(wallet.cs_wallet);

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

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

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

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)
{
AvailableCoins(wallet, vCoins, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
}

CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
{
LOCK(wallet.cs_wallet);

CAmount balance = 0;
std::vector<COutput> vCoins;
AvailableCoins(wallet, vCoins, coinControl);
AvailableCoinsListUnspent(wallet, vCoins, coinControl);
for (const COutput& out : vCoins) {
if (out.spendable) {
balance += out.txout.nValue;
Expand Down Expand Up @@ -257,7 +262,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;

AvailableCoins(wallet, availableCoins);
AvailableCoinsListUnspent(wallet, availableCoins);

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

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

// Get available coins
std::vector<COutput> vAvailableCoins;
AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
AvailableCoins(wallet, vAvailableCoins, &coin_control, coin_selection_params.m_effective_feerate, 1, MAX_MONEY, MAX_MONEY, 0);

// Choose coins to use
std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
Expand Down
8 changes: 7 additions & 1 deletion src/wallet/spend.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
/**
* populate vCoins with vector of available COutputs.
*/
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);
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);

/**
* Wrapper function for AvailableCoins which skips the `feerate` parameter. Use this function
* to list all available coins (e.g. listunspent RPC) while not intending to fund a transaction.
*/
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);

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

Expand Down
Loading

0 comments on commit 6fbb0ed

Please sign in to comment.