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")};
30 bool operator()(
const OutputGroup& a,
const OutputGroup& b)
const
32 if (a.GetSelectionAmount() == b.GetSelectionAmount()) {
34 return (a.fee - a.long_term_fee) < (b.fee - b.long_term_fee);
36 return a.GetSelectionAmount() > b.GetSelectionAmount();
42 bool operator()(
const OutputGroup& a,
const OutputGroup& b)
const
44 if (a.GetSelectionAmount() == b.GetSelectionAmount()) {
46 return a.m_weight < b.m_weight;
48 return a.GetSelectionAmount() > b.GetSelectionAmount();
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()) {
486 while (should_shift) {
488 if (curr_selection.empty()) {
494 next_utxo = curr_selection.back() + 1;
496 should_shift =
false;
503 while (utxo_pool[next_utxo - 1].GetSelectionAmount() == utxo_pool[next_utxo].GetSelectionAmount()) {
504 if (next_utxo >= utxo_pool.size() - 1) {
517 if (best_selection.empty()) {
521 for (
const size_t& i : best_selection) {
538 int max_selection_weight)
549 std::vector<size_t> indexes;
550 indexes.resize(utxo_pool.size());
551 std::iota(indexes.begin(), indexes.end(), 0);
552 std::shuffle(indexes.begin(), indexes.end(), rng);
554 CAmount selected_eff_value = 0;
556 bool max_tx_weight_exceeded =
false;
557 for (
const size_t i : indexes) {
563 selected_eff_value +=
group.GetSelectionAmount();
564 weight +=
group.m_weight;
568 if (weight > max_selection_weight) {
569 max_tx_weight_exceeded =
true;
575 }
while (!heap.empty() && weight > max_selection_weight);
579 if (selected_eff_value >= target_value) {
581 while (!heap.empty()) {
605 std::vector<char>& vfBest,
CAmount& nBest,
int max_selection_weight,
int iterations = 1000)
607 std::vector<char> vfIncluded;
610 vfBest.assign(groups.size(),
true);
613 for (
int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
615 vfIncluded.assign(groups.size(),
false);
617 int selected_coins_weight{0};
618 bool fReachedTarget =
false;
619 for (
int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
621 for (
unsigned int i = 0; i < groups.size(); i++)
629 if (nPass == 0 ? insecure_rand.
randbool() : !vfIncluded[i])
631 nTotal += groups[i].GetSelectionAmount();
632 selected_coins_weight += groups[i].m_weight;
633 vfIncluded[i] =
true;
634 if (nTotal >= nTargetValue && selected_coins_weight <= max_selection_weight) {
635 fReachedTarget =
true;
643 nTotal -= groups[i].GetSelectionAmount();
644 selected_coins_weight -= groups[i].m_weight;
645 vfIncluded[i] =
false;
658 bool max_weight_exceeded{
false};
660 std::optional<OutputGroup> lowest_larger;
663 std::vector<OutputGroup> applicable_groups;
666 std::shuffle(groups.begin(), groups.end(), rng);
669 if (
group.m_weight > max_selection_weight) {
670 max_weight_exceeded =
true;
673 if (
group.GetSelectionAmount() == nTargetValue) {
676 }
else if (
group.GetSelectionAmount() < nTargetValue + change_target) {
677 applicable_groups.push_back(
group);
678 nTotalLower +=
group.GetSelectionAmount();
679 }
else if (!lowest_larger ||
group.GetSelectionAmount() < lowest_larger->GetSelectionAmount()) {
680 lowest_larger =
group;
684 if (nTotalLower == nTargetValue) {
685 for (
const auto&
group : applicable_groups) {
688 if (result.
GetWeight() <= max_selection_weight)
return result;
689 else max_weight_exceeded =
true;
695 if (nTotalLower < nTargetValue) {
696 if (!lowest_larger) {
705 std::sort(applicable_groups.begin(), applicable_groups.end(),
descending);
706 std::vector<char> vfBest;
709 ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest, max_selection_weight);
710 if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target) {
711 ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue + change_target, vfBest, nBest, max_selection_weight);
717 ((nBest != nTargetValue && nBest < nTargetValue + change_target) || lowest_larger->GetSelectionAmount() <= nBest)) {
720 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
722 result.
AddInput(applicable_groups[i]);
727 if (result.
GetWeight() > max_selection_weight) {
737 std::string log_message{
"Coin selection best subset: "};
738 for (
unsigned int i = 0; i < applicable_groups.size(); i++) {
760 fee += coin.GetFee();
778 if (output->input_bytes > 0) {
797 if (
group.m_outputs.empty())
return;
800 if (insert_positive &&
group.GetSelectionAmount() > 0) {
816 const auto upper_bound = std::min(payment_value * 2,
CHANGE_UPPER);
836 const COutput& coin = *coin_ptr;
842 if (
GetChange(min_viable_change, change_fee)) {
845 waste += change_cost;
850 waste += selected_effective_value -
m_target;
918 m_weight += std::accumulate(inputs.cbegin(), inputs.cend(), 0, [](
int sum,
const auto& coin) {
919 return sum + std::max(coin->input_bytes, 0) * WITNESS_SCALE_FACTOR;
988 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.
CAmount GetFee(uint32_t num_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.
std::string ToString() const
int operator()(const OutputGroup &group1, const OutputGroup &group2) const
static const int WITNESS_SCALE_FACTOR
static bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
Return true if log accepts specified category, at the specified level.
#define LogDebug(category,...)
std::string FormatMoney(const CAmount n)
Money parsing/formatting utilities.
static constexpr CAmount CHANGE_UPPER
upper bound for randomly-chosen target change amount
struct wallet::@17 descending
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)
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...
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::@18 descending_effval_weight
std::string GetAlgorithmName(const SelectionAlgorithm algo)
static const size_t TOTAL_TRIES
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.
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 uint64_t max_descendants
Maximum number of descendants that a single UTXO in the OutputGroup may have.
const int conf_theirs
Minimum number of confirmations for outputs received from a different wallet.
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.
void Insert(const std::shared_ptr< COutput > &output, size_t ancestors, size_t descendants)
bool m_subtract_fee_outputs
Indicate that we are subtracting the fee from outputs.
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.
size_t m_descendants
The maximum count of descendants of a single UTXO in this output group.
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.
std::set< std::shared_ptr< COutput > > m_selected_inputs
Set of inputs selected by the algorithm to use in the transaction.
CAmount bump_fee_group_discount
How much individual inputs overestimated the bump fees for the shared ancestry.
void Merge(const SelectionResult &other)
Combines the.
size_t GetSelectionsEvaluated() const
Get selections_evaluated.
const std::set< std::shared_ptr< COutput > > & GetInputSet() const
Get m_selected_inputs.
SelectionAlgorithm m_algo
The algorithm used to produce this result.
bool GetAlgoCompleted() const
Get m_algo_completed.
void SetBumpFeeDiscount(const CAmount discount)
How much individual inputs overestimated the bump fees for shared ancestries.
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.
CAmount m_target
The target the algorithm selected for.
CAmount GetChange(const CAmount min_viable_change, const CAmount change_fee) const
Get the amount for the change output after paying needed fees.
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...
CAmount GetSelectedValue() const
Get the sum of the input values.
void RecalculateWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee)
Calculates and stores the waste for this result given the cost of change and the opportunity cost of ...
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.
void AddInputs(const std::set< std::shared_ptr< COutput > > &inputs, bool subtract_fee_outputs)
std::vector< std::shared_ptr< COutput > > GetShuffledInputVector() const
Get the vector of COutputs that will be used to fill in a CTransaction's vin.
bilingual_str _(ConstevalStringLiteral str)
Translation function.