diff --git a/src/errors.rs b/src/errors.rs index 5bbacaa..35caf38 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -5,24 +5,22 @@ use std::{error::Error, fmt}; #[derive(Debug)] pub enum LibError { Multiplication(u64, u64), - FeeRateSize, - WeightSize, + FeeRate, + Size, } -#[cfg(any(test, feature = "rand"))] impl Error for LibError {} -#[cfg(any(test, feature = "rand"))] impl fmt::Display for LibError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - LibError::Multiplication(vbytes, fee_rate) => { - write!(f, "{} * {} exceeds u64 Max", vbytes, fee_rate) + LibError::Multiplication(size, fee_rate) => { + write!(f, "{} * {} exceeds u64 Max", size, fee_rate) } - LibError::FeeRateSize => { + LibError::FeeRate => { write!(f, "fee-rate is equal to zero") } - LibError::WeightSize => { + LibError::Size => { write!(f, "weight is equal to zero") } } diff --git a/src/fee.rs b/src/fee.rs index 6938e2b..bc3fdb1 100644 --- a/src/fee.rs +++ b/src/fee.rs @@ -1,19 +1,23 @@ -/// Function for computing a fee. +/// Function(s) for computing a fee. use crate::errors::LibError; -/// Get fee in vbytes -pub fn get_fee_vbytes(fee_rate: u64, weight: u64) -> Result { +/// Takes as input the fee_rate in $ per Kilovirtualbytes +/// and a size in Kilovirtualbytes and returns the fee amount. +/// +/// * fee_rate: $ per kilovirtualbytes $KvB +/// * size: kilovirtualbytes KvB +/// * fee: $ +pub fn get_fee(fee_rate: u64, size: u64) -> Result { if fee_rate == 0 { - Err(LibError::FeeRateSize) - } else if weight == 0 { - Err(LibError::WeightSize) + Err(LibError::FeeRate) + } else if size == 0 { + Err(LibError::Size) } else { - let vbytes = weight / 4; - let fee = fee_rate.checked_mul(vbytes); + let fee = fee_rate.checked_mul(size); match fee { - None => Err(LibError::Multiplication(vbytes, fee_rate)), + None => Err(LibError::Multiplication(size, fee_rate)), Some(fee) => Ok(fee) } } @@ -25,36 +29,35 @@ mod tests { #[test] fn get_effective_value() { - let fee_rate = 10; - let weight = 1_000; + let fee_rate = 1103; + let size = 1_00; - // fee in sats/vbytes - let fee = get_fee_vbytes(fee_rate, weight).expect("fee calculation failed"); - assert_eq!(2_500, fee); + let fee = get_fee(fee_rate, size).expect("fee calculation failed"); + assert_eq!(110_300, fee); } #[test] - fn fee_rate_size_error() { + fn fee_rate_error() { let fee_rate = 0; - let weight = 1_000; - let error = get_fee_vbytes(fee_rate, weight).expect_err("expected fee_rate size error"); + let size = 1_000; + let error = get_fee(fee_rate, size).expect_err("expected fee_rate size error"); assert_eq!(error.to_string(), "fee-rate is equal to zero"); } #[test] - fn weight_size_error() { + fn size_error() { let fee_rate = 10; - let weight = 0; - let error = get_fee_vbytes(fee_rate, weight).expect_err("expected weight size error"); + let size = 0; + let error = get_fee(fee_rate, size).expect_err("expected weight size error"); assert_eq!(error.to_string(), "weight is equal to zero"); } #[test] fn multiplication_error() { let fee_rate = std::u64::MAX; - let weight = 1_000; + let size = 1_000; - let error = get_fee_vbytes(fee_rate, weight).expect_err("expected multiplication overflow"); - assert_eq!(error.to_string(), "250 * 18446744073709551615 exceeds u64 Max"); + let error = get_fee(fee_rate, size).expect_err("expected multiplication overflow"); + assert_eq!(error.to_string(), "1000 * 18446744073709551615 exceeds u64 Max"); } } diff --git a/src/lib.rs b/src/lib.rs index 260febb..b48e13b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,8 +44,8 @@ pub trait Utxo: Clone { /// Return the value of the UTXO. fn get_value(&self) -> u64; - /// Return the weight of the UTXO. - fn get_weight(&self) -> u64; + /// Return the size of the UTXO in Kilovirtualbytes. + fn get_size(&self) -> u64; } /// Select coins first using BnB algorithm similar to what is done in bitcoin diff --git a/src/single_random_draw.rs b/src/single_random_draw.rs index 0673c9f..ff60d75 100644 --- a/src/single_random_draw.rs +++ b/src/single_random_draw.rs @@ -4,7 +4,7 @@ use crate::Utxo; use crate::CHANGE_LOWER; use rand::seq::SliceRandom; -use crate::fee::get_fee_vbytes; +use crate::fee::get_fee; use crate::errors::LibError; /// Randomly select coins for the given target by shuffling the utxo pool and @@ -31,7 +31,7 @@ pub fn select_coins_srd( while sum < lower_bound && i < utxo_pool.len() { let utxo = &utxo_pool[i]; - let fee = get_fee_vbytes(fee_rate, utxo.get_weight())?; + let fee = get_fee(fee_rate, utxo.get_size())?; let effective_value = utxo.get_value() - fee; sum += effective_value; @@ -55,18 +55,22 @@ mod tests { // https://github.com/bitcoin/bitcoin/blob/b264410e012a61b103e1a03c43df4e17b9b75452/src/test/util/setup_common.h#L80 const CENT: u64 = 1_000_000; - const WEIGHT: u64 = 1_00; - const FEE_RATE: u64 = 10; + + // Kilovirtualbytes + const SIZE: u64 = 2; + + // $/KvB + const FEE_RATE: u64 = 1013; const UTXO_POOL: [MinimalUtxo; 2] = [ - MinimalUtxo { value: CENT + CHANGE_LOWER, weight: WEIGHT }, - MinimalUtxo { value: 2 * CENT + CHANGE_LOWER, weight: WEIGHT }, + MinimalUtxo { value: CENT + CHANGE_LOWER, size: SIZE }, + MinimalUtxo { value: 2 * CENT + CHANGE_LOWER, size: SIZE }, ]; #[derive(Clone, Debug, Eq, PartialEq)] struct MinimalUtxo { value: u64, - weight: u64, + size: u64, } impl Utxo for MinimalUtxo { @@ -74,8 +78,8 @@ mod tests { self.value } - fn get_weight(&self) -> u64 { - self.weight + fn get_size(&self) -> u64 { + self.size } } @@ -95,8 +99,8 @@ mod tests { // For each UTXO that's in the result set, subtract the FEE for each UTXO. fn target_minus_fee(target: u64, expected_size: usize) -> u64 { - let vbytes = WEIGHT / 4; - let fee = vbytes * FEE_RATE; + //let vbytes = WEIGHT / 4; + let fee = SIZE * FEE_RATE; target - (fee * expected_size as u64) } @@ -111,7 +115,7 @@ mod tests { #[test] fn select_coins_srd_with_solution() { - let expected_result = vec![MinimalUtxo { value: 2 * CENT + CHANGE_LOWER, weight: WEIGHT }]; + let expected_result = vec![MinimalUtxo { value: 2 * CENT + CHANGE_LOWER, size: SIZE}]; let utxo_match = select_coins_srd( target_minus_fee(2 * CENT, 1), &mut UTXO_POOL.clone(), @@ -126,8 +130,8 @@ mod tests { #[test] fn select_coins_all_solution() { let expected_result = vec![ - MinimalUtxo { value: CENT * 2 + CHANGE_LOWER, weight: WEIGHT }, - MinimalUtxo { value: CENT + CHANGE_LOWER, weight: WEIGHT }, + MinimalUtxo { value: CENT * 2 + CHANGE_LOWER, size: SIZE }, + MinimalUtxo { value: CENT + CHANGE_LOWER, size: SIZE }, ]; let utxo_match = select_coins_srd( @@ -143,7 +147,7 @@ mod tests { #[test] fn select_coins_srd_effective_value_too_small() { - let mut utxo_pool = [MinimalUtxo { value: CENT, weight: WEIGHT }]; + let mut utxo_pool = [MinimalUtxo { value: CENT, size: SIZE}]; // This test will fail if SRD is using value instead of effective_value let utxo_match = select_coins_srd(CENT, &mut utxo_pool, FEE_RATE, &mut get_rng()) @@ -153,23 +157,24 @@ mod tests { #[test] fn select_coins_srd_with_error() { - let mut utxo_pool = vec![MinimalUtxo { value: std::u64::MAX, weight: WEIGHT }]; + let mut utxo_pool = vec![MinimalUtxo { value: std::u64::MAX, size: SIZE}]; let fee_rate = std::u64::MAX; let error = select_coins_srd(CENT, &mut utxo_pool, fee_rate, &mut get_rng()) .expect_err("expected multiplication overflow"); - assert_eq!(error.to_string(), "25 * 18446744073709551615 exceeds u64 Max"); + assert_eq!(error.to_string(), "2 * 18446744073709551615 exceeds u64 Max"); } #[test] fn select_coins_srd_with_error_can_succeed() { let value = CENT + CHANGE_LOWER; - let expected_result = vec![MinimalUtxo { value, weight: WEIGHT }]; + let expected_result = vec![MinimalUtxo { value, size: SIZE }]; let mut utxo_pool = vec![ - MinimalUtxo { value, weight: std::u64::MAX }, - MinimalUtxo { value, weight: WEIGHT }, + MinimalUtxo { value, size: std::u64::MAX }, + MinimalUtxo { value, size: SIZE }, ]; + let utxo_match = select_coins_srd(target_minus_fee(CENT, 1), &mut utxo_pool, FEE_RATE, &mut get_rng()) .expect("unexpected error"); @@ -179,7 +184,7 @@ mod tests { #[test] fn select_coins_srd_change_output_too_small() { - let mut utxo_pool = [MinimalUtxo { value: CENT, weight: WEIGHT }]; + let mut utxo_pool = [MinimalUtxo { value: CENT, size: SIZE }]; let utxo_match = select_coins_srd(target_minus_fee(CENT, 1), &mut utxo_pool, FEE_RATE, &mut get_rng())