From 4ce8015cbb44f7a20c6d663362dc0fa59160b31b Mon Sep 17 00:00:00 2001 From: yancy Date: Wed, 28 Feb 2024 11:04:12 +0100 Subject: [PATCH] wip: remove backtrack subtree --- README.md | 2 +- src/branch_and_bound.rs | 136 ++++++++++++++-------------------------- 2 files changed, 49 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index d2a5d38..1fa1675 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ A basic performance comparison between this current [Rust BnB](https://github.co |implementation|pool size|ns/iter| |-------------:|---------|-------| -| Rust BnB| 1,000|695,860| +| Rust BnB| 1,000|897,810| | C++ Core BnB| 1,000|816,374| Note: The measurements where recorded using rustc 1.75. Expect worse performance with MSRV. diff --git a/src/branch_and_bound.rs b/src/branch_and_bound.rs index 532471b..d45fc23 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 @@ -273,47 +227,28 @@ pub fn select_coins_bnb( current_waste = current_waste.checked_sub(waste)?; } - // * Backtrack one node if backtrack { - let last_index = index_selection.pop().unwrap(); - let (eff_value, utxo_waste, _) = w_utxos[last_index]; - - 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]; - - // Reset waste counter since we are starting a new search branch. - current_waste = SignedAmount::ZERO; + loop { + 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 index <= *index_selection.last().unwrap() { + break; + } - // 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); - } + let (eff_value, _, _) = w_utxos[index]; + available_value += eff_value; + }; - // 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()); + let (eff_value, utxo_waste, _) = w_utxos[index]; + current_waste = current_waste.checked_sub(utxo_waste)?; + value = value.checked_sub(eff_value)?; + index_selection.pop().unwrap(); } // * Add next node to the inclusion branch. else { @@ -392,7 +327,7 @@ mod tests { .collect() } - fn create_weighted_utxos_from_values(fee: Amount, values: Vec) -> Vec { + fn create_weighted_utxos_from_values(values: Vec) -> Vec { values .iter() .map(|amt| WeightedUtxo { @@ -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(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,17 +707,18 @@ 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 weighted_utxos = create_weighted_utxos_from_values(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("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()); + } + }