diff options
Diffstat (limited to 'src/wallet/spend.cpp')
-rw-r--r-- | src/wallet/spend.cpp | 682 |
1 files changed, 405 insertions, 277 deletions
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 83eaececc1..d84310c323 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -11,6 +11,7 @@ #include <util/fees.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <util/trace.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> @@ -19,35 +20,27 @@ #include <wallet/transaction.h> #include <wallet/wallet.h> +#include <cmath> + using interfaces::FoundBlock; namespace wallet { static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; -int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig) -{ - return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig); -} - -std::string COutput::ToString() const -{ - return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); -} - -int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig) +int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control) { CMutableTransaction txn; - txn.vin.push_back(CTxIn(COutPoint())); - if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) { + txn.vin.push_back(CTxIn(outpoint)); + if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) { return -1; } return GetVirtualTransactionInputSize(txn.vin[0]); } -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control) { const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey); - return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig); + return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control); } // txouts needs to be in the order of tx.vin @@ -86,12 +79,82 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control); } -void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +size_t CoinsResult::Size() const +{ + size_t size{0}; + for (const auto& it : coins) { + size += it.second.size(); + } + return size; +} + +std::vector<COutput> CoinsResult::All() const +{ + std::vector<COutput> all; + all.reserve(coins.size()); + for (const auto& it : coins) { + all.insert(all.end(), it.second.begin(), it.second.end()); + } + return all; +} + +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) +{ + 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, + const CCoinControl* coinControl, + std::optional<CFeeRate> feerate, + const CAmount& nMinimumAmount, + const CAmount& nMaximumAmount, + const CAmount& nMinimumSumAmount, + const uint64_t nMaximumCount, + bool only_spendable) { AssertLockHeld(wallet.cs_wallet); - vCoins.clear(); - CAmount nTotal = 0; + CoinsResult result; // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses bool allow_used_addresses = !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); @@ -158,71 +221,106 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C continue; } + bool tx_from_me = CachedTxIsFromMe(wallet, wtx, ISMINE_ALL); + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - // Only consider selected coins if add_inputs is false - if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) { - continue; - } + const CTxOut& output = wtx.tx->vout[i]; + const COutPoint outpoint(wtxid, i); - if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) + if (output.nValue < nMinimumAmount || output.nValue > nMaximumAmount) continue; - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) + if (coinControl && coinControl->HasSelected() && !coinControl->m_allow_other_inputs && !coinControl->IsSelected(outpoint)) continue; - if (wallet.IsLockedCoin(entry.first, i)) + if (wallet.IsLockedCoin(outpoint)) continue; - if (wallet.IsSpent(wtxid, i)) + if (wallet.IsSpent(outpoint)) continue; - isminetype mine = wallet.IsMine(wtx.tx->vout[i]); + isminetype mine = wallet.IsMine(output); if (mine == ISMINE_NO) { continue; } - if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) { + if (!allow_used_addresses && wallet.IsSpentKey(output.scriptPubKey)) { continue; } - std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); + std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(output.scriptPubKey); - bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; + int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl); + // Because CalculateMaximumSignedInputSize just uses ProduceSignature and makes a dummy signature, + // it is safe to assume that this input is solvable if input_bytes is greater -1. + bool solvable = input_bytes > -1; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + // Filter by spendable outputs only + if (!spendable && only_spendable) continue; + + // If the Output is P2SH and spendable, 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 + // as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript + CScript script; + bool is_from_p2sh{false}; + if (output.scriptPubKey.IsPayToScriptHash() && solvable) { + CTxDestination destination; + if (!ExtractDestination(output.scriptPubKey, destination)) + continue; + const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination)); + if (!provider->GetCScript(hash, script)) + continue; + is_from_p2sh = true; + } else { + script = output.scriptPubKey; + } + + COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate); + // 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; + type = Solver(script, return_values_unused); + result.Add(GetOutputType(type, is_from_p2sh), coin); + + // Cache total amount as we go + result.total_amount += output.nValue; // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { - nTotal += wtx.tx->vout[i].nValue; - - if (nTotal >= nMinimumSumAmount) { - return; + if (result.total_amount >= nMinimumSumAmount) { + return result; } } // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { - return; + if (nMaximumCount > 0 && result.Size() >= nMaximumCount) { + return result; } } } + + return result; +} + +CoinsResult AvailableCoinsListUnspent(const CWallet& wallet, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) +{ + return AvailableCoins(wallet, coinControl, /*feerate=*/ std::nullopt, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, /*only_spendable=*/false); } CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl) { LOCK(wallet.cs_wallet); - - CAmount balance = 0; - std::vector<COutput> vCoins; - AvailableCoins(wallet, vCoins, coinControl); - for (const COutput& out : vCoins) { - if (out.fSpendable) { - balance += out.tx->tx->vout[out.i].nValue; - } - } - return balance; + return AvailableCoins(wallet, coinControl, + /*feerate=*/ std::nullopt, + /*nMinimumAmount=*/ 1, + /*nMaximumAmount=*/ MAX_MONEY, + /*nMinimumSumAmount=*/ MAX_MONEY, + /*nMaximumCount=*/ 0 + ).total_amount; } const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) @@ -243,19 +341,22 @@ const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransactio return ptx->vout[n]; } +const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const COutPoint& outpoint) +{ + AssertLockHeld(wallet.cs_wallet); + return FindNonChangeParentOutput(wallet, *wallet.GetWalletTx(outpoint.hash)->tx, outpoint.n); +} + std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) { AssertLockHeld(wallet.cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; - std::vector<COutput> availableCoins; - AvailableCoins(wallet, availableCoins); - - for (const COutput& coin : availableCoins) { + for (COutput& coin : AvailableCoinsListUnspent(wallet).All()) { CTxDestination address; - if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && - ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) { + if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) && + ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } } @@ -268,14 +369,16 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) for (const COutPoint& output : lockedCoins) { auto it = wallet.mapWallet.find(output.hash); if (it != wallet.mapWallet.end()) { - int depth = wallet.GetTxDepthInMainChain(it->second); - if (depth >= 0 && output.n < it->second.tx->vout.size() && - wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter + const auto& wtx = it->second; + int depth = wallet.GetTxDepthInMainChain(wtx); + if (depth >= 0 && output.n < wtx.tx->vout.size() && + wallet.IsMine(wtx.tx->vout[output.n]) == is_mine_filter ) { CTxDestination address; - if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) { + if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) { + const auto out = wtx.tx->vout.at(output.n); result[address].emplace_back( - wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + COutPoint(wtx.GetHash(), output.n), out, depth, CalculateMaximumSignedInputSize(out, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)); } } } @@ -292,15 +395,14 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C // 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; + if (!output.spendable) continue; size_t ancestors, descendants; - wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); + wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants); // Make an OutputGroup containing just this output OutputGroup group{coin_sel_params}; - group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); + group.Insert(output, ancestors, descendants, positive_only); // Check the OutputGroup's eligibility. Only add the eligible ones. if (positive_only && group.GetSelectionAmount() <= 0) continue; @@ -312,18 +414,17 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C // We want to combine COutputs that have the same scriptPubKey into single OutputGroups // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup. // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups. - // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added + // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput is added // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has - // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector. + // OUTPUT_GROUP_MAX_ENTRIES COutputs, a new OutputGroup is added to the end of the vector. std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map; for (const auto& output : outputs) { // Skip outputs we cannot spend - if (!output.fSpendable) continue; + if (!output.spendable) continue; size_t ancestors, descendants; - wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); - CScript spk = input_coin.txout.scriptPubKey; + wallet.chain().getTransactionAncestry(output.outpoint.hash, ancestors, descendants); + CScript spk = output.txout.scriptPubKey; std::vector<OutputGroup>& groups = spk_to_groups_map[spk]; @@ -332,7 +433,7 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C groups.emplace_back(coin_sel_params); } - // Get the last OutputGroup in the vector so that we can add the CInputCoin to it + // Get the last OutputGroup in the vector so that we can add the COutput to it // A pointer is used here so that group can be reassigned later if it is full. OutputGroup* group = &groups.back(); @@ -344,8 +445,8 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C group = &groups.back(); } - // Add the input_coin to group - group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only); + // Add the output to group + group->Insert(output, ancestors, descendants, positive_only); } // Now we go through the entire map and pull out the OutputGroups @@ -370,33 +471,51 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C return groups_out; } -std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, - const CoinSelectionParams& coin_selection_params) +std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const CoinsResult& available_coins, + const CoinSelectionParams& coin_selection_params, bool allow_mixed_output_types) +{ + // Run coin selection on each OutputType and compute the Waste Metric + std::vector<SelectionResult> results; + 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 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; + } + } + // 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) { // 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, 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)}) { - bnb_result->ComputeAndSetWaste(CAmount(0)); 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, 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. - if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee)}) { - 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); } - // We include the minimum final change for SRD as we do want to avoid making really small change. - // KnapsackSolver does not need this because it includes MIN_CHANGE internally. - const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + MIN_FINAL_CHANGE; - if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target)}) { - 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); } @@ -411,82 +530,74 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm return best_result; } -std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) +std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control, const CoinSelectionParams& coin_selection_params) { - std::vector<COutput> vCoins(vAvailableCoins); CAmount value_to_select = nTargetValue; OutputGroup preset_inputs(coin_selection_params); - // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) - { - for (const COutput& out : vCoins) { - if (!out.fSpendable) continue; - /* Set depth, from_me, ancestors, and descendants to 0 or false as these don't matter for preset inputs as no actual selection is being done. - * positive_only is set to false because we want to include all preset inputs, even if they are dust. - */ - preset_inputs.Insert(out.GetInputCoin(), 0, false, 0, 0, false); - } - SelectionResult result(nTargetValue); - result.AddInput(preset_inputs); - if (result.GetSelectedValue() < nTargetValue) return std::nullopt; - return result; - } - // calculate value from preset inputs and store them - std::set<CInputCoin> setPresetCoins; + std::set<COutPoint> preset_coins; std::vector<COutPoint> vPresetInputs; coin_control.ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { int input_bytes = -1; CTxOut txout; - std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash); - if (it != wallet.mapWallet.end()) { - const CWalletTx& wtx = it->second; + auto ptr_wtx = wallet.GetWalletTx(outpoint.hash); + if (ptr_wtx) { // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) { + if (ptr_wtx->tx->vout.size() <= outpoint.n) { return std::nullopt; } - input_bytes = GetTxSpendSize(wallet, wtx, outpoint.n, false); - txout = wtx.tx->vout.at(outpoint.n); + txout = ptr_wtx->tx->vout.at(outpoint.n); + input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control); } else { // The input is external. We did not find the tx in mapWallet. if (!coin_control.GetExternalOutput(outpoint, txout)) { return std::nullopt; } - input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true); } + + 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); } - CInputCoin coin(outpoint, txout, input_bytes); - if (coin.m_input_bytes == -1) { + if (input_bytes == -1) { return std::nullopt; // 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); + + /* 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, coin_selection_params.m_effective_feerate); if (coin_selection_params.m_subtract_fee_outputs) { - value_to_select -= coin.txout.nValue; + value_to_select -= output.txout.nValue; } else { - value_to_select -= coin.effective_value; + value_to_select -= output.GetEffectiveValue(); } - setPresetCoins.insert(coin); - /* Set depth, from_me, ancestors, and descendants to 0 or false as don't matter for preset inputs as no actual selection is being done. + 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. * positive_only is set to false because we want to include all preset inputs, even if they are dust. */ - preset_inputs.Insert(coin, 0, false, 0, 0, false); + preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false); } - // remove preset inputs from vCoins so that Coin Selection doesn't pick them. - for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) - { - if (setPresetCoins.count(it->GetInputCoin())) - it = vCoins.erase(it); - else - ++it; + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + 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.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.Erase(preset_coins); } unsigned int limit_ancestor_count = 0; @@ -498,42 +609,46 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec // 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 && vCoins.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(vCoins.begin(), vCoins.end(), FastRandomContext()); + 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)); + 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. - if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, coin_selection_params)}) return r1; - if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, coin_selection_params)}) return r2; + if (auto r1{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/false)}) return r1; + // Allow mixing only if no solution from any single output type can be found + if (auto r2{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 1, 0), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r2; // 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 (wallet.m_spend_zero_conf_change) { - if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, coin_selection_params)}) return r3; + if (auto r3{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) return r3; if (auto r4{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r4; } if (auto r5{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r5; } // 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 (auto r6{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r6; } // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs @@ -541,7 +656,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (coin_control.m_include_unsafe_inputs) { if (auto r7{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r7; } } @@ -551,7 +666,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (!fRejectLongChains) { if (auto r8{AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), - vCoins, coin_selection_params)}) { + available_coins, coin_selection_params, /*allow_mixed_output_types=*/true)}) { return r8; } } @@ -563,7 +678,10 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec if (!res) return std::nullopt; // Add preset inputs to result - res->AddInput(preset_inputs); + 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; } @@ -583,12 +701,14 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& } /** - * Return a height-based locktime for new transactions (uses the height of the + * Set a height-based locktime for new transactions (uses the height of the * current chain tip unless we are not synced with the current chain */ -static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height) +static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng_fast, + interfaces::Chain& chain, const uint256& block_hash, int block_height) { - uint32_t locktime; + // All inputs must be added by now + assert(!tx.vin.empty()); // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and @@ -610,41 +730,53 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. if (IsCurrentForAntiFeeSniping(chain, block_hash)) { - locktime = block_height; + tx.nLockTime = block_height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, have // better privacy. - if (GetRandInt(10) == 0) - locktime = std::max(0, (int)locktime - GetRandInt(100)); + if (rng_fast.randrange(10) == 0) { + tx.nLockTime = std::max(0, int(tx.nLockTime) - int(rng_fast.randrange(100))); + } } else { // If our chain is lagging behind, we can't discourage fee sniping nor help // the privacy of high-latency transactions. To avoid leaking a potentially // unique "nLockTime fingerprint", set nLockTime to a constant. - locktime = 0; + tx.nLockTime = 0; + } + // Sanity check all values + assert(tx.nLockTime < LOCKTIME_THRESHOLD); // Type must be block height + assert(tx.nLockTime <= uint64_t(block_height)); + for (const auto& in : tx.vin) { + // Can not be FINAL for locktime to work + assert(in.nSequence != CTxIn::SEQUENCE_FINAL); + // May be MAX NONFINAL to disable both BIP68 and BIP125 + if (in.nSequence == CTxIn::MAX_SEQUENCE_NONFINAL) continue; + // May be MAX BIP125 to disable BIP68 and enable BIP125 + if (in.nSequence == MAX_BIP125_RBF_SEQUENCE) continue; + // The wallet does not support any other sequence-use right now. + assert(false); } - assert(locktime < LOCKTIME_THRESHOLD); - return locktime; } -static bool CreateTransactionInternal( +static util::Result<CreatedTransactionResult> CreateTransactionInternal( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, + int change_pos, const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { AssertLockHeld(wallet.cs_wallet); + // out variables, to be packed into returned result structure + CAmount nFeeRet; + int nChangePosInOut = change_pos; + + FastRandomContext rng_fast; CMutableTransaction txNew; // The resulting transaction that we make - txNew.nLockTime = GetLocktimeForNewTransaction(wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); - CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy + CoinSelectionParams coin_selection_params{rng_fast}; // Parameters for coin selection, init with dummy coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; // Set the long term feerate estimate to the wallet's consolidate feerate @@ -665,6 +797,7 @@ static bool CreateTransactionInternal( // Create change script that will be used if we need change CScript scriptChange; + bilingual_str error; // possible error str // coin control: send change to custom address if (!std::get_if<CNoDestination>(&coin_control.destChange)) { @@ -680,11 +813,13 @@ static bool CreateTransactionInternal( // Reserve a new key pair from key pool. If it fails, provide a dummy // destination in case we don't need change. CTxDestination dest; - bilingual_str dest_err; - if (!reservedest.GetReservedDestination(dest, true, dest_err)) { - error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + dest_err; + auto op_dest = reservedest.GetReservedDestination(true); + if (!op_dest) { + error = _("Transaction needs a change address, but we can't generate it.") + Untranslated(" ") + util::ErrorString(op_dest); + } else { + dest = *op_dest; + scriptChange = GetScriptForDestination(dest); } - scriptChange = GetScriptForDestination(dest); // A valid destination implies a change script (and // vice-versa). An empty change script will abort later, if the // change keypool ran out, but change is required. @@ -712,13 +847,11 @@ static bool CreateTransactionInternal( // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly // provided one if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { - error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); - return false; + return util::Error{strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB))}; } if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) { // eventually allow a fallback fee - error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return false; + return util::Error{_("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.")}; } // Calculate the cost of change @@ -729,9 +862,19 @@ static bool 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 = 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 = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size) + coin_selection_params.tx_noinputs_size += GetSizeOfCompactSize(vecSend.size()); // bytes for output count } for (const auto& recipient : vecSend) { @@ -742,10 +885,8 @@ static bool CreateTransactionInternal( coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); } - if (IsDust(txout, wallet.chain().relayDustFee())) - { - error = _("Transaction amount too small"); - return false; + if (IsDust(txout, wallet.chain().relayDustFee())) { + return util::Error{_("Transaction amount too small")}; } txNew.vout.push_back(txout); } @@ -755,41 +896,40 @@ static bool CreateTransactionInternal( CAmount selection_target = recipients_sum + not_input_fees; // Get available coins - std::vector<COutput> vAvailableCoins; - AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + auto available_coins = AvailableCoins(wallet, + &coin_control, + coin_selection_params.m_effective_feerate, + 1, /*nMinimumAmount*/ + MAX_MONEY, /*nMaximumAmount*/ + MAX_MONEY, /*nMinimumSumAmount*/ + 0); /*nMaximumCount*/ // 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, available_coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params); if (!result) { - error = _("Insufficient funds"); - return false; - } - - // 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 = GetRandInt(txNew.vout.size()+1); + return util::Error{_("Insufficient funds")}; } - else if ((unsigned int)nChangePosInOut > txNew.vout.size()) - { - error = _("Transaction change output index out of range"); - return false; + 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<CInputCoin> selected_coins = result->GetShuffledInputVector(); + std::vector<COutput> selected_coins = result->GetShuffledInputVector(); - // Note how the sequence number is set to non-maxint so that - // the nLockTime set above actually works. + // The sequence number is set to non-maxint so that DiscourageFeeSniping + // works. // // BIP125 defines opt-in RBF as any nSequence < maxint-1, so // we use the highest possible value in that range (maxint-2) @@ -800,50 +940,31 @@ static bool CreateTransactionInternal( for (const auto& coin : selected_coins) { txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); } + DiscourageFeeSniping(txNew, rng_fast, wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight()); // Calculate the transaction fee TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control); int nBytes = tx_sizes.vsize; if (nBytes == -1) { - error = _("Missing solving data for estimating transaction size"); - return false; + return util::Error{_("Missing solving data for estimating transaction size")}; } - nFeeRet = 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; - // 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); - } - - // 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) @@ -866,11 +987,10 @@ static bool CreateTransactionInternal( // Error if this output is reduced to be below dust if (IsDust(txout, wallet.chain().relayDustFee())) { if (txout.nValue < 0) { - error = _("The transaction amount is too small to pay the fee"); + return util::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 util::Error{_("The transaction amount is too small to send after the fee has been deducted")}; } - return false; } } ++i; @@ -880,42 +1000,37 @@ static bool CreateTransactionInternal( // Give up if change keypool ran out and change is required if (scriptChange.empty() && nChangePosInOut != -1) { - return false; + return util::Error{error}; } if (sign && !wallet.SignTransaction(txNew)) { - error = _("Signing transaction failed"); - return false; + return util::Error{_("Signing transaction failed")}; } // Return the constructed transaction data. - tx = MakeTransactionRef(std::move(txNew)); + CTransactionRef tx = MakeTransactionRef(std::move(txNew)); // Limit size if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || (!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT)) { - error = _("Transaction too large"); - return false; + return util::Error{_("Transaction too large")}; } if (nFeeRet > wallet.m_default_max_tx_fee) { - error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return false; + return util::Error{TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)}; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!wallet.chain().checkChainLimits(tx)) { - error = _("Transaction has too long of a mempool chain"); - return false; + return util::Error{_("Transaction has too long of a mempool chain")}; } } // Before we return success, we assume any change key will be used to prevent // accidental re-use. reservedest.KeepDestination(); - fee_calc_out = feeCalc; wallet.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, @@ -925,52 +1040,45 @@ static bool CreateTransactionInternal( feeCalc.est.fail.start, feeCalc.est.fail.end, (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); - return true; + return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc); } -bool CreateTransaction( +util::Result<CreatedTransactionResult> CreateTransaction( CWallet& wallet, const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, + int change_pos, const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, bool sign) { if (vecSend.empty()) { - error = _("Transaction must have at least one recipient"); - return false; + return util::Error{_("Transaction must have at least one recipient")}; } if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) { - error = _("Transaction amounts must not be negative"); - return false; + return util::Error{_("Transaction amounts must not be negative")}; } LOCK(wallet.cs_wallet); - int nChangePosIn = nChangePosInOut; - Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) - bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); + auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign); + TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), bool(res), + res ? res->fee : 0, res ? res->change_pos : 0); + if (!res) return res; + const auto& txr_ungrouped = *res; // try with avoidpartialspends unless it's enabled already - if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str()); CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; - CAmount nFeeRet2; - CTransactionRef tx2; - int nChangePosInOut2 = nChangePosIn; - bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - if (CreateTransactionInternal(wallet, vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { - // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee; - wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); - if (use_aps) { - tx = tx2; - nFeeRet = nFeeRet2; - nChangePosInOut = nChangePosInOut2; - } + auto txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign); + // if fee of this alternative one is within the range of the max fee, we use this one + const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false}; + TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(), + txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0); + if (txr_grouped) { + wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", + txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped"); + if (use_aps) return txr_grouped; } } return res; @@ -987,21 +1095,41 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, vecSend.push_back(recipient); } - coinControl.fAllowOtherInputs = true; + // Acquire the locks to prevent races to the new locked unspents between the + // CreateTransaction call and LockCoin calls (when lockUnspents is true). + LOCK(wallet.cs_wallet); + // Fetch specified UTXOs from the UTXO set to get the scriptPubKeys and values of the outputs being selected + // and to match with the given solving_data. Only used for non-wallet outputs. + std::map<COutPoint, Coin> coins; for (const CTxIn& txin : tx.vin) { - coinControl.Select(txin.prevout); + coins[txin.prevout]; // Create empty map entry keyed by prevout. } + wallet.chain().findCoins(coins); - // Acquire the locks to prevent races to the new locked unspents between the - // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK(wallet.cs_wallet); + for (const CTxIn& txin : tx.vin) { + const auto& outPoint = txin.prevout; + 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); + } + } - CTransactionRef tx_new; - FeeCalculation fee_calc_out; - if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { + auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false); + if (!res) { + error = util::ErrorString(res); return false; } + const auto& txr = *res; + CTransactionRef tx_new = txr.tx; + nFeeRet = txr.fee; + nChangePosInOut = txr.change_pos; if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); |