mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-20 19:58:57 +02:00
wallet: return accurate error messages from Coin Selection
and not the general "Insufficient funds" when the wallet actually have funds. Two new error messages: 1) If the selection result exceeds the maximum transaction weight, we now will return: "The inputs size exceeds the maximum weight". 2) If the user preselected inputs and disallowed the automatic coin selection process (no other inputs are allowed), we now will return: "The preselected coins total amount does not cover the transaction target".
This commit is contained in:
@@ -510,15 +510,20 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||
return groups_out;
|
||||
}
|
||||
|
||||
// Returns true if the result contains an error and the message is not empty
|
||||
static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
|
||||
|
||||
util::Result<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);
|
||||
}
|
||||
auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)};
|
||||
// If any specific error message appears here, then something particularly wrong happened.
|
||||
if (HasErrorMsg(result)) return result; // So let's return the specific error.
|
||||
// Append the favorable result.
|
||||
if (result) 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
|
||||
@@ -528,9 +533,7 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
|
||||
// over all available coins, which would allow mixing.
|
||||
// If TypesCount() <= 1, there is nothing to mix.
|
||||
if (allow_mixed_output_types && available_coins.TypesCount() > 1) {
|
||||
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params)}) {
|
||||
return result;
|
||||
}
|
||||
return ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params);
|
||||
}
|
||||
// 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
|
||||
@@ -571,7 +574,8 @@ util::Result<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const
|
||||
});
|
||||
|
||||
if (eligible_results.empty()) {
|
||||
return util::Error();
|
||||
return util::Error{_("The inputs size exceeds the maximum weight. "
|
||||
"Please try sending a smaller amount or manually consolidating your wallet's UTXOs")};
|
||||
}
|
||||
|
||||
// Choose the result with the least waste
|
||||
@@ -588,7 +592,10 @@ util::Result<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& av
|
||||
CAmount selection_target = nTargetValue - pre_set_inputs.total_amount;
|
||||
|
||||
// Return if automatic coin selection is disabled, and we don't cover the selection target
|
||||
if (!coin_control.m_allow_other_inputs && selection_target > 0) return util::Error();
|
||||
if (!coin_control.m_allow_other_inputs && selection_target > 0) {
|
||||
return util::Error{_("The preselected coins total amount does not cover the transaction target. "
|
||||
"Please allow other inputs to be automatically selected or include more coins manually")};
|
||||
}
|
||||
|
||||
// Return if we can cover the target only with the preset inputs
|
||||
if (selection_target <= 0) {
|
||||
@@ -681,13 +688,23 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
|
||||
}
|
||||
}
|
||||
|
||||
// Walk-through the filters until the solution gets found
|
||||
// Walk-through the filters until the solution gets found.
|
||||
// If no solution is found, return the first detailed error (if any).
|
||||
// future: add "error level" so the worst one can be picked instead.
|
||||
std::vector<util::Result<SelectionResult>> res_detailed_errors;
|
||||
for (const auto& select_filter : ordered_filters) {
|
||||
if (auto res{AttemptSelection(wallet, value_to_select, select_filter.filter, available_coins,
|
||||
coin_selection_params, select_filter.allow_mixed_output_types)}) return res;
|
||||
coin_selection_params, select_filter.allow_mixed_output_types)}) {
|
||||
return res; // result found
|
||||
} else {
|
||||
// If any specific error message appears here, then something particularly wrong might have happened.
|
||||
// Save the error and continue the selection process. So if no solutions gets found, we can return
|
||||
// the detailed error to the upper layers.
|
||||
if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res);
|
||||
}
|
||||
}
|
||||
// Coin Selection failed.
|
||||
return util::Result<SelectionResult>(util::Error());
|
||||
return res_detailed_errors.empty() ? util::Result<SelectionResult>(util::Error()) : res_detailed_errors.front();
|
||||
}();
|
||||
|
||||
return res;
|
||||
@@ -916,7 +933,9 @@ static util::Result<CreatedTransactionResult> CreateTransactionInternal(
|
||||
// Choose coins to use
|
||||
auto select_coins_res = SelectCoins(wallet, available_coins, preset_inputs, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
|
||||
if (!select_coins_res) {
|
||||
return util::Error{_("Insufficient funds")};
|
||||
// 'SelectCoins' either returns a specific error message or, if empty, means a general "Insufficient funds".
|
||||
const bilingual_str& err = util::ErrorString(select_coins_res);
|
||||
return util::Error{err.empty() ?_("Insufficient funds") : err};
|
||||
}
|
||||
const SelectionResult& result = *select_coins_res;
|
||||
TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result.GetAlgo()).c_str(), result.GetTarget(), result.GetWaste(), result.GetSelectedValue());
|
||||
|
||||
@@ -119,7 +119,9 @@ std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<C
|
||||
* param@[in] coin_selection_params Parameters for the coin selection
|
||||
* param@[in] allow_mixed_output_types Relax restriction that SelectionResults must be of the same OutputType
|
||||
* returns If successful, a SelectionResult containing the input set
|
||||
* If failed, a nullopt
|
||||
* If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
|
||||
* or (2) an specific error message if there was something particularly wrong (e.g. a selection
|
||||
* result that surpassed the tx max weight size).
|
||||
*/
|
||||
util::Result<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);
|
||||
@@ -135,7 +137,9 @@ util::Result<SelectionResult> AttemptSelection(const CWallet& wallet, const CAmo
|
||||
* param@[in] available_coins The struct of coins, organized by OutputType, available for selection prior to filtering
|
||||
* param@[in] coin_selection_params Parameters for the coin selection
|
||||
* returns If successful, a SelectionResult containing the input set
|
||||
* If failed, a nullopt
|
||||
* If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
|
||||
* or (2) an specific error message if there was something particularly wrong (e.g. a selection
|
||||
* result that surpassed the tx max weight size).
|
||||
*/
|
||||
util::Result<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
|
||||
const CoinSelectionParams& coin_selection_params);
|
||||
@@ -175,7 +179,9 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
|
||||
* param@[in] coin_selection_params Parameters for this coin selection such as feerates, whether to avoid partial spends,
|
||||
* and whether to subtract the fee from the outputs.
|
||||
* returns If successful, a SelectionResult containing the selected coins
|
||||
* If failed, a nullopt.
|
||||
* If failed, returns (1) an empty error message if the target was not reached (general "Insufficient funds")
|
||||
* or (2) an specific error message if there was something particularly wrong (e.g. a selection
|
||||
* result that surpassed the tx max weight size).
|
||||
*/
|
||||
util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, CoinsResult& available_coins, const CAmount& nTargetValue, const CCoinControl& coin_control,
|
||||
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
|
||||
|
||||
Reference in New Issue
Block a user