Skip to content

Commit

Permalink
wip: remove backtrack subtree
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Feb 28, 2024
1 parent 2d5b15c commit 4ce8015
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 89 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
136 changes: 48 additions & 88 deletions src/branch_and_bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -392,7 +327,7 @@ mod tests {
.collect()
}

fn create_weighted_utxos_from_values(fee: Amount, values: Vec<Amount>) -> Vec<WeightedUtxo> {
fn create_weighted_utxos_from_values(values: Vec<Amount>) -> Vec<WeightedUtxo> {
values
.iter()
.map(|amt| WeightedUtxo {
Expand Down Expand Up @@ -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![
Expand All @@ -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());
}

}

0 comments on commit 4ce8015

Please sign in to comment.