diff --git a/src/lib.rs b/src/lib.rs index c3b54de..cc6ff3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,17 +154,19 @@ pub fn select_coins_bnb( ) -> Option> { // Total_Tries in Core: // https://github.com/bitcoin/bitcoin/blob/1d9da8da309d1dbf9aef15eb8dc43b4a2dc3d309/src/wallet/coinselection.cpp#L74 - const ITERATION_LIMIT: i32 = 100_000; + const ITERATION_LIMIT: i32 = 10; let mut iteration = 0; let mut index = 0; + let mut backtrack; + let mut new_tree; 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(); + let mut available_value: Amount = weighted_utxos.iter().map(|u| u.utxo.value).sum(); if available_value < target { return None; @@ -173,15 +175,201 @@ pub fn select_coins_bnb( weighted_utxos.sort_by(|a, b| b.utxo.value.cmp(&a.utxo.value)); while iteration < ITERATION_LIMIT { - //println!("iteration: {} index: {} value: {} selection: {:?} best_selection {:?}", iteration, index, value, selection, best_selection); - // backtrack + // There are two conditions for backtracking: // - // There are three 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. // - // * value meets or exceeded target. - // * leaf node (nothing left to explorer). - // * not enough value to make it to target. + // From: + // o + // / \ + // 4 + // / \ + // 3 + // / \ + // 2 + // / + // 1 + // + // To: + // o + // / \ + // 4 + // / \ + // 3 + // + // * This condition sets both new_tree and backtrack to true + // + // 2_ value meets or exceeded target. + // In this condition, we only backtrack one node + // + // From: + // o + // / + // 4 + // / + // 3 + // + // To: + // o + // / + // 4 + // / \ + // 3 + // + // * This condition sets backtrack to true + // Set the inital loop state + backtrack = false; + new_tree = false; + + // * not enouch value to make it to the target. + // Therefore, explore a new new subtree. + // + // - condition - + // + // o + // / \ + // 4 + // / \ + // 3 + // / \ + // 2 + // / + // 1 + // + // target = 5 + // selection = [4, 1] + // value = 5 + // available_value = 0 + // + if available_value + value < target { + backtrack = true; + new_tree = true; + } + + // * value meets or exceeds the target. + // Therefore backtrack one node to + // try another perumutation. + // + // - condition - + // + // o + // / + // 4 + // / + // 3 + // + // Check if selection is better than the previous known best, and + // update best_selection accordingly. + // + // + // Before: + // target = 5 + // available_value = [2, 1] = 3 + // selection = [4, 3] + // value = 7 + // best_selection = [] + // + // After: + // target = 5 + // available_value = [2, 1] = 3 + // selection = [4, 3] + // value = 7 + // best_selection = [4, 3] + if value >= target { + backtrack = true; + + let current_waste = value - target; + + if current_waste <= waste { + best_selection = selection.clone(); + } + } + + // * 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: + // target = 5 + // selection = [4, 3] + // value = 7 + // best_selection = [4, 3] + // available_value = [2, 1] = 3 + // + // After: + // target = 5 + // selection = [4] + // value = 4 + // availale_value = [2, 1] = 3 + // + if backtrack { + let _utxo_index = selection.pop().unwrap(); + //value -= utxo_value; + } + + // 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 + // selection = [0, 3] + // available_value = [] = 0 + // + // After: + // index = 1 + // target = 5 + // value = 0 + // selection = [] + // available_value = [3,2,1] = 6 + if new_tree { + // add 1 to start the new subtree + index = selection.remove(0) + 1; + selection = vec![]; + //available_value = sum(index to last item) + } // value meets or exceeds the target: // // For example: @@ -202,23 +390,14 @@ pub fn select_coins_bnb( // is subtracted by the current search index. Therefore // the omission branch at the search index is tested // next. - if value >= target { - let current_waste = value - target; - - if current_waste <= waste { - best_selection = selection.clone(); - } + //if value >= target { - selection.pop(); - - if selection.is_empty() { - return Some(best_selection); - } + //if index >= weighted_utxos.len() { + //return Some(best_selection); + //} - let utxo = &weighted_utxos[index]; - value -= utxo.utxo.value; - index -= 1; - } + //index -= 1; + //} // value is a leaf node, therefore, we remove the root // and try the exclusion branch of that root node. // o @@ -238,22 +417,52 @@ pub fn select_coins_bnb( // / \ // 3 // - // A new subtree is checked by resetting the search index + // A new newtree is checked by resetting the search index // to the root that was just removed. Therefore, the next // iteration, the search index is incremented by 1 and we // start with a root node that is the next in list. - else if index == weighted_utxos.len() { - index = selection[0]; - selection = vec![]; - } + //else if index == weighted_utxos.len() { + //index = selection[0]; + //selection = vec![]; + //} //if available_value + value < target { //} - // proceed with depth first search. + // * Add next node to the inclusion branch. + // + // target = 5 + // index = 1 + // + // Before: + // selection = [4] + // value = 4 + // available_value = [3,2,1] = 6 + // + // After: + // selection = [4, 3] + // value = 7 + // available_value = [2, 1] = 3 + // + // - Transformation - + // + // From + // o + // / + // 4 + // + // To + // o + // / + // 4 + // / + // 3 else { - let utxo = &weighted_utxos[index]; + let w_utxo = &weighted_utxos[index]; + let utxo_value = w_utxo.utxo.value; + selection.push(index); - value += utxo.utxo.value; + value += utxo_value; + available_value -= utxo_value; } index += 1; @@ -313,48 +522,74 @@ mod tests { 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 index_list = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); - let expected_index_list = vec![0]; - assert_eq!(index_list, expected_index_list); - } + //#[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 index_list = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); + //let expected_index_list = vec![0]; + //assert_eq!(index_list, expected_index_list); + //} - #[test] - 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(), - }, - }, - ]; + //#[test] + //fn one_coin_solution_of_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 index_list = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); + //let expected_index_list = vec![0]; + //assert_eq!(index_list, expected_index_list); + //} - let index_list = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); - let expected_index_list = vec![0]; - assert_eq!(index_list, expected_index_list); - } + //#[test] + //fn two_coin_solution_of_two_coins() { + //let target = Amount::from_str("7 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 index_list = select_coins_bnb(target, FEE_RATE, &mut weighted_utxos).unwrap(); + //let expected_index_list = vec![0, 1]; + //assert_eq!(index_list, expected_index_list); + //} //#[test] //fn find_solution_2_btc() {