@@ -232,6 +232,10 @@ pub trait CoinSelectionAlgorithm: core::fmt::Debug {
232232 params : CoinSelectionParams < ' _ , R > ,
233233 ) -> Result < CoinSelectionResult , InsufficientFunds > ;
234234}
235+ fn group_utxos_if_applies ( utxos : Vec < WeightedUtxo > , _: bool ) -> Vec < Vec < WeightedUtxo > > {
236+ // No grouping: every UTXO is its own group.
237+ return utxos. into_iter ( ) . map ( |u| vec ! [ u] ) . collect ( ) ;
238+ }
235239
236240/// Simple and dumb coin selection
237241///
@@ -247,21 +251,31 @@ impl CoinSelectionAlgorithm for LargestFirstCoinSelection {
247251 ) -> Result < CoinSelectionResult , InsufficientFunds > {
248252 let CoinSelectionParams {
249253 required_utxos,
250- mut optional_utxos,
254+ optional_utxos,
251255 fee_rate,
252256 target_amount,
253257 drain_script,
254258 rand : _,
255- avoid_partial_spends : _ ,
259+ avoid_partial_spends,
256260 } = params;
261+ let required_utxo_group =
262+ group_utxos_if_applies ( required_utxos. clone ( ) , avoid_partial_spends) ;
263+ let mut optional_utxos_group = group_utxos_if_applies ( optional_utxos, avoid_partial_spends) ;
257264 // We put the "required UTXOs" first and make sure the optional UTXOs are sorted,
258265 // initially smallest to largest, before being reversed with `.rev()`.
259266 let utxos = {
260- optional_utxos. sort_unstable_by_key ( |wu| wu. utxo . txout ( ) . value ) ;
261- required_utxos
267+ optional_utxos_group. sort_unstable_by_key ( |group| {
268+ group. iter ( ) . map ( |wu| wu. utxo . txout ( ) . value ) . sum :: < Amount > ( )
269+ } ) ;
270+ required_utxo_group
262271 . into_iter ( )
263272 . map ( |utxo| ( true , utxo) )
264- . chain ( optional_utxos. into_iter ( ) . rev ( ) . map ( |utxo| ( false , utxo) ) )
273+ . chain (
274+ optional_utxos_group
275+ . into_iter ( )
276+ . rev ( )
277+ . map ( |utxo| ( false , utxo) ) ,
278+ )
265279 } ;
266280
267281 select_sorted_utxos ( utxos, fee_rate, target_amount, drain_script)
@@ -282,26 +296,30 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection {
282296 ) -> Result < CoinSelectionResult , InsufficientFunds > {
283297 let CoinSelectionParams {
284298 required_utxos,
285- mut optional_utxos,
299+ optional_utxos,
286300 fee_rate,
287301 target_amount,
288302 drain_script,
289303 rand : _,
290- avoid_partial_spends : _ ,
304+ avoid_partial_spends,
291305 } = params;
306+ let required_utxo_group =
307+ group_utxos_if_applies ( required_utxos. clone ( ) , avoid_partial_spends) ;
308+ let mut optional_utxos_group =
309+ group_utxos_if_applies ( optional_utxos. clone ( ) , avoid_partial_spends) ;
292310 // We put the "required UTXOs" first and make sure the optional UTXOs are sorted from
293311 // oldest to newest according to blocktime
294312 // For utxo that doesn't exist in DB, they will have lowest priority to be selected
295313 let utxos = {
296- optional_utxos . sort_unstable_by_key ( |wu | match & wu . utxo {
297- Utxo :: Local ( local) => Some ( local. chain_position ) ,
314+ optional_utxos_group . sort_unstable_by_key ( |group | match group [ 0 ] . utxo {
315+ Utxo :: Local ( ref local) => Some ( local. chain_position ) ,
298316 Utxo :: Foreign { .. } => None ,
299317 } ) ;
300318
301- required_utxos
319+ required_utxo_group
302320 . into_iter ( )
303321 . map ( |utxo| ( true , utxo) )
304- . chain ( optional_utxos . into_iter ( ) . map ( |utxo| ( false , utxo) ) )
322+ . chain ( optional_utxos_group . into_iter ( ) . map ( |utxo| ( false , utxo) ) )
305323 } ;
306324
307325 select_sorted_utxos ( utxos, fee_rate, target_amount, drain_script)
@@ -336,7 +354,7 @@ pub fn decide_change(remaining_amount: Amount, fee_rate: FeeRate, drain_script:
336354}
337355
338356fn select_sorted_utxos (
339- utxos : impl Iterator < Item = ( bool , WeightedUtxo ) > ,
357+ utxos : impl Iterator < Item = ( bool , Vec < WeightedUtxo > ) > ,
340358 fee_rate : FeeRate ,
341359 target_amount : Amount ,
342360 drain_script : & Script ,
@@ -346,20 +364,23 @@ fn select_sorted_utxos(
346364 let selected = utxos
347365 . scan (
348366 ( & mut selected_amount, & mut fee_amount) ,
349- |( selected_amount, fee_amount) , ( must_use, weighted_utxo ) | {
367+ |( selected_amount, fee_amount) , ( must_use, group ) | {
350368 if must_use || * * selected_amount < target_amount + * * fee_amount {
351- * * fee_amount += fee_rate
352- * TxIn :: default ( )
353- . segwit_weight ( )
354- . checked_add ( weighted_utxo. satisfaction_weight )
355- . expect ( "`Weight` addition should not cause an integer overflow" ) ;
356- * * selected_amount += weighted_utxo. utxo . txout ( ) . value ;
357- Some ( weighted_utxo. utxo )
369+ for weighted_utxo in & group {
370+ * * fee_amount += fee_rate
371+ * TxIn :: default ( )
372+ . segwit_weight ( )
373+ . checked_add ( weighted_utxo. satisfaction_weight )
374+ . expect ( "`Weight` addition should not cause an integer overflow" ) ;
375+ * * selected_amount += weighted_utxo. utxo . txout ( ) . value ;
376+ }
377+ Some ( group. into_iter ( ) . map ( |wu| wu. utxo ) . collect :: < Vec < _ > > ( ) )
358378 } else {
359379 None
360380 }
361381 } ,
362382 )
383+ . flatten ( )
363384 . collect :: < Vec < _ > > ( ) ;
364385
365386 let amount_needed_with_fees = target_amount + fee_amount;
@@ -469,26 +490,42 @@ impl<Cs: CoinSelectionAlgorithm> CoinSelectionAlgorithm for BranchAndBoundCoinSe
469490 rand : _,
470491 avoid_partial_spends,
471492 } = params;
493+ let required_utxo_group =
494+ group_utxos_if_applies ( required_utxos. clone ( ) , avoid_partial_spends) ;
495+ let optional_utxos_group =
496+ group_utxos_if_applies ( optional_utxos. clone ( ) , avoid_partial_spends) ;
472497 // Mapping every (UTXO, usize) to an output group
473- let required_ogs: Vec < OutputGroup > = required_utxos
474- . iter ( )
475- . map ( |u| OutputGroup :: new ( u. clone ( ) , fee_rate) )
498+ let required_ogs: Vec < Vec < OutputGroup > > = required_utxo_group
499+ . into_iter ( )
500+ . map ( |group| {
501+ group
502+ . into_iter ( )
503+ . map ( |weighted_utxo| OutputGroup :: new ( weighted_utxo, fee_rate) )
504+ . collect ( )
505+ } )
476506 . collect ( ) ;
477507
478508 // Mapping every (UTXO, usize) to an output group, filtering UTXOs with a negative
479509 // effective value
480- let optional_ogs: Vec < OutputGroup > = optional_utxos
481- . iter ( )
482- . map ( |u| OutputGroup :: new ( u. clone ( ) , fee_rate) )
483- . filter ( |u| u. effective_value . is_positive ( ) )
510+ let optional_ogs: Vec < Vec < OutputGroup > > = optional_utxos_group
511+ . into_iter ( )
512+ . map ( |group| {
513+ group
514+ . into_iter ( )
515+ . map ( |weighted_utxo| OutputGroup :: new ( weighted_utxo, fee_rate) )
516+ . filter ( |og| og. effective_value . is_positive ( ) )
517+ . collect ( )
518+ } )
484519 . collect ( ) ;
485520
486521 let curr_value = required_ogs
487522 . iter ( )
523+ . flat_map ( |group| group. iter ( ) )
488524 . fold ( SignedAmount :: ZERO , |acc, x| acc + x. effective_value ) ;
489525
490526 let curr_available_value = optional_ogs
491527 . iter ( )
528+ . flat_map ( |group| group. iter ( ) )
492529 . fold ( SignedAmount :: ZERO , |acc, x| acc + x. effective_value ) ;
493530
494531 let cost_of_change = ( Weight :: from_vb ( self . size_of_change ) . expect ( "overflow occurred" )
@@ -515,9 +552,11 @@ impl<Cs: CoinSelectionAlgorithm> CoinSelectionAlgorithm for BranchAndBoundCoinSe
515552 // positive effective value), sum their value and their fee cost.
516553 let ( utxo_fees, utxo_value) = required_ogs. iter ( ) . chain ( optional_ogs. iter ( ) ) . fold (
517554 ( Amount :: ZERO , Amount :: ZERO ) ,
518- |( mut fees, mut value) , utxo| {
519- fees += utxo. fee ;
520- value += utxo. weighted_utxo . utxo . txout ( ) . value ;
555+ |( mut fees, mut value) , group| {
556+ for utxo in group {
557+ fees += utxo. fee ;
558+ value += utxo. weighted_utxo . utxo . txout ( ) . value ;
559+ }
521560 ( fees, value)
522561 } ,
523562 ) ;
@@ -580,8 +619,8 @@ impl<Cs> BranchAndBoundCoinSelection<Cs> {
580619 #[ allow( clippy:: too_many_arguments) ]
581620 fn bnb (
582621 & self ,
583- required_utxos : Vec < OutputGroup > ,
584- mut optional_utxos : Vec < OutputGroup > ,
622+ required_utxos : Vec < Vec < OutputGroup > > ,
623+ mut optional_utxos : Vec < Vec < OutputGroup > > ,
585624 mut curr_value : SignedAmount ,
586625 mut curr_available_value : SignedAmount ,
587626 target_amount : SignedAmount ,
@@ -596,7 +635,12 @@ impl<Cs> BranchAndBoundCoinSelection<Cs> {
596635 let mut current_selection: Vec < bool > = Vec :: with_capacity ( optional_utxos. len ( ) ) ;
597636
598637 // Sort the utxo_pool
599- optional_utxos. sort_unstable_by_key ( |a| a. effective_value ) ;
638+ optional_utxos. sort_unstable_by_key ( |group| {
639+ group
640+ . iter ( )
641+ . map ( |og| og. effective_value )
642+ . sum :: < SignedAmount > ( )
643+ } ) ;
600644 optional_utxos. reverse ( ) ;
601645
602646 // Contains the best selection we found
@@ -637,7 +681,10 @@ impl<Cs> BranchAndBoundCoinSelection<Cs> {
637681 // Walk backwards to find the last included UTXO that still needs to have its omission branch traversed.
638682 while let Some ( false ) = current_selection. last ( ) {
639683 current_selection. pop ( ) ;
640- curr_available_value += optional_utxos[ current_selection. len ( ) ] . effective_value ;
684+ curr_available_value += optional_utxos[ current_selection. len ( ) ]
685+ . iter ( )
686+ . map ( |og| og. effective_value )
687+ . sum :: < SignedAmount > ( ) ;
641688 }
642689
643690 if current_selection. last_mut ( ) . is_none ( ) {
@@ -655,17 +702,26 @@ impl<Cs> BranchAndBoundCoinSelection<Cs> {
655702 }
656703
657704 let utxo = & optional_utxos[ current_selection. len ( ) - 1 ] ;
658- curr_value -= utxo. effective_value ;
705+ curr_value -= utxo
706+ . iter ( )
707+ . map ( |og| og. effective_value )
708+ . sum :: < SignedAmount > ( ) ;
659709 } else {
660710 // Moving forwards, continuing down this branch
661711 let utxo = & optional_utxos[ current_selection. len ( ) ] ;
662712
663713 // Remove this utxo from the curr_available_value utxo amount
664- curr_available_value -= utxo. effective_value ;
714+ curr_available_value -= utxo
715+ . iter ( )
716+ . map ( |og| og. effective_value )
717+ . sum :: < SignedAmount > ( ) ;
665718
666719 // Inclusion branch first (Largest First Exploration)
667720 current_selection. push ( true ) ;
668- curr_value += utxo. effective_value ;
721+ curr_value += utxo
722+ . iter ( )
723+ . map ( |og| og. effective_value )
724+ . sum :: < SignedAmount > ( ) ;
669725 }
670726 }
671727
@@ -679,7 +735,7 @@ impl<Cs> BranchAndBoundCoinSelection<Cs> {
679735 . into_iter ( )
680736 . zip ( best_selection)
681737 . filter_map ( |( optional, is_in_best) | if is_in_best { Some ( optional) } else { None } )
682- . collect :: < Vec < OutputGroup > > ( ) ;
738+ . collect :: < Vec < Vec < OutputGroup > > > ( ) ;
683739
684740 let selected_amount = best_selection_value. unwrap ( ) ;
685741
@@ -707,21 +763,23 @@ impl CoinSelectionAlgorithm for SingleRandomDraw {
707763 ) -> Result < CoinSelectionResult , InsufficientFunds > {
708764 let CoinSelectionParams {
709765 required_utxos,
710- mut optional_utxos,
766+ optional_utxos,
711767 fee_rate,
712768 target_amount,
713769 drain_script,
714770 rand,
715- avoid_partial_spends : _ ,
771+ avoid_partial_spends,
716772 } = params;
773+ let required_utxo_group = group_utxos_if_applies ( required_utxos, avoid_partial_spends) ;
774+ let mut optional_utxos_group = group_utxos_if_applies ( optional_utxos, avoid_partial_spends) ;
717775 // We put the required UTXOs first and then the randomize optional UTXOs to take as needed
718776 let utxos = {
719- shuffle_slice ( & mut optional_utxos , rand) ;
777+ shuffle_slice ( & mut optional_utxos_group , rand) ;
720778
721- required_utxos
779+ required_utxo_group
722780 . into_iter ( )
723781 . map ( |utxo| ( true , utxo) )
724- . chain ( optional_utxos . into_iter ( ) . map ( |utxo| ( false , utxo) ) )
782+ . chain ( optional_utxos_group . into_iter ( ) . map ( |utxo| ( false , utxo) ) )
725783 } ;
726784
727785 // select required UTXOs and then random optional UTXOs.
@@ -730,15 +788,20 @@ impl CoinSelectionAlgorithm for SingleRandomDraw {
730788}
731789
732790fn calculate_cs_result (
733- mut selected_utxos : Vec < OutputGroup > ,
734- mut required_utxos : Vec < OutputGroup > ,
791+ mut selected_utxos : Vec < Vec < OutputGroup > > ,
792+ mut required_utxos : Vec < Vec < OutputGroup > > ,
735793 excess : Excess ,
736794) -> CoinSelectionResult {
737795 selected_utxos. append ( & mut required_utxos) ;
738- let fee_amount = selected_utxos. iter ( ) . map ( |u| u. fee ) . sum ( ) ;
796+ let fee_amount = selected_utxos
797+ . iter ( )
798+ . flat_map ( |group| group. iter ( ) )
799+ . map ( |u| u. fee )
800+ . sum ( ) ;
739801 let selected = selected_utxos
740802 . into_iter ( )
741- . map ( |u| u. weighted_utxo . utxo )
803+ . flatten ( )
804+ . map ( |og| og. weighted_utxo . utxo )
742805 . collect :: < Vec < _ > > ( ) ;
743806
744807 CoinSelectionResult {
@@ -1444,7 +1507,7 @@ mod test {
14441507 let target_amount = SignedAmount :: from_sat ( 20_000 ) + FEE_AMOUNT . to_signed ( ) . unwrap ( ) ;
14451508 let result = BranchAndBoundCoinSelection :: new ( size_of_change, SingleRandomDraw ) . bnb (
14461509 vec ! [ ] ,
1447- utxos,
1510+ utxos. into_iter ( ) . map ( |u| vec ! [ u ] ) . collect ( ) ,
14481511 SignedAmount :: ZERO ,
14491512 curr_available_value,
14501513 target_amount,
@@ -1477,7 +1540,7 @@ mod test {
14771540
14781541 let result = BranchAndBoundCoinSelection :: new ( size_of_change, SingleRandomDraw ) . bnb (
14791542 vec ! [ ] ,
1480- utxos,
1543+ utxos. into_iter ( ) . map ( |u| vec ! [ u ] ) . collect ( ) ,
14811544 SignedAmount :: ZERO ,
14821545 curr_available_value,
14831546 target_amount,
@@ -1518,7 +1581,7 @@ mod test {
15181581 let result = BranchAndBoundCoinSelection :: new ( size_of_change, SingleRandomDraw )
15191582 . bnb (
15201583 vec ! [ ] ,
1521- utxos,
1584+ utxos. into_iter ( ) . map ( |u| vec ! [ u ] ) . collect ( ) ,
15221585 curr_value,
15231586 curr_available_value,
15241587 target_amount,
@@ -1558,7 +1621,7 @@ mod test {
15581621 let result = BranchAndBoundCoinSelection :: < SingleRandomDraw > :: default ( )
15591622 . bnb (
15601623 vec ! [ ] ,
1561- optional_utxos,
1624+ optional_utxos. into_iter ( ) . map ( |u| vec ! [ u ] ) . collect ( ) ,
15621625 curr_value,
15631626 curr_available_value,
15641627 target_amount,
0 commit comments