diff --git a/src/lib.rs b/src/lib.rs index ee14c93..9b76ff2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,6 @@ #[cfg(bench)] extern crate test; -use std::cmp::Reverse; - mod single_random_draw; use bitcoin::Amount; @@ -43,6 +41,7 @@ const CHANGE_LOWER: Amount = Amount::from_sat(50_000); /// The idea of using a WeightUtxo type was inspired by the BDK implementation: /// #[derive(Clone, Debug, PartialEq)] +// note, change this to private? No good reason to be public. pub struct WeightedUtxo { /// The satisfaction_weight is the size of the required params to satisfy the UTXO. pub satisfaction_weight: Weight, @@ -59,12 +58,11 @@ pub struct WeightedUtxo { #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] pub fn select_coins( target: Amount, - cost_of_change: u64, + cost_of_change: FeeRate, fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], - utxo_pool: &mut [T], ) -> Option> { - match select_coins_bnb(target.to_sat(), cost_of_change, utxo_pool) { + match select_coins_bnb(target, cost_of_change, weighted_utxos.to_vec()) { Some(_res) => Some(Vec::new()), None => select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()), } @@ -73,219 +71,678 @@ pub fn select_coins( /// Select coins using BnB algorithm similar to what is done in bitcoin /// core see: /// Returns None if BnB doesn't find a solution. -pub fn select_coins_bnb( - target: u64, - cost_of_change: u64, - utxo_pool: &mut [T], -) -> Option> { - let solution = find_solution(target, cost_of_change, utxo_pool)?; - Some( - solution - .into_iter() - .zip(utxo_pool.iter()) - .filter_map(|(include, utxo)| if include { Some(utxo.clone()) } else { None }) - .collect::>(), - ) -} - -fn find_solution( - target: u64, - cost_of_change: u64, - utxo_pool: &mut [T], -) -> Option> { - let utxo_sum = utxo_pool.iter().fold(0u64, |mut s, u| { - s += u.get_value(); - s - }); - - let utxo_pool_length = utxo_pool.len(); - utxo_pool.sort_by_key(|u| Reverse(u.get_value())); - - let mut curr_selection: Vec = vec![false; utxo_pool_length]; - let mut best_selection = None; - let mut remainder = utxo_sum; - - let lower_bound = target; - let upper_bound = cost_of_change + lower_bound; - - if utxo_sum < lower_bound { +// select_coins_bnb performs a depth first search on a binary tree. This can be thought of as +// exploring a binary tree where the left branch is the inclusion of a node and the right branch is +// the exclusion. For example, if the utxo set consist of a list of utxos: [4,3,2,1], and the +// target is 5, the selection process works as follows: +// +// Start at 4 and try including 4 in the total the first loop. We therefore have a tree with only +// one root node that is less than the total, so the next iteration occurs. The second iteration +// examines a tree where 4 is the root and the left branch is 3. +// o +// / +// 4 +// / +// 3 +// +// At this point, the total is determined to be 7 which exceeds the target of 5. We therefore +// remove 3 from the left branch and it becomes the right branch since 3 is now excluded +// (backtrack). +// o +// / +// 4 +// / \ +// 3 +// +// We next try including 2 on the left branch of 3 (add 2 to the inclusion branch). +// o +// / +// 4 +// / \ +// 3 +// / +// 2 +// +// The sum is now 6, since the sum of the right branch totals 6. Once again, we find the total +// exceeds 5, so we explore the exclusion branch of 2. +// o +// / +// 4 +// / \ +// 3 +// / \ +// 2 +// +// Finally, we add 1 to the inclusion branch. This ends our depth first search by matching two +// conditions, it is both the leaf node (end of the list) and matches our search criteria of +// matching 5. Both 4 and 1 are on the left inclusion branch. We therefore record our solution +// and backtrack to next try the exclusion branch of our root node 4. +// o +// / \ +// 4 +// / \ +// 3 +// / \ +// 2 +// / +// 1 +// +// We try excluding 4 now +// o +// / \ +// 4 +// / \ +// 3 +// +// 3 is less than our target, so we next add 2 to our inclusion branch +// o +// / \ +// 4 +// / \ +// 3 +// / +// 2 +// +// We now stop our search again noticing that 3 and 2 equals our target as 5, and since this +// solution was found last, then [3, 2] overwrites the previously found solution [4, 1]. We next +// backtrack and exclude our root node of this sub tree 3. Since our new sub tree starting at 2 +// doesn't have enough value left to meet the target, we conclude our search at [3, 2]. +pub fn select_coins_bnb( + target: Amount, + _fee_rate: FeeRate, + mut weighted_utxos: Vec, +) -> Option> { + // Total_Tries in Core: + // https://github.com/bitcoin/bitcoin/blob/1d9da8da309d1dbf9aef15eb8dc43b4a2dc3d309/src/wallet/coinselection.cpp#L74 + const ITERATION_LIMIT: i32 = 10; + + let mut iteration = 0; + let mut index = 0; + let mut backtrack; + let mut backtrack_subtree; + + let mut value = Amount::ZERO; + let mut waste = Amount::MAX_MONEY; + + let mut index_selection: Vec = vec![]; + let mut best_selection: Vec = vec![]; + let mut available_value: Amount = weighted_utxos.iter().map(|u| u.utxo.value).sum(); + + if available_value < target { return None; } - for m in 0..utxo_pool_length { - let mut curr_sum = 0; - let mut slice_remainder = remainder; + weighted_utxos.sort_by(|a, b| b.utxo.value.cmp(&a.utxo.value)); + + while iteration < ITERATION_LIMIT { + // There are two conditions for backtracking: + // + // 1_ Not enough value to make it to target. + // This condition happens before reaching a leaf node. + // Looking for a leaf node condition should not make a difference. + // This backtrack removes more than one node and instead starts + // the exploration of a new subtree. + // + // From: + // o + // / \ + // 4 + // / \ + // 3 + // / \ + // 2 + // / + // 1 + // + // To: + // o + // / \ + // 4 + // / \ + // 3 + // + // + // 2_ value meets or exceeded target. + // In this condition, we only backtrack one node + // + // From: + // o + // / + // 4 + // / + // 3 + // + // To: + // o + // / + // 4 + // / \ + // 3 + + // Set inital loop state + backtrack = false; + backtrack_subtree = false; + + // * not enouch value to make it to the target. + // Therefore, explore a new new subtree. + // + // - condition - + // + // o + // / \ + // 4 + // / \ + // 3 + // / \ + // 2 + // / + // 1 + // + // index = 3 + // target = 5 + // index_selection = [0, 3] + // value = 5 + // available_value = 0 + if available_value + value < target { + backtrack_subtree = true; + } - for n in m..utxo_pool_length { - if slice_remainder + curr_sum < lower_bound { - break; + // * value meets or exceeds the target. + // Therefore backtrack one node to + // try another permutation. + // + // - condition - + // + // o + // / + // 4 + // / + // 3 + // + // Check if index_selection is better than the previous known best, and + // update best_selection accordingly. + // + // + // Before: + // index = 1 + // target = 5 + // available_value = [2, 1] = 3 + // index_selection = [0, 1] + // value = 7 + // best_selection = [] + // + // After: + // index = 1 + // target = 5 + // available_value = [2, 1] = 3 + // index_selection = [0, 1] + // value = 7 + // best_selection = [0, 1] + if value >= target { + backtrack = true; + + let current_waste = value - target; + + if current_waste <= waste { + best_selection = index_selection.clone(); + waste = current_waste; } + } - let utxo_value = utxo_pool[n].get_value(); - curr_sum += utxo_value; - curr_selection[n] = true; + // * Backtrack + // + // - Transformation - + // + // From + // o + // / + // 4 + // / + // 3 + // + // To + // o + // / + // 4 + // / \ + // 3 + // + // since [4, 3] meet or exceed our target, we now backtrack + // and add 3 to the exclusion branch. + // + // Before: + // index = 1 + // target = 5 + // index_selection = [0, 1] + // value = 7 + // best_selection = [0, 1] + // available_value = [2, 1] = 3 + // + // After: + // index = 1 + // target = 5 + // index_selection = [0] + // value = 4 + // availale_value = [2, 1] = 3 + if backtrack { + let last_index = index_selection.pop().unwrap(); + value -= weighted_utxos[last_index].utxo.value; + index -= 1; + assert_eq!(index, last_index); + } + // * Backtrack to new tree + // Not enough value in the tree to continue checking, + // so start checking a new subtree by removing the root + // from the previous tree. + // + // o + // / \ + // 4 + // / \ + // 3 + // / \ + // 2 + // / + // 1 + // + // We try excluding 4 now + // o + // / \ + // 4 + // + // pool: [4, 3, 2, 1] + // index: [0, 1, 2, 3] + // + // Before: + // index = 3 + // target = 5 + // value = 5 + // index_selection = [0, 3] + // available_value = [] = 0 + // + // After: + // index = 1 + // target = 5 + // value = 0 + // index_selection = [] + // available_value = [3,2,1] = 6 + else if backtrack_subtree { + // no new subtree to explore. + if index_selection.is_empty() { + return Some(best_selection); + } - if curr_sum >= lower_bound { - if curr_sum <= upper_bound { - best_selection = Some(curr_selection.clone()); - } + index = index_selection.remove(0); - curr_selection[n] = false; - curr_sum -= utxo_value; + // no new subtree to explore. + if index + 1 == weighted_utxos.len() { + return Some(best_selection); } - slice_remainder -= utxo_value; + index += 1; + index_selection = vec![]; + index_selection.push(index); + value = weighted_utxos[index].utxo.value; + + available_value = + Amount::from_sat(weighted_utxos[index + 1..].iter().fold(0u64, |mut s, u| { + s += u.utxo.value.to_sat(); + s + })); + } + // * Add next node to the inclusion branch. + // + // target = 5 + // index = 1 + // + // Before: + // index_selection = [0] + // value = 4 + // available_value = [3,2,1] = 6 + // + // After: + // index_selection = [0, 1] + // value = 7 + // available_value = [2, 1] = 3 + // + // - Transformation - + // + // From + // o + // / + // 4 + // + // To + // o + // / + // 4 + // / + // 3 + else { + let utxo_value = weighted_utxos[index].utxo.value; + + index_selection.push(index); + value += utxo_value; + available_value -= utxo_value; } - remainder -= utxo_pool[m].get_value(); - curr_selection[m] = false; + index += 1; + iteration += 1; } - best_selection + Some(best_selection) } #[cfg(test)] mod tests { - use crate::*; - - const ONE_BTC: u64 = 100000000; - const TWO_BTC: u64 = 2 * 100000000; - const THREE_BTC: u64 = 3 * 100000000; - const FOUR_BTC: u64 = 4 * 100000000; - - const UTXO_POOL: [MinimalUtxo; 4] = [ - MinimalUtxo { value: ONE_BTC }, - MinimalUtxo { value: TWO_BTC }, - MinimalUtxo { value: THREE_BTC }, - MinimalUtxo { value: FOUR_BTC }, - ]; - - const COST_OF_CHANGE: u64 = 50000000; - - #[derive(Clone, Debug, Eq, PartialEq)] - struct MinimalUtxo { - value: u64, - } - - impl Utxo for MinimalUtxo { - fn get_value(&self) -> u64 { - self.value - } + use super::*; + use crate::WeightedUtxo; + use bitcoin::Amount; + use bitcoin::ScriptBuf; + use bitcoin::TxOut; + use bitcoin::Weight; + use core::str::FromStr; + + const FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(10); + const SATISFACTION_SIZE: Weight = Weight::from_wu(204); + + fn create_weighted_utxos() -> Vec { + let utxo_one = WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("1 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }; + + let utxo_two = WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("2 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }; + + let utxo_three = WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("3 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }; + + let utxo_four = WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("4 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }; + + vec![utxo_one, utxo_two, utxo_three, utxo_four] } #[test] - fn find_solution_1_btc() { - let utxo_match = find_solution(ONE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, false, false, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn one_solution_one_coin() { + let target = Amount::from_str("1 cBTC").unwrap(); + + let weighted_utxos = vec![WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("1 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }]; + + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_2_btc() { - let utxo_match = find_solution(TWO_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, false, true, false]; - assert_eq!(expected_bool_vec, utxo_match); + fn one_coin_solution_of_two_coins() { + let target = Amount::from_str("4 cBTC").unwrap(); + + let weighted_utxos = vec![ + WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("4 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }, + WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("3 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }, + ]; + + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_3_btc() { - let utxo_match = find_solution(THREE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, false, true, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn two_coin_solution_of_two_coins() { + let target = Amount::from_str("7 cBTC").unwrap(); + + let weighted_utxos = vec![ + WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("4 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }, + WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("3 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }, + ]; + + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0, 1]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_4_btc() { - let utxo_match = find_solution(FOUR_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, true, false, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn one() { + let target = Amount::from_str("1 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_5_btc() { - let five_btc = FOUR_BTC + ONE_BTC; - let utxo_match = find_solution(five_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, true, true, false]; - assert_eq!(expected_bool_vec, utxo_match); + fn two() { + let target = Amount::from_str("2 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![2]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_6_btc() { - let six_btc = FOUR_BTC + TWO_BTC; - let utxo_match = find_solution(six_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, true, true, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn three() { + let target = Amount::from_str("3 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![2, 3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_7_btc() { - let seven_btc = FOUR_BTC + THREE_BTC; - let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![true, false, true, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn four() { + let target = Amount::from_str("4 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![1, 3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_8_btc() { - let seven_btc = FOUR_BTC + THREE_BTC + ONE_BTC; - let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![true, true, false, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn five() { + let target = Amount::from_str("5 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![1, 2]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_9_btc() { - let seven_btc = FOUR_BTC + THREE_BTC + TWO_BTC; - let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![true, true, true, false]; - assert_eq!(expected_bool_vec, utxo_match); + fn six() { + let target = Amount::from_str("6 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![1, 2, 3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_10_btc() { - let ten_btc = ONE_BTC + TWO_BTC + THREE_BTC + FOUR_BTC; - let utxo_match = find_solution(ten_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![true, true, true, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn seven() { + let target = Amount::from_str("7 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0, 2, 3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_11_btc_not_possible() { - let ten_btc = ONE_BTC + TWO_BTC + THREE_BTC + FOUR_BTC; - let utxo_match = find_solution(ten_btc + ONE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()); - assert_eq!(None, utxo_match); + fn eight() { + let target = Amount::from_str("8 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0, 1, 3]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_with_large_cost_of_change() { - let utxo_match = - find_solution(ONE_BTC * 9 / 10, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - let expected_bool_vec = vec![false, false, false, true]; - assert_eq!(expected_bool_vec, utxo_match); + fn nine() { + let target = Amount::from_str("9 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0, 1, 2]; + assert_eq!(index_list, expected_index_list); } #[test] - fn find_solution_with_no_cost_of_change() { - let utxo_match = find_solution(ONE_BTC * 9 / 10, 0, &mut UTXO_POOL.clone()); - assert_eq!(None, utxo_match); + fn ten() { + let target = Amount::from_str("10 cBTC").unwrap(); + let weighted_utxos = create_weighted_utxos(); + let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap(); + let expected_index_list = vec![0, 1, 2, 3]; + assert_eq!(index_list, expected_index_list); } - #[test] - fn find_solution_with_not_input_fee() { - let utxo_match = find_solution(ONE_BTC + 1, COST_OF_CHANGE, &mut UTXO_POOL.clone()); - assert_eq!(None, utxo_match); - } - - #[test] - fn select_coins_bnb_with_match() { - select_coins_bnb(ONE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); - } - - #[test] - fn select_coins_bnb_with_no_match() { - let utxo_match = select_coins_bnb(1, COST_OF_CHANGE, &mut UTXO_POOL.clone()); - assert_eq!(None, utxo_match); - } + //#[test] + //fn find_solution_3_btc() { + //let utxo_match = find_solution(THREE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![false, false, true, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_4_btc() { + //let utxo_match = find_solution(FOUR_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![false, true, false, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_5_btc() { + //let five_btc = FOUR_BTC + ONE_BTC; + //let utxo_match = find_solution(five_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![false, true, true, false]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_6_btc() { + //let six_btc = FOUR_BTC + TWO_BTC; + //let utxo_match = find_solution(six_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![false, true, true, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_7_btc() { + //let seven_btc = FOUR_BTC + THREE_BTC; + //let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![true, false, true, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_8_btc() { + //let seven_btc = FOUR_BTC + THREE_BTC + ONE_BTC; + //let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![true, true, false, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_9_btc() { + //let seven_btc = FOUR_BTC + THREE_BTC + TWO_BTC; + //let utxo_match = find_solution(seven_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![true, true, true, false]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_10_btc() { + //let ten_btc = ONE_BTC + TWO_BTC + THREE_BTC + FOUR_BTC; + //let utxo_match = find_solution(ten_btc, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![true, true, true, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn select_coins_bnb_not_enough_value() { + //let target: Amount = Amount::from_str("11 cBTC").unwrap(); + //let mut weighted_utxos: Vec = create_weighted_utxos(); + + //let result = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos); + + //assert!(result.is_none()); + //} + + //#[test] + //fn find_solution_with_large_cost_of_change() { + //let utxo_match = + //find_solution(ONE_BTC * 9 / 10, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //let expected_bool_vec = vec![false, false, false, true]; + //assert_eq!(expected_bool_vec, utxo_match); + //} + + //#[test] + //fn find_solution_with_no_cost_of_change() { + //let utxo_match = find_solution(ONE_BTC * 9 / 10, 0, &mut UTXO_POOL.clone()); + //assert_eq!(None, utxo_match); + //} + + //#[test] + //fn find_solution_with_not_input_fee() { + //let utxo_match = find_solution(ONE_BTC + 1, COST_OF_CHANGE, &mut UTXO_POOL.clone()); + //assert_eq!(None, utxo_match); + //} + + //#[test] + //fn select_coins_bnb_with_match() { + //select_coins_bnb(ONE_BTC, COST_OF_CHANGE, &mut UTXO_POOL.clone()).unwrap(); + //} + + //#[test] + //fn select_coins_bnb_with_no_match() { + //let utxo_match = select_coins_bnb(1, COST_OF_CHANGE, &mut UTXO_POOL.clone()); + //assert_eq!(None, utxo_match); + //} } #[cfg(bench)]