1use crate::{
2 types::{
3 CoinSelectionOpt, EffectiveValue, OutputGroup, SelectionError, SelectionOutput, WasteMetric,
4 },
5 utils::{calculate_fee, calculate_waste, effective_value},
6};
7use rand::{thread_rng, Rng};
8use std::collections::HashSet;
9pub fn select_coin_knapsack(
10 inputs: &[OutputGroup],
11 options: &CoinSelectionOpt,
12) -> Result<SelectionOutput, SelectionError> {
13 let base_fees = calculate_fee(options.base_weight, options.target_feerate).unwrap_or_default();
15 let adjusted_target =
16 options.target_value + options.min_change_value + base_fees.max(options.min_absolute_fee);
17
18 let mut smaller_coins = inputs
19 .iter()
20 .enumerate()
21 .filter(|&(_, output_group)| output_group.value < adjusted_target)
22 .map(|(index, output_group)| {
23 (
24 index,
25 output_group.value,
26 output_group.weight,
27 effective_value(output_group, options.target_feerate),
28 )
29 })
30 .collect::<Vec<_>>();
31
32 smaller_coins.sort_by(|(_, _, _, a), (_, _, _, b)| b.unwrap_or(0).cmp(&a.unwrap_or(0)));
34
35 let smaller_coins: Vec<_> = smaller_coins
36 .into_iter()
37 .filter_map(|(index, value, weight, eff_value)| {
38 eff_value.ok().map(|v| (index, value, weight, v))
39 })
40 .collect();
41
42 knap_sack(&smaller_coins, adjusted_target, options)
43}
44
45fn knap_sack(
46 smaller_coins: &[(usize, u64, u64, EffectiveValue)],
47 adjusted_target: u64,
48 options: &CoinSelectionOpt,
49) -> Result<SelectionOutput, SelectionError> {
50 let mut selected_inputs: HashSet<usize> = HashSet::new();
51 let mut accumulated_value: u64 = 0;
52 let mut accumulated_weight: u64 = 0;
53 let mut best_set: HashSet<usize> = HashSet::new();
54 let mut best_set_value: u64 = u64::MAX;
55 let mut best_set_weight: u64 = 0;
56 let mut rng = thread_rng();
57
58 for _ in 1..=1000 {
59 for pass in 1..=2 {
60 for &(index, value, weight, _eff_value) in smaller_coins {
61 let toss_result: bool = rng.gen_bool(0.5);
62 if (pass == 2 && !selected_inputs.contains(&index)) || (pass == 1 && toss_result) {
63 selected_inputs.insert(index);
64 accumulated_value += value;
65 accumulated_weight += weight;
66
67 let estimated_fees = calculate_fee(accumulated_weight, options.target_feerate)?;
69 let required_value = adjusted_target + estimated_fees;
70
71 if accumulated_value == required_value {
72 let waste = calculate_waste(
73 options,
74 accumulated_value,
75 accumulated_weight,
76 estimated_fees,
77 );
78 let index_vector: Vec<usize> = selected_inputs.into_iter().collect();
79 return Ok(SelectionOutput {
80 selected_inputs: index_vector,
81 waste: WasteMetric(waste),
82 });
83 } else if accumulated_value >= required_value {
84 if accumulated_value < best_set_value {
85 best_set_value = accumulated_value;
86 best_set_weight = accumulated_weight;
87 best_set.clone_from(&selected_inputs);
88 }
89 selected_inputs.remove(&index);
90 accumulated_value -= value;
91 accumulated_weight -= weight;
92 }
93 }
94 }
95 }
96 accumulated_value = 0;
97 accumulated_weight = 0;
98 selected_inputs.clear();
99 }
100
101 if best_set_value == u64::MAX {
102 Err(SelectionError::NoSolutionFound)
103 } else {
104 let estimated_fees = calculate_fee(best_set_weight, options.target_feerate)?;
105 let waste = calculate_waste(options, best_set_value, best_set_weight, estimated_fees);
106 let index_vector: Vec<usize> = best_set.into_iter().collect();
107 Ok(SelectionOutput {
108 selected_inputs: index_vector,
109 waste: WasteMetric(waste),
110 })
111 }
112}
113
114#[cfg(test)]
115mod test {
116
117 use crate::{
118 algorithms::knapsack::select_coin_knapsack,
119 types::{CoinSelectionOpt, ExcessStrategy, OutputGroup, SelectionError},
120 utils::calculate_fee,
121 };
122
123 const CENT: f64 = 1000000.0;
124 const COIN: f64 = 100000000.0;
125 const RUN_TESTS: u32 = 100;
126 const RUN_TESTS_SLIM: u32 = 10;
127
128 fn knapsack_setup_options(adjusted_target: u64, target_feerate: f32) -> CoinSelectionOpt {
129 let min_change_value = 500;
130 let base_weight = 10;
131 let target_value = adjusted_target
132 - min_change_value
133 - calculate_fee(base_weight, target_feerate).unwrap_or_default();
134 CoinSelectionOpt {
135 target_value,
136 target_feerate, long_term_feerate: Some(0.4),
138 min_absolute_fee: 0,
139 base_weight,
140 change_weight: 50,
141 change_cost: 10,
142 avg_input_weight: 20,
143 avg_output_weight: 10,
144 min_change_value,
145 excess_strategy: ExcessStrategy::ToChange,
146 }
147 }
148
149 fn knapsack_setup_output_groups(
150 value: Vec<u64>,
151 weights: Vec<u64>,
152 target_feerate: f32,
153 ) -> Vec<OutputGroup> {
154 let mut inputs: Vec<OutputGroup> = Vec::new();
155 for (i, j) in value.into_iter().zip(weights.into_iter()) {
156 let k = i.saturating_add(calculate_fee(j, target_feerate).unwrap_or_default());
159 inputs.push(OutputGroup {
160 value: k,
161 weight: j,
162 input_count: 1,
163 creation_sequence: None,
164 })
165 }
166 inputs
167 }
168
169 fn knapsack_add_to_output_group(
170 inputs: &mut Vec<OutputGroup>,
171 value: Vec<u64>,
172 weights: Vec<u64>,
173 target_feerate: f32,
174 ) {
175 for (i, j) in value.into_iter().zip(weights.into_iter()) {
176 let k = i.saturating_add(calculate_fee(j, target_feerate).unwrap_or_default());
179 inputs.push(OutputGroup {
180 value: k,
181 weight: j,
182 input_count: 1,
183 creation_sequence: None,
184 })
185 }
186 }
187
188 fn knapsack_test_vectors() {
189 let mut inputs_verify: Vec<usize> = Vec::new();
190 for _ in 0..RUN_TESTS {
191 let mut inputs: Vec<OutputGroup> = Vec::new();
193 let mut options = knapsack_setup_options(1000, 0.33);
194 let mut result = select_coin_knapsack(&inputs, &options);
195 assert!(matches!(result, Err(SelectionError::NoSolutionFound)));
196
197 inputs = knapsack_setup_output_groups(
199 vec![(2.0 * CENT).round() as u64, (1.0 * CENT).round() as u64],
200 vec![130, 100],
201 0.56,
202 );
203 options = knapsack_setup_options((3.0 * CENT).round() as u64, 0.56);
204 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
205 assert_eq!(result.selected_inputs.len(), 2);
207 inputs_verify = vec![0, 1];
209 assert!(inputs_verify
210 .iter()
211 .all(|&item| result.selected_inputs.contains(&item)));
212 }
213 inputs_verify.clear();
214 knapsack_add_to_output_group(
216 &mut inputs,
217 vec![
218 (5.0 * CENT).round() as u64,
219 (10.0 * CENT).round() as u64,
220 (20.0 * CENT).round() as u64,
221 ],
222 vec![100, 10, 50],
223 0.56,
224 );
225 options = knapsack_setup_options((37.0 * CENT).round() as u64, 0.56);
227 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
228 assert_eq!(result.selected_inputs.len(), 4);
230 inputs_verify = vec![4, 3, 2, 0];
232 assert!(inputs_verify
233 .iter()
234 .all(|&item| result.selected_inputs.contains(&item)));
235 }
236 inputs_verify.clear();
237 options = knapsack_setup_options((38.0 * CENT).round() as u64, 0.56);
239 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
240 assert_eq!(result.selected_inputs.len(), 5);
242 inputs_verify = vec![4, 3, 2, 1, 0];
244 assert!(inputs_verify
245 .iter()
246 .all(|&item| result.selected_inputs.contains(&item)));
247 }
248 inputs_verify.clear();
249 options = knapsack_setup_options((34.0 * CENT).round() as u64, 0.56);
251 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
252 assert_eq!(result.selected_inputs.len(), 3);
254 inputs_verify = vec![4, 3, 2];
256 assert!(inputs_verify
257 .iter()
258 .all(|&item| result.selected_inputs.contains(&item)));
259 }
260 inputs_verify.clear();
261 options = knapsack_setup_options((7.0 * CENT).round() as u64, 0.56);
263 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
264 assert_eq!(result.selected_inputs.len(), 2);
266 inputs_verify = vec![0, 2];
268 assert!(inputs_verify
269 .iter()
270 .all(|&item| result.selected_inputs.contains(&item)));
271 }
272 inputs_verify.clear();
273 options = knapsack_setup_options((8.0 * CENT).round() as u64, 0.56);
275 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
276 assert_eq!(result.selected_inputs.len(), 3);
278 inputs_verify = vec![0, 2, 1];
280 assert!(inputs_verify
281 .iter()
282 .all(|&item| result.selected_inputs.contains(&item)));
283 }
284 inputs_verify.clear();
285 options = knapsack_setup_options((10.0 * CENT).round() as u64, 0.56);
287 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
288 assert_eq!(result.selected_inputs.len(), 1);
290 inputs_verify = vec![10];
292 assert!(inputs_verify
293 .iter()
294 .all(|&item| result.selected_inputs.contains(&item)));
295 }
296 inputs_verify.clear();
297 inputs.clear();
299 inputs = knapsack_setup_output_groups(
302 vec![
303 (6.0 * CENT).round() as u64,
304 (7.0 * CENT).round() as u64,
305 (8.0 * CENT).round() as u64,
306 (20.0 * CENT).round() as u64,
307 (30.0 * CENT).round() as u64,
308 ],
309 vec![100, 200, 100, 10, 5],
310 0.77,
311 );
312 options = knapsack_setup_options((72.0 * CENT).round() as u64, 0.77);
314 result = select_coin_knapsack(&inputs, &options);
315 assert!(matches!(result, Err(SelectionError::NoSolutionFound)));
316 options = knapsack_setup_options((16.0 * CENT).round() as u64, 0.77);
318 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
319 assert_eq!(result.selected_inputs.len(), 3);
321 inputs_verify = vec![0, 1, 2];
323 assert!(inputs_verify
324 .iter()
325 .all(|&item| result.selected_inputs.contains(&item)));
326 }
327 inputs_verify.clear();
328 knapsack_add_to_output_group(
330 &mut inputs,
331 vec![(5.0 * CENT).round() as u64],
332 vec![10],
333 0.77,
334 );
335 options = knapsack_setup_options((16.0 * CENT).round() as u64, 0.77);
337 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
338 assert_eq!(result.selected_inputs.len(), 3);
340 inputs_verify = vec![0, 1, 5];
342 assert!(inputs_verify
343 .iter()
344 .all(|&item| result.selected_inputs.contains(&item)));
345 }
346 inputs_verify.clear();
347
348 knapsack_add_to_output_group(
350 &mut inputs,
351 vec![(18.0 * CENT).round() as u64],
352 vec![1],
353 0.77,
354 );
355 options = knapsack_setup_options((11.0 * CENT).round() as u64, 0.77);
357 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
358 assert_eq!(result.selected_inputs.len(), 2);
360 inputs_verify = vec![0, 5];
362 assert!(inputs_verify
363 .iter()
364 .all(|&item| result.selected_inputs.contains(&item)));
365 }
366 inputs_verify.clear();
367 inputs.clear();
369 inputs = knapsack_setup_output_groups(
371 vec![
372 (0.101 * CENT).round() as u64,
373 (0.201 * CENT).round() as u64,
374 (0.301 * CENT).round() as u64,
375 (0.401 * CENT).round() as u64,
376 (0.501 * CENT).round() as u64,
377 ],
378 vec![14, 45, 6, 10, 100],
379 0.56,
380 );
381 options = knapsack_setup_options((1.0 * CENT).round() as u64, 0.56);
383 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
384 assert_eq!(result.selected_inputs.len(), 3);
386 inputs_verify = vec![0, 3, 4];
388 let valid_inputs_1 = inputs_verify
389 .iter()
390 .all(|&item| result.selected_inputs.contains(&item));
391 inputs_verify.clear();
392 inputs_verify = vec![1, 2, 4];
393 let valid_inputs_2 = inputs_verify
394 .iter()
395 .all(|&item| result.selected_inputs.contains(&item));
396 assert!(valid_inputs_1 || valid_inputs_2);
397 }
398 inputs_verify.clear();
399 inputs.clear();
401 inputs = knapsack_setup_output_groups(
403 vec![
404 (50000.0 * COIN).round() as u64,
405 (50000.0 * COIN).round() as u64,
406 (50000.0 * COIN).round() as u64,
407 (50000.0 * COIN).round() as u64,
408 (50000.0 * COIN).round() as u64,
409 (50000.0 * COIN).round() as u64,
410 (50000.0 * COIN).round() as u64,
411 (50000.0 * COIN).round() as u64,
412 (50000.0 * COIN).round() as u64,
413 (50000.0 * COIN).round() as u64,
414 (50000.0 * COIN).round() as u64,
415 ],
416 vec![1, 20, 3, 200, 150, 5, 88, 93, 101, 34, 17],
417 0.59,
418 );
419 options = knapsack_setup_options((500000.0 * COIN).round() as u64, 0.59);
421 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
422 assert_eq!(result.selected_inputs.len(), 10);
424 }
425 inputs.clear();
427 inputs = knapsack_setup_output_groups(
429 vec![
430 (0.4 * CENT).round() as u64,
431 (0.6 * CENT).round() as u64,
432 (0.8 * CENT).round() as u64,
433 (1111.0 * CENT).round() as u64,
434 ],
435 vec![14, 45, 6, 10],
436 0.56,
437 );
438 options = knapsack_setup_options((1.0 * CENT).round() as u64, 0.56);
440 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
441 assert_eq!(result.selected_inputs.len(), 2);
443 inputs_verify = vec![0, 1];
445 assert!(inputs_verify
446 .iter()
447 .all(|&item| result.selected_inputs.contains(&item)));
448 }
449 inputs_verify.clear();
450 inputs.clear();
452 inputs = knapsack_setup_output_groups(
454 vec![
455 (100.0 * CENT).round() as u64,
456 (1.0 * CENT).round() as u64,
457 (0.05 * CENT).round() as u64,
458 ],
459 vec![14, 45, 6],
460 0.56,
461 );
462 options = CoinSelectionOpt {
464 target_value: (100.01 * CENT).round() as u64,
465 target_feerate: 0.56, long_term_feerate: Some(0.4),
467 min_absolute_fee: 0,
468 base_weight: 10,
469 change_weight: 50,
470 change_cost: 10,
471 avg_input_weight: 20,
472 avg_output_weight: 10,
473 min_change_value: (0.05 * CENT).round() as u64, excess_strategy: ExcessStrategy::ToChange,
475 };
476 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
477 assert_eq!(result.selected_inputs.len(), 2);
479 inputs_verify = vec![0, 1];
481 assert!(inputs_verify
482 .iter()
483 .all(|&item| result.selected_inputs.contains(&item)));
484 }
485 inputs_verify.clear();
486 inputs.clear();
488 }
489 let mut inputs: Vec<OutputGroup> = Vec::new();
491 let mut amt = 1500;
492 while amt < COIN as u64 {
494 inputs.clear();
495 let mut input_value: Vec<u64> = Vec::new();
497 let mut input_weight: Vec<u64> = Vec::new();
498 for _ in 0..676 {
499 input_value.push(amt);
502 input_weight.push(23);
503 }
504 let inputs = knapsack_setup_output_groups(input_value, input_weight, 0.34);
505 let options = knapsack_setup_options(2000, 0.34);
507 for _ in 0..RUN_TESTS_SLIM {
509 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
510 if let Some(amt_in_inputs) = inputs.first() {
511 if amt_in_inputs.value.checked_sub(2000) < Some(CENT as u64) {
514 let return_size = ((2000.0) / amt as f64).ceil();
516 assert_eq!(result.selected_inputs.len(), return_size as usize);
517 } else {
518 assert_eq!(result.selected_inputs.len(), 1);
520 }
521 } else {
522 println!("unable to access 0th element of input vector");
523 }
524 }
525 }
526 amt *= 10;
527 }
528 inputs.clear();
529 let mut input_value: Vec<u64> = Vec::new();
532 let mut input_weight: Vec<u64> = Vec::new();
533 for _ in 0..=100 {
534 input_value.push(COIN as u64);
536 input_weight.push(23);
537 }
538 let mut inputs = knapsack_setup_output_groups(input_value, input_weight, 0.34);
540 let options = knapsack_setup_options((50.0 * COIN).round() as u64, 0.34);
542 let mut selected_input_1: Vec<usize> = Vec::new();
543 let mut selected_input_2: Vec<usize> = Vec::new();
544 for _ in 0..RUN_TESTS {
545 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
546 selected_input_1.clone_from(&result.selected_inputs);
547 }
548 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
549 selected_input_2.clone_from(&result.selected_inputs);
550 }
551 assert_ne!(selected_input_1, selected_input_2);
553 }
554 selected_input_1.clear();
555 selected_input_2.clear();
556 knapsack_add_to_output_group(
558 &mut inputs,
559 vec![
560 (5.0 * CENT).round() as u64,
561 (10.0 * CENT).round() as u64,
562 (15.0 * CENT).round() as u64,
563 (20.0 * CENT).round() as u64,
564 (25.0 * CENT).round() as u64,
565 ],
566 vec![100, 10, 50, 52, 13],
567 0.34,
568 );
569 }
570
571 #[test]
572 fn test_knapsack() {
573 knapsack_test_vectors();
574 }
575}