aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/spend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/spend.cpp')
-rw-r--r--src/wallet/spend.cpp318
1 files changed, 153 insertions, 165 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 61d86844df..6833f9a095 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -79,30 +79,68 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
}
-uint64_t CoinsResult::size() const
+size_t CoinsResult::Size() const
{
- return bech32m.size() + bech32.size() + P2SH_segwit.size() + legacy.size() + other.size();
+ size_t size{0};
+ for (const auto& it : coins) {
+ size += it.second.size();
+ }
+ return size;
}
-std::vector<COutput> CoinsResult::all() const
+std::vector<COutput> CoinsResult::All() const
{
std::vector<COutput> all;
- all.reserve(this->size());
- all.insert(all.end(), bech32m.begin(), bech32m.end());
- all.insert(all.end(), bech32.begin(), bech32.end());
- all.insert(all.end(), P2SH_segwit.begin(), P2SH_segwit.end());
- all.insert(all.end(), legacy.begin(), legacy.end());
- all.insert(all.end(), other.begin(), other.end());
+ all.reserve(coins.size());
+ for (const auto& it : coins) {
+ all.insert(all.end(), it.second.begin(), it.second.end());
+ }
return all;
}
-void CoinsResult::clear()
+void CoinsResult::Clear() {
+ coins.clear();
+}
+
+void CoinsResult::Erase(std::set<COutPoint>& preset_coins)
+{
+ for (auto& it : coins) {
+ auto& vec = it.second;
+ auto i = std::find_if(vec.begin(), vec.end(), [&](const COutput &c) { return preset_coins.count(c.outpoint);});
+ if (i != vec.end()) {
+ vec.erase(i);
+ break;
+ }
+ }
+}
+
+void CoinsResult::Shuffle(FastRandomContext& rng_fast)
+{
+ for (auto& it : coins) {
+ ::Shuffle(it.second.begin(), it.second.end(), rng_fast);
+ }
+}
+
+void CoinsResult::Add(OutputType type, const COutput& out)
+{
+ coins[type].emplace_back(out);
+}
+
+static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
{
- bech32m.clear();
- bech32.clear();
- P2SH_segwit.clear();
- legacy.clear();
- other.clear();
+ switch (type) {
+ case TxoutType::WITNESS_V1_TAPROOT:
+ return OutputType::BECH32M;
+ case TxoutType::WITNESS_V0_KEYHASH:
+ case TxoutType::WITNESS_V0_SCRIPTHASH:
+ if (is_from_p2sh) return OutputType::P2SH_SEGWIT;
+ else return OutputType::BECH32;
+ case TxoutType::SCRIPTHASH:
+ case TxoutType::PUBKEYHASH:
+ return OutputType::LEGACY;
+ default:
+ return OutputType::UNKNOWN;
+ }
}
CoinsResult AvailableCoins(const CWallet& wallet,
@@ -222,51 +260,24 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Filter by spendable outputs only
if (!spendable && only_spendable) continue;
- // When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
- // We don't need those here, so we are leaving them in return_values_unused
- std::vector<std::vector<uint8_t>> return_values_unused;
- TxoutType type;
- bool is_from_p2sh{false};
+ // Obtain script type
+ std::vector<std::vector<uint8_t>> script_solutions;
+ TxoutType type = Solver(output.scriptPubKey, script_solutions);
- // If the Output is P2SH and spendable, we want to know if it is
+ // If the output is P2SH and solvable, we want to know if it is
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
- // this from the redeemScript. If the Output is not spendable, it will be classified
+ // this from the redeemScript. If the output is not solvable, it will be classified
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
- if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
- CScript redeemScript;
- CTxDestination destination;
- if (!ExtractDestination(output.scriptPubKey, destination))
- continue;
- const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
- if (!provider->GetCScript(hash, redeemScript))
- continue;
- type = Solver(redeemScript, return_values_unused);
+ bool is_from_p2sh{false};
+ if (type == TxoutType::SCRIPTHASH && solvable) {
+ CScript script;
+ if (!provider->GetCScript(CScriptID(uint160(script_solutions[0])), script)) continue;
+ type = Solver(script, script_solutions);
is_from_p2sh = true;
- } else {
- type = Solver(output.scriptPubKey, return_values_unused);
}
- COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
- switch (type) {
- case TxoutType::WITNESS_UNKNOWN:
- case TxoutType::WITNESS_V1_TAPROOT:
- result.bech32m.push_back(coin);
- break;
- case TxoutType::WITNESS_V0_KEYHASH:
- case TxoutType::WITNESS_V0_SCRIPTHASH:
- if (is_from_p2sh) {
- result.P2SH_segwit.push_back(coin);
- break;
- }
- result.bech32.push_back(coin);
- break;
- case TxoutType::SCRIPTHASH:
- case TxoutType::PUBKEYHASH:
- result.legacy.push_back(coin);
- break;
- default:
- result.other.push_back(coin);
- };
+ result.Add(GetOutputType(type, is_from_p2sh),
+ COutput(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate));
// Cache total amount as we go
result.total_amount += output.nValue;
@@ -278,7 +289,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
}
// Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && result.size() >= nMaximumCount) {
+ if (nMaximumCount > 0 && result.Size() >= nMaximumCount) {
return result;
}
}
@@ -334,7 +345,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
std::map<CTxDestination, std::vector<COutput>> result;
- for (const COutput& coin : AvailableCoinsListUnspent(wallet).all()) {
+ for (COutput& coin : AvailableCoinsListUnspent(wallet).All()) {
CTxDestination address;
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -457,31 +468,25 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
{
// Run coin selection on each OutputType and compute the Waste Metric
std::vector<SelectionResult> results;
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.P2SH_segwit, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32, coin_selection_params)}) {
- results.push_back(*result);
- }
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32m, coin_selection_params)}) {
- results.push_back(*result);
+ for (const auto& it : available_coins.coins) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)}) {
+ results.push_back(*result);
+ }
}
-
- // If we can't fund the transaction from any individual OutputType, run coin selection
- // over all available coins, else pick the best solution from the results
- if (results.size() == 0) {
- if (allow_mixed_output_types) {
- if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params)}) {
- return result;
- }
+ // If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric
+ // and return the result
+ if (results.size() > 0) return *std::min_element(results.begin(), results.end());
+
+ // If we can't fund the transaction from any individual OutputType, run coin selection one last time
+ // over all available coins, which would allow mixing
+ if (allow_mixed_output_types) {
+ if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) {
+ return result;
}
- return std::optional<SelectionResult>();
- };
- std::optional<SelectionResult> result{*std::min_element(results.begin(), results.end())};
- return result;
+ }
+ // Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
+ // find a solution using all available coins
+ return std::nullopt;
};
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins, const CoinSelectionParams& coin_selection_params)
@@ -489,32 +494,20 @@ std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, cons
// Vector of results. We will choose the best one based on waste.
std::vector<SelectionResult> results;
- // 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(wallet, available_coins, coin_selection_params, eligibility_filter, true /* positive_only */);
+ std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/true);
if (auto bnb_result{SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change)}) {
results.push_back(*bnb_result);
}
// 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(wallet, available_coins, coin_selection_params, eligibility_filter, false /* positive_only */);
- CAmount target_with_change = nTargetValue;
- // 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 and SRD as well, as we are expecting to create a change output.
- if (!coin_selection_params.m_subtract_fee_outputs) {
- target_with_change += coin_selection_params.m_change_fee;
- }
- if (auto knapsack_result{KnapsackSolver(all_groups, target_with_change, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
- knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ std::vector<OutputGroup> all_groups = GroupOutputs(wallet, available_coins, coin_selection_params, eligibility_filter, /*positive_only=*/false);
+ if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
+ knapsack_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*knapsack_result);
}
- // Include change for SRD as we want to avoid making really small change if the selection just
- // barely meets the target. Just use the lower bound change target instead of the randomly
- // generated one, since SRD will result in a random change amount anyway; avoid making the
- // target needlessly large.
- const CAmount srd_target = target_with_change + CHANGE_LOWER;
- if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
- srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ if (auto srd_result{SelectCoinsSRD(positive_groups, nTargetValue, coin_selection_params.rng_fast)}) {
+ srd_result->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
results.push_back(*srd_result);
}
@@ -556,8 +549,12 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
if (!coin_control.GetExternalOutput(outpoint, txout)) {
return std::nullopt;
}
+ }
+
+ if (input_bytes == -1) {
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
}
+
// If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) {
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
@@ -585,18 +582,20 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
if (coin_control.HasSelected() && !coin_control.m_allow_other_inputs) {
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
result.AddInput(preset_inputs);
- if (result.GetSelectedValue() < nTargetValue) return std::nullopt;
- result.ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+
+ if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) {
+ return std::nullopt;
+ } else if (result.GetSelectedValue() < nTargetValue) {
+ return std::nullopt;
+ }
+
+ result.ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
return result;
}
// remove preset inputs from coins so that Coin Selection doesn't pick them.
if (coin_control.HasSelected()) {
- available_coins.legacy.erase(remove_if(available_coins.legacy.begin(), available_coins.legacy.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.legacy.end());
- available_coins.P2SH_segwit.erase(remove_if(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.P2SH_segwit.end());
- available_coins.bech32.erase(remove_if(available_coins.bech32.begin(), available_coins.bech32.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32.end());
- available_coins.bech32m.erase(remove_if(available_coins.bech32m.begin(), available_coins.bech32m.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32m.end());
- available_coins.other.erase(remove_if(available_coins.other.begin(), available_coins.other.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.other.end());
+ available_coins.Erase(preset_coins);
}
unsigned int limit_ancestor_count = 0;
@@ -608,23 +607,22 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
- if (coin_control.m_avoid_partial_spends && available_coins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
+ if (coin_control.m_avoid_partial_spends && available_coins.Size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 101+ outputs all pointing to the same destination may result in
// privacy leaks as they will potentially be deterministically sorted. We solve that by
// explicitly shuffling the outputs before processing
- Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.bech32.begin(), available_coins.bech32.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.bech32m.begin(), available_coins.bech32m.end(), coin_selection_params.rng_fast);
- Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
+ available_coins.Shuffle(coin_selection_params.rng_fast);
}
+ SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL);
+ preselected.AddInput(preset_inputs);
+
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
// permissive CoinEligibilityFilter.
std::optional<SelectionResult> res = [&] {
// Pre-selected inputs already cover the target amount.
- if (value_to_select <= 0) return std::make_optional(SelectionResult(nTargetValue, SelectionAlgorithm::MANUAL));
+ if (value_to_select <= 0) return std::make_optional(SelectionResult(value_to_select, SelectionAlgorithm::MANUAL));
// 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.
@@ -678,9 +676,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
if (!res) return std::nullopt;
// Add preset inputs to result
- res->AddInput(preset_inputs);
- if (res->m_algo == SelectionAlgorithm::MANUAL) {
- res->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
+ res->Merge(preselected);
+ if (res->GetAlgo() == SelectionAlgorithm::MANUAL) {
+ res->ComputeAndSetWaste(coin_selection_params.min_viable_change, coin_selection_params.m_cost_of_change, coin_selection_params.m_change_fee);
}
return res;
@@ -794,7 +792,6 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
coin_selection_params.m_subtract_fee_outputs = true;
}
}
- coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), rng_fast);
// Create change script that will be used if we need change
CScript scriptChange;
@@ -863,6 +860,15 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
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;
+ coin_selection_params.m_min_change_target = GenerateChangeTarget(std::floor(recipients_sum / vecSend.size()), coin_selection_params.m_change_fee, rng_fast);
+
+ // The smallest change amount should be:
+ // 1. at least equal to dust threshold
+ // 2. at least 1 sat greater than fees to spend it at m_discard_feerate
+ const auto dust = GetDustThreshold(change_prototype_txout, coin_selection_params.m_discard_feerate);
+ const auto change_spend_fee = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size);
+ coin_selection_params.min_viable_change = std::max(change_spend_fee + 1, dust);
+
// vouts to the payees
if (!coin_selection_params.m_subtract_fee_outputs) {
coin_selection_params.tx_noinputs_size = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size)
@@ -901,25 +907,22 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
if (!result) {
return util::Error{_("Insufficient funds")};
}
- TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
-
- // 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 = result->GetSelectedValue() - recipients_sum;
- assert(change_and_fee >= 0);
- CTxOut newTxOut(change_and_fee, scriptChange);
-
- if (nChangePosInOut == -1) {
- // Insert change txn at random position:
- nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
- }
- else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
- return util::Error{_("Transaction change output index out of range")};
+ TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->GetAlgo()).c_str(), result->GetTarget(), result->GetWaste(), result->GetSelectedValue());
+
+ const CAmount change_amount = result->GetChange(coin_selection_params.min_viable_change, coin_selection_params.m_change_fee);
+ if (change_amount > 0) {
+ CTxOut newTxOut(change_amount, scriptChange);
+ if (nChangePosInOut == -1) {
+ // Insert change txn at random position:
+ nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
+ } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
+ return util::Error{_("Transaction change output index out of range")};
+ }
+ txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
+ } else {
+ nChangePosInOut = -1;
}
- assert(nChangePosInOut != -1);
- auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut);
-
// Shuffle selected coins and fill in final vin
std::vector<COutput> selected_coins = result->GetShuffledInputVector();
@@ -943,42 +946,23 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
if (nBytes == -1) {
return util::Error{_("Missing solving data for estimating transaction size")};
}
- nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
-
- // Subtract fee from the change output if not subtracting it from recipient outputs
- CAmount fee_needed = nFeeRet;
- if (!coin_selection_params.m_subtract_fee_outputs) {
- 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), &wallet, &coin_control);
- nBytes = tx_sizes.vsize;
- fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
- }
+ CAmount fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
+ nFeeRet = result->GetSelectedValue() - recipients_sum - change_amount;
- // The only time that fee_needed should be less than the amount available for fees (in change_and_fee - change_amount) is when
+ // The only time that fee_needed should be less than the amount available for fees is when
// we are subtracting the fee from the outputs. If this occurs at any other time, it is a bug.
- assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount);
+ assert(coin_selection_params.m_subtract_fee_outputs || fee_needed <= nFeeRet);
- // 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 there is a change output and we overpay the fees then increase the change to match the fee needed
+ if (nChangePosInOut != -1 && fee_needed < nFeeRet) {
+ auto& change = txNew.vout.at(nChangePosInOut);
+ change.nValue += nFeeRet - fee_needed;
+ nFeeRet = fee_needed;
}
// Reduce output values for subtractFeeFromAmount
if (coin_selection_params.m_subtract_fee_outputs) {
- CAmount to_reduce = fee_needed + change_amount - change_and_fee;
+ CAmount to_reduce = fee_needed - nFeeRet;
int i = 0;
bool fFirst = true;
for (const auto& recipient : vecSend)
@@ -1122,12 +1106,16 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
wallet.chain().findCoins(coins);
for (const CTxIn& txin : tx.vin) {
- // if it's not in the wallet and corresponding UTXO is found than select as external output
const auto& outPoint = txin.prevout;
- if (wallet.mapWallet.find(outPoint.hash) == wallet.mapWallet.end() && !coins[outPoint].out.IsNull()) {
- coinControl.SelectExternal(outPoint, coins[outPoint].out);
- } else {
+ if (wallet.IsMine(outPoint)) {
+ // The input was found in the wallet, so select as internal
coinControl.Select(outPoint);
+ } else if (coins[outPoint].out.IsNull()) {
+ error = _("Unable to find UTXO for external input");
+ return false;
+ } else {
+ // The input was not in the wallet, but is in the UTXO set, so select as external
+ coinControl.SelectExternal(outPoint, coins[outPoint].out);
}
}