diff --git a/Cargo.toml b/Cargo.toml index 181920d..3dc44fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ keywords = ["crypto", "bitcoin"] readme = "README.md" [dependencies] -bitcoin = { git="https://github.com/rust-bitcoin/rust-bitcoin", branch="master" } +bitcoin = { git="https://github.com/yancyribbens/rust-bitcoin", branch="add-effective-value-calculation" } rand = {version = "0.8.5", default-features = false, optional = true} [dev-dependencies] @@ -23,4 +23,4 @@ rust-bitcoin-coin-selection = {path = ".", features = ["rand"]} rand = "0.8.5" [patch.crates-io] -bitcoin_hashes = { git="https://github.com/rust-bitcoin/rust-bitcoin.git" } +bitcoin-io = { git = "https://github.com/yancyribbens/rust-bitcoin", branch = "add-effective-value-calculation" } diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index 3f6ff32..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,26 +0,0 @@ -/// Error types. -use bitcoin::FeeRate; -use bitcoin::Weight; -use std::error::Error as E; -use std::fmt; - -#[derive(Debug)] -pub enum Error { - MultiplicationOverflow(Weight, FeeRate), - AdditionOverflow(Weight, Weight), -} - -impl E for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::MultiplicationOverflow(one, two) => { - write!(f, "{} * {} exceeds u64 Max", one, two) - } - Error::AdditionOverflow(one, two) => { - write!(f, "{} + {} exceeds u64 Max", one, two) - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index a5653eb..ee14c93 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,6 @@ #![deny(non_snake_case)] #![deny(unused_mut)] #![deny(missing_docs)] - // Experimental features we need. #![cfg_attr(bench, feature(test))] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -19,7 +18,6 @@ extern crate test; use std::cmp::Reverse; -mod errors; mod single_random_draw; use bitcoin::Amount; @@ -27,7 +25,6 @@ use bitcoin::FeeRate; use bitcoin::TxOut; use bitcoin::Weight; -use crate::errors::Error; use crate::single_random_draw::select_coins_srd; use rand::thread_rng; @@ -47,9 +44,9 @@ const CHANGE_LOWER: Amount = Amount::from_sat(50_000); /// #[derive(Clone, Debug, PartialEq)] pub struct WeightedUtxo { - /// The satisfaction_weight is the size of the required params to satisfy the UTXO. + /// The satisfaction_weight is the size of the required params to satisfy the UTXO. pub satisfaction_weight: Weight, - /// The corresponding UTXO. + /// The corresponding UTXO. pub utxo: TxOut, } @@ -66,14 +63,10 @@ pub fn select_coins( fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], utxo_pool: &mut [T], -) -> Result, Error> { +) -> Option> { match select_coins_bnb(target.to_sat(), cost_of_change, utxo_pool) { - Some(_res) => Ok(Vec::new()), - None => Ok(select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()) - .unwrap() - .into_iter() - .map(|w| w.utxo) - .collect()), + Some(_res) => Some(Vec::new()), + None => select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()), } } diff --git a/src/single_random_draw.rs b/src/single_random_draw.rs index 42c602f..bab835b 100644 --- a/src/single_random_draw.rs +++ b/src/single_random_draw.rs @@ -1,39 +1,13 @@ //! This library provides efficient algorithms to compose a set of unspent transaction outputs //! (UTXOs). -use crate::errors::Error; use crate::WeightedUtxo; use crate::CHANGE_LOWER; +use bitcoin::blockdata::effective_value; use bitcoin::Amount; use bitcoin::FeeRate; -use bitcoin::TxIn; use rand::seq::SliceRandom; -/// Calculates the effective_value of an input. -/// -/// Returns `Ok(None)` if the effective_value is negative. If the effective_value is positive, return `Ok(Some(Amount))`. -/// -/// ## Errors -/// -/// Returns `Err(Error::Multiplication)` if `FeeRate` * `Weight` overflows. -/// -fn get_effective_value( - weighted_utxo: &WeightedUtxo, - fee_rate: FeeRate, -) -> Result, Error> { - let satisfaction_weight = weighted_utxo.satisfaction_weight; - - let weight = satisfaction_weight - .checked_add(TxIn::BASE_WEIGHT) - .ok_or(Error::AdditionOverflow(satisfaction_weight, TxIn::BASE_WEIGHT))?; - - let input_fee = fee_rate - .checked_mul_by_weight(weight) - .ok_or(Error::MultiplicationOverflow(satisfaction_weight, fee_rate))?; - - Ok(weighted_utxo.utxo.value.checked_sub(input_fee)) -} - /// Randomly select coins for the given target by shuffling the UTXO pool and /// taking UTXOs until the given target is reached. /// @@ -59,7 +33,7 @@ pub fn select_coins_srd( fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], rng: &mut R, -) -> Result, Error> { +) -> Option> { let mut result: Vec = Vec::new(); weighted_utxos.shuffle(rng); @@ -68,22 +42,22 @@ pub fn select_coins_srd( let mut value = Amount::ZERO; for w_utxo in weighted_utxos { - let effective_value: Option = get_effective_value(w_utxo, fee_rate)?; + let utxo_value = w_utxo.utxo.value; + let effective_value = effective_value(fee_rate, w_utxo.satisfaction_weight, utxo_value)?; - // skip if effective_value is negative. - match effective_value { - Some(e) => value += e, - None => continue, - } + value += match effective_value.to_unsigned() { + Ok(amt) => amt, + Err(_) => continue, + }; result.push(w_utxo.clone()); if value >= threshold { - return Ok(result); + return Some(result); } } - Ok(Vec::new()) + Some(Vec::new()) } #[cfg(test)] @@ -171,20 +145,24 @@ mod tests { #[test] fn select_coins_skip_negative_effective_value() { - let target: Amount = Amount::from_str("1 cBTC").unwrap() - CHANGE_LOWER; + let target: Amount = Amount::from_str("2 cBTC").unwrap() - CHANGE_LOWER; - let mut weighted_utxos: Vec = vec![WeightedUtxo { + let mut weighted_utxos = create_weighted_utxos(); + weighted_utxos.push(WeightedUtxo { satisfaction_weight: Weight::ZERO, utxo: TxOut { value: Amount::from_str("1 sat").unwrap(), script_pubkey: ScriptBuf::new(), }, - }]; + }); - let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng()) + let mut rng = get_rng(); + let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut rng) .expect("unexpected error"); - assert!(result.is_empty()); + let mut expected_utxos = create_weighted_utxos(); + expected_utxos.shuffle(&mut rng); + assert_eq!(result, expected_utxos); } #[test] @@ -192,11 +170,8 @@ mod tests { let target: Amount = Amount::from_str("2 cBTC").unwrap(); let mut weighted_utxos: Vec = create_weighted_utxos(); - let result: Error = - select_coins_srd(target, FeeRate::MAX, &mut weighted_utxos, &mut get_rng()) - .expect_err("expected error"); - - assert_eq!(result.to_string(), "204 * 18446744073709551615 exceeds u64 Max"); + let result = select_coins_srd(target, FeeRate::MAX, &mut weighted_utxos, &mut get_rng()); + assert!(result.is_none()); } #[test] @@ -212,10 +187,14 @@ mod tests { #[test] fn select_coins_srd_with_high_fee() { - let target: Amount = Amount::from_str("1.905 cBTC").unwrap(); - // The high fee_rate will cause both utxos to be consumed - // instead of just one. - let fee_rate: FeeRate = FeeRate::from_sat_per_kwu(250); + // 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 mut weighted_utxos: Vec = create_weighted_utxos(); let result = select_coins_srd(target, fee_rate, &mut weighted_utxos, &mut get_rng()) @@ -236,9 +215,7 @@ mod tests { }, }]; - let result: Error = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng()) - .expect_err("expected error"); - - assert_eq!(result.to_string(), "18446744073709551615 + 40 exceeds u64 Max"); + let result = select_coins_srd(target, FEE_RATE, &mut weighted_utxos, &mut get_rng()); + assert!(result.is_none()); } }