aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/wallet.cpp')
-rw-r--r--src/wallet/wallet.cpp384
1 files changed, 159 insertions, 225 deletions
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 7cdf2fcda0..e603ce7d0b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2396,44 +2396,28 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
}
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
- std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
+ std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const
{
setCoinsRet.clear();
nValueRet = 0;
- if (coin_selection_params.use_bnb) {
- // Get the feerate for effective value.
- // When subtracting the fee from the outputs, we want the effective feerate to be 0
- CFeeRate effective_feerate{0};
- if (!coin_selection_params.m_subtract_fee_outputs) {
- effective_feerate = coin_selection_params.m_effective_feerate;
- }
-
- std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, coin_selection_params.m_long_term_feerate, eligibility_filter, true /* positive_only */);
-
- // Calculate cost of change
- CAmount cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
-
- // Calculate the fees for things that aren't inputs
- CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
- bnb_used = true;
- return SelectCoinsBnB(groups, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
- } else {
- std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, CFeeRate(0), CFeeRate(0), eligibility_filter, false /* positive_only */);
-
- bnb_used = false;
- return KnapsackSolver(nTargetValue, groups, setCoinsRet, nValueRet);
+ // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output.
+ std::vector<OutputGroup> positive_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, true /* positive_only */);
+ if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, setCoinsRet, nValueRet)) {
+ return true;
}
+ // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
+ std::vector<OutputGroup> all_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, false /* positive_only */);
+ // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
+ // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
+ return KnapsackSolver(nTargetValue + coin_selection_params.m_change_fee, all_groups, setCoinsRet, nValueRet);
}
-bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
+bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const
{
std::vector<COutput> vCoins(vAvailableCoins);
CAmount value_to_select = nTargetValue;
- // Default to bnb was not used. If we use it, we set it later
- bnb_used = false;
-
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
@@ -2470,10 +2454,10 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return false; // Not solvable, can't estimate size for fee
}
coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.use_bnb) {
- value_to_select -= coin.effective_value;
- } else {
+ if (coin_selection_params.m_subtract_fee_outputs) {
value_to_select -= coin.txout.nValue;
+ } else {
+ value_to_select -= coin.effective_value;
}
setPresetCoins.insert(coin);
} else {
@@ -2515,26 +2499,26 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six
// confirmations on outputs received from other wallets and only spend confirmed change.
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
+ if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
+ if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
// Fall back to using zero confirmation change (but with as few ancestors in the mempool as
// possible) if we cannot fund the transaction otherwise.
if (m_spend_zero_conf_change) {
- if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) return true;
+ if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
}
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
}
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety.
if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
}
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
@@ -2542,7 +2526,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
if (coin_control.m_include_unsafe_inputs
&& SelectCoinsMinConf(value_to_select,
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
}
// Try with unlimited ancestors/descendants. The transaction will still need to meet
@@ -2550,7 +2534,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// OutputGroups use heuristics that may overestimate ancestor/descendant counts.
if (!fRejectLongChains && SelectCoinsMinConf(value_to_select,
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */),
- vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) {
+ vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
}
}
@@ -2815,7 +2799,6 @@ bool CWallet::CreateTransactionInternal(
CAmount nValue = 0;
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
ReserveDestination reservedest(this, change_type);
- int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto& recipient : vecSend)
{
@@ -2837,7 +2820,6 @@ bool CWallet::CreateTransactionInternal(
CMutableTransaction txNew;
FeeCalculation feeCalc;
- CAmount nFeeNeeded;
std::pair<int64_t, int64_t> tx_sizes;
int nBytes;
{
@@ -2881,6 +2863,16 @@ bool CWallet::CreateTransactionInternal(
CTxOut change_prototype_txout(0, scriptChange);
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
+ // Get size of spending the change output
+ int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
+ // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
+ // as lower-bound to allow BnB to do it's thing
+ if (change_spend_size == -1) {
+ coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
+ } else {
+ coin_selection_params.change_spend_size = (size_t)change_spend_size;
+ }
+
// Set discard feerate
coin_selection_params.m_discard_feerate = GetDiscardRate(*this);
@@ -2903,205 +2895,147 @@ bool CWallet::CreateTransactionInternal(
cc_temp.m_confirm_target = chain().estimateMaxBlocks();
coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr);
- nFeeRet = 0;
- bool pick_new_inputs = true;
- CAmount nValueIn = 0;
+ // Calculate the cost of change
+ // Cost of change is the cost of creating the change output + cost of spending the change output in the future.
+ // For creating the change output now, we use the effective feerate.
+ // For spending the change output in the future, we use the discard feerate for now.
+ // So cost of change = (change output size * effective feerate) + (size of spending change output * discard feerate)
+ coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size);
+ coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee;
- // BnB selector is the only selector used when this is true.
- // That should only happen on the first pass through the loop.
- coin_selection_params.use_bnb = true;
coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
- // Start with no fee and loop until there is enough fee
- while (true)
- {
- nChangePosInOut = nChangePosRequest;
- txNew.vin.clear();
- txNew.vout.clear();
- bool fFirst = true;
- CAmount nValueToSelect = nValue;
- if (nSubtractFeeFromAmount == 0)
- nValueToSelect += nFeeRet;
+ // vouts to the payees
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ }
+ for (const auto& recipient : vecSend)
+ {
+ CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
- // vouts to the payees
+ // Include the fee cost for outputs.
if (!coin_selection_params.m_subtract_fee_outputs) {
- coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
}
- for (const auto& recipient : vecSend)
+
+ if (IsDust(txout, chain().relayDustFee()))
{
- CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
+ error = _("Transaction amount too small");
+ return false;
+ }
+ txNew.vout.push_back(txout);
+ }
- if (recipient.fSubtractFeeFromAmount)
- {
- assert(nSubtractFeeFromAmount != 0);
- txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
+ // Include the fees for things that aren't inputs, excluding the change output
+ const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size);
+ CAmount nValueToSelect = nValue + not_input_fees;
- if (fFirst) // first receiver pays the remainder not divisible by output count
- {
- fFirst = false;
- txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
- }
- }
- // Include the fee cost for outputs. Note this is only used for BnB right now
- if (!coin_selection_params.m_subtract_fee_outputs) {
- coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
- }
+ // Choose coins to use
+ CAmount inputs_sum = 0;
+ setCoins.clear();
+ if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params))
+ {
+ error = _("Insufficient funds");
+ return false;
+ }
- if (IsDust(txout, chain().relayDustFee()))
- {
- if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
- {
- if (txout.nValue < 0)
- error = _("The transaction amount is too small to pay the fee");
- else
- error = _("The transaction amount is too small to send after the fee has been deducted");
- }
- else
- error = _("Transaction amount too small");
- return false;
- }
- txNew.vout.push_back(txout);
- }
+ // Always make a change output
+ // We will reduce the fee from this change output later, and remove the output if it is too small.
+ const CAmount change_and_fee = inputs_sum - nValue;
+ assert(change_and_fee >= 0);
+ CTxOut newTxOut(change_and_fee, scriptChange);
- // Choose coins to use
- bool bnb_used = false;
- if (pick_new_inputs) {
- nValueIn = 0;
- setCoins.clear();
- int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
- // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
- // as lower-bound to allow BnB to do it's thing
- if (change_spend_size == -1) {
- coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE;
- } else {
- coin_selection_params.change_spend_size = (size_t)change_spend_size;
- }
- if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
- {
- // If BnB was used, it was the first pass. No longer the first pass and continue loop with knapsack.
- if (bnb_used) {
- coin_selection_params.use_bnb = false;
- continue;
- }
- else {
- error = _("Insufficient funds");
- return false;
- }
- }
- } else {
- bnb_used = false;
- }
+ if (nChangePosInOut == -1)
+ {
+ // Insert change txn at random position:
+ nChangePosInOut = GetRandInt(txNew.vout.size()+1);
+ }
+ else if ((unsigned int)nChangePosInOut > txNew.vout.size())
+ {
+ error = _("Change index out of range");
+ return false;
+ }
- const CAmount nChange = nValueIn - nValueToSelect;
- if (nChange > 0)
- {
- // Fill a vout to ourself
- CTxOut newTxOut(nChange, scriptChange);
+ assert(nChangePosInOut != -1);
+ auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
- // Never create dust outputs; if we would, just
- // add the dust to the fee.
- // The nChange when BnB is used is always going to go to fees.
- if (IsDust(newTxOut, coin_selection_params.m_discard_feerate) || bnb_used)
- {
- nChangePosInOut = -1;
- nFeeRet += nChange;
- }
- else
- {
- if (nChangePosInOut == -1)
- {
- // Insert change txn at random position:
- nChangePosInOut = GetRandInt(txNew.vout.size()+1);
- }
- else if ((unsigned int)nChangePosInOut > txNew.vout.size())
- {
- error = _("Change index out of range");
- return false;
- }
+ // Dummy fill vin for maximum size estimation
+ //
+ for (const auto& coin : setCoins) {
+ txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
+ }
- std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
- txNew.vout.insert(position, newTxOut);
- }
- } else {
- nChangePosInOut = -1;
- }
+ // Calculate the transaction fee
+ tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
+ nBytes = tx_sizes.first;
+ if (nBytes < 0) {
+ error = _("Signing transaction failed");
+ return false;
+ }
+ nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- // Dummy fill vin for maximum size estimation
- //
- for (const auto& coin : setCoins) {
- txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
- }
+ // Subtract fee from the change output if not subtrating it from recipient outputs
+ CAmount fee_needed = nFeeRet;
+ if (nSubtractFeeFromAmount == 0) {
+ change_position->nValue -= fee_needed;
+ }
+
+ // We want to drop the change to fees if:
+ // 1. The change output would be dust
+ // 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change)
+ CAmount change_amount = change_position->nValue;
+ if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change)
+ {
+ nChangePosInOut = -1;
+ change_amount = 0;
+ txNew.vout.erase(change_position);
+ // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
nBytes = tx_sizes.first;
- if (nBytes < 0) {
- error = _("Signing transaction failed");
- return false;
- }
+ fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
+ }
- nFeeNeeded = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- if (nFeeRet >= nFeeNeeded) {
- // Reduce fee to only the needed amount if possible. This
- // prevents potential overpayment in fees if the coins
- // selected to meet nFeeNeeded result in a transaction that
- // requires less fee than the prior iteration.
-
- // If we have no change and a big enough excess fee, then
- // try to construct transaction again only without picking
- // new inputs. We now know we only need the smaller fee
- // (because of reduced tx size) and so we should add a
- // change output. Only try this once.
- if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
- unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size
- CAmount fee_needed_with_change = coin_selection_params.m_effective_feerate.GetFee(tx_size_with_change);
- CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate);
- if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) {
- pick_new_inputs = false;
- nFeeRet = fee_needed_with_change;
- continue;
- }
- }
+ // Update nFeeRet in case fee_needed changed due to dropping the change output
+ if (fee_needed <= change_and_fee - change_amount) {
+ nFeeRet = change_and_fee - change_amount;
+ }
- // If we have change output already, just increase it
- if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
- CAmount extraFeePaid = nFeeRet - nFeeNeeded;
- std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
- change_position->nValue += extraFeePaid;
- nFeeRet -= extraFeePaid;
+ // Reduce output values for subtractFeeFromAmount
+ if (nSubtractFeeFromAmount != 0) {
+ CAmount to_reduce = fee_needed + change_amount - change_and_fee;
+ int i = 0;
+ bool fFirst = true;
+ for (const auto& recipient : vecSend)
+ {
+ if (i == nChangePosInOut) {
+ ++i;
}
- break; // Done, enough fee included.
- }
- else if (!pick_new_inputs) {
- // This shouldn't happen, we should have had enough excess
- // fee to pay for the new output and still meet nFeeNeeded
- // Or we should have just subtracted fee from recipients and
- // nFeeNeeded should not have changed
- error = _("Transaction fee and change calculation failed");
- return false;
- }
+ CTxOut& txout = txNew.vout[i];
- // Try to reduce change to include necessary fee
- if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
- CAmount additionalFeeNeeded = nFeeNeeded - nFeeRet;
- std::vector<CTxOut>::iterator change_position = txNew.vout.begin()+nChangePosInOut;
- // Only reduce change if remaining amount is still a large enough output.
- if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) {
- change_position->nValue -= additionalFeeNeeded;
- nFeeRet += additionalFeeNeeded;
- break; // Done, able to increase fee from change
- }
- }
+ if (recipient.fSubtractFeeFromAmount)
+ {
+ txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
- // If subtracting fee from recipients, we now know what fee we
- // need to subtract, we have no reason to reselect inputs
- if (nSubtractFeeFromAmount > 0) {
- pick_new_inputs = false;
- }
+ if (fFirst) // first receiver pays the remainder not divisible by output count
+ {
+ fFirst = false;
+ txout.nValue -= to_reduce % nSubtractFeeFromAmount;
+ }
- // Include more fee and try again.
- nFeeRet = nFeeNeeded;
- coin_selection_params.use_bnb = false;
- continue;
+ // Error if this output is reduced to be below dust
+ if (IsDust(txout, chain().relayDustFee())) {
+ if (txout.nValue < 0) {
+ error = _("The transaction amount is too small to pay the fee");
+ } else {
+ error = _("The transaction amount is too small to send after the fee has been deducted");
+ }
+ return false;
+ }
+ }
+ ++i;
+ }
+ nFeeRet = fee_needed;
}
// Give up if change keypool ran out and change is required
@@ -3163,8 +3097,8 @@ bool CWallet::CreateTransactionInternal(
reservedest.KeepDestination();
fee_calc_out = feeCalc;
- WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Needed:%d Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
- nFeeRet, nBytes, nFeeNeeded, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
+ WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
+ nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
feeCalc.est.pass.start, feeCalc.est.pass.end,
(feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0,
feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
@@ -4298,12 +4232,12 @@ bool CWalletTx::IsImmatureCoinBase() const
return GetBlocksToMaturity() > 0;
}
-std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const
+std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const
{
std::vector<OutputGroup> groups_out;
- if (separate_coins) {
- // Single coin means no grouping. Each COutput gets its own OutputGroup.
+ if (!coin_sel_params.m_avoid_partial_spends) {
+ // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup.
for (const COutput& output : outputs) {
// Skip outputs we cannot spend
if (!output.fSpendable) continue;
@@ -4313,11 +4247,11 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
CInputCoin input_coin = output.GetInputCoin();
// Make an OutputGroup containing just this output
- OutputGroup group{effective_feerate, long_term_feerate};
+ OutputGroup group{coin_sel_params};
group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
// Check the OutputGroup's eligibility. Only add the eligible ones.
- if (positive_only && group.effective_value <= 0) continue;
+ if (positive_only && group.GetSelectionAmount() <= 0) continue;
if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
}
return groups_out;
@@ -4343,7 +4277,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
if (groups.size() == 0) {
// No OutputGroups for this scriptPubKey yet, add one
- groups.emplace_back(effective_feerate, long_term_feerate);
+ groups.emplace_back(coin_sel_params);
}
// Get the last OutputGroup in the vector so that we can add the CInputCoin to it
@@ -4354,7 +4288,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
// to avoid surprising users with very high fees.
if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
// The last output group is full, add a new group to the vector and use that group for the insertion
- groups.emplace_back(effective_feerate, long_term_feerate);
+ groups.emplace_back(coin_sel_params);
group = &groups.back();
}
@@ -4376,7 +4310,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
}
// Check the OutputGroup's eligibility. Only add the eligible ones.
- if (positive_only && group.effective_value <= 0) continue;
+ if (positive_only && group.GetSelectionAmount() <= 0) continue;
if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
}
}