]> Untitled Git - bdk/commitdiff
ci: bump build_docs rust version to nightly-2024-05-12
authorSteve Myers <steve@notmandatory.org>
Tue, 14 May 2024 15:15:51 +0000 (10:15 -0500)
committerSteve Myers <steve@notmandatory.org>
Thu, 23 May 2024 04:02:52 +0000 (23:02 -0500)
.github/workflows/nightly_docs.yml
crates/bitcoind_rpc/Cargo.toml
crates/chain/src/lib.rs
crates/esplora/Cargo.toml
crates/file_store/README.md
crates/persist/README.md
nursery/coin_select/src/bnb.rs

index f9840fe3f647e6705f04cf529c7f19f87f92ff45..5fa4ecb5de695086536a1a614ecb6b0866e9307e 100644 (file)
@@ -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:
index 3aceea4678283326fe78bc0850900342ba2b0c3e..88c6dfc515afe3728b00a6a4b381e482cc0b096a 100644 (file)
@@ -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"]
index 512ea3b06b54c4595f48f11f5be2f8bd3c332959..9709699427f4a8c3f5fca29cb96e402112246af8 100644 (file)
@@ -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;
index 7e1657493fb82c21bc50df1059493c2b65e9b2e9..a39c3c00f2659871a55c5891b951d8ff497541d0 100644 (file)
@@ -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]
index 54e41e007fb777727fae42d0512f5b4b89261145..58b572ce002638936cba2862bee64582ff24079a 100644 (file)
@@ -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/
index 1ed6ec8d27b93158c3832a5cdc44bac47c7fac2e..c82354860ae8a918cff56ff7226e324b214ff7dc 100644 (file)
@@ -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.
index 6938185b9bcbfb061f944ea8df95bbe65c73d8fd..d355894ab503b6e96ab22886457aab7e46b29ebd 100644 (file)
@@ -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<Evaluation, EvaluationError> {
-        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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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::<Vec<WeightedValue>>();
-        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<Evaluation, EvaluationError> {
+//         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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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<WeightedValue> = 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::<Vec<WeightedValue>>();
+//         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() {}
+// }