From 54942a902d3203ee59731f35d2a1c402ac626878 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Tue, 14 May 2024 10:15:51 -0500 Subject: [PATCH] ci: bump build_docs rust version to nightly-2024-05-12 --- .github/workflows/nightly_docs.yml | 4 +- crates/bitcoind_rpc/Cargo.toml | 2 +- crates/chain/src/lib.rs | 3 - crates/esplora/Cargo.toml | 2 +- crates/file_store/README.md | 9 +- crates/persist/README.md | 4 +- nursery/coin_select/src/bnb.rs | 676 ++++++++++++++--------------- 7 files changed, 347 insertions(+), 353 deletions(-) diff --git a/.github/workflows/nightly_docs.yml b/.github/workflows/nightly_docs.yml index f9840fe3..5fa4ecb5 100644 --- a/.github/workflows/nightly_docs.yml +++ b/.github/workflows/nightly_docs.yml @@ -10,15 +10,13 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Set default toolchain - run: rustup default nightly-2022-12-14 + run: rustup default nightly-2024-05-12 - name: Set profile run: rustup set profile minimal - name: Update toolchain run: rustup update - name: Rust Cache uses: Swatinem/rust-cache@v2.2.1 - - name: Pin dependencies for MSRV - run: cargo update -p home --precise "0.5.5" - name: Build docs run: cargo doc --no-deps env: diff --git a/crates/bitcoind_rpc/Cargo.toml b/crates/bitcoind_rpc/Cargo.toml index 3aceea46..88c6dfc5 100644 --- a/crates/bitcoind_rpc/Cargo.toml +++ b/crates/bitcoind_rpc/Cargo.toml @@ -19,7 +19,7 @@ bitcoincore-rpc = { version = "0.18" } bdk_chain = { path = "../chain", version = "0.14", default-features = false } [dev-dependencies] -bdk_testenv = { path = "../testenv", default_features = false } +bdk_testenv = { path = "../testenv", default-features = false } [features] default = ["std"] diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 512ea3b0..97096994 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -58,9 +58,6 @@ extern crate alloc; #[cfg(feature = "serde")] pub extern crate serde_crate as serde; -#[cfg(feature = "bincode")] -extern crate bincode; - #[cfg(feature = "std")] #[macro_use] extern crate std; diff --git a/crates/esplora/Cargo.toml b/crates/esplora/Cargo.toml index 7e165749..a39c3c00 100644 --- a/crates/esplora/Cargo.toml +++ b/crates/esplora/Cargo.toml @@ -22,7 +22,7 @@ bitcoin = { version = "0.31.0", optional = true, default-features = false } miniscript = { version = "11.0.0", optional = true, default-features = false } [dev-dependencies] -bdk_testenv = { path = "../testenv", default_features = false } +bdk_testenv = { path = "../testenv", default-features = false } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [features] diff --git a/crates/file_store/README.md b/crates/file_store/README.md index 54e41e00..58b572ce 100644 --- a/crates/file_store/README.md +++ b/crates/file_store/README.md @@ -1,10 +1,7 @@ # BDK File Store -This is a simple append-only flat file implementation of -[`PersistBackend`](bdk_persist::PersistBackend). +This is a simple append-only flat file implementation of [`PersistBackend`](bdk_persist::PersistBackend). -The main structure is [`Store`](crate::Store), which can be used with [`bdk`]'s -`Wallet` to persist wallet data into a flat file. +The main structure is [`Store`] which works with any [`bdk_chain`] based changesets to persist data into a flat file. -[`bdk`]: https://docs.rs/bdk/latest -[`bdk_persist`]: https://docs.rs/bdk_persist/latest +[`bdk_chain`]:https://docs.rs/bdk_chain/latest/bdk_chain/ diff --git a/crates/persist/README.md b/crates/persist/README.md index 1ed6ec8d..c8235486 100644 --- a/crates/persist/README.md +++ b/crates/persist/README.md @@ -1,3 +1,5 @@ # BDK Persist -This crate is home to the [`PersistBackend`](crate::PersistBackend) trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures. The [`Persist`](crate::Persist) type provides a convenient wrapper around a `PersistBackend` that allows staging changes before committing them. \ No newline at end of file +This crate is home to the [`PersistBackend`] trait which defines the behavior of a database to perform the task of persisting changes made to BDK data structures. + +The [`Persist`] type provides a convenient wrapper around a [`PersistBackend`] that allows staging changes before committing them. diff --git a/nursery/coin_select/src/bnb.rs b/nursery/coin_select/src/bnb.rs index 6938185b..d355894a 100644 --- a/nursery/coin_select/src/bnb.rs +++ b/nursery/coin_select/src/bnb.rs @@ -305,341 +305,341 @@ where }? } -#[cfg(all(test, feature = "miniscript"))] -mod test { - use bitcoin::secp256k1::Secp256k1; - - use crate::coin_select::{evaluate_cs::evaluate, ExcessStrategyKind}; - - use super::{ - coin_select_bnb, - evaluate_cs::{Evaluation, EvaluationError}, - tester::Tester, - CoinSelector, CoinSelectorOpt, Vec, WeightedValue, - }; - - fn tester() -> Tester { - const DESC_STR: &str = "tr(xprv9uBuvtdjghkz8D1qzsSXS9Vs64mqrUnXqzNccj2xcvnCHPpXKYE1U2Gbh9CDHk8UPyF2VuXpVkDA7fk5ZP4Hd9KnhUmTscKmhee9Dp5sBMK)"; - Tester::new(&Secp256k1::default(), DESC_STR) - } - - fn evaluate_bnb( - initial_selector: CoinSelector, - max_tries: usize, - ) -> Result { - evaluate(initial_selector, |cs| { - coin_select_bnb(max_tries, cs.clone()).map_or(false, |new_cs| { - *cs = new_cs; - true - }) - }) - } - - #[test] - fn not_enough_coins() { - let t = tester(); - let candidates: Vec = vec![ - t.gen_candidate(0, 100_000).into(), - t.gen_candidate(1, 100_000).into(), - ]; - let opts = t.gen_opts(200_000); - let selector = CoinSelector::new(&candidates, &opts); - assert!(!coin_select_bnb(10_000, selector).is_some()); - } - - #[test] - fn exactly_enough_coins_preselected() { - let t = tester(); - let candidates: Vec = vec![ - t.gen_candidate(0, 100_000).into(), // to preselect - t.gen_candidate(1, 100_000).into(), // to preselect - t.gen_candidate(2, 100_000).into(), - ]; - let opts = CoinSelectorOpt { - target_feerate: 0.0, - ..t.gen_opts(200_000) - }; - let selector = { - let mut selector = CoinSelector::new(&candidates, &opts); - selector.select(0); // preselect - selector.select(1); // preselect - selector - }; - - let evaluation = evaluate_bnb(selector, 10_000).expect("eval failed"); - println!("{}", evaluation); - assert_eq!(evaluation.solution.selected, (0..=1).collect()); - assert_eq!(evaluation.solution.excess_strategies.len(), 1); - assert_eq!( - evaluation.feerate_offset(ExcessStrategyKind::ToFee).floor(), - 0.0 - ); - } - - /// `cost_of_change` acts as the upper-bound in Bnb; we check whether these boundaries are - /// enforced in code - #[test] - fn cost_of_change() { - let t = tester(); - let candidates: Vec = vec![ - t.gen_candidate(0, 200_000).into(), - t.gen_candidate(1, 200_000).into(), - t.gen_candidate(2, 200_000).into(), - ]; - - // lowest and highest possible `recipient_value` opts for derived `drain_waste`, assuming - // that we want 2 candidates selected - let (lowest_opts, highest_opts) = { - let opts = t.gen_opts(0); - - let fee_from_inputs = - (candidates[0].weight as f32 * opts.target_feerate).ceil() as u64 * 2; - let fee_from_template = - ((opts.base_weight + 2) as f32 * opts.target_feerate).ceil() as u64; - - let lowest_opts = CoinSelectorOpt { - target_value: Some( - 400_000 - fee_from_inputs - fee_from_template - opts.drain_waste() as u64, - ), - ..opts - }; - - let highest_opts = CoinSelectorOpt { - target_value: Some(400_000 - fee_from_inputs - fee_from_template), - ..opts - }; - - (lowest_opts, highest_opts) - }; - - // test lowest possible target we can select - let lowest_eval = evaluate_bnb(CoinSelector::new(&candidates, &lowest_opts), 10_000); - assert!(lowest_eval.is_ok()); - let lowest_eval = lowest_eval.unwrap(); - println!("LB {}", lowest_eval); - assert_eq!(lowest_eval.solution.selected.len(), 2); - assert_eq!(lowest_eval.solution.excess_strategies.len(), 1); - assert_eq!( - lowest_eval - .feerate_offset(ExcessStrategyKind::ToFee) - .floor(), - 0.0 - ); - - // test the highest possible target we can select - let highest_eval = evaluate_bnb(CoinSelector::new(&candidates, &highest_opts), 10_000); - assert!(highest_eval.is_ok()); - let highest_eval = highest_eval.unwrap(); - println!("UB {}", highest_eval); - assert_eq!(highest_eval.solution.selected.len(), 2); - assert_eq!(highest_eval.solution.excess_strategies.len(), 1); - assert_eq!( - highest_eval - .feerate_offset(ExcessStrategyKind::ToFee) - .floor(), - 0.0 - ); - - // test lower out of bounds - let loob_opts = CoinSelectorOpt { - target_value: lowest_opts.target_value.map(|v| v - 1), - ..lowest_opts - }; - let loob_eval = evaluate_bnb(CoinSelector::new(&candidates, &loob_opts), 10_000); - assert!(loob_eval.is_err()); - println!("Lower OOB: {}", loob_eval.unwrap_err()); - - // test upper out of bounds - let uoob_opts = CoinSelectorOpt { - target_value: highest_opts.target_value.map(|v| v + 1), - ..highest_opts - }; - let uoob_eval = evaluate_bnb(CoinSelector::new(&candidates, &uoob_opts), 10_000); - assert!(uoob_eval.is_err()); - println!("Upper OOB: {}", uoob_eval.unwrap_err()); - } - - #[test] - fn try_select() { - let t = tester(); - let candidates: Vec = vec![ - t.gen_candidate(0, 300_000).into(), - t.gen_candidate(1, 300_000).into(), - t.gen_candidate(2, 300_000).into(), - t.gen_candidate(3, 200_000).into(), - t.gen_candidate(4, 200_000).into(), - ]; - let make_opts = |v: u64| -> CoinSelectorOpt { - CoinSelectorOpt { - target_feerate: 0.0, - ..t.gen_opts(v) - } - }; - - let test_cases = vec![ - (make_opts(100_000), false, 0), - (make_opts(200_000), true, 1), - (make_opts(300_000), true, 1), - (make_opts(500_000), true, 2), - (make_opts(1_000_000), true, 4), - (make_opts(1_200_000), false, 0), - (make_opts(1_300_000), true, 5), - (make_opts(1_400_000), false, 0), - ]; - - for (opts, expect_solution, expect_selected) in test_cases { - let res = evaluate_bnb(CoinSelector::new(&candidates, &opts), 10_000); - assert_eq!(res.is_ok(), expect_solution); - - match res { - Ok(eval) => { - println!("{}", eval); - assert_eq!(eval.feerate_offset(ExcessStrategyKind::ToFee), 0.0); - assert_eq!(eval.solution.selected.len(), expect_selected as _); - } - Err(err) => println!("expected failure: {}", err), - } - } - } - - #[test] - fn early_bailout_optimization() { - let t = tester(); - - // target: 300_000 - // candidates: 2x of 125_000, 1000x of 100_000, 1x of 50_000 - // expected solution: 2x 125_000, 1x 50_000 - // set bnb max tries: 1100, should succeed - let candidates = { - let mut candidates: Vec = vec![ - t.gen_candidate(0, 125_000).into(), - t.gen_candidate(1, 125_000).into(), - t.gen_candidate(2, 50_000).into(), - ]; - (3..3 + 1000_u32) - .for_each(|index| candidates.push(t.gen_candidate(index, 100_000).into())); - candidates - }; - let opts = CoinSelectorOpt { - target_feerate: 0.0, - ..t.gen_opts(300_000) - }; - - let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 1100); - assert!(result.is_ok()); - - let eval = result.unwrap(); - println!("{}", eval); - assert_eq!(eval.solution.selected, (0..=2).collect()); - } - - #[test] - fn should_exhaust_iteration() { - static MAX_TRIES: usize = 1000; - let t = tester(); - let candidates = (0..MAX_TRIES + 1) - .map(|index| t.gen_candidate(index as _, 10_000).into()) - .collect::>(); - let opts = t.gen_opts(10_001 * MAX_TRIES as u64); - let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), MAX_TRIES); - assert!(result.is_err()); - println!("error as expected: {}", result.unwrap_err()); - } - - /// Solution should have fee >= min_absolute_fee (or no solution at all) - #[test] - fn min_absolute_fee() { - let t = tester(); - let candidates = { - let mut candidates = Vec::new(); - t.gen_weighted_values(&mut candidates, 5, 10_000); - t.gen_weighted_values(&mut candidates, 5, 20_000); - t.gen_weighted_values(&mut candidates, 5, 30_000); - t.gen_weighted_values(&mut candidates, 10, 10_300); - t.gen_weighted_values(&mut candidates, 10, 10_500); - t.gen_weighted_values(&mut candidates, 10, 10_700); - t.gen_weighted_values(&mut candidates, 10, 10_900); - t.gen_weighted_values(&mut candidates, 10, 11_000); - t.gen_weighted_values(&mut candidates, 10, 12_000); - t.gen_weighted_values(&mut candidates, 10, 13_000); - candidates - }; - let mut opts = CoinSelectorOpt { - min_absolute_fee: 1, - ..t.gen_opts(100_000) - }; - - (1..=120_u64).for_each(|fee_factor| { - opts.min_absolute_fee = fee_factor * 31; - - let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 21_000); - match result { - Ok(result) => { - println!("Solution {}", result); - let fee = result.solution.excess_strategies[&ExcessStrategyKind::ToFee].fee; - assert!(fee >= opts.min_absolute_fee); - assert_eq!(result.solution.excess_strategies.len(), 1); - } - Err(err) => { - println!("No Solution: {}", err); - } - } - }); - } - - /// For a decreasing feerate (long-term feerate is lower than effective feerate), we should - /// select less. For increasing feerate (long-term feerate is higher than effective feerate), we - /// should select more. - #[test] - fn feerate_difference() { - let t = tester(); - let candidates = { - let mut candidates = Vec::new(); - t.gen_weighted_values(&mut candidates, 10, 2_000); - t.gen_weighted_values(&mut candidates, 10, 5_000); - t.gen_weighted_values(&mut candidates, 10, 20_000); - candidates - }; - - let decreasing_feerate_opts = CoinSelectorOpt { - target_feerate: 1.25, - long_term_feerate: Some(0.25), - ..t.gen_opts(100_000) - }; - - let increasing_feerate_opts = CoinSelectorOpt { - target_feerate: 0.25, - long_term_feerate: Some(1.25), - ..t.gen_opts(100_000) - }; - - let decreasing_res = evaluate_bnb( - CoinSelector::new(&candidates, &decreasing_feerate_opts), - 21_000, - ) - .expect("no result"); - let decreasing_len = decreasing_res.solution.selected.len(); - - let increasing_res = evaluate_bnb( - CoinSelector::new(&candidates, &increasing_feerate_opts), - 21_000, - ) - .expect("no result"); - let increasing_len = increasing_res.solution.selected.len(); - - println!("decreasing_len: {}", decreasing_len); - println!("increasing_len: {}", increasing_len); - assert!(decreasing_len < increasing_len); - } - - /// TODO: UNIMPLEMENTED TESTS: - /// * Excess strategies: - /// * We should always have `ExcessStrategy::ToFee`. - /// * We should only have `ExcessStrategy::ToRecipient` when `max_extra_target > 0`. - /// * We should only have `ExcessStrategy::ToDrain` when `drain_value >= min_drain_value`. - /// * Fuzz - /// * Solution feerate should never be lower than target feerate - /// * Solution fee should never be lower than `min_absolute_fee`. - /// * Preselected should always remain selected - fn _todo() {} -} +// #[cfg(all(test, feature = "miniscript"))] +// mod test { +// use bitcoin::secp256k1::Secp256k1; +// +// use crate::coin_select::{evaluate_cs::evaluate, ExcessStrategyKind}; +// +// use super::{ +// coin_select_bnb, +// evaluate_cs::{Evaluation, EvaluationError}, +// tester::Tester, +// CoinSelector, CoinSelectorOpt, Vec, WeightedValue, +// }; +// +// fn tester() -> Tester { +// const DESC_STR: &str = "tr(xprv9uBuvtdjghkz8D1qzsSXS9Vs64mqrUnXqzNccj2xcvnCHPpXKYE1U2Gbh9CDHk8UPyF2VuXpVkDA7fk5ZP4Hd9KnhUmTscKmhee9Dp5sBMK)"; +// Tester::new(&Secp256k1::default(), DESC_STR) +// } +// +// fn evaluate_bnb( +// initial_selector: CoinSelector, +// max_tries: usize, +// ) -> Result { +// evaluate(initial_selector, |cs| { +// coin_select_bnb(max_tries, cs.clone()).map_or(false, |new_cs| { +// *cs = new_cs; +// true +// }) +// }) +// } +// +// #[test] +// fn not_enough_coins() { +// let t = tester(); +// let candidates: Vec = vec![ +// t.gen_candidate(0, 100_000).into(), +// t.gen_candidate(1, 100_000).into(), +// ]; +// let opts = t.gen_opts(200_000); +// let selector = CoinSelector::new(&candidates, &opts); +// assert!(!coin_select_bnb(10_000, selector).is_some()); +// } +// +// #[test] +// fn exactly_enough_coins_preselected() { +// let t = tester(); +// let candidates: Vec = vec![ +// t.gen_candidate(0, 100_000).into(), // to preselect +// t.gen_candidate(1, 100_000).into(), // to preselect +// t.gen_candidate(2, 100_000).into(), +// ]; +// let opts = CoinSelectorOpt { +// target_feerate: 0.0, +// ..t.gen_opts(200_000) +// }; +// let selector = { +// let mut selector = CoinSelector::new(&candidates, &opts); +// selector.select(0); // preselect +// selector.select(1); // preselect +// selector +// }; +// +// let evaluation = evaluate_bnb(selector, 10_000).expect("eval failed"); +// println!("{}", evaluation); +// assert_eq!(evaluation.solution.selected, (0..=1).collect()); +// assert_eq!(evaluation.solution.excess_strategies.len(), 1); +// assert_eq!( +// evaluation.feerate_offset(ExcessStrategyKind::ToFee).floor(), +// 0.0 +// ); +// } +// +// /// `cost_of_change` acts as the upper-bound in Bnb; we check whether these boundaries are +// /// enforced in code +// #[test] +// fn cost_of_change() { +// let t = tester(); +// let candidates: Vec = vec![ +// t.gen_candidate(0, 200_000).into(), +// t.gen_candidate(1, 200_000).into(), +// t.gen_candidate(2, 200_000).into(), +// ]; +// +// // lowest and highest possible `recipient_value` opts for derived `drain_waste`, assuming +// // that we want 2 candidates selected +// let (lowest_opts, highest_opts) = { +// let opts = t.gen_opts(0); +// +// let fee_from_inputs = +// (candidates[0].weight as f32 * opts.target_feerate).ceil() as u64 * 2; +// let fee_from_template = +// ((opts.base_weight + 2) as f32 * opts.target_feerate).ceil() as u64; +// +// let lowest_opts = CoinSelectorOpt { +// target_value: Some( +// 400_000 - fee_from_inputs - fee_from_template - opts.drain_waste() as u64, +// ), +// ..opts +// }; +// +// let highest_opts = CoinSelectorOpt { +// target_value: Some(400_000 - fee_from_inputs - fee_from_template), +// ..opts +// }; +// +// (lowest_opts, highest_opts) +// }; +// +// // test lowest possible target we can select +// let lowest_eval = evaluate_bnb(CoinSelector::new(&candidates, &lowest_opts), 10_000); +// assert!(lowest_eval.is_ok()); +// let lowest_eval = lowest_eval.unwrap(); +// println!("LB {}", lowest_eval); +// assert_eq!(lowest_eval.solution.selected.len(), 2); +// assert_eq!(lowest_eval.solution.excess_strategies.len(), 1); +// assert_eq!( +// lowest_eval +// .feerate_offset(ExcessStrategyKind::ToFee) +// .floor(), +// 0.0 +// ); +// +// // test the highest possible target we can select +// let highest_eval = evaluate_bnb(CoinSelector::new(&candidates, &highest_opts), 10_000); +// assert!(highest_eval.is_ok()); +// let highest_eval = highest_eval.unwrap(); +// println!("UB {}", highest_eval); +// assert_eq!(highest_eval.solution.selected.len(), 2); +// assert_eq!(highest_eval.solution.excess_strategies.len(), 1); +// assert_eq!( +// highest_eval +// .feerate_offset(ExcessStrategyKind::ToFee) +// .floor(), +// 0.0 +// ); +// +// // test lower out of bounds +// let loob_opts = CoinSelectorOpt { +// target_value: lowest_opts.target_value.map(|v| v - 1), +// ..lowest_opts +// }; +// let loob_eval = evaluate_bnb(CoinSelector::new(&candidates, &loob_opts), 10_000); +// assert!(loob_eval.is_err()); +// println!("Lower OOB: {}", loob_eval.unwrap_err()); +// +// // test upper out of bounds +// let uoob_opts = CoinSelectorOpt { +// target_value: highest_opts.target_value.map(|v| v + 1), +// ..highest_opts +// }; +// let uoob_eval = evaluate_bnb(CoinSelector::new(&candidates, &uoob_opts), 10_000); +// assert!(uoob_eval.is_err()); +// println!("Upper OOB: {}", uoob_eval.unwrap_err()); +// } +// +// #[test] +// fn try_select() { +// let t = tester(); +// let candidates: Vec = vec![ +// t.gen_candidate(0, 300_000).into(), +// t.gen_candidate(1, 300_000).into(), +// t.gen_candidate(2, 300_000).into(), +// t.gen_candidate(3, 200_000).into(), +// t.gen_candidate(4, 200_000).into(), +// ]; +// let make_opts = |v: u64| -> CoinSelectorOpt { +// CoinSelectorOpt { +// target_feerate: 0.0, +// ..t.gen_opts(v) +// } +// }; +// +// let test_cases = vec![ +// (make_opts(100_000), false, 0), +// (make_opts(200_000), true, 1), +// (make_opts(300_000), true, 1), +// (make_opts(500_000), true, 2), +// (make_opts(1_000_000), true, 4), +// (make_opts(1_200_000), false, 0), +// (make_opts(1_300_000), true, 5), +// (make_opts(1_400_000), false, 0), +// ]; +// +// for (opts, expect_solution, expect_selected) in test_cases { +// let res = evaluate_bnb(CoinSelector::new(&candidates, &opts), 10_000); +// assert_eq!(res.is_ok(), expect_solution); +// +// match res { +// Ok(eval) => { +// println!("{}", eval); +// assert_eq!(eval.feerate_offset(ExcessStrategyKind::ToFee), 0.0); +// assert_eq!(eval.solution.selected.len(), expect_selected as _); +// } +// Err(err) => println!("expected failure: {}", err), +// } +// } +// } +// +// #[test] +// fn early_bailout_optimization() { +// let t = tester(); +// +// // target: 300_000 +// // candidates: 2x of 125_000, 1000x of 100_000, 1x of 50_000 +// // expected solution: 2x 125_000, 1x 50_000 +// // set bnb max tries: 1100, should succeed +// let candidates = { +// let mut candidates: Vec = vec![ +// t.gen_candidate(0, 125_000).into(), +// t.gen_candidate(1, 125_000).into(), +// t.gen_candidate(2, 50_000).into(), +// ]; +// (3..3 + 1000_u32) +// .for_each(|index| candidates.push(t.gen_candidate(index, 100_000).into())); +// candidates +// }; +// let opts = CoinSelectorOpt { +// target_feerate: 0.0, +// ..t.gen_opts(300_000) +// }; +// +// let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 1100); +// assert!(result.is_ok()); +// +// let eval = result.unwrap(); +// println!("{}", eval); +// assert_eq!(eval.solution.selected, (0..=2).collect()); +// } +// +// #[test] +// fn should_exhaust_iteration() { +// static MAX_TRIES: usize = 1000; +// let t = tester(); +// let candidates = (0..MAX_TRIES + 1) +// .map(|index| t.gen_candidate(index as _, 10_000).into()) +// .collect::>(); +// let opts = t.gen_opts(10_001 * MAX_TRIES as u64); +// let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), MAX_TRIES); +// assert!(result.is_err()); +// println!("error as expected: {}", result.unwrap_err()); +// } +// +// /// Solution should have fee >= min_absolute_fee (or no solution at all) +// #[test] +// fn min_absolute_fee() { +// let t = tester(); +// let candidates = { +// let mut candidates = Vec::new(); +// t.gen_weighted_values(&mut candidates, 5, 10_000); +// t.gen_weighted_values(&mut candidates, 5, 20_000); +// t.gen_weighted_values(&mut candidates, 5, 30_000); +// t.gen_weighted_values(&mut candidates, 10, 10_300); +// t.gen_weighted_values(&mut candidates, 10, 10_500); +// t.gen_weighted_values(&mut candidates, 10, 10_700); +// t.gen_weighted_values(&mut candidates, 10, 10_900); +// t.gen_weighted_values(&mut candidates, 10, 11_000); +// t.gen_weighted_values(&mut candidates, 10, 12_000); +// t.gen_weighted_values(&mut candidates, 10, 13_000); +// candidates +// }; +// let mut opts = CoinSelectorOpt { +// min_absolute_fee: 1, +// ..t.gen_opts(100_000) +// }; +// +// (1..=120_u64).for_each(|fee_factor| { +// opts.min_absolute_fee = fee_factor * 31; +// +// let result = evaluate_bnb(CoinSelector::new(&candidates, &opts), 21_000); +// match result { +// Ok(result) => { +// println!("Solution {}", result); +// let fee = result.solution.excess_strategies[&ExcessStrategyKind::ToFee].fee; +// assert!(fee >= opts.min_absolute_fee); +// assert_eq!(result.solution.excess_strategies.len(), 1); +// } +// Err(err) => { +// println!("No Solution: {}", err); +// } +// } +// }); +// } +// +// /// For a decreasing feerate (long-term feerate is lower than effective feerate), we should +// /// select less. For increasing feerate (long-term feerate is higher than effective feerate), we +// /// should select more. +// #[test] +// fn feerate_difference() { +// let t = tester(); +// let candidates = { +// let mut candidates = Vec::new(); +// t.gen_weighted_values(&mut candidates, 10, 2_000); +// t.gen_weighted_values(&mut candidates, 10, 5_000); +// t.gen_weighted_values(&mut candidates, 10, 20_000); +// candidates +// }; +// +// let decreasing_feerate_opts = CoinSelectorOpt { +// target_feerate: 1.25, +// long_term_feerate: Some(0.25), +// ..t.gen_opts(100_000) +// }; +// +// let increasing_feerate_opts = CoinSelectorOpt { +// target_feerate: 0.25, +// long_term_feerate: Some(1.25), +// ..t.gen_opts(100_000) +// }; +// +// let decreasing_res = evaluate_bnb( +// CoinSelector::new(&candidates, &decreasing_feerate_opts), +// 21_000, +// ) +// .expect("no result"); +// let decreasing_len = decreasing_res.solution.selected.len(); +// +// let increasing_res = evaluate_bnb( +// CoinSelector::new(&candidates, &increasing_feerate_opts), +// 21_000, +// ) +// .expect("no result"); +// let increasing_len = increasing_res.solution.selected.len(); +// +// println!("decreasing_len: {}", decreasing_len); +// println!("increasing_len: {}", increasing_len); +// assert!(decreasing_len < increasing_len); +// } +// +// /// TODO: UNIMPLEMENTED TESTS: +// /// * Excess strategies: +// /// * We should always have `ExcessStrategy::ToFee`. +// /// * We should only have `ExcessStrategy::ToRecipient` when `max_extra_target > 0`. +// /// * We should only have `ExcessStrategy::ToDrain` when `drain_value >= min_drain_value`. +// /// * Fuzz +// /// * Solution feerate should never be lower than target feerate +// /// * Solution fee should never be lower than `min_absolute_fee`. +// /// * Preselected should always remain selected +// fn _todo() {} +// }