5
5
//! This module introduces the Branch and Bound Coin-Selection Algorithm.
6
6
7
7
use bitcoin:: amount:: CheckedSum ;
8
- use bitcoin:: { Amount , FeeRate , SignedAmount } ;
8
+ use bitcoin:: { Amount , FeeRate } ;
9
9
10
10
use crate :: { Return , WeightedUtxo } ;
11
11
@@ -161,18 +161,18 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
161
161
let mut index = 0 ;
162
162
let mut backtrack;
163
163
164
- let mut value = Amount :: ZERO ;
164
+ let mut value = 0 ;
165
165
166
- let mut current_waste: SignedAmount = SignedAmount :: ZERO ;
167
- let mut best_waste = SignedAmount :: MAX_MONEY ;
166
+ let mut current_waste: i64 = 0 ;
167
+ let mut best_waste: i64 = Amount :: MAX_MONEY . to_sat ( ) as i64 ;
168
168
169
169
let mut index_selection: Vec < usize > = vec ! [ ] ;
170
170
let mut best_selection: Vec < usize > = vec ! [ ] ;
171
171
172
172
let upper_bound = target. checked_add ( cost_of_change) ?;
173
173
174
174
// Creates a tuple of (effective_value, waste, weighted_utxo)
175
- let mut w_utxos: Vec < ( Amount , SignedAmount , & Utxo ) > = weighted_utxos
175
+ let w_utxos = weighted_utxos
176
176
. iter ( )
177
177
// calculate effective_value and waste for each w_utxo.
178
178
. map ( |wu| ( wu. effective_value ( fee_rate) , wu. waste ( fee_rate, long_term_fee_rate) , wu) )
@@ -183,18 +183,24 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
183
183
// filter out all effective_values that are negative.
184
184
. filter ( |( eff_val, _, _) | eff_val. is_positive ( ) )
185
185
// all utxo effective_values are now positive (see previous step) - cast to unsigned.
186
- . map ( |( eff_val, waste, wu) | ( eff_val. to_unsigned ( ) . unwrap ( ) , waste, wu) )
187
- . collect ( ) ;
186
+ . map ( |( eff_val, waste, wu) | ( eff_val. to_unsigned ( ) . unwrap ( ) , waste, wu) ) ;
188
187
189
- // descending sort by effective_value using satisfaction weight as tie breaker.
190
- w_utxos. sort_by ( |a, b| b. 0 . cmp ( & a. 0 ) . then ( b. 2 . weight ( ) . cmp ( & a. 2 . weight ( ) ) ) ) ;
191
-
192
- let mut available_value = w_utxos. clone ( ) . into_iter ( ) . map ( |( ev, _, _) | ev) . checked_sum ( ) ?;
188
+ let available_value = w_utxos. clone ( ) . map ( |( ev, _, _) | ev) . checked_sum ( ) ?;
193
189
194
190
if available_value < target || target == Amount :: ZERO {
195
191
return None ;
196
192
}
197
193
194
+ let mut available_value = available_value. to_sat ( ) ;
195
+
196
+ // cast from Amount/SignedAmount to u64/i64 for more performant operations.
197
+ let mut w_utxos: Vec < ( u64 , i64 , & Utxo ) > =
198
+ w_utxos. map ( |( e, w, u) | ( e. to_sat ( ) , w. to_sat ( ) , u) ) . collect ( ) ;
199
+ let target = target. to_sat ( ) ;
200
+
201
+ // descending sort by effective_value using satisfaction weight as tie breaker.
202
+ w_utxos. sort_by ( |a, b| b. 0 . cmp ( & a. 0 ) . then ( b. 2 . weight ( ) . cmp ( & a. 2 . weight ( ) ) ) ) ;
203
+
198
204
while iteration < ITERATION_LIMIT {
199
205
backtrack = false ;
200
206
@@ -203,7 +209,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
203
209
// unchecked_add is used here for performance. Before entering the search loop, all
204
210
// utxos are summed and checked for overflow. Since there was no overflow then, any
205
211
// subset of addition will not overflow.
206
- if available_value. unchecked_add ( value) < target
212
+ if available_value + value < target
207
213
// Provides an upper bound on the excess value that is permissible.
208
214
// Since value is lost when we create a change output due to increasing the size of the
209
215
// transaction by an output (the change output), we accept solutions that may be
@@ -215,7 +221,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
215
221
//
216
222
// That is, the range includes solutions that exactly equal the target up to but not
217
223
// including values greater than target + cost_of_change.
218
- || value > upper_bound
224
+ || value > upper_bound. to_sat ( )
219
225
// if current_waste > best_waste, then backtrack. However, only backtrack if
220
226
// it's high fee_rate environment. During low fee environments, a utxo may
221
227
// have negative waste, therefore adding more utxos in such an environment
@@ -229,9 +235,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
229
235
else if value >= target {
230
236
backtrack = true ;
231
237
232
- let v = value. to_signed ( ) . ok ( ) ?;
233
- let t = target. to_signed ( ) . ok ( ) ?;
234
- let waste: SignedAmount = v. checked_sub ( t) ?;
238
+ let waste: i64 = ( value as i64 ) . checked_sub ( target as i64 ) ?;
235
239
current_waste = current_waste. checked_add ( waste) ?;
236
240
237
241
// Check if index_selection is better than the previous known best, and
@@ -263,6 +267,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
263
267
assert_eq ! ( index, * index_selection. last( ) . unwrap( ) ) ;
264
268
let ( eff_value, utxo_waste, _) = w_utxos[ index] ;
265
269
current_waste = current_waste. checked_sub ( utxo_waste) ?;
270
+
266
271
value = value. checked_sub ( eff_value) ?;
267
272
index_selection. pop ( ) . unwrap ( ) ;
268
273
}
@@ -273,7 +278,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
273
278
// unchecked sub is used her for performance.
274
279
// The bounds for available_value are at most the sum of utxos
275
280
// and at least zero.
276
- available_value = available_value. unchecked_sub ( eff_value) ;
281
+ available_value = available_value - eff_value;
277
282
278
283
// Check if we can omit the currently selected depending on if the last
279
284
// was omitted. Therefore, check if index_selection has a previous one.
@@ -288,7 +293,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
288
293
289
294
// unchecked add is used here for performance. Since the sum of all utxo values
290
295
// did not overflow, then any positive subset of the sum will not overflow.
291
- value = value. unchecked_add ( eff_value) ;
296
+ value = value + eff_value;
292
297
}
293
298
}
294
299
@@ -303,7 +308,7 @@ pub fn select_coins_bnb<Utxo: WeightedUtxo>(
303
308
fn index_to_utxo_list < Utxo : WeightedUtxo > (
304
309
iterations : u32 ,
305
310
index_list : Vec < usize > ,
306
- wu : Vec < ( Amount , SignedAmount , & Utxo ) > ,
311
+ wu : Vec < ( u64 , i64 , & Utxo ) > ,
307
312
) -> Return < Utxo > {
308
313
let mut result: Vec < _ > = Vec :: new ( ) ;
309
314
let list = index_list;
@@ -327,7 +332,7 @@ mod tests {
327
332
328
333
use arbitrary:: { Arbitrary , Unstructured } ;
329
334
use arbtest:: arbtest;
330
- use bitcoin:: { Amount , Weight } ;
335
+ use bitcoin:: { Amount , SignedAmount , Weight } ;
331
336
332
337
use super :: * ;
333
338
use crate :: tests:: { assert_proptest_bnb, assert_ref_eq, parse_fee_rate, Utxo , UtxoPool } ;
@@ -387,7 +392,7 @@ mod tests {
387
392
// see: https://github.com/rust-fuzz/arbitrary/pull/192
388
393
fn arb_amount_in_range ( u : & mut Unstructured , r : std:: ops:: RangeInclusive < u64 > ) -> Amount {
389
394
let u = u. int_in_range :: < u64 > ( r) . unwrap ( ) ;
390
- Amount :: from_sat ( u)
395
+ Amount :: from_sat ( u) . unwrap ( )
391
396
}
392
397
393
398
// Use in place of arbitrary_in_range()
@@ -627,7 +632,7 @@ mod tests {
627
632
cost_of_change : "0" ,
628
633
fee_rate : "0" ,
629
634
lt_fee_rate : "0" ,
630
- weighted_utxos : & [ "18446744073709551615 sats/68 vB" , "1 sats/68 vB" ] , // [u64 ::MAX, 1 sat]
635
+ weighted_utxos : & [ "2100000000000000 sats/68 vB" , "1 sats/68 vB" ] , // [Amount ::MAX, 1 sat]
631
636
expected_utxos : None ,
632
637
expected_iterations : 0 ,
633
638
}
@@ -638,7 +643,7 @@ mod tests {
638
643
fn select_coins_bnb_upper_bound_overflow ( ) {
639
644
TestBnB {
640
645
target : "1 sats" ,
641
- cost_of_change : "18446744073709551615 sats" , // u64 ::MAX
646
+ cost_of_change : "2100000000000000 sats" , // Amount ::MAX
642
647
fee_rate : "0" ,
643
648
lt_fee_rate : "0" ,
644
649
weighted_utxos : & [ "1 sats/68 vB" ] ,
@@ -648,20 +653,6 @@ mod tests {
648
653
. assert ( ) ;
649
654
}
650
655
651
- #[ test]
652
- fn select_coins_bnb_utxo_greater_than_max_money ( ) {
653
- TestBnB {
654
- target : "1 sats" ,
655
- cost_of_change : "18141417255681066410 sats" ,
656
- fee_rate : "1 sat/kwu" ,
657
- lt_fee_rate : "0" ,
658
- weighted_utxos : & [ "8740670712339394302 sats/68 vB" ] ,
659
- expected_utxos : None ,
660
- expected_iterations : 0 ,
661
- }
662
- . assert ( ) ;
663
- }
664
-
665
656
#[ test]
666
657
fn select_coins_bnb_effective_value_tie_high_fee_rate ( ) {
667
658
// If the fee_rate is expensive prefer lower weight UTXOS
@@ -786,7 +777,7 @@ mod tests {
786
777
// Takes 327,661 iterations to find a solution.
787
778
let base: usize = 2 ;
788
779
let alpha = ( 0 ..17 ) . enumerate ( ) . map ( |( i, _) | base. pow ( 17 + i as u32 ) ) ;
789
- let target = Amount :: from_sat ( alpha. clone ( ) . sum :: < usize > ( ) as u64 ) ;
780
+ let target = Amount :: from_sat ( alpha. clone ( ) . sum :: < usize > ( ) as u64 ) . unwrap ( ) ;
790
781
791
782
let beta = ( 0 ..17 ) . enumerate ( ) . map ( |( i, _) | {
792
783
let a = base. pow ( 17 + i as u32 ) ;
@@ -798,7 +789,7 @@ mod tests {
798
789
// flatten requires iterable types.
799
790
// use once() to make tuple iterable.
800
791
. flat_map ( |tup| once ( tup. 0 ) . chain ( once ( tup. 1 ) ) )
801
- . map ( |a| Amount :: from_sat ( a as u64 ) )
792
+ . map ( |a| Amount :: from_sat ( a as u64 ) . unwrap ( ) )
802
793
. collect ( ) ;
803
794
804
795
let pool: Vec < _ > = amts. into_iter ( ) . map ( |a| Utxo :: new ( a, Weight :: ZERO ) ) . collect ( ) ;
@@ -819,11 +810,11 @@ mod tests {
819
810
vec ! [ a, a + 2 ]
820
811
} ) ;
821
812
822
- let amts: Vec < _ > = vals. map ( Amount :: from_sat) . collect ( ) ;
813
+ let amts: Vec < _ > = vals. map ( |v| Amount :: from_sat ( v ) . unwrap ( ) ) . collect ( ) ;
823
814
let pool: Vec < _ > = amts. into_iter ( ) . map ( |a| Utxo :: new ( a, Weight :: ZERO ) ) . collect ( ) ;
824
815
825
816
let list = select_coins_bnb (
826
- Amount :: from_sat ( target) ,
817
+ Amount :: from_sat ( target) . unwrap ( ) ,
827
818
Amount :: ONE_SAT ,
828
819
FeeRate :: ZERO ,
829
820
FeeRate :: ZERO ,
@@ -845,14 +836,14 @@ mod tests {
845
836
vec ! [ a, a + 2 ]
846
837
} ) ;
847
838
848
- let mut amts: Vec < _ > = amts. map ( Amount :: from_sat) . collect ( ) ;
839
+ let mut amts: Vec < _ > = amts. map ( |v| Amount :: from_sat ( v ) . unwrap ( ) ) . collect ( ) ;
849
840
850
841
// Add a value that will match the target before iteration exhaustion occurs.
851
- amts. push ( Amount :: from_sat ( target) ) ;
842
+ amts. push ( Amount :: from_sat ( target) . unwrap ( ) ) ;
852
843
let pool: Vec < _ > = amts. into_iter ( ) . map ( |a| Utxo :: new ( a, Weight :: ZERO ) ) . collect ( ) ;
853
844
854
845
let ( iterations, utxos) = select_coins_bnb (
855
- Amount :: from_sat ( target) ,
846
+ Amount :: from_sat ( target) . unwrap ( ) ,
856
847
Amount :: ONE_SAT ,
857
848
FeeRate :: ZERO ,
858
849
FeeRate :: ZERO ,
@@ -861,7 +852,7 @@ mod tests {
861
852
. unwrap ( ) ;
862
853
863
854
assert_eq ! ( utxos. len( ) , 1 ) ;
864
- assert_eq ! ( utxos[ 0 ] . value( ) , Amount :: from_sat( target) ) ;
855
+ assert_eq ! ( utxos[ 0 ] . value( ) , Amount :: from_sat( target) . unwrap ( ) ) ;
865
856
assert_eq ! ( 100000 , iterations) ;
866
857
}
867
858
@@ -897,7 +888,7 @@ mod tests {
897
888
if let Some ( f) = max_fee_rate {
898
889
let fee_rate = arb_fee_rate_in_range ( u, 1 ..=f. to_sat_per_kwu ( ) ) ;
899
890
900
- //TODO update eff value interface
891
+ // TODO update eff value interface
901
892
let target_effective_value =
902
893
effective_value ( fee_rate, utxo. weight ( ) , utxo. value ( ) ) . unwrap ( ) ;
903
894
@@ -909,7 +900,8 @@ mod tests {
909
900
. clone ( )
910
901
. into_iter ( )
911
902
. map ( |u| effective_value ( fee_rate, u. weight ( ) , u. value ( ) ) . unwrap ( ) )
912
- . sum ( ) ;
903
+ . checked_sum ( )
904
+ . unwrap ( ) ;
913
905
let amount_sum = sum. to_unsigned ( ) . unwrap ( ) ;
914
906
assert_eq ! ( amount_sum, target) ;
915
907
@@ -985,7 +977,8 @@ mod tests {
985
977
. to_unsigned ( )
986
978
. unwrap ( )
987
979
} )
988
- . sum ( ) ;
980
+ . checked_sum ( )
981
+ . unwrap ( ) ;
989
982
assert_eq ! ( effective_value_sum, target) ;
990
983
991
984
// TODO checked_add not available in Weight
@@ -1028,9 +1021,10 @@ mod tests {
1028
1021
1029
1022
let result = select_coins_bnb ( target, cost_of_change, fee_rate, lt_fee_rate, & utxos) ;
1030
1023
1031
- assert_proptest_bnb ( target, cost_of_change, fee_rate, pool, result) ;
1024
+ assert_proptest_bnb ( target, cost_of_change, fee_rate, lt_fee_rate , pool, result) ;
1032
1025
1033
1026
Ok ( ( ) )
1034
- } ) ;
1027
+ } )
1028
+ . seed ( 0xcde68a8900000060 ) ;
1035
1029
}
1036
1030
}
0 commit comments