@@ -188,6 +188,286 @@ util::Result<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool
188
188
return result;
189
189
}
190
190
191
+ /*
192
+ * TL;DR: Coin Grinder is a DFS-based algorithm that deterministically searches for the minimum-weight input set to fund
193
+ * the transaction. The algorithm is similar to the Branch and Bound algorithm, but will produce a transaction _with_ a
194
+ * change output instead of a changeless transaction.
195
+ *
196
+ * Full description: CoinGrinder can be thought of as a graph walking algorithm. It explores a binary tree
197
+ * representation of the powerset of the UTXO pool. Each node in the tree represents a candidate input set. The tree’s
198
+ * root is the empty set. Each node in the tree has two children which are formed by either adding or skipping the next
199
+ * UTXO ("inclusion/omission branch"). Each level in the tree after the root corresponds to a decision about one UTXO in
200
+ * the UTXO pool.
201
+ *
202
+ * Example:
203
+ * We represent UTXOs as _alias=[effective_value/weight]_ and indicate omitted UTXOs with an underscore. Given a UTXO
204
+ * pool {A=[10/2], B=[7/1], C=[5/1], D=[4/2]} sorted by descending effective value, our search tree looks as follows:
205
+ *
206
+ * _______________________ {} ________________________
207
+ * / \
208
+ * A=[10/2] __________ {A} _________ __________ {_} _________
209
+ * / \ / \
210
+ * B=[7/1] {AB} _ {A_} _ {_B} _ {__} _
211
+ * / \ / \ / \ / \
212
+ * C=[5/1] {ABC} {AB_} {A_C} {A__} {_BC} {_B_} {__C} {___}
213
+ * / \ / \ / \ / \ / \ / \ / \ / \
214
+ * D=[4/2] {ABCD} {ABC_} {AB_D} {AB__} {A_CD} {A_C_} {A__D} {A___} {_BCD} {_BC_} {_B_D} {_B__} {__CD} {__C_} {___D} {____}
215
+ *
216
+ *
217
+ * CoinGrinder uses a depth-first search to walk this tree. It first tries inclusion branches, then omission branches. A
218
+ * naive exploration of a tree with four UTXOs requires visiting all 31 nodes:
219
+ *
220
+ * {} {A} {AB} {ABC} {ABCD} {ABC_} {AB_} {AB_D} {AB__} {A_} {A_C} {A_CD} {A_C_} {A__} {A__D} {A___} {_} {_B} {_BC}
221
+ * {_BCD} {_BC_} {_B_} {_B_D} {_B__} {__} {__C} {__CD} {__C} {___} {___D} {____}
222
+ *
223
+ * As powersets grow exponentially with the set size, walking the entire tree would quickly get computationally
224
+ * infeasible with growing UTXO pools. Thanks to traversing the tree in a deterministic order, we can keep track of the
225
+ * progress of the search solely on basis of the current selection (and the best selection so far). We visit as few
226
+ * nodes as possible by recognizing and skipping any branches that can only contain solutions worse than the best
227
+ * solution so far. This makes CoinGrinder a branch-and-bound algorithm
228
+ * (https://en.wikipedia.org/wiki/Branch_and_bound).
229
+ * CoinGrinder is searching for the input set with lowest weight that can fund a transaction, so for example we can only
230
+ * ever find a _better_ candidate input set in a node that adds a UTXO, but never in a node that skips a UTXO. After
231
+ * visiting {A} and exploring the inclusion branch {AB} and its descendants, the candidate input set in the omission
232
+ * branch {A_} is equivalent to the parent {A} in effective value and weight. While CoinGrinder does need to visit the
233
+ * descendants of the omission branch {A_}, it is unnecessary to evaluate the candidate input set in the omission branch
234
+ * itself. By skipping evaluation of all nodes on an omission branch we reduce the visited nodes to 15:
235
+ *
236
+ * {A} {AB} {ABC} {ABCD} {AB_D} {A_C} {A_CD} {A__D} {_B} {_BC} {_BCD} {_B_D} {__C} {__CD} {___D}
237
+ *
238
+ * _______________________ {} ________________________
239
+ * / \
240
+ * A=[10/2] __________ {A} _________ ___________\____________
241
+ * / \ / \
242
+ * B=[7/1] {AB} __ __\_____ {_B} __ __\_____
243
+ * / \ / \ / \ / \
244
+ * C=[5/1] {ABC} \ {A_C} \ {_BC} \ {__C} \
245
+ * / / / / / / / /
246
+ * D=[4/2] {ABCD} {AB_D} {A_CD} {A__D} {_BCD} {_B_D} {__CD} {___D}
247
+ *
248
+ *
249
+ * We refer to the move from the inclusion branch {AB} via the omission branch {A_} to its inclusion-branch child {A_C}
250
+ * as _shifting to the omission branch_ or just _SHIFT_. (The index of the ultimate element in the candidate input set
251
+ * shifts right by one: {AB} ⇒ {A_C}.)
252
+ * When we reach a leaf node in the last level of the tree, shifting to the omission branch is not possible. Instead we
253
+ * go to the omission branch of the node’s last ancestor on an inclusion branch: from {ABCD}, we go to {AB_D}. From
254
+ * {AB_D}, we go to {A_C}. We refer to this operation as a _CUT_. (The ultimate element in
255
+ * the input set is deselected, and the penultimate element is shifted right by one: {AB_D} ⇒ {A_C}.)
256
+ * If a candidate input set in a node has not selected sufficient funds to build the transaction, we continue directly
257
+ * along the next inclusion branch. We call this operation _EXPLORE_. (We go from one inclusion branch to the next
258
+ * inclusion branch: {_B} ⇒ {_BC}.)
259
+ * Further, any prefix that already has selected sufficient effective value to fund the transaction cannot be improved
260
+ * by adding more UTXOs. If for example the candidate input set in {AB} is a valid solution, all potential descendant
261
+ * solutions {ABC}, {ABCD}, and {AB_D} must have a higher weight, thus instead of exploring the descendants of {AB}, we
262
+ * can SHIFT from {AB} to {A_C}.
263
+ *
264
+ * Given the above UTXO set, using a target of 11, and following these initial observations, the basic implementation of
265
+ * CoinGrinder visits the following 10 nodes:
266
+ *
267
+ * Node [eff_val/weight] Evaluation
268
+ * ---------------------------------------------------------------
269
+ * {A} [10/2] Insufficient funds: EXPLORE
270
+ * {AB} [17/3] Solution: SHIFT to omission branch
271
+ * {A_C} [15/3] Better solution: SHIFT to omission branch
272
+ * {A__D} [14/4] Worse solution, shift impossible due to leaf node: CUT to omission branch of {A__D},
273
+ * i.e. SHIFT to omission branch of {A}
274
+ * {_B} [7/1] Insufficient funds: EXPLORE
275
+ * {_BC} [12/2] Better solution: SHIFT to omission branch
276
+ * {_B_D} [11/3] Worse solution, shift impossible due to leaf node: CUT to omission branch of {_B_D},
277
+ * i.e. SHIFT to omission branch of {_B}
278
+ * {__C} [5/1] Insufficient funds: EXPLORE
279
+ * {__CD} [9/3] Insufficient funds, leaf node: CUT
280
+ * {___D} [4/2] Insufficient funds, leaf node, cannot CUT since only one UTXO selected: done.
281
+ *
282
+ * _______________________ {} ________________________
283
+ * / \
284
+ * A=[10/2] __________ {A} _________ ___________\____________
285
+ * / \ / \
286
+ * B=[7/1] {AB} __\_____ {_B} __ __\_____
287
+ * / \ / \ / \
288
+ * C=[5/1] {A_C} \ {_BC} \ {__C} \
289
+ * / / / /
290
+ * D=[4/2] {A__D} {_B_D} {__CD} {___D}
291
+ *
292
+ *
293
+ * We implement this tree walk in the following algorithm:
294
+ * 1. Add `next_utxo`
295
+ * 2. Evaluate candidate input set
296
+ * 3. Determine `next_utxo` by deciding whether to
297
+ * a) EXPLORE: Add next inclusion branch, e.g. {_B} ⇒ {_B} + `next_uxto`: C
298
+ * b) SHIFT: Replace last selected UTXO by next higher index, e.g. {A_C} ⇒ {A__} + `next_utxo`: D
299
+ * c) CUT: deselect last selected UTXO and shift to omission branch of penultimate UTXO, e.g. {AB_D} ⇒ {A_} + `next_utxo: C
300
+ *
301
+ * The implementation then adds further optimizations by discovering further situations in which either the inclusion
302
+ * branch can be skipped, or both the inclusion and omission branch can be skipped after evaluating the candidate input
303
+ * set in the node.
304
+ *
305
+ * @param std::vector<OutputGroup>& utxo_pool The UTXOs that we are choosing from. These UTXOs will be sorted in
306
+ * descending order by effective value, with lower waste preferred as a tie-breaker. (We can think of an output
307
+ * group with multiple as a heavier UTXO with the combined amount here.)
308
+ * @param const CAmount& selection_target This is the minimum amount that we need for the transaction without considering change.
309
+ * @param const CAmount& change_target The minimum budget for creating a change output, by which we increase the selection_target.
310
+ * @param int max_weight The maximum permitted weight for the input set.
311
+ * @returns The result of this coin selection algorithm, or std::nullopt
312
+ */
313
+ util::Result<SelectionResult> CoinGrinder (std::vector<OutputGroup>& utxo_pool, const CAmount& selection_target, CAmount change_target, int max_weight)
314
+ {
315
+ std::sort (utxo_pool.begin (), utxo_pool.end (), descending);
316
+
317
+ // Check that there are sufficient funds
318
+ CAmount total_available = 0 ;
319
+ for (const OutputGroup& utxo : utxo_pool) {
320
+ // Assert UTXOs with non-positive effective value have been filtered
321
+ Assume (utxo.GetSelectionAmount () > 0 );
322
+ total_available += utxo.GetSelectionAmount ();
323
+ }
324
+
325
+ const CAmount total_target = selection_target + change_target;
326
+ if (total_available < total_target) {
327
+ // Insufficient funds
328
+ return util::Error ();
329
+ }
330
+
331
+ // The current selection and the best input set found so far, stored as the utxo_pool indices of the UTXOs forming them
332
+ std::vector<size_t > curr_selection;
333
+ std::vector<size_t > best_selection;
334
+
335
+ // The currently selected effective amount, and the effective amount of the best selection so far
336
+ CAmount curr_amount = 0 ;
337
+ CAmount best_selection_amount = MAX_MONEY;
338
+
339
+ // The weight of the currently selected input set, and the weight of the best selection
340
+ int curr_weight = 0 ;
341
+ int best_selection_weight = std::numeric_limits<int >::max ();
342
+
343
+ // Whether the input sets generated during this search have exceeded the maximum transaction weight at any point
344
+ bool max_tx_weight_exceeded = false ;
345
+
346
+ // Index of the next UTXO to consider in utxo_pool
347
+ size_t next_utxo = 0 ;
348
+
349
+ /*
350
+ * You can think of the current selection as a vector of booleans that has decided inclusion or exclusion of all
351
+ * UTXOs before `next_utxo`. When we consider the next UTXO, we extend this hypothetical boolean vector either with
352
+ * a true value if the UTXO is included or a false value if it is omitted. The equivalent state is stored more
353
+ * compactly as the list of indices of the included UTXOs and the `next_utxo` index.
354
+ *
355
+ * We can never find a new solution by deselecting a UTXO, because we then revisit a previously evaluated
356
+ * selection. Therefore, we only need to check whether we found a new solution _after adding_ a new UTXO.
357
+ *
358
+ * Each iteration of CoinGrinder starts by selecting the `next_utxo` and evaluating the current selection. We
359
+ * use three state transitions to progress from the current selection to the next promising selection:
360
+ *
361
+ * - EXPLORE inclusion branch: We do not have sufficient funds, yet. Add `next_utxo` to the current selection, then
362
+ * nominate the direct successor of the just selected UTXO as our `next_utxo` for the
363
+ * following iteration.
364
+ *
365
+ * Example:
366
+ * Current Selection: {0, 5, 7}
367
+ * Evaluation: EXPLORE, next_utxo: 8
368
+ * Next Selection: {0, 5, 7, 8}
369
+ *
370
+ * - SHIFT to omission branch: Adding more UTXOs to the current selection cannot produce a solution that is better
371
+ * than the current best, e.g. the current selection weight exceeds the max weight or
372
+ * the current selection amount is equal to or greater than the target.
373
+ * We designate our `next_utxo` the one after the tail of our current selection, then
374
+ * deselect the tail of our current selection.
375
+ *
376
+ * Example:
377
+ * Current Selection: {0, 5, 7}
378
+ * Evaluation: SHIFT, next_utxo: 8, omit last selected: {0, 5}
379
+ * Next Selection: {0, 5, 8}
380
+ *
381
+ * - CUT entire subtree: We have exhausted the inclusion branch for the penultimately selected UTXO, both the
382
+ * inclusion and the omission branch of the current prefix are barren. E.g. we have
383
+ * reached the end of the UTXO pool, so neither further EXPLORING nor SHIFTING can find
384
+ * any solutions. We designate our `next_utxo` the one after our penultimate selected,
385
+ * then deselect both the last and penultimate selected.
386
+ *
387
+ * Example:
388
+ * Current Selection: {0, 5, 7}
389
+ * Evaluation: CUT, next_utxo: 6, omit two last selected: {0}
390
+ * Next Selection: {0, 6}
391
+ */
392
+ auto deselect_last = [&]() {
393
+ OutputGroup& utxo = utxo_pool[curr_selection.back ()];
394
+ curr_amount -= utxo.GetSelectionAmount ();
395
+ curr_weight -= utxo.m_weight ;
396
+ curr_selection.pop_back ();
397
+ };
398
+
399
+ SelectionResult result (selection_target, SelectionAlgorithm::CG);
400
+ size_t curr_try = 0 ;
401
+ while (true ) {
402
+ bool should_shift{false }, should_cut{false };
403
+ // Select `next_utxo`
404
+ OutputGroup& utxo = utxo_pool[next_utxo];
405
+ curr_amount += utxo.GetSelectionAmount ();
406
+ curr_weight += utxo.m_weight ;
407
+ curr_selection.push_back (next_utxo);
408
+ ++next_utxo;
409
+ ++curr_try;
410
+
411
+ // EVALUATE current selection: check for solutions and see whether we can CUT or SHIFT before EXPLORING further
412
+ if (curr_weight > max_weight) {
413
+ // max_weight exceeded: SHIFT
414
+ max_tx_weight_exceeded = true ;
415
+ should_shift = true ;
416
+ } else if (curr_amount >= total_target) {
417
+ // Success, adding more weight cannot be better: SHIFT
418
+ should_shift = true ;
419
+ if (curr_weight < best_selection_weight || (curr_weight == best_selection_weight && curr_amount < best_selection_amount)) {
420
+ // New lowest weight, or same weight with fewer funds tied up
421
+ best_selection = curr_selection;
422
+ best_selection_weight = curr_weight;
423
+ best_selection_amount = curr_amount;
424
+ }
425
+ }
426
+
427
+ if (curr_try >= TOTAL_TRIES) {
428
+ // Solution is not guaranteed to be optimal if `curr_try` hit TOTAL_TRIES
429
+ break ;
430
+ }
431
+
432
+ if (next_utxo == utxo_pool.size ()) {
433
+ // Last added UTXO was end of UTXO pool, nothing left to add on inclusion or omission branch: CUT
434
+ should_cut = true ;
435
+ }
436
+
437
+ if (should_cut) {
438
+ // Neither adding to the current selection nor exploring the omission branch of the last selected UTXO can
439
+ // find any solutions. Redirect to exploring the Omission branch of the penultimate selected UTXO (i.e.
440
+ // set `next_utxo` to one after the penultimate selected, then deselect the last two selected UTXOs)
441
+ should_cut = false ;
442
+ deselect_last ();
443
+ should_shift = true ;
444
+ }
445
+
446
+ if (should_shift) {
447
+ // Set `next_utxo` to one after last selected, then deselect last selected UTXO
448
+ if (curr_selection.empty ()) {
449
+ // Exhausted search space before running into attempt limit
450
+ break ;
451
+ }
452
+ next_utxo = curr_selection.back () + 1 ;
453
+ deselect_last ();
454
+ should_shift = false ;
455
+ }
456
+ }
457
+
458
+ result.SetSelectionsEvaluated (curr_try);
459
+
460
+ if (best_selection.empty ()) {
461
+ return max_tx_weight_exceeded ? ErrorMaxWeightExceeded () : util::Error ();
462
+ }
463
+
464
+ for (const size_t & i : best_selection) {
465
+ result.AddInput (utxo_pool[i]);
466
+ }
467
+
468
+ return result;
469
+ }
470
+
191
471
class MinOutputGroupComparator
192
472
{
193
473
public:
@@ -514,6 +794,16 @@ void SelectionResult::ComputeAndSetWaste(const CAmount min_viable_change, const
514
794
}
515
795
}
516
796
797
+ void SelectionResult::SetSelectionsEvaluated (size_t attempts)
798
+ {
799
+ m_selections_evaluated = attempts;
800
+ }
801
+
802
+ size_t SelectionResult::GetSelectionsEvaluated () const
803
+ {
804
+ return m_selections_evaluated;
805
+ }
806
+
517
807
CAmount SelectionResult::GetWaste () const
518
808
{
519
809
return *Assert (m_waste);
@@ -607,6 +897,7 @@ std::string GetAlgorithmName(const SelectionAlgorithm algo)
607
897
case SelectionAlgorithm::BNB: return " bnb" ;
608
898
case SelectionAlgorithm::KNAPSACK: return " knapsack" ;
609
899
case SelectionAlgorithm::SRD: return " srd" ;
900
+ case SelectionAlgorithm::CG: return " cg" ;
610
901
case SelectionAlgorithm::MANUAL: return " manual" ;
611
902
// No default case to allow for compiler to warn
612
903
}
0 commit comments