24 return util::Error{
_(
"The inputs size exceeds the maximum weight. "
25 "Please try sending a smaller amount or manually consolidating your wallet's UTXOs")};
94 int max_selection_weight)
98 std::vector<size_t> curr_selection;
99 int curr_selection_weight = 0;
102 CAmount curr_available_value = 0;
106 assert(utxo.GetSelectionAmount() > 0);
107 curr_available_value += utxo.GetSelectionAmount();
109 if (curr_available_value < selection_target) {
114 std::sort(utxo_pool.begin(), utxo_pool.end(),
descending);
117 std::vector<size_t> best_selection;
120 bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee;
121 bool max_tx_weight_exceeded =
false;
124 for (
size_t curr_try = 0, utxo_pool_index = 0; curr_try <
TOTAL_TRIES; ++curr_try, ++utxo_pool_index) {
126 bool backtrack =
false;
127 if (curr_value + curr_available_value < selection_target ||
128 curr_value > selection_target + cost_of_change ||
129 (curr_waste > best_waste && is_feerate_high)) {
131 }
else if (curr_selection_weight > max_selection_weight) {
132 max_tx_weight_exceeded =
true;
134 }
else if (curr_value >= selection_target) {
135 curr_waste += (curr_value - selection_target);
140 if (curr_waste <= best_waste) {
141 best_selection = curr_selection;
142 best_waste = curr_waste;
144 curr_waste -= (curr_value - selection_target);
149 if (curr_selection.empty()) {
154 for (--utxo_pool_index; utxo_pool_index > curr_selection.back(); --utxo_pool_index) {
155 curr_available_value += utxo_pool.at(utxo_pool_index).GetSelectionAmount();
159 assert(utxo_pool_index == curr_selection.back());
163 curr_selection_weight -= utxo.
m_weight;
164 curr_selection.pop_back();
171 if (curr_selection.empty() ||
173 (utxo_pool_index - 1) == curr_selection.back() ||
176 utxo.
GetSelectionAmount() != utxo_pool.at(utxo_pool_index - 1).GetSelectionAmount() ||
177 utxo.
fee != utxo_pool.at(utxo_pool_index - 1).fee)
180 curr_selection.push_back(utxo_pool_index);
183 curr_selection_weight += utxo.
m_weight;
189 if (best_selection.empty()) {
194 for (
const size_t& i : best_selection) {
329 std::vector<CAmount> lookahead(utxo_pool.size());
331 std::vector<int> min_tail_weight(utxo_pool.size());
335 int min_group_weight = std::numeric_limits<int>::max();
336 for (
size_t i = 0; i < utxo_pool.size(); ++i) {
337 size_t index = utxo_pool.size() - 1 - i;
338 lookahead[index] = total_available;
339 min_tail_weight[index] = min_group_weight;
341 Assume(utxo_pool[index].GetSelectionAmount() > 0);
342 total_available += utxo_pool[index].GetSelectionAmount();
343 min_group_weight = std::min(min_group_weight, utxo_pool[index].m_weight);
346 const CAmount total_target = selection_target + change_target;
347 if (total_available < total_target) {
353 std::vector<size_t> curr_selection;
354 std::vector<size_t> best_selection;
362 int best_selection_weight = max_selection_weight;
365 bool max_tx_weight_exceeded =
false;
368 size_t next_utxo = 0;
413 auto deselect_last = [&]() {
414 OutputGroup& utxo = utxo_pool[curr_selection.back()];
417 curr_selection.pop_back();
421 bool is_done =
false;
424 bool should_shift{
false}, should_cut{
false};
429 curr_selection.push_back(next_utxo);
434 auto curr_tail = curr_selection.back();
435 if (curr_amount + lookahead[curr_tail] < total_target) {
438 }
else if (curr_weight > best_selection_weight) {
440 if (curr_weight > max_selection_weight) max_tx_weight_exceeded =
true;
443 if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
448 }
else if (curr_amount >= total_target) {
451 if (curr_weight < best_selection_weight || (curr_weight == best_selection_weight && curr_amount < best_selection_amount)) {
453 best_selection = curr_selection;
454 best_selection_weight = curr_weight;
455 best_selection_amount = curr_amount;
457 }
else if (!best_selection.empty() && curr_weight + int64_t{min_tail_weight[curr_tail]} * ((total_target - curr_amount + utxo_pool[curr_tail].GetSelectionAmount() - 1) / utxo_pool[curr_tail].GetSelectionAmount()) > best_selection_weight) {
459 if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
472 if (next_utxo == utxo_pool.size()) {
485 while (should_shift) {
487 if (curr_selection.empty()) {
493 next_utxo = curr_selection.back() + 1;
495 should_shift =
false;
502 while (utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount()) {
503 if (next_utxo >= utxo_pool.size() - 1) {
516 if (best_selection.empty()) {
520 for (
const size_t& i : best_selection) {
537 int max_selection_weight)
548 std::vector<size_t> indexes;
549 indexes.resize(utxo_pool.size());
550 std::iota(indexes.begin(), indexes.end(), 0);
551 std::shuffle(indexes.begin(), indexes.end(), rng);
553 CAmount selected_eff_value = 0;
555 bool max_tx_weight_exceeded =
false;
556 for (
const size_t i : indexes) {
558 Assume(group.GetSelectionAmount() > 0);
562 selected_eff_value += group.GetSelectionAmount();
563 weight += group.m_weight;
567 if (weight > max_selection_weight) {
568 max_tx_weight_exceeded =
true;
574 }
while (!heap.empty() && weight > max_selection_weight);
578 if (selected_eff_value >= target_value) {
580 while (!heap.empty()) {
604 std::vector<char>& vfBest,
CAmount& nBest,
int max_selection_weight,
int iterations = 1000)
606 std::vector<char> vfIncluded;
609 vfBest.assign(groups.size(),
true);
612 for (
int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
614 vfIncluded.assign(groups.size(),
false);
616 int selected_coins_weight{0};
617 bool fReachedTarget =
false;
618 for (
int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
620 for (
unsigned int i = 0; i < groups.size(); i++)
628 if (nPass == 0 ? insecure_rand.
randbool() : !vfIncluded[i])
630 nTotal += groups[i].GetSelectionAmount();
631 selected_coins_weight += groups[i].m_weight;
632 vfIncluded[i] =
true;
633 if (nTotal >= nTargetValue && selected_coins_weight <= max_selection_weight) {
634 fReachedTarget =
true;
642 nTotal -= groups[i].GetSelectionAmount();
643 selected_coins_weight -= groups[i].m_weight;
644 vfIncluded[i] =
false;
657 bool max_weight_exceeded{
false};
659 std::optional<OutputGroup> lowest_larger;
662 std::vector<OutputGroup> applicable_groups;
665 std::shuffle(groups.begin(), groups.end(), rng);
668 if (group.m_weight > max_selection_weight) {
669 max_weight_exceeded =
true;
672 if (group.GetSelectionAmount() == nTargetValue) {
675 }
else if (group.GetSelectionAmount() < nTargetValue + change_target) {
676 applicable_groups.push_back(group);
677 nTotalLower += group.GetSelectionAmount();
678 }
else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
679 lowest_larger = group;
683 if (nTotalLower == nTargetValue) {
684 for (
const auto& group : applicable_groups) {
687 if (result.
GetWeight() <= max_selection_weight)
return result;
688 else max_weight_exceeded =
true;
694 if (nTotalLower < nTargetValue) {
695 if (!lowest_larger) {
704 std::sort(applicable_groups.begin(), applicable_groups.end(),
descending);
705 std::vector<char> vfBest;
708 ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest, max_selection_weight);
709 if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target) {
710 ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest, max_selection_weight);
716 ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
719 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
721 result.
AddInput(applicable_groups[i]);
726 if (result.
GetWeight() > max_selection_weight) {
736 std::string log_message{
"Coin selection best subset: "};
737 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
759 fee += coin.GetFee();
761 coin.long_term_fee = coin.input_bytes < 0 ? 0 :
m_long_term_feerate.GetFee(coin.input_bytes);
777 if (output->input_bytes > 0) {
796 if (group.m_outputs.empty())
return;
799 if (insert_positive && group.GetSelectionAmount() > 0) {
801 all_groups.positive_group.emplace_back(group);
815 const auto upper_bound = std::min(payment_value * 2,
CHANGE_UPPER);
835 const COutput& coin = *coin_ptr;
841 if (
GetChange(min_viable_change, change_fee)) {
844 waste += change_cost;
849 waste += selected_effective_value -
m_target;
917 m_weight += std::accumulate(inputs.cbegin(), inputs.cend(), 0, [](
int sum,
const auto& coin) {
918 return sum + std::max(coin->input_bytes, 0) * WITNESS_SCALE_FACTOR;
987 if (change < min_viable_change) {
static constexpr CAmount MAX_MONEY
No amount larger than this (in satoshi) is valid.
int64_t CAmount
Amount in satoshis (Can be negative).
#define Assert(val)
Identity function.
#define Assume(val)
Assume is the identity function.
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
bool randbool() noexcept
Generate a random boolean.
int operator()(const OutputGroup &group1, const OutputGroup &group2) const
static const int WITNESS_SCALE_FACTOR
#define LogDebug(category,...)
static bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
Return true if log accepts specified category, at the specified level.
std::string FormatMoney(const CAmount n)
Money parsing/formatting utilities.
static constexpr CAmount CHANGE_UPPER
upper bound for randomly-chosen target change amount
static constexpr CAmount CHANGE_LOWER
lower bound for randomly-chosen target change amount
util::Result< SelectionResult > SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, const CAmount &cost_of_change, int max_selection_weight)
util::Result< SelectionResult > CoinGrinder(std::vector< OutputGroup > &utxo_pool, const CAmount &selection_target, CAmount change_target, int max_selection_weight)
util::Result< SelectionResult > KnapsackSolver(std::vector< OutputGroup > &groups, const CAmount &nTargetValue, CAmount change_target, FastRandomContext &rng, int max_selection_weight)
static util::Result< SelectionResult > ErrorMaxWeightExceeded()
struct wallet::@357212275161224214143020235362156164047150316212 descending_effval_weight
CAmount GenerateChangeTarget(const CAmount payment_value, const CAmount change_fee, FastRandomContext &rng)
Choose a random change target for each transaction to make it harder to fingerprint the Core wallet b...
std::string GetAlgorithmName(const SelectionAlgorithm algo)
std::set< std::shared_ptr< COutput >, OutputPtrComparator > OutputSet
static const size_t TOTAL_TRIES
struct wallet::@046330327236245075270357046167147267377303105277 descending
static void ApproximateBestSubset(FastRandomContext &insecure_rand, const std::vector< OutputGroup > &groups, const CAmount &nTotalLower, const CAmount &nTargetValue, std::vector< char > &vfBest, CAmount &nBest, int max_selection_weight, int iterations=1000)
Find a subset of the OutputGroups that is at least as large as, but as close as possible to,...
util::Result< SelectionResult > SelectCoinsSRD(const std::vector< OutputGroup > &utxo_pool, CAmount target_value, CAmount change_fee, FastRandomContext &rng, int max_selection_weight)
Select coins by Single Random Draw (SRD).
A group of UTXOs paid to the same output script.
CAmount GetSelectionAmount() const
int m_weight
Total weight of the UTXOs in this group.
CAmount fee
The fee to spend these UTXOs at the effective feerate.
CAmount long_term_fee
The fee to spend these UTXOs at the long term feerate.
A UTXO under consideration for use in funding a new transaction.
CAmount long_term_fee
The fee required to spend this output at the consolidation feerate.
COutPoint outpoint
The outpoint identifying this UTXO.
int depth
Depth in block chain.
std::string ToString() const
CTxOut txout
The output itself.
Parameters for filtering which OutputGroups we may use in coin selection.
const uint64_t max_ancestors
Maximum number of unconfirmed ancestors aggregated across all UTXOs in an OutputGroup.
const int conf_theirs
Minimum number of confirmations for outputs received from a different wallet.
const uint64_t max_cluster_count
Maximum cluster count that a single UTXO in the OutputGroup may have.
const int conf_mine
Minimum number of confirmations for outputs that we sent to ourselves.
std::vector< OutputGroup > positive_group
std::vector< OutputGroup > mixed_group
A group of UTXOs paid to the same output script.
CFeeRate m_long_term_feerate
The feerate for spending a created change output eventually (i.e.
bool m_from_me
Whether the UTXOs were sent by the wallet to itself.
CAmount m_value
The total value of the UTXOs in sum.
bool m_subtract_fee_outputs
Indicate that we are subtracting the fee from outputs.
size_t m_max_cluster_count
The maximum cluster count of a single UTXO in this output group.
void Insert(const std::shared_ptr< COutput > &output, size_t ancestors, size_t cluster_count)
CAmount GetSelectionAmount() const
int m_depth
The minimum number of confirmations the UTXOs in the group have.
int m_weight
Total weight of the UTXOs in this group.
bool EligibleForSpending(const CoinEligibilityFilter &eligibility_filter) const
CAmount effective_value
The value of the UTXOs after deducting the cost of spending them at the effective feerate.
size_t m_ancestors
The aggregated count of unconfirmed ancestors of all UTXOs in this group.
CAmount fee
The fee to spend these UTXOs at the effective feerate.
CAmount long_term_fee
The fee to spend these UTXOs at the long term feerate.
std::vector< std::shared_ptr< COutput > > m_outputs
The list of UTXOs contained in this output group.
void Push(const OutputGroup &group, OutputType type, bool insert_positive, bool insert_mixed)
std::map< OutputType, Groups > groups_by_type
int m_weight
Total weight of the selected inputs.
bool operator<(SelectionResult other) const
size_t m_selections_evaluated
The count of selections that were evaluated by this coin selection attempt.
void AddInputs(const OutputSet &inputs, bool subtract_fee_outputs)
CAmount bump_fee_group_discount
How much individual inputs overestimated the bump fees for the shared ancestry.
CAmount GetChange(CAmount min_viable_change, CAmount change_fee) const
Get the amount for the change output after paying needed fees.
void Merge(const SelectionResult &other)
Combines the.
OutputSet m_selected_inputs
Set of inputs selected by the algorithm to use in the transaction.
size_t GetSelectionsEvaluated() const
Get selections_evaluated.
SelectionAlgorithm m_algo
The algorithm used to produce this result.
void SetBumpFeeDiscount(CAmount discount)
How much individual inputs overestimated the bump fees for shared ancestries.
bool GetAlgoCompleted() const
Get m_algo_completed.
void RecalculateWaste(CAmount min_viable_change, CAmount change_cost, CAmount change_fee)
Calculates and stores the waste for this result given the cost of change and the opportunity cost of ...
void AddInput(const OutputGroup &group)
CAmount GetSelectedEffectiveValue() const
CAmount GetTotalBumpFees() const
bool m_algo_completed
False if algorithm was cut short by hitting limit of attempts and solution is non-optimal.
const OutputSet & GetInputSet() const
Get m_selected_inputs.
CAmount m_target
The target the algorithm selected for.
void InsertInputs(const T &inputs)
void SetAlgoCompleted(bool algo_completed)
Tracks that algorithm was able to exhaustively search the entire combination space before hitting lim...
SelectionResult(const CAmount target, SelectionAlgorithm algo)
CAmount GetSelectedValue() const
Get the sum of the input values.
std::optional< CAmount > m_waste
The computed waste.
bool m_use_effective
Whether the input values for calculations should be the effective value (true) or normal value (false...
void SetSelectionsEvaluated(size_t attempts)
Record the number of selections that were evaluated.
std::vector< std::shared_ptr< COutput > > GetShuffledInputVector() const
Get the vector of COutputs that will be used to fill in a CTransaction's vin.
consteval auto _(util::TranslatedLiteral str)