diff --git a/src/lib.rs b/src/lib.rs index 0e29ce4..7282c5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -151,8 +151,19 @@ pub fn select_coins_bnb( _fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], ) -> Option> { + // Total_Tries in Core: + // https://github.com/bitcoin/bitcoin/blob/1d9da8da309d1dbf9aef15eb8dc43b4a2dc3d309/src/wallet/coinselection.cpp#L74 + const ITERATION_LIMIT: i32 = 100_000; + + let mut iteration = 0; + let mut index = 0; + let mut backtrack; + let mut value = Amount::ZERO; + let waste = Amount::MAX_MONEY; + let mut selection: Vec = vec![]; + let mut best_selection: Vec = vec![]; let available_value: Amount = weighted_utxos.iter().map(|u| u.utxo.value).sum(); if available_value < target { @@ -161,29 +172,67 @@ pub fn select_coins_bnb( weighted_utxos.sort_by(|a, b| b.utxo.value.cmp(&a.utxo.value)); - for (_i, utxo) in weighted_utxos.iter().enumerate() { + while iteration < ITERATION_LIMIT { + backtrack = false; + // backtrack // // There are three conditions for backtracking: // - // * value meets or exceeded target - // * leaf node (nothing left to explorer) - // * not enough value to make it to target - if value >= target - || available_value + value < target - || utxo == weighted_utxos.last().unwrap() - { - // backtrack + // * value meets or exceeded target. + // * leaf node (nothing left to explorer). + // * not enough value to make it to target. + + // value meets or exceeds the target: + // + // For example: + // o + // / + // 4 + // / + // 3 + // + // Transform to: + // o + // / + // 4 + // / \ + // 3 + if value >= target { + backtrack = true; + let current_waste = value - target; + + if current_waste <= waste { + best_selection = selection.clone(); + selection.pop(); + } + + index -= 1; } + //if index == selection.len() { + //} + + //if available_value + value < target { + //} + + if backtrack { + if selection.is_empty() { + return Some(best_selection); + } + } // proceed with depth first search. else { + let utxo = &weighted_utxos[index]; selection.push(utxo.clone()); value += utxo.utxo.value; } + + index += 1; + iteration += 1; } - Some(selection) + Some(best_selection) } #[cfg(test)] @@ -216,17 +265,82 @@ mod tests { }, }; - vec![utxo_one, utxo_two] + 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 one_solution_one_coin() { + let target = Amount::from_str("1 cBTC").unwrap(); + + let mut weighted_utxos = vec![WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("1 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }]; + + let utxo_match = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); + + let expected_utxos = vec![WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("1 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }]; + + assert_eq!(utxo_match, expected_utxos); } #[test] - fn find_solution_one_cbtc() { - let _target = Amount::from_str("1.5 cBTC").unwrap(); - let _weighted_utxos: Vec = create_weighted_utxos(); + fn one_solution_two_coins() { + let target = Amount::from_str("4 cBTC").unwrap(); + + let mut 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 utxo_match = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); + + let expected_utxos = vec![WeightedUtxo { + satisfaction_weight: SATISFACTION_SIZE, + utxo: TxOut { + value: Amount::from_str("4 cBTC").unwrap(), + script_pubkey: ScriptBuf::new(), + }, + }]; - //let utxo_match = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos); - //let expected_bool_vec = vec![false, false, false, true]; - //assert_eq!(expected_bool_vec, utxo_match); + assert_eq!(utxo_match, expected_utxos); } //#[test] @@ -299,8 +413,8 @@ mod tests { //} #[test] - fn select_coins_bnb_no_solution() { - let target: Amount = Amount::from_str("4 cBTC").unwrap(); + 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);