Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/wallet.cpp
Show First 20 Lines • Show All 2,528 Lines • ▼ Show 20 Lines | if (coin_selection_params.use_bnb) { | ||||
coin.m_input_bytes < 0 | coin.m_input_bytes < 0 | ||||
? Amount::zero() | ? Amount::zero() | ||||
: coin_selection_params.effective_fee.GetFee( | : coin_selection_params.effective_fee.GetFee( | ||||
coin.m_input_bytes); | coin.m_input_bytes); | ||||
group.long_term_fee += | group.long_term_fee += | ||||
coin.m_input_bytes < 0 | coin.m_input_bytes < 0 | ||||
? Amount::zero() | ? Amount::zero() | ||||
: long_term_feerate.GetFee(coin.m_input_bytes); | : long_term_feerate.GetFee(coin.m_input_bytes); | ||||
if (coin_selection_params.m_subtract_fee_outputs) { | |||||
group.effective_value += coin.txout.nValue; | |||||
} else { | |||||
group.effective_value += effective_value; | group.effective_value += effective_value; | ||||
} | |||||
++it; | ++it; | ||||
} else { | } else { | ||||
it = group.Discard(coin); | it = group.Discard(coin); | ||||
} | } | ||||
} | } | ||||
if (group.effective_value > Amount::zero()) { | if (group.effective_value > Amount::zero()) { | ||||
utxo_pool.push_back(group); | utxo_pool.push_back(group); | ||||
} | } | ||||
Show All 19 Lines | |||||
bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins, | bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins, | ||||
const Amount nTargetValue, | const Amount nTargetValue, | ||||
std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, | std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, | ||||
const CCoinControl &coin_control, | const CCoinControl &coin_control, | ||||
CoinSelectionParams &coin_selection_params, | CoinSelectionParams &coin_selection_params, | ||||
bool &bnb_used) const { | bool &bnb_used) const { | ||||
std::vector<COutput> vCoins(vAvailableCoins); | std::vector<COutput> vCoins(vAvailableCoins); | ||||
Amount 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 | // coin control -> return all selected outputs (we want all selected to go | ||||
// into the transaction for sure) | // into the transaction for sure) | ||||
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) { | if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) { | ||||
// We didn't use BnB here, so set it to false. | |||||
bnb_used = false; | |||||
for (const COutput &out : vCoins) { | for (const COutput &out : vCoins) { | ||||
if (!out.fSpendable) { | if (!out.fSpendable) { | ||||
continue; | continue; | ||||
} | } | ||||
nValueRet += out.tx->tx->vout[out.i].nValue; | nValueRet += out.tx->tx->vout[out.i].nValue; | ||||
setCoinsRet.insert(out.GetInputCoin()); | setCoinsRet.insert(out.GetInputCoin()); | ||||
} | } | ||||
return (nValueRet >= nTargetValue); | return (nValueRet >= nTargetValue); | ||||
} | } | ||||
// Calculate value from preset inputs and store them. | // Calculate value from preset inputs and store them. | ||||
std::set<CInputCoin> setPresetCoins; | std::set<CInputCoin> setPresetCoins; | ||||
Amount nValueFromPresetInputs = Amount::zero(); | Amount nValueFromPresetInputs = Amount::zero(); | ||||
std::vector<COutPoint> vPresetInputs; | std::vector<COutPoint> vPresetInputs; | ||||
coin_control.ListSelected(vPresetInputs); | coin_control.ListSelected(vPresetInputs); | ||||
for (const COutPoint &outpoint : vPresetInputs) { | for (const COutPoint &outpoint : vPresetInputs) { | ||||
// For now, don't use BnB if preset inputs are selected. TODO: Enable | |||||
// this later | |||||
bnb_used = false; | |||||
coin_selection_params.use_bnb = false; | |||||
std::map<TxId, CWalletTx>::const_iterator it = | std::map<TxId, CWalletTx>::const_iterator it = | ||||
mapWallet.find(outpoint.GetTxId()); | mapWallet.find(outpoint.GetTxId()); | ||||
if (it != mapWallet.end()) { | if (it == mapWallet.end()) { | ||||
// TODO: Allow non-wallet inputs | |||||
return false; | |||||
} | |||||
const CWalletTx &wtx = it->second; | const CWalletTx &wtx = it->second; | ||||
// Clearly invalid input, fail | // Clearly invalid input, fail. | ||||
if (wtx.tx->vout.size() <= outpoint.GetN()) { | if (wtx.tx->vout.size() <= outpoint.GetN()) { | ||||
return false; | return false; | ||||
} | } | ||||
// Just to calculate the marginal byte size | // Just to calculate the marginal byte size | ||||
CInputCoin coin(wtx.tx, outpoint.GetN(), | nValueFromPresetInputs += wtx.tx->vout[outpoint.GetN()].nValue; | ||||
wtx.GetSpendSize(outpoint.GetN(), false)); | setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.GetN())); | ||||
nValueFromPresetInputs += coin.txout.nValue; | |||||
if (coin.m_input_bytes <= 0) { | |||||
// Not solvable, can't estimate size for fee | |||||
return false; | |||||
} | |||||
coin.effective_value = | |||||
coin.txout.nValue - | |||||
coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); | |||||
if (coin_selection_params.use_bnb) { | |||||
value_to_select -= coin.effective_value; | |||||
} else { | |||||
value_to_select -= coin.txout.nValue; | |||||
} | |||||
setPresetCoins.insert(coin); | |||||
} else { | |||||
return false; // TODO: Allow non-wallet inputs | |||||
} | |||||
} | } | ||||
// Remove preset inputs from vCoins | // Remove preset inputs from vCoins | ||||
for (std::vector<COutput>::iterator it = vCoins.begin(); | for (std::vector<COutput>::iterator it = vCoins.begin(); | ||||
it != vCoins.end() && coin_control.HasSelected();) { | it != vCoins.end() && coin_control.HasSelected();) { | ||||
if (setPresetCoins.count(it->GetInputCoin())) { | if (setPresetCoins.count(it->GetInputCoin())) { | ||||
it = vCoins.erase(it); | it = vCoins.erase(it); | ||||
} else { | } else { | ||||
Show All 16 Lines | bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins, | ||||
size_t max_ancestors{0}; | size_t max_ancestors{0}; | ||||
size_t max_descendants{0}; | size_t max_descendants{0}; | ||||
chain().getPackageLimits(max_ancestors, max_descendants); | chain().getPackageLimits(max_ancestors, max_descendants); | ||||
bool fRejectLongChains = gArgs.GetBoolArg( | bool fRejectLongChains = gArgs.GetBoolArg( | ||||
"-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); | "-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); | ||||
bool res = | bool res = | ||||
value_to_select <= Amount::zero() || | nTargetValue <= nValueFromPresetInputs || | ||||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), | SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, | ||||
groups, setCoinsRet, nValueRet, | CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, | ||||
coin_selection_params, bnb_used) || | nValueRet, coin_selection_params, bnb_used) || | ||||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), | SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, | ||||
groups, setCoinsRet, nValueRet, | CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, | ||||
coin_selection_params, bnb_used) || | nValueRet, coin_selection_params, bnb_used) || | ||||
(m_spend_zero_conf_change && | (m_spend_zero_conf_change && | ||||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), | SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, | ||||
groups, setCoinsRet, nValueRet, | CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, | ||||
coin_selection_params, bnb_used)) || | nValueRet, coin_selection_params, bnb_used)) || | ||||
(m_spend_zero_conf_change && | (m_spend_zero_conf_change && | ||||
SelectCoinsMinConf( | SelectCoinsMinConf( | ||||
value_to_select, | nTargetValue - nValueFromPresetInputs, | ||||
CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors / 3), | CoinEligibilityFilter(0, 1, std::min<size_t>(4, max_ancestors / 3), | ||||
std::min((size_t)4, max_descendants / 3)), | std::min<size_t>(4, max_descendants / 3)), | ||||
groups, setCoinsRet, nValueRet, coin_selection_params, | groups, setCoinsRet, nValueRet, coin_selection_params, | ||||
bnb_used)) || | bnb_used)) || | ||||
(m_spend_zero_conf_change && | (m_spend_zero_conf_change && | ||||
SelectCoinsMinConf(value_to_select, | SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, | ||||
CoinEligibilityFilter(0, 1, max_ancestors / 2, | CoinEligibilityFilter(0, 1, max_ancestors / 2, | ||||
max_descendants / 2), | max_descendants / 2), | ||||
groups, setCoinsRet, nValueRet, | groups, setCoinsRet, nValueRet, | ||||
coin_selection_params, bnb_used)) || | coin_selection_params, bnb_used)) || | ||||
(m_spend_zero_conf_change && | (m_spend_zero_conf_change && | ||||
SelectCoinsMinConf(value_to_select, | SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, | ||||
CoinEligibilityFilter(0, 1, max_ancestors - 1, | CoinEligibilityFilter(0, 1, max_ancestors - 1, | ||||
max_descendants - 1), | max_descendants - 1), | ||||
groups, setCoinsRet, nValueRet, | groups, setCoinsRet, nValueRet, | ||||
coin_selection_params, bnb_used)) || | coin_selection_params, bnb_used)) || | ||||
(m_spend_zero_conf_change && !fRejectLongChains && | (m_spend_zero_conf_change && !fRejectLongChains && | ||||
SelectCoinsMinConf( | SelectCoinsMinConf( | ||||
value_to_select, | nTargetValue - nValueFromPresetInputs, | ||||
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), | CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), | ||||
groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); | groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); | ||||
// Because SelectCoinsMinConf clears the setCoinsRet, we now add the | // Because SelectCoinsMinConf clears the setCoinsRet, we now add the | ||||
// possible inputs to the coinset. | // possible inputs to the coinset. | ||||
util::insert(setCoinsRet, setPresetCoins); | util::insert(setCoinsRet, setPresetCoins); | ||||
// Add preset inputs to the total value selected. | // Add preset inputs to the total value selected. | ||||
▲ Show 20 Lines • Show All 264 Lines • ▼ Show 20 Lines | txNew.nLockTime = GetLocktimeForNewTransaction(chain(), locked_chainIn); | ||||
CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coinControl); | CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coinControl); | ||||
nFeeRet = Amount::zero(); | nFeeRet = Amount::zero(); | ||||
bool pick_new_inputs = true; | bool pick_new_inputs = true; | ||||
Amount nValueIn = Amount::zero(); | Amount nValueIn = Amount::zero(); | ||||
// BnB selector is the only selector used when this is true. | // BnB selector is the only selector used when this is true. | ||||
// That should only happen on the first pass through the loop. | // That should only happen on the first pass through the loop. | ||||
coin_selection_params.use_bnb = true; | // If we are doing subtract fee from recipient, then don't use BnB | ||||
// If we are doing subtract fee from recipient, don't use effective | coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; | ||||
// values | |||||
coin_selection_params.m_subtract_fee_outputs = | |||||
nSubtractFeeFromAmount != 0; | |||||
// Start with no fee and loop until there is enough fee | // Start with no fee and loop until there is enough fee | ||||
while (true) { | while (true) { | ||||
nChangePosInOut = nChangePosRequest; | nChangePosInOut = nChangePosRequest; | ||||
txNew.vin.clear(); | txNew.vin.clear(); | ||||
txNew.vout.clear(); | txNew.vout.clear(); | ||||
bool fFirst = true; | bool fFirst = true; | ||||
Amount nValueToSelect = nValue; | Amount nValueToSelect = nValue; | ||||
if (nSubtractFeeFromAmount == 0) { | if (nSubtractFeeFromAmount == 0) { | ||||
nValueToSelect += nFeeRet; | nValueToSelect += nFeeRet; | ||||
} | } | ||||
// vouts to the payees | // Static size overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 | ||||
if (!coin_selection_params.m_subtract_fee_outputs) { | // input count, 1 output count | ||||
// Static size overhead + outputs vsize. 4 nVersion, 4 | |||||
// nLocktime, 1 input count, 1 output count | |||||
coin_selection_params.tx_noinputs_size = 10; | coin_selection_params.tx_noinputs_size = 10; | ||||
} | |||||
// vouts to the payees | // vouts to the payees | ||||
for (const auto &recipient : vecSend) { | for (const auto &recipient : vecSend) { | ||||
CTxOut txout(recipient.nAmount, recipient.scriptPubKey); | CTxOut txout(recipient.nAmount, recipient.scriptPubKey); | ||||
if (recipient.fSubtractFeeFromAmount) { | if (recipient.fSubtractFeeFromAmount) { | ||||
assert(nSubtractFeeFromAmount != 0); | assert(nSubtractFeeFromAmount != 0); | ||||
// Subtract fee equally from each selected recipient. | // Subtract fee equally from each selected recipient. | ||||
txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount); | txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount); | ||||
// First receiver pays the remainder not divisible by output | // First receiver pays the remainder not divisible by output | ||||
// count. | // count. | ||||
if (fFirst) { | if (fFirst) { | ||||
fFirst = false; | fFirst = false; | ||||
txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount); | txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount); | ||||
} | } | ||||
} | } | ||||
// Include the fee cost for outputs. Note this is only used for | // Include the fee cost for outputs. Note this is only used for | ||||
// BnB right now | // BnB right now | ||||
if (!coin_selection_params.m_subtract_fee_outputs) { | |||||
coin_selection_params.tx_noinputs_size += | coin_selection_params.tx_noinputs_size += | ||||
::GetSerializeSize(txout, PROTOCOL_VERSION); | ::GetSerializeSize(txout, PROTOCOL_VERSION); | ||||
} | |||||
if (IsDust(txout, chain().relayDustFee())) { | if (IsDust(txout, chain().relayDustFee())) { | ||||
if (recipient.fSubtractFeeFromAmount && | if (recipient.fSubtractFeeFromAmount && | ||||
nFeeRet > Amount::zero()) { | nFeeRet > Amount::zero()) { | ||||
if (txout.nValue < Amount::zero()) { | if (txout.nValue < Amount::zero()) { | ||||
error = _("The transaction amount is too small to " | error = _("The transaction amount is too small to " | ||||
"pay the fee"); | "pay the fee"); | ||||
} else { | } else { | ||||
error = _("The transaction amount is too small to " | error = _("The transaction amount is too small to " | ||||
"send after the fee has been deducted"); | "send after the fee has been deducted"); | ||||
} | } | ||||
} else { | } else { | ||||
error = _("Transaction amount too small"); | error = _("Transaction amount too small"); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
txNew.vout.push_back(txout); | txNew.vout.push_back(txout); | ||||
} | } | ||||
// Choose coins to use | // Choose coins to use | ||||
bool bnb_used = false; | bool bnb_used; | ||||
if (pick_new_inputs) { | if (pick_new_inputs) { | ||||
nValueIn = Amount::zero(); | nValueIn = Amount::zero(); | ||||
setCoins.clear(); | setCoins.clear(); | ||||
coin_selection_params.change_spend_size = | coin_selection_params.change_spend_size = | ||||
CalculateMaximumSignedInputSize(change_prototype_txout, | CalculateMaximumSignedInputSize(change_prototype_txout, | ||||
this); | this); | ||||
coin_selection_params.effective_fee = nFeeRateNeeded; | coin_selection_params.effective_fee = nFeeRateNeeded; | ||||
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, | if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, | ||||
▲ Show 20 Lines • Show All 1,491 Lines • Show Last 20 Lines |