diff --git a/src/branch_and_bound.rs b/src/branch_and_bound.rs index 532471b..412bf7b 100644 --- a/src/branch_and_bound.rs +++ b/src/branch_and_bound.rs @@ -143,7 +143,6 @@ pub fn select_coins_bnb( let mut iteration = 0; let mut index = 0; let mut backtrack; - let mut backtrack_subtree; let mut value = Amount::ZERO; @@ -180,59 +179,14 @@ pub fn select_coins_bnb( } 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 initial loop state backtrack = false; - backtrack_subtree = false; - // * not enough value to make it to the target. - // Therefore, explore a new new subtree. // // unchecked_add is used here for performance. Before entering the search loop, all // utxos are summed and checked for overflow. Since there was no overflow then, any // subset of addition will not overflow. if available_value.unchecked_add(value) < target { - backtrack_subtree = true; + backtrack = true; } // This optimization provides an upper bound on the amount of waste that is acceptable. // Since value is lost when we create a change output due to increasing the size of the @@ -282,38 +236,19 @@ pub fn select_coins_bnb( current_waste = current_waste.checked_sub(utxo_waste)?; value = value.checked_sub(eff_value)?; index -= 1; - assert_eq!(index, last_index); - } - // * Backtrack to new tree - else if backtrack_subtree { - // No new subtree left to explore. - if index_selection.is_empty() { - return index_to_utxo_list(best_selection, w_utxos); - } - // Anchor the new subtree at the next available index. - // - // if our index selection is: [0,1,2,3] - // then copy the head 0 into index. - // At the end of this loop, index is incremented to 1. - // Therefore, we will be starting our next search tree at index 1. - index = index_selection[0]; + loop { + let i = index_selection[index]; + if i == index { + break; + } - // Reset waste counter since we are starting a new search branch. - current_waste = SignedAmount::ZERO; + let (eff_value, _, _) = w_utxos[i]; + available_value += eff_value; + index -= 1; + }; - // The available value of the next iteration. This should never overflow - // since the value is always less than the last available_value calculation. - available_value = w_utxos[index + 1..].iter().map(|&(v, _, _)| v).sum(); - - // If the new subtree does not have enough value, we are done searching. - if available_value < target { - return index_to_utxo_list(best_selection, w_utxos); - } - - // Start a new selection and add the root of the new subtree to the index selection. - index_selection.clear(); - value = Amount::ZERO; + assert_eq!(index, *index_selection.last().unwrap()); } // * Add next node to the inclusion branch. else { @@ -737,7 +672,31 @@ mod tests { } #[test] - fn select_coins_bnb_extended_set() { + fn select_coins_bnb_set_size_five() { + let target = Amount::from_str("6 cBTC").unwrap(); + let cost_of_change = Amount::ZERO; + let vals = vec![ + Amount::from_str("3 cBTC").unwrap(), + Amount::from_str("2.9 cBTC").unwrap(), + Amount::from_str("2 cBTC").unwrap(), + Amount::from_str("1.9 cBTC").unwrap(), + Amount::from_str("1 cBTC").unwrap(), + ]; + + let weighted_utxos = create_weighted_utxos_from_values(Amount::ZERO, vals); + let list: Vec<_> = + select_coins_bnb(target, cost_of_change, FeeRate::ZERO, FeeRate::ZERO, &weighted_utxos) + .unwrap() + .collect(); + + assert_eq!(list.len(), 3); + assert_eq!(list[0].utxo.value, Amount::from_str("3 cBTC").unwrap()); + assert_eq!(list[1].utxo.value, Amount::from_str("2 cBTC").unwrap()); + assert_eq!(list[2].utxo.value, Amount::from_str("1 cBTC").unwrap()); + } + + #[test] + fn select_coins_bnb_set_size_seven() { let target = Amount::from_str("18 cBTC").unwrap(); let cost_of_change = Amount::from_str("50 sats").unwrap(); let vals = vec![ @@ -748,7 +707,7 @@ mod tests { Amount::from_str("3 cBTC").unwrap(), Amount::from_str("2 cBTC").unwrap(), Amount::from_str("1 cBTC").unwrap() + Amount::from_str("5 sats").unwrap(), - ]; + ]; let weighted_utxos = create_weighted_utxos_from_values(Amount::ZERO, vals); let list: Vec<_> = @@ -756,9 +715,10 @@ mod tests { .unwrap() .collect(); - assert_eq!(list.len(), 3); - assert_eq!(list[0].utxo.value, Amount::from_str("10 cBTC").unwrap()); - assert_eq!(list[1].utxo.value, Amount::from_str("6 cBTC").unwrap()); - assert_eq!(list[2].utxo.value, Amount::from_str("2 cBTC").unwrap()); - } + assert_eq!(list.len(), 3); + assert_eq!(list[0].utxo.value, Amount::from_str("10 cBTC").unwrap()); + assert_eq!(list[1].utxo.value, Amount::from_str("6 cBTC").unwrap()); + assert_eq!(list[2].utxo.value, Amount::from_str("2 cBTC").unwrap()); + } + }