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")};
116 int max_selection_weight)
118 std::sort(utxo_pool.begin(), utxo_pool.end(),
descending);
120 std::vector<CAmount> lookahead(utxo_pool.size());
124 for (
int index =
static_cast<int>(utxo_pool.size()) - 1; index >= 0; --index) {
125 lookahead[index] = total_available;
127 Assume(utxo_pool[index].GetSelectionAmount() > 0);
128 total_available += utxo_pool[index].GetSelectionAmount();
131 if (total_available < selection_target) {
138 std::vector<size_t> curr_selection;
139 std::vector<size_t> best_selection;
145 CAmount curr_selection_waste = 0;
152 bool max_tx_weight_exceeded =
false;
155 size_t next_utxo = 0;
157 auto deselect_last = [&]() {
158 OutputGroup& utxo = utxo_pool[curr_selection.back()];
162 curr_selection.pop_back();
167 bool is_done =
false;
169 bool is_feerate_high = utxo_pool.at(0).fee > utxo_pool.at(0).long_term_fee;
171 bool should_shift{
false}, should_cut{
false};
177 curr_selection.push_back(next_utxo);
182 if (curr_amount + lookahead[curr_selection.back()] < selection_target) {
185 }
else if (curr_weight > max_selection_weight) {
187 max_tx_weight_exceeded =
true;
189 }
else if (curr_amount > selection_target + cost_of_change) {
192 }
else if (is_feerate_high && curr_selection_waste > best_waste) {
199 }
else if (curr_amount >= selection_target) {
204 CAmount curr_excess = curr_amount - selection_target;
205 CAmount curr_waste = curr_selection_waste + curr_excess;
206 if (curr_waste <= best_waste) {
208 best_selection = curr_selection;
209 best_waste = curr_waste;
219 if (next_utxo == utxo_pool.size()) {
232 while (should_shift) {
233 if (curr_selection.empty()) {
240 next_utxo = curr_selection.back() + 1;
242 should_shift =
false;
250 Assume(next_utxo < utxo_pool.size());
251 while (utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount()) {
252 if (next_utxo >= utxo_pool.size() - 1) {
265 if (best_selection.empty()) {
269 for (
const size_t& i : best_selection) {
403 std::vector<CAmount> lookahead(utxo_pool.size());
405 std::vector<int> min_tail_weight(utxo_pool.size());
409 int min_group_weight = std::numeric_limits<int>::max();
410 for (
size_t i = 0; i < utxo_pool.size(); ++i) {
411 size_t index = utxo_pool.size() - 1 - i;
412 lookahead[index] = total_available;
413 min_tail_weight[index] = min_group_weight;
415 Assume(utxo_pool[index].GetSelectionAmount() > 0);
416 total_available += utxo_pool[index].GetSelectionAmount();
417 min_group_weight = std::min(min_group_weight, utxo_pool[index].m_weight);
420 const CAmount total_target = selection_target + change_target;
421 if (total_available < total_target) {
427 std::vector<size_t> curr_selection;
428 std::vector<size_t> best_selection;
436 int best_selection_weight = max_selection_weight;
439 bool max_tx_weight_exceeded =
false;
442 size_t next_utxo = 0;
487 auto deselect_last = [&]() {
488 OutputGroup& utxo = utxo_pool[curr_selection.back()];
491 curr_selection.pop_back();
495 bool is_done =
false;
498 bool should_shift{
false}, should_cut{
false};
503 curr_selection.push_back(next_utxo);
508 auto curr_tail = curr_selection.back();
509 if (curr_amount + lookahead[curr_tail] < total_target) {
512 }
else if (curr_weight > best_selection_weight) {
514 if (curr_weight > max_selection_weight) max_tx_weight_exceeded =
true;
517 if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
522 }
else if (curr_amount >= total_target) {
525 if (curr_weight < best_selection_weight || (curr_weight == best_selection_weight && curr_amount < best_selection_amount)) {
527 best_selection = curr_selection;
528 best_selection_weight = curr_weight;
529 best_selection_amount = curr_amount;
531 }
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) {
533 if (utxo_pool[curr_tail].m_weight <= min_tail_weight[curr_tail]) {
546 if (next_utxo == utxo_pool.size()) {
559 while (should_shift) {
561 if (curr_selection.empty()) {
567 next_utxo = curr_selection.back() + 1;
569 should_shift =
false;
576 while (utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount()) {
577 if (next_utxo >= utxo_pool.size() - 1) {
590 if (best_selection.empty()) {
594 for (
const size_t& i : best_selection) {
611 int max_selection_weight)
622 std::vector<size_t> indexes;
623 indexes.resize(utxo_pool.size());
624 std::iota(indexes.begin(), indexes.end(), 0);
625 std::shuffle(indexes.begin(), indexes.end(),
rng);
627 CAmount selected_eff_value = 0;
629 bool max_tx_weight_exceeded =
false;
630 for (
const size_t i : indexes) {
632 Assume(group.GetSelectionAmount() > 0);
636 selected_eff_value += group.GetSelectionAmount();
637 weight += group.m_weight;
641 if (weight > max_selection_weight) {
642 max_tx_weight_exceeded =
true;
648 }
while (!heap.empty() && weight > max_selection_weight);
652 if (selected_eff_value >= target_value) {
654 while (!heap.empty()) {
678 std::vector<char>& vfBest,
CAmount& nBest,
int max_selection_weight,
int iterations = 1000)
680 std::vector<char> vfIncluded;
683 vfBest.assign(groups.size(),
true);
686 for (
int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
688 vfIncluded.assign(groups.size(),
false);
690 int selected_coins_weight{0};
691 bool fReachedTarget =
false;
692 for (
int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
694 for (
unsigned int i = 0; i < groups.size(); i++)
702 if (nPass == 0 ? insecure_rand.
randbool() : !vfIncluded[i])
704 nTotal += groups[i].GetSelectionAmount();
705 selected_coins_weight += groups[i].m_weight;
706 vfIncluded[i] =
true;
707 if (nTotal >= nTargetValue && selected_coins_weight <= max_selection_weight) {
708 fReachedTarget =
true;
716 nTotal -= groups[i].GetSelectionAmount();
717 selected_coins_weight -= groups[i].m_weight;
718 vfIncluded[i] =
false;
731 bool max_weight_exceeded{
false};
733 std::optional<OutputGroup> lowest_larger;
736 std::vector<OutputGroup> applicable_groups;
739 std::shuffle(groups.begin(), groups.end(),
rng);
742 if (group.m_weight > max_selection_weight) {
743 max_weight_exceeded =
true;
746 if (group.GetSelectionAmount() == nTargetValue) {
749 }
else if (group.GetSelectionAmount() < nTargetValue + change_target) {
750 applicable_groups.push_back(group);
751 nTotalLower += group.GetSelectionAmount();
752 }
else if (!lowest_larger || group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
753 lowest_larger = group;
757 if (nTotalLower == nTargetValue) {
758 for (
const auto& group : applicable_groups) {
761 if (result.
GetWeight() <= max_selection_weight)
return result;
762 else max_weight_exceeded =
true;
768 if (nTotalLower < nTargetValue) {
769 if (!lowest_larger) {
778 std::sort(applicable_groups.begin(), applicable_groups.end(),
descending);
779 std::vector<char> vfBest;
783 if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target) {
784 ApproximateBestSubset(
rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest, max_selection_weight);
790 ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
793 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
795 result.
AddInput(applicable_groups[i]);
800 if (result.
GetWeight() > max_selection_weight) {
810 std::string log_message{
"Coin selection best subset: "};
811 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
833 fee += coin.GetFee();
851 if (output->input_bytes > 0) {
870 if (group.m_outputs.empty())
return;
873 if (insert_positive && group.GetSelectionAmount() > 0) {
889 const auto upper_bound = std::min(payment_value * 2,
CHANGE_UPPER);
909 const COutput& coin = *coin_ptr;
915 if (
GetChange(min_viable_change, change_fee)) {
918 waste += change_cost;
923 waste += selected_effective_value -
m_target;
991 m_weight += std::accumulate(inputs.cbegin(), inputs.cend(), 0, [](
int sum,
const auto& coin) {
992 return sum + std::max(coin->input_bytes, 0) * WITNESS_SCALE_FACTOR;
1055 if (change < min_viable_change) {
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.
CAmount GetFee(int32_t virtual_bytes) const
Return the fee in satoshis for the given vsize in vbytes.
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
constexpr int WITNESS_SCALE_FACTOR
#define LogDebug(category,...)
std::string FormatMoney(const CAmount n)
Money parsing/formatting utilities.
bool ShouldDebugLog(Category category)
Return whether messages with specified category should be debug logged.
constexpr CAmount CHANGE_UPPER
upper bound for randomly-chosen target change amount
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 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.
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)