Skip to content

Commit

Permalink
wip: cost_of_change
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Dec 26, 2023
1 parent 4b80c2a commit 4e5a2bb
Showing 1 changed file with 55 additions and 88 deletions.
143 changes: 55 additions & 88 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub struct WeightedUtxo {
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn select_coins<T: Utxo>(
target: Amount,
cost_of_change: FeeRate,
cost_of_change: Amount,
fee_rate: FeeRate,
weighted_utxos: &mut [WeightedUtxo],
) -> Option<Vec<WeightedUtxo>> {
Expand Down Expand Up @@ -149,7 +149,7 @@ pub fn select_coins<T: Utxo>(
// doesn't have enough value left to meet the target, we conclude our search at [3, 2].
pub fn select_coins_bnb(
target: Amount,
_fee_rate: FeeRate,
cost_of_change: Amount,
mut weighted_utxos: Vec<WeightedUtxo>,
) -> Option<Vec<usize>> {
// Total_Tries in Core:
Expand Down Expand Up @@ -225,32 +225,27 @@ pub fn select_coins_bnb(

// * not enough value to make it to the target.
// Therefore, explore a new new subtree.
//
// - condition -
//
// o
// / \
// 4
// / \
// 3
// / \
// 2
// /
// 1
if available_value + value < target {
backtrack_subtree = true;
}
// * value meets or exceeds the target.
// Therefore backtrack one node to
// try another permutation.
// * value exceeds target, however it the solution is too wasteful.
//
// - condition -
// 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
// transaction by an output, we accept solutions that may be larger than the target as
// if they are exactly equal to the target and consider the overage waste or a throw
// away amount. However we do not consider values greater than value + cost_of_change.
//
// o
// /
// 4
// /
// 3
// This effectively creates a range of possible solution where;
// range = (target, target + cost_of_change]
//
// That is, the range includes solutions that exactly equal the target up to but not
// including values greater than target + cost_of_change.
else if value > target + cost_of_change {
backtrack = true;
}
// * value meets or exceeds the target.
// Record the solution and the waste then continue.
//
// Check if index_selection is better than the previous known best, and
// update best_selection accordingly.
Expand All @@ -266,50 +261,13 @@ pub fn select_coins_bnb(
}

// * 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.
if backtrack {
let last_index = index_selection.pop().unwrap();
value -= weighted_utxos[last_index].utxo.value;
index -= 1;
assert_eq!(index, last_index);
}
// * Backtrack to new tree
// 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
else if backtrack_subtree {
// No new subtree left to explore.
if index_selection.is_empty() {
Expand Down Expand Up @@ -337,20 +295,6 @@ pub fn select_coins_bnb(
value = Amount::ZERO;
}
// * Add next node to the inclusion branch.
//
// - Transformation -
//
// From
// o
// /
// 4
//
// To
// o
// /
// 4
// /
// 3
else {
let utxo_value = weighted_utxos[index].utxo.value;

Expand All @@ -376,7 +320,6 @@ mod tests {
use bitcoin::Weight;
use core::str::FromStr;

const FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(10);
const SATISFACTION_SIZE: Weight = Weight::from_wu(204);

fn create_weighted_utxos() -> Vec<WeightedUtxo> {
Expand Down Expand Up @@ -427,7 +370,7 @@ mod tests {
},
}];

let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -453,7 +396,7 @@ mod tests {
},
];

let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -479,7 +422,7 @@ mod tests {
},
];

let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -488,7 +431,7 @@ mod tests {
fn one() {
let target = Amount::from_str("1 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -497,7 +440,7 @@ mod tests {
fn two() {
let target = Amount::from_str("2 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -506,7 +449,7 @@ mod tests {
fn three() {
let target = Amount::from_str("3 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -515,7 +458,7 @@ mod tests {
fn four() {
let target = Amount::from_str("4 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -524,7 +467,7 @@ mod tests {
fn five() {
let target = Amount::from_str("5 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -533,7 +476,7 @@ mod tests {
fn six() {
let target = Amount::from_str("6 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -542,7 +485,7 @@ mod tests {
fn seven() {
let target = Amount::from_str("7 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -551,7 +494,7 @@ mod tests {
fn eight() {
let target = Amount::from_str("8 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -560,7 +503,7 @@ mod tests {
fn nine() {
let target = Amount::from_str("9 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -569,10 +512,34 @@ mod tests {
fn ten() {
let target = Amount::from_str("10 cBTC").unwrap();
let weighted_utxos = create_weighted_utxos();
let index_list = select_coins_bnb(target, FEE_RATE, weighted_utxos).unwrap();
let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 2, 3];
assert_eq!(index_list, expected_index_list);
}

#[test]
fn cost_of_change() {
let target = Amount::from_str("1 cBTC").unwrap();

// Since cost of change here is one, we accept any solution
// between 1 and 2. Range = (1, 2]
let cost_of_change = target;

let weighted_utxos = vec![WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
utxo: TxOut {
value: Amount::from_str("1.5 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
}];

let index_list = select_coins_bnb(target, cost_of_change, weighted_utxos.clone()).unwrap();
let expected_index_list = vec![0];
assert_eq!(index_list, expected_index_list);

let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
assert_eq!(index_list, vec![]);
}
}

#[cfg(bench)]
Expand Down

0 comments on commit 4e5a2bb

Please sign in to comment.