From c278fdd35de8210d3d77af32b80b802941447635 Mon Sep 17 00:00:00 2001 From: yancy Date: Tue, 26 Dec 2023 15:17:18 +0100 Subject: [PATCH] Use effective value instead of value --- src/lib.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 112 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba64ebb..a1f80e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -62,7 +63,7 @@ pub fn select_coins( fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], ) -> Option> { - match select_coins_bnb(target, cost_of_change, weighted_utxos) { + match select_coins_bnb(target, cost_of_change, fee_rate, weighted_utxos) { Some(_res) => Some(Vec::new()), None => select_coins_srd(target, fee_rate, weighted_utxos, &mut thread_rng()), } @@ -78,6 +79,7 @@ pub fn select_coins( /// /// * `target` - Target spend `Amount` /// * `cost_of_change` - The `Amount` needed to produce a change output +/// * `fee_rate` - `FeeRate` used to calculate each effective_value output value /// * `weighted_utxos` - The candidate Weighted UTXOs from which to choose a selection from // This search can be thought of as exploring a binary tree where the left branch is the inclusion // of a node and the right branch is the exclusion. For example, if the utxo set consist of a @@ -157,6 +159,7 @@ pub fn select_coins( pub fn select_coins_bnb( target: Amount, cost_of_change: Amount, + fee_rate: FeeRate, weighted_utxos: &mut [WeightedUtxo], ) -> Option> { // Total_Tries in Core: @@ -173,13 +176,28 @@ pub fn select_coins_bnb( let mut index_selection: Vec = vec![]; let mut best_selection: Option> = None; - let mut available_value: Amount = weighted_utxos.iter().map(|u| u.utxo.value).sum(); + let mut utxo_candidate_amounts: Vec = 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::(); 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: @@ -268,7 +286,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); } @@ -284,11 +302,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 { @@ -301,7 +321,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; @@ -365,7 +385,8 @@ mod tests { fn one() { let target = Amount::from_str("1 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![3]; assert_eq!(index_list, expected_index_list); } @@ -374,7 +395,8 @@ mod tests { fn two() { let target = Amount::from_str("2 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![2]; assert_eq!(index_list, expected_index_list); } @@ -383,7 +405,8 @@ mod tests { fn three() { let target = Amount::from_str("3 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![2, 3]; assert_eq!(index_list, expected_index_list); } @@ -392,7 +415,8 @@ mod tests { fn four() { let target = Amount::from_str("4 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![1, 3]; assert_eq!(index_list, expected_index_list); } @@ -401,7 +425,8 @@ mod tests { fn five() { let target = Amount::from_str("5 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![1, 2]; assert_eq!(index_list, expected_index_list); } @@ -410,7 +435,8 @@ mod tests { fn six() { let target = Amount::from_str("6 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![1, 2, 3]; assert_eq!(index_list, expected_index_list); } @@ -419,7 +445,8 @@ mod tests { fn seven() { let target = Amount::from_str("7 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![0, 2, 3]; assert_eq!(index_list, expected_index_list); } @@ -428,7 +455,8 @@ mod tests { fn eight() { let target = Amount::from_str("8 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![0, 1, 3]; assert_eq!(index_list, expected_index_list); } @@ -437,7 +465,8 @@ mod tests { fn nine() { let target = Amount::from_str("9 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![0, 1, 2]; assert_eq!(index_list, expected_index_list); } @@ -446,7 +475,8 @@ mod tests { fn ten() { let target = Amount::from_str("10 cBTC").unwrap(); let mut weighted_utxos = create_weighted_utxos(); - let index_list = select_coins_bnb(target, Amount::ZERO, &mut weighted_utxos).unwrap(); + let index_list = + select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos).unwrap(); let expected_index_list = vec![0, 1, 2, 3]; assert_eq!(index_list, expected_index_list); } @@ -468,12 +498,72 @@ mod tests { }]; let index_list = - select_coins_bnb(target, cost_of_change, &mut weighted_utxos.clone()).unwrap(); + select_coins_bnb(target, cost_of_change, FeeRate::ZERO, &mut 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, &mut weighted_utxos).unwrap(); - assert_eq!(index_list, vec![]); + let index_list = select_coins_bnb(target, Amount::ZERO, FeeRate::ZERO, &mut weighted_utxos); + assert_eq!(index_list, None); + } + + #[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, &mut 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, &mut weighted_utxos.clone()) + .unwrap(); + let expected_index_list = vec![0]; + assert_eq!(index_list, expected_index_list); } }