diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index a8ecce119af..60af9f9c669 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -407,7 +407,8 @@ std::map> ListCoins(const CWallet& wallet) FilteredOutputGroups GroupOutputs(const CWallet& wallet, const CoinsResult& coins, const CoinSelectionParams& coin_sel_params, - const std::vector& filters) + const std::vector& filters, + std::vector& ret_discarded_groups) { FilteredOutputGroups filtered_groups; @@ -427,11 +428,14 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet, group.Insert(std::make_shared(output), ancestors, descendants); // Each filter maps to a different set of groups + bool accepted = false; for (const auto& sel_filter : filters) { const auto& filter = sel_filter.filter; if (!group.EligibleForSpending(filter)) continue; filtered_groups[filter].Push(group, type, /*insert_positive=*/true, /*insert_mixed=*/true); + accepted = true; } + if (!accepted) ret_discarded_groups.emplace_back(group); } } return filtered_groups; @@ -497,6 +501,7 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet, const OutputGroup& group = *group_it; // Each filter maps to a different set of groups + bool accepted = false; for (const auto& sel_filter : filters) { const auto& filter = sel_filter.filter; if (!group.EligibleForSpending(filter)) continue; @@ -509,7 +514,9 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet, OutputType type = script.second; // Either insert the group into the positive-only groups or the mixed ones. filtered_groups[filter].Push(group, type, positive_only, /*insert_mixed=*/!positive_only); + accepted = true; } + if (!accepted) ret_discarded_groups.emplace_back(group); } } }; @@ -520,6 +527,15 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet, return filtered_groups; } +FilteredOutputGroups GroupOutputs(const CWallet& wallet, + const CoinsResult& coins, + const CoinSelectionParams& params, + const std::vector& filters) +{ + std::vector unused; + return GroupOutputs(wallet, coins, params, filters, unused); +} + // Returns true if the result contains an error and the message is not empty static bool HasErrorMsg(const util::Result& res) { return !util::ErrorString(res).empty(); } @@ -692,7 +708,24 @@ util::Result AutomaticCoinSelection(const CWallet& wallet, Coin } // Group outputs and map them by coin eligibility filter - FilteredOutputGroups filtered_groups = GroupOutputs(wallet, available_coins, coin_selection_params, ordered_filters); + std::vector discarded_groups; + FilteredOutputGroups filtered_groups = GroupOutputs(wallet, available_coins, coin_selection_params, ordered_filters, discarded_groups); + + // Check if we still have enough balance after applying filters (some coins might be discarded) + CAmount total_discarded = 0; + CAmount total_unconf_long_chain = 0; + for (const auto& group : discarded_groups) { + total_discarded += group.GetSelectionAmount(); + if (group.m_ancestors >= max_ancestors || group.m_descendants >= max_descendants) total_unconf_long_chain += group.GetSelectionAmount(); + } + + if (CAmount total_amount = available_coins.GetTotalAmount() - total_discarded < value_to_select) { + // Special case, too-long-mempool cluster. + if (total_amount + total_unconf_long_chain > value_to_select) { + return util::Result({_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")}); + } + return util::Result(util::Error()); // General "Insufficient Funds" + } // Walk-through the filters until the solution gets found. // If no solution is found, return the first detailed error (if any). @@ -711,8 +744,13 @@ util::Result AutomaticCoinSelection(const CWallet& wallet, Coin if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res); } } - // Coin Selection failed. - return res_detailed_errors.empty() ? util::Result(util::Error()) : res_detailed_errors.front(); + + // Return right away if we have a detailed error + if (!res_detailed_errors.empty()) return res_detailed_errors.front(); + + + // General "Insufficient Funds" + return util::Result(util::Error()); }(); return res;