rust_coinselect/algorithms/
srd.rs1use crate::{
2 types::{CoinSelectionOpt, OutputGroup, SelectionError, SelectionOutput, WasteMetric},
3 utils::{calculate_fee, calculate_waste},
4};
5use rand::{seq::SliceRandom, thread_rng};
6
7pub fn select_coin_srd(
11 inputs: &[OutputGroup],
12 options: &CoinSelectionOpt,
13) -> Result<SelectionOutput, SelectionError> {
14 let mut randomized_inputs: Vec<_> = inputs.iter().enumerate().collect();
17 let base_fees = calculate_fee(options.base_weight, options.target_feerate).unwrap_or_default();
18 let target =
19 options.target_value + options.min_change_value + base_fees.max(options.min_absolute_fee);
20
21 let mut rng = thread_rng();
23 randomized_inputs.shuffle(&mut rng);
24
25 let mut accumulated_value = 0;
26 let mut selected_inputs = Vec::new();
27 let mut accumulated_weight = 0;
28 let mut estimated_fee = 0;
29 let mut _input_counts = 0;
30
31 for (index, input) in randomized_inputs {
32 selected_inputs.push(index);
33 accumulated_value += input.value;
34 accumulated_weight += input.weight;
35 _input_counts += input.input_count;
36
37 estimated_fee = calculate_fee(accumulated_weight, options.target_feerate)?;
38
39 if accumulated_value >= target + estimated_fee {
40 break;
41 }
42 }
43
44 if accumulated_value < target + estimated_fee {
45 return Err(SelectionError::InsufficientFunds);
46 }
47 let waste = calculate_waste(
48 options,
49 accumulated_value,
50 accumulated_weight,
51 estimated_fee,
52 );
53
54 Ok(SelectionOutput {
55 selected_inputs,
56 waste: WasteMetric(waste),
57 })
58}
59
60#[cfg(test)]
61mod test {
62
63 use crate::{
64 algorithms::srd::select_coin_srd,
65 types::{CoinSelectionOpt, ExcessStrategy, OutputGroup, SelectionError},
66 };
67
68 fn setup_basic_output_groups() -> Vec<OutputGroup> {
69 vec![
70 OutputGroup {
71 value: 1000,
72 weight: 100,
73 input_count: 1,
74 creation_sequence: None,
75 },
76 OutputGroup {
77 value: 2000,
78 weight: 200,
79 input_count: 1,
80 creation_sequence: None,
81 },
82 OutputGroup {
83 value: 3000,
84 weight: 300,
85 input_count: 1,
86 creation_sequence: None,
87 },
88 ]
89 }
90
91 fn setup_output_groups_withsequence() -> Vec<OutputGroup> {
92 vec![
93 OutputGroup {
94 value: 1000,
95 weight: 100,
96 input_count: 1,
97 creation_sequence: Some(1),
98 },
99 OutputGroup {
100 value: 2000,
101 weight: 200,
102 input_count: 1,
103 creation_sequence: Some(5000),
104 },
105 OutputGroup {
106 value: 3000,
107 weight: 300,
108 input_count: 1,
109 creation_sequence: Some(1001),
110 },
111 OutputGroup {
112 value: 1500,
113 weight: 150,
114 input_count: 1,
115 creation_sequence: None,
116 },
117 ]
118 }
119
120 fn setup_options(target_value: u64) -> CoinSelectionOpt {
121 CoinSelectionOpt {
122 target_value,
123 target_feerate: 0.4, long_term_feerate: Some(0.4),
125 min_absolute_fee: 0,
126 base_weight: 10,
127 change_weight: 50,
128 change_cost: 10,
129 avg_input_weight: 20,
130 avg_output_weight: 10,
131 min_change_value: 500,
132 excess_strategy: ExcessStrategy::ToChange,
133 }
134 }
135
136 fn test_successful_selection() {
137 let mut inputs = setup_basic_output_groups();
138 let mut options = setup_options(2500);
139 let mut result = select_coin_srd(&inputs, &options);
140 assert!(result.is_ok());
141 let mut selection_output = result.unwrap();
142 assert!(!selection_output.selected_inputs.is_empty());
143
144 inputs = setup_output_groups_withsequence();
145 options = setup_options(500);
146 result = select_coin_srd(&inputs, &options);
147 assert!(result.is_ok());
148 selection_output = result.unwrap();
149 assert!(!selection_output.selected_inputs.is_empty());
150 }
151
152 fn test_insufficient_funds() {
153 let inputs = setup_basic_output_groups();
154 let options = setup_options(7000); let result = select_coin_srd(&inputs, &options);
156 assert!(matches!(result, Err(SelectionError::InsufficientFunds)));
157 }
158
159 #[test]
160 fn test_srd() {
161 test_successful_selection();
162 test_insufficient_funds();
163 }
164}