From 0d50d7c6cbda0dd615fac26ec2036fbee7d98c71 Mon Sep 17 00:00:00 2001 From: yancy Date: Wed, 25 Sep 2024 11:10:36 -0500 Subject: [PATCH] refactor srd tests --- src/single_random_draw.rs | 211 +++++++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 84 deletions(-) diff --git a/src/single_random_draw.rs b/src/single_random_draw.rs index 6e2c297..084f9bb 100644 --- a/src/single_random_draw.rs +++ b/src/single_random_draw.rs @@ -22,11 +22,21 @@ use crate::{WeightedUtxo, CHANGE_LOWER}; /// /// /// ## Parameters -/// /// -/// /// * `target` - target value to send to recipient. Include the fee to pay for the known parts of the transaction excluding the fee for the inputs. -/// /// * `fee_rate` - ratio of transaction amount per size. -/// /// * `weighted_utxos` - Weighted UTXOs from which to sum the target amount. -/// /// * `rng` - used primarily by tests to make the selection deterministic. +/// +/// * `target` - target value to send to recipient. Include the fee to pay for the known parts of the transaction excluding the fee for the inputs. +/// * `fee_rate` - ratio of transaction amount per size. +/// * `weighted_utxos` - Weighted UTXOs from which to sum the target amount. +/// * `rng` - used primarily by tests to make the selection deterministic. +/// +/// # Returns +/// +/// * `Some(Vec)` where `Vec` is empty on no matches found. An empty +/// vec signifies that all possibilities where explored successfully and no match could be +/// found with the given parameters. +/// +/// * `None` un-expected results during search. A future implementation can replace all `None` +/// returns with a more informative error. Example of error: iteration limit hit, overflow +/// when summing the UTXO space, Not enough potential amount to meet the target, etc. pub fn select_coins_srd<'a, R: rand::Rng + ?Sized, Utxo: WeightedUtxo>( target: Amount, fee_rate: FeeRate, @@ -52,7 +62,7 @@ pub fn select_coins_srd<'a, R: rand::Rng + ?Sized, Utxo: WeightedUtxo>( value += match effective_value.to_unsigned() { Ok(amt) => amt, - Err(_) => continue, + Err(_) => {println!("error"); continue}, }; result.push(w_utxo); @@ -85,6 +95,13 @@ mod tests { satisfaction_weight: Weight, } + #[derive(Debug)] + pub struct ParamsStr<'a> { + target: &'a str, + fee_rate: &'a str, + weighted_utxos: Vec<&'a str>, + } + impl WeightedUtxo for Utxo { fn satisfaction_weight(&self) -> Weight { self.satisfaction_weight } @@ -123,127 +140,153 @@ mod tests { StepRng::new(0, 0) } - #[test] - fn select_coins_srd_with_solution() { - let target: Amount = Amount::from_str("1.5 cBTC").unwrap(); - let pool = build_pool(); - - let result: Vec<&Utxo> = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()) - .expect("unexpected error") - .collect(); + fn format_utxo_list(i: &[&Utxo]) -> Vec { + i.iter().map(|u| u.value().to_string()).collect() + } - let expected_result = Amount::from_str("2 cBTC").unwrap(); - assert_eq!(result.len(), 1); - assert_eq!(expected_result, result[0].output.value); + fn format_expected_str_list(e: &[&str]) -> Vec { + e.iter().map(|s| Amount::from_str(s).unwrap().to_string()).collect() } - #[test] - fn select_coins_srd_no_solution() { - let target: Amount = Amount::from_str("4 cBTC").unwrap(); - let pool = build_pool(); + fn assert_coin_select_params(p: &ParamsStr, expected_inputs: Option<&[&str]>) { + let fee_rate = p.fee_rate.parse::().unwrap(); // would be nice if FeeRate had + // from_str like Amount::from_str() + let target = Amount::from_str(p.target).unwrap(); + let fee_rate = FeeRate::from_sat_per_kwu(fee_rate); + + let w_utxos: Vec<_> = p + .weighted_utxos + .iter() + .map(|s| { + let v: Vec<_> = s.split("/").collect(); + match v.len() { + 2 => { + let a = Amount::from_str(v[0]).unwrap(); + let w = Weight::from_wu(v[1].parse().unwrap()); + (a, w) + }, + 1 => { + let a = Amount::from_str(v[0]).unwrap(); + (a, Weight::ZERO) + }, + _ => panic!() + } + }) + .map(|(a, w)| build_utxo(a, w)) + .collect(); + + let result = select_coins_srd(target, fee_rate, &w_utxos, &mut get_rng()); - let result = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()); - assert!(result.is_none()) + if expected_inputs.is_none() { + assert!(result.is_none()); + } else { + let inputs: Vec<_> = result.unwrap().collect(); + let expected_str_list: Vec = + expected_inputs.unwrap().iter().map(|s| Amount::from_str(s).unwrap().to_string()).collect(); + let input_str_list: Vec = format_utxo_list(&inputs); + assert_eq!(input_str_list, expected_str_list); + } } - #[test] - fn select_coins_srd_all_solution() { - let target: Amount = Amount::from_str("2.5 cBTC").unwrap(); + fn assert_coin_select(target_str: &str, expected_inputs: &[&str]) { + let target = Amount::from_str(target_str).unwrap(); let pool = build_pool(); - let result: Vec<&Utxo> = select_coins_srd(target, FeeRate::ZERO, &pool, &mut get_rng()) + let inputs: Vec<&Utxo> = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()) .expect("unexpected error") .collect(); - let expected_second_element = Amount::from_str("1 cBTC").unwrap(); - let expected_first_element = Amount::from_str("2 cBTC").unwrap(); + let input_str_list: Vec<_> = format_utxo_list(&inputs); + let expected_str_list: Vec<_> = format_expected_str_list(expected_inputs); + assert_eq!(input_str_list, expected_str_list); + } - assert_eq!(result.len(), 2); - assert_eq!(result[0].output.value, expected_first_element); - assert_eq!(result[1].output.value, expected_second_element); + #[test] + fn select_coins_srd_with_solution() { + assert_coin_select("1.5 cBTC", &["2 cBTC"]); } #[test] - fn select_coins_skip_negative_effective_value() { - let target: Amount = Amount::from_str("2 cBTC").unwrap() - CHANGE_LOWER; - let mut pool = build_pool(); + fn select_coins_srd_all_solution() { + assert_coin_select("2.5 cBTC", &["2 cBTC", "1 cBTC"]); + } - let negative_eff_value = Amount::from_str("1 sat").unwrap(); - let utxo = build_utxo(negative_eff_value, SATISFACTION_WEIGHT); - pool.push(utxo); + #[test] + fn select_coins_srd_no_solution() { + let params = ParamsStr { + target: "4 cBTC", + fee_rate: "0", + weighted_utxos: vec!["1 cBTC", "2 cBTC"], + }; - let result: Vec<_> = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()) - .expect("unexpected error") - .collect(); + assert_coin_select_params(¶ms, None); + } - let expected_second_element = Amount::from_str("1 cBTC").unwrap(); - let expected_first_element = Amount::from_str("2 cBTC").unwrap(); + #[test] + fn select_coins_skip_negative_effective_value() { + let params = ParamsStr { + target: "1.95 cBTC", // 2 cBTC - CHANGE_LOWER + fee_rate: "10", + weighted_utxos: vec!["1 cBTC", "2 cBTC", "1 sat/204"], // 1 sat @ 204 wu has negative effective_value + }; - assert_eq!(result.len(), 2); - assert_eq!(result[0].output.value, expected_first_element); - assert_eq!(result[1].output.value, expected_second_element); + assert_coin_select_params(¶ms, Some(&["2 cBTC", "1 cBTC"])); } #[test] fn select_coins_srd_fee_rate_error() { - let target: Amount = Amount::from_str("2 cBTC").unwrap(); - let pool = build_pool(); + println!("test"); + + let params = ParamsStr { + target: "1 cBTC", + fee_rate: "18446744073709551615", + weighted_utxos: vec!["1 cBTC", "2 cBTC"], + }; - let result = select_coins_srd(target, FeeRate::MAX, &pool, &mut get_rng()); - assert!(result.is_none()); + assert_coin_select_params(¶ms, None); } #[test] fn select_coins_srd_change_output_too_small() { - let target: Amount = Amount::from_str("3 cBTC").unwrap(); - let pool = build_pool(); + let params = ParamsStr { + target: "3 cBTC", + fee_rate: "10", + weighted_utxos: vec!["1 cBTC", "2 cBTC"], + }; - let result = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()); - assert!(result.is_none()); + assert_coin_select_params(¶ms, None); } #[test] fn select_coins_srd_with_high_fee() { - // the first UTXO is 2 cBTC. If the fee is greater than 10 sats, - // then more than the single 2 cBTC output will need to be selected - // if the target is 1.99999 cBTC. That is, 2 cBTC - 1.9999 cBTC = 10 sats. - let target: Amount = Amount::from_str("1.99999 cBTC").unwrap(); - - // fee = 15 sats, since - // 40 sat/kwu * (204 + BASE_WEIGHT) = 15 sats - let fee_rate: FeeRate = FeeRate::from_sat_per_kwu(40); - let pool = build_pool(); - - let result: Vec<_> = select_coins_srd(target, fee_rate, &pool, &mut get_rng()) - .expect("unexpected error") - .collect(); - let expected_second_element = Amount::from_str("1 cBTC").unwrap(); - let expected_first_element = Amount::from_str("2 cBTC").unwrap(); + let params = ParamsStr { + target: "1.99999 cBTC", + fee_rate: "10", + weighted_utxos: vec!["1 cBTC", "2 cBTC"], + }; - assert_eq!(result.len(), 2); - assert_eq!(result[0].output.value, expected_first_element); - assert_eq!(result[1].output.value, expected_second_element); + assert_coin_select_params(¶ms, Some(&["2 cBTC", "1 cBTC"])); } #[test] fn select_coins_srd_addition_overflow() { - let target: Amount = Amount::from_str("2 cBTC").unwrap(); - let amt = Amount::from_str("1 cBTC").unwrap(); - let utxo = build_utxo(amt, Weight::MAX); - let pool = vec![utxo]; + let params = ParamsStr { + target: "2 cBTC", + fee_rate: "10", + weighted_utxos: vec!["1 cBTC/18446744073709551615"], // weight= u64::MAX + }; - let result = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()); - assert!(result.is_none()); + assert_coin_select_params(¶ms, None); } #[test] fn select_coins_srd_threshold_overflow() { - let target: Amount = Amount::MAX; - let amt = Amount::from_str("1 cBTC").unwrap(); - let utxo = build_utxo(amt, Weight::MAX); - let pool = vec![utxo]; + let params = ParamsStr { + target: "18446744073709551615 sat", // u64::MAX + fee_rate: "10", + weighted_utxos: vec!["1 cBTC/18446744073709551615"], + }; - let result = select_coins_srd(target, FEE_RATE, &pool, &mut get_rng()); - assert!(result.is_none()); + assert_coin_select_params(¶ms, None); } }