@@ -273,19 +273,27 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection {
273
273
drain_script : & Script ,
274
274
_: & mut R ,
275
275
) -> Result < CoinSelectionResult , InsufficientFunds > {
276
- // We put the "required UTXOs " first and make sure the optional UTXOs are sorted from
276
+ // We put the "required UTxOs " first and make sure the optional UTxOs are sorted from
277
277
// oldest to newest according to blocktime
278
- // For utxo that doesn't exist in DB, they will have lowest priority to be selected
278
+ // For UTxOs that doesn't exist in DB (Utxo::Foreign), they will have lowest priority to be
279
+ // selected
279
280
let utxos = {
280
281
optional_utxos. sort_unstable_by_key ( |wu| match & wu. utxo {
281
282
Utxo :: Local ( local) => Some ( local. chain_position ) ,
282
283
Utxo :: Foreign { .. } => None ,
283
284
} ) ;
284
285
286
+ let ( local_utxos, foreign_utxos) : ( Vec < WeightedUtxo > , Vec < WeightedUtxo > ) =
287
+ optional_utxos. into_iter ( ) . partition ( |wu| match & wu. utxo {
288
+ Utxo :: Local ( ..) => true ,
289
+ Utxo :: Foreign { .. } => false ,
290
+ } ) ;
291
+
285
292
required_utxos
286
293
. into_iter ( )
287
294
. map ( |utxo| ( true , utxo) )
288
- . chain ( optional_utxos. into_iter ( ) . map ( |utxo| ( false , utxo) ) )
295
+ . chain ( local_utxos. into_iter ( ) . map ( |utxo| ( false , utxo) ) )
296
+ . chain ( foreign_utxos. into_iter ( ) . map ( |utxo| ( false , utxo) ) )
289
297
} ;
290
298
291
299
select_sorted_utxos ( utxos, fee_rate, target_amount, drain_script)
@@ -726,10 +734,11 @@ fn calculate_cs_result(
726
734
mod test {
727
735
use assert_matches:: assert_matches;
728
736
use bitcoin:: hashes:: Hash ;
729
- use bitcoin:: OutPoint ;
737
+ use bitcoin:: { psbt , OutPoint , Sequence } ;
730
738
use chain:: { ChainPosition , ConfirmationBlockTime } ;
731
739
use core:: str:: FromStr ;
732
740
use rand:: rngs:: StdRng ;
741
+ use std:: boxed:: Box ;
733
742
734
743
use bitcoin:: { Amount , BlockHash , ScriptBuf , TxIn , TxOut } ;
735
744
@@ -778,6 +787,30 @@ mod test {
778
787
)
779
788
}
780
789
790
+ fn foreign_utxo ( value : Amount , index : u32 ) -> WeightedUtxo {
791
+ assert ! ( index < 10 ) ;
792
+ let outpoint = OutPoint :: from_str ( & format ! (
793
+ "000000000000000000000000000000000000000000000000000000000000000{}:0" ,
794
+ index
795
+ ) )
796
+ . unwrap ( ) ;
797
+ WeightedUtxo {
798
+ utxo : Utxo :: Foreign {
799
+ outpoint,
800
+ sequence : Sequence ( 0xFFFFFFFD ) ,
801
+ psbt_input : Box :: new ( psbt:: Input {
802
+ witness_utxo : Some ( TxOut {
803
+ value,
804
+ script_pubkey : ScriptBuf :: new ( ) ,
805
+ } ) ,
806
+ non_witness_utxo : None ,
807
+ ..Default :: default ( )
808
+ } ) ,
809
+ } ,
810
+ satisfaction_weight : Weight :: from_wu_usize ( P2WPKH_SATISFACTION_SIZE ) ,
811
+ }
812
+ }
813
+
781
814
fn utxo (
782
815
value : Amount ,
783
816
index : u32 ,
@@ -1051,6 +1084,56 @@ mod test {
1051
1084
assert_eq ! ( result. fee_amount, Amount :: from_sat( 204 ) ) ;
1052
1085
}
1053
1086
1087
+ #[ test]
1088
+ fn test_oldest_first_coin_selection_uses_all_optional_with_foreign_utxo_locals_sorted_first ( ) {
1089
+ let utxos = get_oldest_first_test_utxos ( ) ;
1090
+ let mut all_utxos = vec ! [ foreign_utxo( Amount :: from_sat( 120_000 ) , 1 ) ] ;
1091
+ all_utxos. extend_from_slice ( & utxos) ;
1092
+ let drain_script = ScriptBuf :: default ( ) ;
1093
+ let target_amount = Amount :: from_sat ( 619_000 ) + FEE_AMOUNT ;
1094
+
1095
+ let result = OldestFirstCoinSelection
1096
+ . coin_select (
1097
+ vec ! [ ] ,
1098
+ all_utxos,
1099
+ FeeRate :: from_sat_per_vb_unchecked ( 1 ) ,
1100
+ target_amount,
1101
+ & drain_script,
1102
+ & mut thread_rng ( ) ,
1103
+ )
1104
+ . unwrap ( ) ;
1105
+
1106
+ assert_eq ! ( result. selected. len( ) , 4 ) ;
1107
+ assert_eq ! ( result. selected_amount( ) , Amount :: from_sat( 620_000 ) ) ;
1108
+ assert_eq ! ( result. fee_amount, Amount :: from_sat( 272 ) ) ;
1109
+ assert ! ( matches!( result. selected[ 3 ] , Utxo :: Foreign { .. } ) ) ;
1110
+ }
1111
+
1112
+ #[ test]
1113
+ fn test_oldest_first_coin_selection_uses_only_all_optional_local_utxos_not_a_single_foreign ( ) {
1114
+ let utxos = get_oldest_first_test_utxos ( ) ;
1115
+ let mut all_utxos = vec ! [ foreign_utxo( Amount :: from_sat( 120_000 ) , 1 ) ] ;
1116
+ all_utxos. extend_from_slice ( & utxos) ;
1117
+ let drain_script = ScriptBuf :: default ( ) ;
1118
+ let target_amount = Amount :: from_sat ( 499_000 ) + FEE_AMOUNT ;
1119
+
1120
+ let result = OldestFirstCoinSelection
1121
+ . coin_select (
1122
+ vec ! [ ] ,
1123
+ all_utxos,
1124
+ FeeRate :: from_sat_per_vb_unchecked ( 1 ) ,
1125
+ target_amount,
1126
+ & drain_script,
1127
+ & mut thread_rng ( ) ,
1128
+ )
1129
+ . unwrap ( ) ;
1130
+
1131
+ assert_eq ! ( result. selected. len( ) , 3 ) ;
1132
+ assert_eq ! ( result. selected_amount( ) , Amount :: from_sat( 500_000 ) ) ;
1133
+ assert_eq ! ( result. fee_amount, Amount :: from_sat( 204 ) ) ;
1134
+ assert ! ( matches!( result. selected[ 2 ] , Utxo :: Local ( ..) ) ) ;
1135
+ }
1136
+
1054
1137
#[ test]
1055
1138
fn test_oldest_first_coin_selection_use_only_necessary ( ) {
1056
1139
let utxos = get_oldest_first_test_utxos ( ) ;
0 commit comments