Bitcoin ABC 0.30.7
P2P Digital Currency
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 {
20 }
22
23static const size_t TOTAL_TRIES = 100000;
24
73bool SelectCoinsBnB(std::vector<OutputGroup> &utxo_pool,
74 const Amount &target_value, const Amount &cost_of_change,
75 std::set<CInputCoin> &out_set, Amount &value_ret,
76 const Amount not_input_fees) {
77 out_set.clear();
78 Amount curr_value = Amount::zero();
79
80 // select the utxo at this index
81 std::vector<bool> curr_selection;
82 curr_selection.reserve(utxo_pool.size());
83 Amount actual_target = not_input_fees + target_value;
84
85 // Calculate curr_available_value
86 Amount curr_available_value = Amount::zero();
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 }
93 if (curr_available_value < actual_target) {
94 return false;
95 }
96
97 // Sort the utxo_pool
98 std::sort(utxo_pool.begin(), utxo_pool.end(), descending);
99
100 Amount curr_waste = Amount::zero();
101 std::vector<bool> best_selection;
102 Amount best_waste = MAX_MONEY;
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;
108 if (curr_value + curr_available_value <
109 actual_target || // Cannot possibly reach target with the amount
110 // remaining in the curr_available_value.
111 curr_value >
112 actual_target +
113 cost_of_change || // Selected value is out of range, go back
114 // and try other branch
115 (curr_waste > best_waste &&
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.
133 curr_waste += (curr_value - actual_target);
134
135 if (curr_waste <= best_waste) {
136 best_selection = curr_selection;
137 best_selection.resize(utxo_pool.size());
138 best_waste = curr_waste;
139 if (best_waste == Amount::zero()) {
140 break;
141 }
142 }
143 // Remove the excess value as we will be selecting different coins
144 // now
145 curr_waste -= (curr_value - actual_target);
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();
155 curr_available_value +=
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);
168 curr_value -= utxo.effective_value;
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
177 curr_available_value -= utxo.effective_value;
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);
191 curr_value += utxo.effective_value;
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
203 value_ret = Amount::zero();
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);
222 nBest = nTotalLower;
223
224 FastRandomContext insecure_rand;
225
226 for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) {
227 vfIncluded.assign(groups.size(), false);
228 Amount nTotal = Amount::zero();
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;
244 vfBest = vfIncluded;
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();
259 nValueRet = Amount::zero();
260
261 // List of values less than target
262 std::optional<OutputGroup> lowest_larger;
263 std::vector<OutputGroup> applicable_groups;
264 Amount nTotalLower = Amount::zero();
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) {
277 lowest_larger = group;
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
289 if (nTotalLower < nTargetValue) {
290 if (!lowest_larger) {
291 return false;
292 }
293 util::insert(setCoinsRet, lowest_larger->m_outputs);
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;
301 Amount nBest;
302
303 ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest,
304 nBest);
305 if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
306 ApproximateBestSubset(applicable_groups, nTotalLower,
307 nTargetValue + MIN_CHANGE, vfBest, 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 &&
314 ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) ||
315 lowest_larger->m_value <= nBest)) {
316 util::insert(setCoinsRet, lowest_larger->m_outputs);
317 nValueRet += lowest_larger->m_value;
318 } else {
319 for (size_t i = 0; i < applicable_groups.size(); i++) {
320 if (vfBest[i]) {
321 util::insert(setCoinsRet, applicable_groups[i].m_outputs);
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 }
338 LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
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
379 m_from_me &= from_me;
380 m_value += output.txout.nValue;
381 m_depth = std::min(m_depth, depth);
382}
383
385 const CoinEligibilityFilter &eligibility_filter) const {
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.
Definition: coinselection.h:47
Amount m_fee
Definition: coinselection.h:40
Amount m_long_term_fee
Definition: coinselection.h:41
CTxOut txout
Definition: coinselection.h:38
Amount effective_value
Definition: coinselection.h:39
Amount nValue
Definition: transaction.h:130
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
Definition: coinselection.h:13
#define LogPrint(category,...)
Definition: logging.h:238
#define LogPrintToBeContinued
Definition: logging.h:260
static bool LogAcceptCategory(BCLog::LogFlags category, BCLog::Level level)
Return true if log accepts specified category, at the specified level.
Definition: logging.h:186
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
Definition: amount.h:19
static constexpr Amount zero() noexcept
Definition: amount.h:32
const int conf_theirs
Minimum number of confirmations for outputs received from a different wallet.
Definition: coinselection.h:68
std::vector< CInputCoin > m_outputs
Definition: coinselection.h:82
Amount m_value
Definition: coinselection.h:84
CFeeRate m_long_term_feerate
Definition: coinselection.h:90
Amount long_term_fee
Definition: coinselection.h:89
CFeeRate m_effective_feerate
Definition: coinselection.h:88
void Insert(const CInputCoin &output, int depth, bool from_me, bool positive_only)
Amount effective_value
Definition: coinselection.h:86
bool EligibleForSpending(const CoinEligibilityFilter &eligibility_filter) const
assert(!tx.IsCoinBase())