mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-05 14:06:27 -05:00
Set effective_value when initializing a COutput
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.
This commit is contained in:
parent
640eb772e5
commit
6fbb0edac2
9 changed files with 137 additions and 60 deletions
|
@ -58,7 +58,7 @@ static void CoinSelection(benchmark::Bench& bench)
|
||||||
// Create coins
|
// Create coins
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
for (const auto& wtx : wtxs) {
|
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);
|
const CoinEligibilityFilter filter_standard(1, 6, 0);
|
||||||
|
@ -88,7 +88,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
|
||||||
CMutableTransaction tx;
|
CMutableTransaction tx;
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
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.emplace_back();
|
||||||
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
set.back().Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
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
|
// 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);
|
m_outputs.push_back(output);
|
||||||
COutput& coin = m_outputs.back();
|
COutput& coin = m_outputs.back();
|
||||||
|
|
||||||
coin.fee = coin_fee;
|
fee += coin.GetFee();
|
||||||
fee += coin.fee;
|
|
||||||
|
|
||||||
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
|
coin.long_term_fee = coin.input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.input_bytes);
|
||||||
long_term_fee += coin.long_term_fee;
|
long_term_fee += coin.long_term_fee;
|
||||||
|
|
||||||
coin.effective_value = ev;
|
effective_value += coin.GetEffectiveValue();
|
||||||
effective_value += coin.effective_value;
|
|
||||||
|
|
||||||
m_from_me &= coin.from_me;
|
m_from_me &= coin.from_me;
|
||||||
m_value += coin.txout.nValue;
|
m_value += coin.txout.nValue;
|
||||||
|
@ -380,8 +374,8 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost,
|
||||||
CAmount waste = 0;
|
CAmount waste = 0;
|
||||||
CAmount selected_effective_value = 0;
|
CAmount selected_effective_value = 0;
|
||||||
for (const COutput& coin : inputs) {
|
for (const COutput& coin : inputs) {
|
||||||
waste += coin.fee - coin.long_term_fee;
|
waste += coin.GetFee() - coin.long_term_fee;
|
||||||
selected_effective_value += use_effective_value ? coin.effective_value : coin.txout.nValue;
|
selected_effective_value += use_effective_value ? coin.GetEffectiveValue() : coin.txout.nValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change_cost) {
|
if (change_cost) {
|
||||||
|
|
|
@ -20,6 +20,14 @@ static constexpr CAmount CHANGE_UPPER{1000000};
|
||||||
|
|
||||||
/** A UTXO under consideration for use in funding a new transaction. */
|
/** A UTXO under consideration for use in funding a new transaction. */
|
||||||
struct COutput {
|
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 */
|
/** The outpoint identifying this UTXO */
|
||||||
COutPoint outpoint;
|
COutPoint outpoint;
|
||||||
|
|
||||||
|
@ -55,16 +63,10 @@ struct COutput {
|
||||||
/** Whether the transaction containing this output is sent from the owning wallet */
|
/** Whether the transaction containing this output is sent from the owning wallet */
|
||||||
bool from_me;
|
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. */
|
/** The fee required to spend this output at the consolidation feerate. */
|
||||||
CAmount long_term_fee{0};
|
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},
|
: outpoint{outpoint},
|
||||||
txout{txout},
|
txout{txout},
|
||||||
depth{depth},
|
depth{depth},
|
||||||
|
@ -73,9 +75,22 @@ struct COutput {
|
||||||
solvable{solvable},
|
solvable{solvable},
|
||||||
safe{safe},
|
safe{safe},
|
||||||
time{time},
|
time{time},
|
||||||
from_me{from_me},
|
from_me{from_me}
|
||||||
effective_value{txout.nValue}
|
{
|
||||||
{}
|
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;
|
std::string ToString() const;
|
||||||
|
|
||||||
|
@ -83,6 +98,18 @@ struct COutput {
|
||||||
{
|
{
|
||||||
return outpoint < rhs.outpoint;
|
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. */
|
/** Parameters for one iteration of Coin Selection. */
|
||||||
|
|
|
@ -635,7 +635,7 @@ RPCHelpMan listunspent()
|
||||||
cctl.m_max_depth = nMaxDepth;
|
cctl.m_max_depth = nMaxDepth;
|
||||||
cctl.m_include_unsafe_inputs = include_unsafe;
|
cctl.m_include_unsafe_inputs = include_unsafe;
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
|
AvailableCoinsListUnspent(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
|
@ -1398,7 +1398,7 @@ RPCHelpMan sendall()
|
||||||
total_input_value += tx->tx->vout[input.prevout.n].nValue;
|
total_input_value += tx->tx->vout[input.prevout.n].nValue;
|
||||||
}
|
}
|
||||||
} else {
|
} 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) {
|
for (const COutput& output : all_the_utxos) {
|
||||||
CHECK_NONFATAL(output.input_bytes > 0);
|
CHECK_NONFATAL(output.input_bytes > 0);
|
||||||
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
|
||||||
|
|
|
@ -84,7 +84,7 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
|
||||||
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
|
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);
|
AssertLockHeld(wallet.cs_wallet);
|
||||||
|
|
||||||
|
@ -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));
|
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));
|
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.
|
// Checks the sum amount of all UTXO's.
|
||||||
if (nMinimumSumAmount != MAX_MONEY) {
|
if (nMinimumSumAmount != MAX_MONEY) {
|
||||||
|
@ -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)
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
|
||||||
{
|
{
|
||||||
LOCK(wallet.cs_wallet);
|
LOCK(wallet.cs_wallet);
|
||||||
|
|
||||||
CAmount balance = 0;
|
CAmount balance = 0;
|
||||||
std::vector<COutput> vCoins;
|
std::vector<COutput> vCoins;
|
||||||
AvailableCoins(wallet, vCoins, coinControl);
|
AvailableCoinsListUnspent(wallet, vCoins, coinControl);
|
||||||
for (const COutput& out : vCoins) {
|
for (const COutput& out : vCoins) {
|
||||||
if (out.spendable) {
|
if (out.spendable) {
|
||||||
balance += out.txout.nValue;
|
balance += out.txout.nValue;
|
||||||
|
@ -257,7 +262,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
|
||||||
std::map<CTxDestination, std::vector<COutput>> result;
|
std::map<CTxDestination, std::vector<COutput>> result;
|
||||||
std::vector<COutput> availableCoins;
|
std::vector<COutput> availableCoins;
|
||||||
|
|
||||||
AvailableCoins(wallet, availableCoins);
|
AvailableCoinsListUnspent(wallet, availableCoins);
|
||||||
|
|
||||||
for (const COutput& coin : availableCoins) {
|
for (const COutput& coin : availableCoins) {
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
|
@ -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. */
|
/* 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);
|
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);
|
||||||
output.effective_value = output.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(output.input_bytes);
|
|
||||||
if (coin_selection_params.m_subtract_fee_outputs) {
|
if (coin_selection_params.m_subtract_fee_outputs) {
|
||||||
value_to_select -= output.txout.nValue;
|
value_to_select -= output.txout.nValue;
|
||||||
} else {
|
} else {
|
||||||
value_to_select -= output.effective_value;
|
value_to_select -= output.GetEffectiveValue();
|
||||||
}
|
}
|
||||||
preset_coins.insert(outpoint);
|
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.
|
/* 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(
|
||||||
|
|
||||||
// Get available coins
|
// Get available coins
|
||||||
std::vector<COutput> vAvailableCoins;
|
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
|
// Choose coins to use
|
||||||
std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
|
std::optional<SelectionResult> result = SelectCoins(wallet, vAvailableCoins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
|
||||||
|
|
|
@ -37,7 +37,13 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
|
||||||
/**
|
/**
|
||||||
* populate vCoins with vector of available COutputs.
|
* 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);
|
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<COutput>& se
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
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);
|
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, /*fees=*/ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
||||||
|
@ -50,7 +50,7 @@ static void add_coin(const CAmount& nValue, int nInput, SelectionResult& result)
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
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);
|
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, /*fees=*/ 0);
|
||||||
OutputGroup group;
|
OutputGroup group;
|
||||||
group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
|
group.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ true);
|
||||||
result.AddInput(group);
|
result.AddInput(group);
|
||||||
|
@ -62,14 +62,12 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set, CAmount fe
|
||||||
tx.vout.resize(nInput + 1);
|
tx.vout.resize(nInput + 1);
|
||||||
tx.vout[nInput].nValue = nValue;
|
tx.vout[nInput].nValue = nValue;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
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);
|
COutput coin(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ 148, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fee);
|
||||||
coin.effective_value = nValue - fee;
|
|
||||||
coin.fee = fee;
|
|
||||||
coin.long_term_fee = long_term_fee;
|
coin.long_term_fee = long_term_fee;
|
||||||
set.insert(coin);
|
set.insert(coin);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
|
static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount& nValue, CFeeRate feerate = CFeeRate(0), int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
|
||||||
{
|
{
|
||||||
CMutableTransaction tx;
|
CMutableTransaction tx;
|
||||||
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
|
||||||
|
@ -88,7 +86,7 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
|
||||||
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
|
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
|
||||||
assert(ret.second);
|
assert(ret.second);
|
||||||
CWalletTx& wtx = (*ret.first).second;
|
CWalletTx& wtx = (*ret.first).second;
|
||||||
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe);
|
coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
/** Check if SelectionResult a is equivalent to SelectionResult b.
|
||||||
|
@ -311,13 +309,13 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
|
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
|
|
||||||
add_coin(coins, *wallet, 1);
|
add_coin(coins, *wallet, 1, coin_selection_params_bnb.m_effective_feerate);
|
||||||
coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
coins.at(0).input_bytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
|
||||||
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
BOOST_CHECK(!SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change));
|
||||||
|
|
||||||
// Test fees subtracted from output:
|
// Test fees subtracted from output:
|
||||||
coins.clear();
|
coins.clear();
|
||||||
add_coin(coins, *wallet, 1 * CENT);
|
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate);
|
||||||
coins.at(0).input_bytes = 40;
|
coins.at(0).input_bytes = 40;
|
||||||
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
coin_selection_params_bnb.m_subtract_fee_outputs = true;
|
||||||
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
const auto result9 = SelectCoinsBnB(GroupCoins(coins), 1 * CENT, coin_selection_params_bnb.m_cost_of_change);
|
||||||
|
@ -334,9 +332,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
|
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
|
|
||||||
add_coin(coins, *wallet, 5 * CENT, 6 * 24, false, 0, true);
|
add_coin(coins, *wallet, 5 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
add_coin(coins, *wallet, 3 * CENT, 6 * 24, false, 0, true);
|
add_coin(coins, *wallet, 3 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
add_coin(coins, *wallet, 2 * CENT, 6 * 24, false, 0, true);
|
add_coin(coins, *wallet, 2 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
CCoinControl coin_control;
|
CCoinControl coin_control;
|
||||||
coin_control.fAllowOtherInputs = true;
|
coin_control.fAllowOtherInputs = true;
|
||||||
coin_control.Select(coins.at(0).outpoint);
|
coin_control.Select(coins.at(0).outpoint);
|
||||||
|
@ -353,36 +351,49 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
|
||||||
|
|
||||||
std::vector<COutput> coins;
|
std::vector<COutput> coins;
|
||||||
|
|
||||||
add_coin(coins, *wallet, 10 * CENT, 6 * 24, false, 0, true);
|
|
||||||
add_coin(coins, *wallet, 9 * CENT, 6 * 24, false, 0, true);
|
|
||||||
add_coin(coins, *wallet, 1 * CENT, 6 * 24, false, 0, true);
|
|
||||||
|
|
||||||
// single coin should be selected when effective fee > long term fee
|
// single coin should be selected when effective fee > long term fee
|
||||||
|
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
||||||
|
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
||||||
|
|
||||||
|
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
|
||||||
expected_result.Clear();
|
expected_result.Clear();
|
||||||
add_coin(10 * CENT, 2, expected_result);
|
add_coin(10 * CENT, 2, expected_result);
|
||||||
CCoinControl coin_control;
|
CCoinControl coin_control;
|
||||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
|
||||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
|
||||||
const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
const auto result11 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||||
BOOST_CHECK(EquivalentResult(expected_result, *result11));
|
BOOST_CHECK(EquivalentResult(expected_result, *result11));
|
||||||
|
coins.clear();
|
||||||
|
|
||||||
// more coins should be selected when effective fee < long term fee
|
// more coins should be selected when effective fee < long term fee
|
||||||
|
coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
|
||||||
|
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
|
||||||
|
|
||||||
|
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
|
||||||
expected_result.Clear();
|
expected_result.Clear();
|
||||||
add_coin(9 * CENT, 2, expected_result);
|
add_coin(9 * CENT, 2, expected_result);
|
||||||
add_coin(1 * CENT, 2, expected_result);
|
add_coin(1 * CENT, 2, expected_result);
|
||||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(3000);
|
|
||||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(5000);
|
|
||||||
const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
const auto result12 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||||
BOOST_CHECK(EquivalentResult(expected_result, *result12));
|
BOOST_CHECK(EquivalentResult(expected_result, *result12));
|
||||||
|
coins.clear();
|
||||||
|
|
||||||
// pre selected coin should be selected even if disadvantageous
|
// pre selected coin should be selected even if disadvantageous
|
||||||
|
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
||||||
|
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
||||||
|
|
||||||
|
add_coin(coins, *wallet, 10 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 9 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
add_coin(coins, *wallet, 1 * CENT, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
|
||||||
|
|
||||||
expected_result.Clear();
|
expected_result.Clear();
|
||||||
add_coin(9 * CENT, 2, expected_result);
|
add_coin(9 * CENT, 2, expected_result);
|
||||||
add_coin(1 * CENT, 2, expected_result);
|
add_coin(1 * CENT, 2, expected_result);
|
||||||
coin_control.fAllowOtherInputs = true;
|
coin_control.fAllowOtherInputs = true;
|
||||||
coin_control.Select(coins.at(1).outpoint); // pre select 9 coin
|
coin_control.Select(coins.at(1).outpoint); // pre select 9 coin
|
||||||
coin_selection_params_bnb.m_effective_feerate = CFeeRate(5000);
|
|
||||||
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
|
|
||||||
const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
const auto result13 = SelectCoins(*wallet, coins, 10 * CENT, coin_control, coin_selection_params_bnb);
|
||||||
BOOST_CHECK(EquivalentResult(expected_result, *result13));
|
BOOST_CHECK(EquivalentResult(expected_result, *result13));
|
||||||
}
|
}
|
||||||
|
@ -409,7 +420,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||||
// with an empty wallet we can't even pay one cent
|
// with an empty wallet we can't even pay one cent
|
||||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
||||||
|
|
||||||
add_coin(coins, *wallet, 1*CENT, 4); // add a new 1 cent coin
|
add_coin(coins, *wallet, 1*CENT, CFeeRate(0), 4); // add a new 1 cent coin
|
||||||
|
|
||||||
// with a new 1 cent coin, we still can't find a mature 1 cent
|
// with a new 1 cent coin, we still can't find a mature 1 cent
|
||||||
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
BOOST_CHECK(!KnapsackSolver(KnapsackGroupOutputs(coins, *wallet, filter_standard), 1 * CENT, CENT));
|
||||||
|
@ -430,7 +441,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
|
||||||
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
|
BOOST_CHECK_EQUAL(result2->GetSelectedValue(), 3 * CENT);
|
||||||
|
|
||||||
add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin,
|
add_coin(coins, *wallet, 5*CENT); // add a mature 5 cent coin,
|
||||||
add_coin(coins, *wallet, 10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
|
add_coin(coins, *wallet, 10*CENT, CFeeRate(0), 3, true); // a new 10 cent coin sent from one of our own addresses
|
||||||
add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin
|
add_coin(coins, *wallet, 20*CENT); // and a mature 20 cent coin
|
||||||
|
|
||||||
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
|
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
|
||||||
|
@ -857,5 +868,40 @@ BOOST_AUTO_TEST_CASE(waste_test)
|
||||||
BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target));
|
BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(effective_value_test)
|
||||||
|
{
|
||||||
|
const int input_bytes = 148;
|
||||||
|
const CFeeRate feerate(1000);
|
||||||
|
const CAmount nValue = 10000;
|
||||||
|
const int nInput = 0;
|
||||||
|
|
||||||
|
CMutableTransaction tx;
|
||||||
|
tx.vout.resize(1);
|
||||||
|
tx.vout[nInput].nValue = nValue;
|
||||||
|
|
||||||
|
// standard case, pass feerate in constructor
|
||||||
|
COutput output1(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate);
|
||||||
|
const CAmount expected_ev1 = 9852; // 10000 - 148
|
||||||
|
BOOST_CHECK_EQUAL(output1.GetEffectiveValue(), expected_ev1);
|
||||||
|
|
||||||
|
// input bytes unknown (input_bytes = -1), pass feerate in constructor
|
||||||
|
COutput output2(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, feerate);
|
||||||
|
BOOST_CHECK_EQUAL(output2.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
|
||||||
|
|
||||||
|
// negative effective value, pass feerate in constructor
|
||||||
|
COutput output3(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, CFeeRate(100000));
|
||||||
|
const CAmount expected_ev3 = -4800; // 10000 - 14800
|
||||||
|
BOOST_CHECK_EQUAL(output3.GetEffectiveValue(), expected_ev3);
|
||||||
|
|
||||||
|
// standard case, pass fees in constructor
|
||||||
|
const CAmount fees = 148;
|
||||||
|
COutput output4(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, fees);
|
||||||
|
BOOST_CHECK_EQUAL(output4.GetEffectiveValue(), expected_ev1);
|
||||||
|
|
||||||
|
// input bytes unknown (input_bytes = -1), pass fees in constructor
|
||||||
|
COutput output5(COutPoint(tx.GetHash(), nInput), tx.vout.at(nInput), /*depth=*/ 1, /*input_bytes=*/ -1, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, /*fees=*/ 0);
|
||||||
|
BOOST_CHECK_EQUAL(output5.GetEffectiveValue(), nValue); // The effective value should be equal to the absolute value if input_bytes is -1
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
} // namespace wallet
|
} // namespace wallet
|
||||||
|
|
|
@ -584,7 +584,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
||||||
{
|
{
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
std::vector<COutput> available;
|
std::vector<COutput> available;
|
||||||
AvailableCoins(*wallet, available);
|
AvailableCoinsListUnspent(*wallet, available);
|
||||||
BOOST_CHECK_EQUAL(available.size(), 2U);
|
BOOST_CHECK_EQUAL(available.size(), 2U);
|
||||||
}
|
}
|
||||||
for (const auto& group : list) {
|
for (const auto& group : list) {
|
||||||
|
@ -596,7 +596,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
|
||||||
{
|
{
|
||||||
LOCK(wallet->cs_wallet);
|
LOCK(wallet->cs_wallet);
|
||||||
std::vector<COutput> available;
|
std::vector<COutput> available;
|
||||||
AvailableCoins(*wallet, available);
|
AvailableCoinsListUnspent(*wallet, available);
|
||||||
BOOST_CHECK_EQUAL(available.size(), 0U);
|
BOOST_CHECK_EQUAL(available.size(), 0U);
|
||||||
}
|
}
|
||||||
// Confirm ListCoins still returns same result as before, despite coins
|
// Confirm ListCoins still returns same result as before, despite coins
|
||||||
|
|
Loading…
Add table
Reference in a new issue