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();
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) {
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;
782 ApproximateBestSubset(rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest, max_selection_weight);
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;
1060 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(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.
std::string ToString() const
int operator()(const OutputGroup &group1, const OutputGroup &group2) const
static const 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.
static constexpr CAmount CHANGE_UPPER
upper bound for randomly-chosen target change amount
struct wallet::@17 descending_effval_weight
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)
struct wallet::@16 descending
std::set< std::shared_ptr< COutput >, OutputPtrComparator > OutputSet
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()
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)
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 (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.
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...
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)