use crate::{
types::{CoinSelectionOpt, OutputGroup, SelectionError, SelectionOutput, WasteMetric},
utils::{calculate_fee, calculate_waste},
};
use rand::{seq::SliceRandom, thread_rng};
pub fn select_coin_srd(
inputs: &[OutputGroup],
options: &CoinSelectionOpt,
) -> Result<SelectionOutput, SelectionError> {
let mut randomized_inputs: Vec<_> = inputs.iter().enumerate().collect();
let base_fees = calculate_fee(options.base_weight, options.target_feerate).unwrap_or_default();
let target =
options.target_value + options.min_change_value + base_fees.max(options.min_absolute_fee);
let mut rng = thread_rng();
randomized_inputs.shuffle(&mut rng);
let mut accumulated_value = 0;
let mut selected_inputs = Vec::new();
let mut accumulated_weight = 0;
let mut estimated_fee = 0;
let mut _input_counts = 0;
for (index, input) in randomized_inputs {
selected_inputs.push(index);
accumulated_value += input.value;
accumulated_weight += input.weight;
_input_counts += input.input_count;
estimated_fee = calculate_fee(accumulated_weight, options.target_feerate)?;
if accumulated_value >= target + estimated_fee {
break;
}
}
if accumulated_value < target + estimated_fee {
return Err(SelectionError::InsufficientFunds);
}
let waste = calculate_waste(
options,
accumulated_value,
accumulated_weight,
estimated_fee,
);
Ok(SelectionOutput {
selected_inputs,
waste: WasteMetric(waste),
})
}
#[cfg(test)]
mod test {
use crate::{
algorithms::srd::select_coin_srd,
types::{CoinSelectionOpt, ExcessStrategy, OutputGroup, SelectionError},
};
fn setup_basic_output_groups() -> Vec<OutputGroup> {
vec![
OutputGroup {
value: 1000,
weight: 100,
input_count: 1,
creation_sequence: None,
},
OutputGroup {
value: 2000,
weight: 200,
input_count: 1,
creation_sequence: None,
},
OutputGroup {
value: 3000,
weight: 300,
input_count: 1,
creation_sequence: None,
},
]
}
fn setup_output_groups_withsequence() -> Vec<OutputGroup> {
vec![
OutputGroup {
value: 1000,
weight: 100,
input_count: 1,
creation_sequence: Some(1),
},
OutputGroup {
value: 2000,
weight: 200,
input_count: 1,
creation_sequence: Some(5000),
},
OutputGroup {
value: 3000,
weight: 300,
input_count: 1,
creation_sequence: Some(1001),
},
OutputGroup {
value: 1500,
weight: 150,
input_count: 1,
creation_sequence: None,
},
]
}
fn setup_options(target_value: u64) -> CoinSelectionOpt {
CoinSelectionOpt {
target_value,
target_feerate: 0.4, long_term_feerate: Some(0.4),
min_absolute_fee: 0,
base_weight: 10,
change_weight: 50,
change_cost: 10,
avg_input_weight: 20,
avg_output_weight: 10,
min_change_value: 500,
excess_strategy: ExcessStrategy::ToChange,
}
}
fn test_successful_selection() {
let mut inputs = setup_basic_output_groups();
let mut options = setup_options(2500);
let mut result = select_coin_srd(&inputs, &options);
assert!(result.is_ok());
let mut selection_output = result.unwrap();
assert!(!selection_output.selected_inputs.is_empty());
inputs = setup_output_groups_withsequence();
options = setup_options(500);
result = select_coin_srd(&inputs, &options);
assert!(result.is_ok());
selection_output = result.unwrap();
assert!(!selection_output.selected_inputs.is_empty());
}
fn test_insufficient_funds() {
let inputs = setup_basic_output_groups();
let options = setup_options(7000); let result = select_coin_srd(&inputs, &options);
assert!(matches!(result, Err(SelectionError::InsufficientFunds)));
}
#[test]
fn test_srd() {
test_successful_selection();
test_insufficient_funds();
}
}