Skip to content

Commit

Permalink
draft
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Feb 23, 2023
1 parent 4b3e4d4 commit d1d38b1
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 54 deletions.
14 changes: 6 additions & 8 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
Expand Down
49 changes: 26 additions & 23 deletions src/fee.rs
Original file line number Diff line number Diff line change
@@ -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<u64, LibError> {
/// 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<u64, LibError> {
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)
}
}
Expand All @@ -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");
}
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 26 additions & 21 deletions src/single_random_draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,7 +31,7 @@ pub fn select_coins_srd<T: Utxo, R: rand::Rng + ?Sized>(
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;

Expand All @@ -55,27 +55,31 @@ 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 {
fn get_value(&self) -> u64 {
self.value
}

fn get_weight(&self) -> u64 {
self.weight
fn get_size(&self) -> u64 {
self.size
}
}

Expand All @@ -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)
}

Expand All @@ -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(),
Expand All @@ -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(
Expand All @@ -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())
Expand All @@ -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");
Expand All @@ -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())
Expand Down

0 comments on commit d1d38b1

Please sign in to comment.