Skip to content

Commit

Permalink
wip: use effective_value
Browse files Browse the repository at this point in the history
  • Loading branch information
yancyribbens committed Dec 27, 2023
1 parent 4e5a2bb commit b912148
Showing 1 changed file with 127 additions and 37 deletions.
164 changes: 127 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ extern crate test;

mod single_random_draw;

use bitcoin::blockdata::effective_value;
use bitcoin::Amount;
use bitcoin::FeeRate;
use bitcoin::TxOut;
Expand Down Expand Up @@ -62,7 +63,7 @@ pub fn select_coins<T: Utxo>(
fee_rate: FeeRate,
weighted_utxos: &mut [WeightedUtxo],
) -> Option<Vec<WeightedUtxo>> {
match select_coins_bnb(target, cost_of_change, weighted_utxos.to_vec()) {
match select_coins_bnb(target, cost_of_change, fee_rate, weighted_utxos.to_vec()) {
Some(_res) => Some(Vec::new()),
None => select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()),
}
Expand Down Expand Up @@ -150,7 +151,8 @@ pub fn select_coins<T: Utxo>(
pub fn select_coins_bnb(
target: Amount,
cost_of_change: Amount,
mut weighted_utxos: Vec<WeightedUtxo>,
fee_rate: FeeRate,
weighted_utxos: Vec<WeightedUtxo>,
) -> Option<Vec<usize>> {
// Total_Tries in Core:
// https://github.com/bitcoin/bitcoin/blob/1d9da8da309d1dbf9aef15eb8dc43b4a2dc3d309/src/wallet/coinselection.cpp#L74
Expand All @@ -166,13 +168,28 @@ pub fn select_coins_bnb(

let mut index_selection: Vec<usize> = vec![];
let mut best_selection: Vec<usize> = vec![];
let mut available_value: Amount = weighted_utxos.iter().map(|u| u.utxo.value).sum();
let mut utxo_candidate_amounts: Vec<Amount> = vec![];

for u in &weighted_utxos {
let effective_value = effective_value(fee_rate, u.satisfaction_weight, u.utxo.value)?;

// Discard negative effective values.
let amount = match effective_value.to_unsigned() {
Ok(amt) => amt,
Err(_) => continue,
};

utxo_candidate_amounts.push(amount);
}

let mut available_value = utxo_candidate_amounts.clone().into_iter().sum::<Amount>();

if available_value < target {
return None;
}

weighted_utxos.sort_by(|a, b| b.utxo.value.cmp(&a.utxo.value));
utxo_candidate_amounts.sort();
utxo_candidate_amounts.reverse();

while iteration < ITERATION_LIMIT {
// There are two conditions for backtracking:
Expand Down Expand Up @@ -263,7 +280,7 @@ pub fn select_coins_bnb(
// * Backtrack
if backtrack {
let last_index = index_selection.pop().unwrap();
value -= weighted_utxos[last_index].utxo.value;
value -= utxo_candidate_amounts[last_index];
index -= 1;
assert_eq!(index, last_index);
}
Expand All @@ -279,11 +296,13 @@ pub fn select_coins_bnb(
index = index_selection.remove(0);

// The available value of the next iteration.
available_value =
Amount::from_sat(weighted_utxos[index + 1..].iter().fold(0u64, |mut s, u| {
s += u.utxo.value.to_sat();
available_value = Amount::from_sat(utxo_candidate_amounts[index + 1..].iter().fold(
0u64,
|mut s, u| {
s += u.to_sat();
s
}));
},
));

// If the new subtree does not have enough value, we are done searching.
if available_value < target {
Expand All @@ -296,7 +315,7 @@ pub fn select_coins_bnb(
}
// * Add next node to the inclusion branch.
else {
let utxo_value = weighted_utxos[index].utxo.value;
let utxo_value = utxo_candidate_amounts[index];

index_selection.push(index);
value += utxo_value;
Expand All @@ -320,35 +339,33 @@ mod tests {
use bitcoin::Weight;
use core::str::FromStr;

const SATISFACTION_SIZE: Weight = Weight::from_wu(204);

fn create_weighted_utxos() -> Vec<WeightedUtxo> {
let utxo_one = WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
satisfaction_weight: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("1 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
};

let utxo_two = WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
satisfaction_weight: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("2 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
};

let utxo_three = WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
satisfaction_weight: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("3 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
};

let utxo_four = WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
satisfaction_weight: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("4 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
Expand All @@ -363,14 +380,15 @@ mod tests {
let target = Amount::from_str("1 cBTC").unwrap();

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

let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -381,22 +399,23 @@ mod tests {

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

let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -407,22 +426,23 @@ mod tests {

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

let index_list = select_coins_bnb(target, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -431,7 +451,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -440,7 +461,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -449,7 +471,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -458,7 +481,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -467,7 +491,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -476,7 +501,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![1, 2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -485,7 +511,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -494,7 +521,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -503,7 +531,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 2];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -512,7 +541,8 @@ 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, Amount::ZERO, weighted_utxos).unwrap();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
let expected_index_list = vec![0, 1, 2, 3];
assert_eq!(index_list, expected_index_list);
}
Expand All @@ -526,20 +556,80 @@ mod tests {
let cost_of_change = target;

let weighted_utxos = vec![WeightedUtxo {
satisfaction_weight: SATISFACTION_SIZE,
satisfaction_weight: Weight::ZERO,
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 index_list =
select_coins_bnb(target, cost_of_change, FeeRate::ZERO, 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();
let index_list =
select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, weighted_utxos).unwrap();
assert_eq!(index_list, vec![]);
}

#[test]
fn effective_value() {
let target = Amount::from_str("1 cBTC").unwrap();
let fee_rate = FeeRate::from_sat_per_kwu(10);
let satisfaction_weight = Weight::from_wu(204);

let weighted_utxos = vec![WeightedUtxo {
satisfaction_weight,
utxo: TxOut {
// This would be a match using value, however since effective_value is used
// the effective_value is calculated, this will fall short of the target.
value: Amount::from_str("1 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
}];

let index_list = select_coins_bnb(target, Amount::ZERO, fee_rate, weighted_utxos.clone());
assert_eq!(index_list, None);
}

#[test]
fn skip_effective_negative_effective_value() {
let target = Amount::from_str("1 cBTC").unwrap();
let fee_rate = FeeRate::from_sat_per_kwu(10);
let satisfaction_weight = Weight::from_wu(204);

// 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: Weight::ZERO,
utxo: TxOut {
value: Amount::from_str("1.5 cBTC").unwrap(),
script_pubkey: ScriptBuf::new(),
},
},
WeightedUtxo {
satisfaction_weight,
utxo: TxOut {
// If this had no fee, a 1 sat utxo would be included since
// there would be less waste. However, since there is a weight
// and fee to spend it, the effective value is negative, so
// it will not be included.
value: Amount::from_str("1 sat").unwrap(),
script_pubkey: ScriptBuf::new(),
},
},
];

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

#[cfg(bench)]
Expand Down

0 comments on commit b912148

Please sign in to comment.