Skip to content

Commit acf0119

Browse files
committed
wallet: return error msg for too-long-mempool-chain failure
We currently return "Insufficient funds" which doesn't really describe what went wrong; the tx creation failed because of a long-mempool-chain, not because of a lack of funds. Also, return early from Coin Selection if the sum of the discarded coins decreases the available balance below the target amount.
1 parent 86bacd7 commit acf0119

File tree

1 file changed

+42
-4
lines changed

1 file changed

+42
-4
lines changed

src/wallet/spend.cpp

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
407407
FilteredOutputGroups GroupOutputs(const CWallet& wallet,
408408
const CoinsResult& coins,
409409
const CoinSelectionParams& coin_sel_params,
410-
const std::vector<SelectionFilter>& filters)
410+
const std::vector<SelectionFilter>& filters,
411+
std::vector<OutputGroup>& ret_discarded_groups)
411412
{
412413
FilteredOutputGroups filtered_groups;
413414

@@ -427,11 +428,14 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
427428
group.Insert(std::make_shared<COutput>(output), ancestors, descendants);
428429

429430
// Each filter maps to a different set of groups
431+
bool accepted = false;
430432
for (const auto& sel_filter : filters) {
431433
const auto& filter = sel_filter.filter;
432434
if (!group.EligibleForSpending(filter)) continue;
433435
filtered_groups[filter].Push(group, type, /*insert_positive=*/true, /*insert_mixed=*/true);
436+
accepted = true;
434437
}
438+
if (!accepted) ret_discarded_groups.emplace_back(group);
435439
}
436440
}
437441
return filtered_groups;
@@ -497,6 +501,7 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
497501
const OutputGroup& group = *group_it;
498502

499503
// Each filter maps to a different set of groups
504+
bool accepted = false;
500505
for (const auto& sel_filter : filters) {
501506
const auto& filter = sel_filter.filter;
502507
if (!group.EligibleForSpending(filter)) continue;
@@ -509,7 +514,9 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
509514
OutputType type = script.second;
510515
// Either insert the group into the positive-only groups or the mixed ones.
511516
filtered_groups[filter].Push(group, type, positive_only, /*insert_mixed=*/!positive_only);
517+
accepted = true;
512518
}
519+
if (!accepted) ret_discarded_groups.emplace_back(group);
513520
}
514521
}
515522
};
@@ -520,6 +527,15 @@ FilteredOutputGroups GroupOutputs(const CWallet& wallet,
520527
return filtered_groups;
521528
}
522529

530+
FilteredOutputGroups GroupOutputs(const CWallet& wallet,
531+
const CoinsResult& coins,
532+
const CoinSelectionParams& params,
533+
const std::vector<SelectionFilter>& filters)
534+
{
535+
std::vector<OutputGroup> unused;
536+
return GroupOutputs(wallet, coins, params, filters, unused);
537+
}
538+
523539
// Returns true if the result contains an error and the message is not empty
524540
static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
525541

@@ -692,7 +708,24 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
692708
}
693709

694710
// Group outputs and map them by coin eligibility filter
695-
FilteredOutputGroups filtered_groups = GroupOutputs(wallet, available_coins, coin_selection_params, ordered_filters);
711+
std::vector<OutputGroup> discarded_groups;
712+
FilteredOutputGroups filtered_groups = GroupOutputs(wallet, available_coins, coin_selection_params, ordered_filters, discarded_groups);
713+
714+
// Check if we still have enough balance after applying filters (some coins might be discarded)
715+
CAmount total_discarded = 0;
716+
CAmount total_unconf_long_chain = 0;
717+
for (const auto& group : discarded_groups) {
718+
total_discarded += group.GetSelectionAmount();
719+
if (group.m_ancestors >= max_ancestors || group.m_descendants >= max_descendants) total_unconf_long_chain += group.GetSelectionAmount();
720+
}
721+
722+
if (CAmount total_amount = available_coins.GetTotalAmount() - total_discarded < value_to_select) {
723+
// Special case, too-long-mempool cluster.
724+
if (total_amount + total_unconf_long_chain > value_to_select) {
725+
return util::Result<SelectionResult>({_("Unconfirmed UTXOs are available, but spending them creates a chain of transactions that will be rejected by the mempool")});
726+
}
727+
return util::Result<SelectionResult>(util::Error()); // General "Insufficient Funds"
728+
}
696729

697730
// Walk-through the filters until the solution gets found.
698731
// If no solution is found, return the first detailed error (if any).
@@ -711,8 +744,13 @@ util::Result<SelectionResult> AutomaticCoinSelection(const CWallet& wallet, Coin
711744
if (HasErrorMsg(res)) res_detailed_errors.emplace_back(res);
712745
}
713746
}
714-
// Coin Selection failed.
715-
return res_detailed_errors.empty() ? util::Result<SelectionResult>(util::Error()) : res_detailed_errors.front();
747+
748+
// Return right away if we have a detailed error
749+
if (!res_detailed_errors.empty()) return res_detailed_errors.front();
750+
751+
752+
// General "Insufficient Funds"
753+
return util::Result<SelectionResult>(util::Error());
716754
}();
717755

718756
return res;

0 commit comments

Comments
 (0)