Bitcoin ABC 0.26.3
P2P Digital Currency
Loading...
Searching...
No Matches
coinselection.cpp
Go to the documentation of this file.
1// Copyright (c) 2017 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
6
7#include <common/system.h>
8#include <consensus/amount.h>
9#include <feerate.h>
10#include <logging.h>
11#include <util/insert.h>
12#include <util/moneystr.h>
13
14#include <optional>
15
16// Descending order comparator
17struct {
18 bool operator()(const OutputGroup &a, const OutputGroup &b) const {
19 return a.effective_value > b.effective_value;
20 }
22
23static const size_t TOTAL_TRIES = 100000;
24
73bool SelectCoinsBnB(std::vector<OutputGroup> &utxo_pool,
75 std::set<CInputCoin> &out_set, Amount &value_ret,
76 const Amount not_input_fees) {
77 out_set.clear();
79
80 // select the utxo at this index
81 std::vector<bool> curr_selection;
82 curr_selection.reserve(utxo_pool.size());
84
85 // Calculate curr_available_value
87 for (const OutputGroup &utxo : utxo_pool) {
88 // Assert that this utxo is not negative. It should never be negative,
89 // effective value calculation should have removed it
90 assert(utxo.effective_value > Amount::zero());
91 curr_available_value += utxo.effective_value;
92 }
94 return false;
95 }
96
97 // Sort the utxo_pool
98 std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
99
101 std::vector<bool> best_selection;
103
104 // Depth First search loop for choosing the UTXOs
105 for (size_t i = 0; i < TOTAL_TRIES; ++i) {
106 // Conditions for starting a backtrack
107 bool backtrack = false;
109 actual_target || // Cannot possibly reach target with the amount
110 // remaining in the curr_available_value.
111 curr_value >
113 cost_of_change || // Selected value is out of range, go back
114 // and try other branch
116 (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) >
117 Amount::zero())) {
118 // Don't select things which we know will be more wasteful if the
119 // waste is increasing
120 backtrack = true;
121 }
122
123 // Selected value is within range
124 else if (curr_value >= actual_target) {
125 // This is the excess value which is added to the waste for the
126 // below comparison. Adding another UTXO after this check could
127 // bring the waste down if the long term fee is higher than the
128 // current fee. However we are not going to explore that because
129 // this optimization for the waste is only done when we have hit our
130 // target value. Adding any more UTXOs will be just burning the
131 // UTXO; it will go entirely to fees. Thus we aren't going to
132 // explore any more UTXOs to avoid burning money like that.
134
135 if (curr_waste <= best_waste) {
137 best_selection.resize(utxo_pool.size());
139 if (best_waste == Amount::zero()) {
140 break;
141 }
142 }
143 // Remove the excess value as we will be selecting different coins
144 // now
146 backtrack = true;
147 }
148
149 // Backtracking, moving backwards
150 if (backtrack) {
151 // Walk backwards to find the last included UTXO that still needs to
152 // have its omission branch traversed.
153 while (!curr_selection.empty() && !curr_selection.back()) {
154 curr_selection.pop_back();
156 utxo_pool.at(curr_selection.size()).effective_value;
157 }
158
159 if (curr_selection.empty()) {
160 // We have walked back to the first utxo and no branch is
161 // untraversed. All solutions searched
162 break;
163 }
164
165 // Output was included on previous iterations, try excluding now.
166 curr_selection.back() = false;
167 OutputGroup &utxo = utxo_pool.at(curr_selection.size() - 1);
169 curr_waste -= utxo.fee - utxo.long_term_fee;
170 }
171
172 // Moving forwards, continuing down this branch
173 else {
174 OutputGroup &utxo = utxo_pool.at(curr_selection.size());
175
176 // Remove this utxo from the curr_available_value utxo amount
178
179 // Avoid searching a branch if the previous UTXO has the same value
180 // and same waste and was excluded. Since the ratio of fee to long
181 // term fee is the same, we only need to check if one of those
182 // values match in order to know that the waste is the same.
183 if (!curr_selection.empty() && !curr_selection.back() &&
184 utxo.effective_value ==
185 utxo_pool.at(curr_selection.size() - 1).effective_value &&
186 utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
187 curr_selection.push_back(false);
188 } else {
189 // Inclusion branch first (Largest First Exploration)
190 curr_selection.push_back(true);
192 curr_waste += utxo.fee - utxo.long_term_fee;
193 }
194 }
195 }
196
197 // Check for solution
198 if (best_selection.empty()) {
199 return false;
200 }
201
202 // Set output set
204 for (size_t i = 0; i < best_selection.size(); ++i) {
205 if (best_selection.at(i)) {
206 util::insert(out_set, utxo_pool.at(i).m_outputs);
207 value_ret += utxo_pool.at(i).m_value;
208 }
209 }
210
211 return true;
212}
213
214static void ApproximateBestSubset(const std::vector<OutputGroup> &groups,
215 const Amount &nTotalLower,
216 const Amount &nTargetValue,
217 std::vector<char> &vfBest, Amount &nBest,
218 int iterations = 1000) {
219 std::vector<char> vfIncluded;
220
221 vfBest.assign(groups.size(), true);
223
224 FastRandomContext insecure_rand;
225
226 for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) {
227 vfIncluded.assign(groups.size(), false);
229 bool fReachedTarget = false;
230 for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) {
231 for (size_t i = 0; i < groups.size(); i++) {
232 // The solver here uses a randomized algorithm, the randomness
233 // serves no real security purpose but is just needed to prevent
234 // degenerate behavior and it is important that the rng is fast.
235 // We do not use a constant random sequence, because there may
236 // be some privacy improvement by making the selection random.
237 if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) {
238 nTotal += groups[i].m_value;
239 vfIncluded[i] = true;
240 if (nTotal >= nTargetValue) {
241 fReachedTarget = true;
242 if (nTotal < nBest) {
243 nBest = nTotal;
245 }
246
247 nTotal -= groups[i].m_value;
248 vfIncluded[i] = false;
249 }
250 }
251 }
252 }
253 }
254}
255
256bool KnapsackSolver(const Amount nTargetValue, std::vector<OutputGroup> &groups,
257 std::set<CInputCoin> &setCoinsRet, Amount &nValueRet) {
258 setCoinsRet.clear();
260
261 // List of values less than target
262 std::optional<OutputGroup> lowest_larger;
263 std::vector<OutputGroup> applicable_groups;
265
266 Shuffle(groups.begin(), groups.end(), FastRandomContext());
267
268 for (const OutputGroup &group : groups) {
269 if (group.m_value == nTargetValue) {
270 util::insert(setCoinsRet, group.m_outputs);
271 nValueRet += group.m_value;
272 return true;
273 } else if (group.m_value < nTargetValue + MIN_CHANGE) {
274 applicable_groups.push_back(group);
275 nTotalLower += group.m_value;
276 } else if (!lowest_larger || group.m_value < lowest_larger->m_value) {
278 }
279 }
280
281 if (nTotalLower == nTargetValue) {
282 for (const auto &group : applicable_groups) {
283 util::insert(setCoinsRet, group.m_outputs);
284 nValueRet += group.m_value;
285 }
286 return true;
287 }
288
290 if (!lowest_larger) {
291 return false;
292 }
294 nValueRet += lowest_larger->m_value;
295 return true;
296 }
297
298 // Solve subset sum by stochastic approximation
299 std::sort(applicable_groups.begin(), applicable_groups.end(), descending);
300 std::vector<char> vfBest;
302
304 nBest);
308 }
309
310 // If we have a bigger coin and (either the stochastic approximation didn't
311 // find a good solution, or the next bigger coin is closer), return the
312 // bigger coin
313 if (lowest_larger &&
315 lowest_larger->m_value <= nBest)) {
317 nValueRet += lowest_larger->m_value;
318 } else {
319 for (size_t i = 0; i < applicable_groups.size(); i++) {
320 if (vfBest[i]) {
322 nValueRet += applicable_groups[i].m_value;
323 }
324 }
325
327 /* Continued */
329 "SelectCoins() best subset: ");
330 for (size_t i = 0; i < applicable_groups.size(); i++) {
331 if (vfBest[i]) {
332 /* Continued */
334 BCLog::SELECTCOINS, "%s ",
335 FormatMoney(applicable_groups[i].m_value));
336 }
337 }
339 }
340 }
341
342 return true;
343}
344
345/******************************************************************************
346
347 OutputGroup
348
349 ******************************************************************************/
350
351void OutputGroup::Insert(const CInputCoin &output, int depth, bool from_me,
352 bool positive_only) {
353 // Compute the effective value first
354 const Amount coin_fee =
355 output.m_input_bytes < 0
356 ? Amount::zero()
358 const Amount ev = output.txout.nValue - coin_fee;
359
360 // Filter for positive only here before adding the coin
361 if (positive_only && ev <= Amount::zero()) {
362 return;
363 }
364
365 m_outputs.push_back(output);
366 CInputCoin &coin = m_outputs.back();
367
368 coin.m_fee = coin_fee;
369 fee += coin.m_fee;
370
371 coin.m_long_term_fee = coin.m_input_bytes < 0
372 ? Amount::zero()
375
376 coin.effective_value = ev;
378
380 m_value += output.txout.nValue;
381 m_depth = std::min(m_depth, depth);
382}
383
386 return m_depth >= (m_from_me ? eligibility_filter.conf_mine
387 : eligibility_filter.conf_theirs);
388}
static constexpr Amount MAX_MONEY
No amount larger than this (in satoshi) is valid.
Definition amount.h:165
Amount GetFee(size_t nBytes) const
Return the fee in satoshis for the given size in bytes.
Definition feerate.cpp:49
int m_input_bytes
Pre-computed estimated size of this output as a fully-signed input in a transaction.
Amount m_long_term_fee
Amount effective_value
Amount nValue
Fast randomness source.
Definition random.h:156
bool randbool() noexcept
Generate a random boolean.
Definition random.h:256
static void ApproximateBestSubset(const std::vector< OutputGroup > &groups, const Amount &nTotalLower, const Amount &nTargetValue, std::vector< char > &vfBest, Amount &nBest, int iterations=1000)
static const size_t TOTAL_TRIES
bool KnapsackSolver(const Amount nTargetValue, std::vector< OutputGroup > &groups, std::set< CInputCoin > &setCoinsRet, Amount &nValueRet)
bool SelectCoinsBnB(std::vector< OutputGroup > &utxo_pool, const Amount &target_value, const Amount &cost_of_change, std::set< CInputCoin > &out_set, Amount &value_ret, const Amount not_input_fees)
This is the Branch and Bound Coin Selection algorithm designed by Murch.
struct @19 descending
static constexpr Amount MIN_CHANGE
target minimum change amount
static bool LogAcceptCategory(BCLog::LogFlags category)
Return true if log accepts specified category.
Definition logging.h:177
#define LogPrint(category,...)
Definition logging.h:211
#define LogPrintToBeContinued
Definition logging.h:224
std::string FormatMoney(const Amount amt)
Do not use these functions to represent or parse monetary amounts to or from JSON but use AmountFromV...
Definition moneystr.cpp:13
@ SELECTCOINS
Definition logging.h:50
void insert(Tdst &dst, const Tsrc &src)
Simplification of std insertion.
Definition insert.h:14
void Shuffle(I first, I last, R &&rng)
More efficient than using std::shuffle on a FastRandomContext.
Definition random.h:291
T GetRand(T nMax=std::numeric_limits< T >::max()) noexcept
Generate a uniform random integer of type T in the range [0..nMax) nMax defaults to std::numeric_limi...
Definition random.h:85
static constexpr Amount zero() noexcept
Definition amount.h:32
std::vector< CInputCoin > m_outputs
CFeeRate m_long_term_feerate
Amount long_term_fee
CFeeRate m_effective_feerate
void Insert(const CInputCoin &output, int depth, bool from_me, bool positive_only)
Amount effective_value
bool EligibleForSpending(const CoinEligibilityFilter &eligibility_filter) const
assert(!tx.IsCoinBase())