From 6c0e553a7cf2f9afe8724fc5d73ef98a838060a6 Mon Sep 17 00:00:00 2001 From: yancy Date: Sat, 5 Oct 2024 19:27:18 -0500 Subject: [PATCH] wip --- Cargo.toml | 18 ++--- src/branch_and_bound.rs | 160 ++++++++++++++++++-------------------- src/lib.rs | 39 ++++------ src/single_random_draw.rs | 9 ++- 4 files changed, 105 insertions(+), 121 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a5c6608..34cb3d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,11 @@ keywords = ["bitcoin", "coin-selection", "coin", "coinselection", "utxo"] readme = "README.md" [dependencies] -bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } +bitcoin = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } rand = {version = "0.8.5", default-features = false, optional = true} [dev-dependencies] -bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610", features = ["arbitrary"] } +bitcoin = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323", features = ["arbitrary"] } criterion = "0.3" bitcoin-coin-selection = {path = ".", features = ["rand"]} rand = "0.8.5" @@ -31,10 +31,10 @@ name = "coin_selection" harness = false [patch.crates-io] -bitcoin_hashes = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -base58ck = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -bitcoin-internals = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -bitcoin-io = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -bitcoin-primitives = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -bitcoin-addresses = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } -bitcoin-units = { git = "https://github.com/rust-bitcoin/rust-bitcoin.git", rev = "894f82e7cc9eb459a297d43e82734621e0824610" } +bitcoin_hashes = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +base58ck = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +bitcoin-internals = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +bitcoin-io = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +bitcoin-primitives = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +bitcoin-addresses = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } +bitcoin-units = { git = "https://github.com/yancyribbens/rust-bitcoin.git", rev = "3e73807c70c85be2d684846cb7fb83633a263323" } diff --git a/src/branch_and_bound.rs b/src/branch_and_bound.rs index 9a7b962..1e367a6 100644 --- a/src/branch_and_bound.rs +++ b/src/branch_and_bound.rs @@ -2,7 +2,7 @@ // //! Bitcoin Branch and Bound Coin Selection. //! -//! This module introduces the Branch and Bound Coin Selection Algorithm. +//! This module introduces the Branch and Bound Coin-Selection Algorithm. use bitcoin::amount::CheckedSum; use bitcoin::{Amount, FeeRate, SignedAmount}; @@ -196,7 +196,9 @@ pub fn select_coins_bnb( w_utxos.sort_by_key(|u| u.0); w_utxos.reverse(); + println!("calc avail values"); let mut available_value = w_utxos.clone().into_iter().map(|(ev, _, _)| ev).checked_sum()?; + println!("done"); if available_value < target || target == Amount::ZERO { return None; @@ -336,9 +338,6 @@ mod tests { use arbtest::arbtest; const TX_IN_BASE_WEIGHT: u64 = 160; - const PROPTEST_POOL_SIZE: usize = 10; - const PROPTEST_MAX_SAT_AMOUNT: u64 = 100_000; - const PROPTEST_MIN_SAT_AMOUNT: u64 = 161; //tx_in base_weight + 1 #[derive(Debug)] pub struct ParamsStr<'a> { @@ -349,22 +348,6 @@ mod tests { weighted_utxos: Vec<&'a str>, } - //#[derive(Debug, Clone, PartialEq, Ord, Eq, PartialOrd, Arbitrary)] - //pub struct Utxo { - //output: TxOut, - //satisfaction_weight: Weight, - //} - - //#[derive(Debug)] - //pub struct UtxoPool { - //utxos: Vec, - //} - - //fn build_utxo(amt: Amount, satisfaction_weight: Weight) -> Utxo { - //let output = TxOut { value: amt, script_pubkey: ScriptBuf::new() }; - //Utxo { output, satisfaction_weight } - //} - fn build_pool(fee: Amount) -> Vec { let amts = [ Amount::from_str("1 cBTC").unwrap() + fee, @@ -450,7 +433,9 @@ mod tests { } fn calculate_max_fee_rate(amount: Amount, weight: Weight) -> FeeRate { - (amount - Amount::from_sat(1)) / (weight + Weight::from_wu(TX_IN_BASE_WEIGHT)) + let amt = Amount::from_sat(18446744073709551); + let weight = weight + Weight::from_wu(TX_IN_BASE_WEIGHT); + amt.checked_div_by_weight(weight).unwrap() - FeeRate::from_sat_per_kwu(1) } #[test] @@ -771,50 +756,44 @@ mod tests { let max_fee_rate = calculate_max_fee_rate(utxo.value(), utxo.satisfaction_weight()); let fee_rate = arb_fee_rate_in_range(u, 1..=max_fee_rate.to_sat_per_kwu()); - let target = effective_value( + let target_effective_value = effective_value( fee_rate, utxo.satisfaction_weight(), utxo.value(), - ) - .unwrap() - .to_unsigned() - .unwrap(); - - let eff_value = effective_value( - fee_rate, - utxo.satisfaction_weight(), - utxo.value(), - ) - .unwrap(); - - assert!(eff_value > SignedAmount::ZERO); - - let coins: Vec<_> = select_coins_bnb( - target, - Amount::ZERO, - fee_rate, - fee_rate, - &pool.utxos, - ).unwrap().collect(); - - let mut effective_value_sum = SignedAmount::ZERO; - for coin in &coins { - let eff_value = effective_value( - fee_rate, - coin.satisfaction_weight(), - coin.value(), - ) - .unwrap(); - - effective_value_sum += eff_value; + ); + // if the effective_value was greater than i64 max, then don't use as a target. + if let Some(positive_signed) = target_effective_value { + // If the effective_value was negative it can't be used as a target. + if let Ok(target) = positive_signed.to_unsigned() { + let coins = select_coins_bnb( + target, + Amount::ZERO, + fee_rate, + fee_rate, + &pool.utxos, + ); + + // If results are some then check the results + // else verify the error. + if let Some(c) = coins { + let result: SignedAmount = c.map(|u| { + effective_value( + fee_rate, + u.satisfaction_weight(), + u.value() + ).unwrap() + }).sum(); + assert_eq!(result.to_unsigned().unwrap(), target); + } else { + let pool_sum = pool.utxos.iter().cloned().map(|u| u.value()).checked_sum(); + assert!(pool_sum.is_none()); // assert error when added. + } + } } - let result = effective_value_sum.to_unsigned().unwrap(); - assert_eq!(result, target); - Ok(()) - }); + }).seed(0x13de95f300000373); } #[test] @@ -843,36 +822,51 @@ mod tests { let min_fee_rate = fee_rates.first().unwrap_or(&FeeRate::ZERO).to_sat_per_kwu(); let fee_rate = arb_fee_rate_in_range(u, 0..=min_fee_rate); - // sum the effective_values and use it asa the target - let target: SignedAmount = selection.iter().map(|u| { - effective_value( + let effective_values: Vec = selection.iter().map(|u| { + let e = effective_value( fee_rate, u.satisfaction_weight(), u.value() - ).unwrap() - }).sum(); + ); - let coins = select_coins_bnb( - target.to_unsigned().unwrap(), - Amount::ZERO, - fee_rate, - fee_rate, - &pool.utxos, - ); + if e.is_some() { + e.unwrap() + } else { + SignedAmount::ZERO + } + }).collect(); - if selection.is_empty() { - assert!(coins.is_none()); - } else { - let result: SignedAmount = coins.unwrap().map(|u| { - effective_value( - fee_rate, - u.satisfaction_weight(), - u.value(), - ) - .unwrap() - }).sum(); + let eff_values_sum = effective_values.into_iter().checked_sum(); + + // if None, then this random subset is an invalid target + if let Some(s) = eff_values_sum { + let target = s.to_unsigned().unwrap(); + + let coins = select_coins_bnb( + target, + Amount::ZERO, + fee_rate, + fee_rate, + &pool.utxos, + ); + + if selection.is_empty() || target == Amount::ZERO { + assert!(coins.is_none()); + } else { + let result: Amount = coins.unwrap().map(|u| { + effective_value( + fee_rate, + u.satisfaction_weight(), + u.value(), + ) + .unwrap() + .to_unsigned() + .unwrap() + }).sum(); + + assert_eq!(result, target); + } - assert_eq!(result, target); } Ok(()) @@ -943,6 +937,6 @@ mod tests { } Ok(()) - }); + }).seed(0x6388c4b200000020); } } diff --git a/src/lib.rs b/src/lib.rs index 1d063e0..dda5abf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ mod tests { use arbtest::arbtest; const PROPTEST_POOL_SIZE: usize = 10; - const PROPTEST_MAX_SAT_AMOUNT: u64 = 100_000; + const PROPTEST_MAX_SAT_AMOUNT: u64 = u64::MAX; const PROPTEST_MIN_SAT_AMOUNT: u64 = 161; //tx_in base_weight + 1 pub fn build_utxo(amt: Amount, satisfaction_weight: Weight) -> Utxo { @@ -167,7 +167,7 @@ mod tests { satisfaction_weight: Weight, } - #[derive(Debug)] + #[derive(Debug, Arbitrary)] pub struct UtxoPool { pub utxos: Vec, } @@ -177,30 +177,18 @@ mod tests { fn value(&self) -> Amount { self.output.value } } - impl<'a> Arbitrary<'a> for UtxoPool - where - Utxo: Arbitrary<'a>, - { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let mut p = Vec::with_capacity(PROPTEST_POOL_SIZE); + //use bitcoin::amount::CheckedSum; + //impl<'a> Arbitrary<'a> for UtxoPool + //where + //Utxo: Arbitrary<'a>, + //{ + //fn arbitrary(u: &mut Unstructured<'a>) -> Result { + //let pool: Vec = Vec::arbitrary(u)?; + //let p = UtxoPool { utxos: pool }; - for _ in 0..PROPTEST_POOL_SIZE { - let amount_int = u.int_in_range::(PROPTEST_MIN_SAT_AMOUNT..=PROPTEST_MAX_SAT_AMOUNT).unwrap(); - let amount = Amount::from_sat(amount_int); - - let weight_int = u.int_in_range::(1..=amount_int).unwrap(); - let weight = Weight::from_wu(weight_int); - - let utxos = build_utxo(amount, weight); - - p.push(utxos); - } - - let pool = UtxoPool { utxos: p }; - - Ok(pool) - } - } + //Ok(p) + //} + //} #[test] fn select_coins_no_solution() { @@ -340,4 +328,3 @@ mod tests { }); } } - diff --git a/src/single_random_draw.rs b/src/single_random_draw.rs index a8f2a70..61bc875 100644 --- a/src/single_random_draw.rs +++ b/src/single_random_draw.rs @@ -1,5 +1,8 @@ -//! This library provides efficient algorithms to compose a set of unspent transaction outputs -//! (UTXOs). +// SPDX-License-Identifier: CC0-1.0 +// +//! Single Random Draw Algorithem. +//! +//! This module introduces the Single Random Draw Coin-Selection Algorithm. use bitcoin::blockdata::transaction::effective_value; use bitcoin::{Amount, FeeRate}; @@ -86,7 +89,7 @@ mod tests { use super::*; use crate::single_random_draw::select_coins_srd; - use crate::{WeightedUtxo, CHANGE_LOWER}; + use crate::WeightedUtxo; use arbitrary::{Arbitrary, Result, Unstructured}; use arbtest::arbtest;