From 40bb187a5f42985bdef44d263db0139cf59932df Mon Sep 17 00:00:00 2001 From: yancy Date: Thu, 13 Feb 2025 09:24:17 -0600 Subject: [PATCH] Switch to weight instead of satisfaction_weight Core uses just weight in coin-grinder, and it's complicated to maintain using both satisfaction_weight and weight. Therefore, switch to just weight units for all algorithms. --- benches/coin_selection.rs | 10 +++---- fuzz/fuzz_targets/select_coins.rs | 6 ++-- fuzz/fuzz_targets/select_coins_bnb.rs | 6 ++-- fuzz/fuzz_targets/select_coins_srd.rs | 6 ++-- src/branch_and_bound.rs | 30 +++++++++----------- src/coin_grinder.rs | 41 +++++++++++---------------- src/lib.rs | 40 +++++++------------------- src/single_random_draw.rs | 7 ++--- 8 files changed, 56 insertions(+), 90 deletions(-) diff --git a/benches/coin_selection.rs b/benches/coin_selection.rs index 914fc95..546e8df 100644 --- a/benches/coin_selection.rs +++ b/benches/coin_selection.rs @@ -5,13 +5,11 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; #[derive(Debug, Clone)] pub struct Utxo { output: TxOut, - satisfaction_weight: Weight, + weight: Weight, } impl WeightedUtxo for Utxo { - fn satisfaction_weight(&self) -> Weight { self.satisfaction_weight } - - fn weight(&self) -> Weight { Weight::ZERO } + fn weight(&self) -> Weight { self.weight } fn value(&self) -> Amount { self.output.value } } @@ -21,12 +19,12 @@ pub fn criterion_benchmark(c: &mut Criterion) { let one = Utxo { output: TxOut { value: Amount::from_sat(1_000), script_pubkey: ScriptBuf::new() }, - satisfaction_weight: Weight::ZERO, + weight: Weight::ZERO, }; let two = Utxo { output: TxOut { value: Amount::from_sat(3), script_pubkey: ScriptBuf::new() }, - satisfaction_weight: Weight::ZERO, + weight: Weight::ZERO, }; let target = Amount::from_sat(1_003); diff --git a/fuzz/fuzz_targets/select_coins.rs b/fuzz/fuzz_targets/select_coins.rs index 020e4cd..f29870e 100644 --- a/fuzz/fuzz_targets/select_coins.rs +++ b/fuzz/fuzz_targets/select_coins.rs @@ -8,12 +8,12 @@ use libfuzzer_sys::fuzz_target; #[derive(Arbitrary, Debug)] pub struct Utxo { output: TxOut, - satisfaction_weight: Weight, + weight: Weight, } impl WeightedUtxo for Utxo { - fn satisfaction_weight(&self) -> Weight { - self.satisfaction_weight + fn weight(&self) -> Weight { + self.weight } fn value(&self) -> Amount { diff --git a/fuzz/fuzz_targets/select_coins_bnb.rs b/fuzz/fuzz_targets/select_coins_bnb.rs index 1e16088..6236e9e 100644 --- a/fuzz/fuzz_targets/select_coins_bnb.rs +++ b/fuzz/fuzz_targets/select_coins_bnb.rs @@ -8,12 +8,12 @@ use libfuzzer_sys::fuzz_target; #[derive(Arbitrary, Debug)] pub struct Utxo { output: TxOut, - satisfaction_weight: Weight + weight: Weight } impl WeightedUtxo for Utxo { - fn satisfaction_weight(&self) -> Weight { - self.satisfaction_weight + fn weight(&self) -> Weight { + self.weight } fn value(&self) -> Amount { diff --git a/fuzz/fuzz_targets/select_coins_srd.rs b/fuzz/fuzz_targets/select_coins_srd.rs index 694d692..123ecec 100644 --- a/fuzz/fuzz_targets/select_coins_srd.rs +++ b/fuzz/fuzz_targets/select_coins_srd.rs @@ -9,12 +9,12 @@ use rand::thread_rng; #[derive(Arbitrary, Debug)] pub struct Utxo { output: TxOut, - satisfaction_weight: Weight + weight: Weight } impl WeightedUtxo for Utxo { - fn satisfaction_weight(&self) -> Weight { - self.satisfaction_weight + fn weight(&self) -> Weight { + self.weight } fn value(&self) -> Amount { diff --git a/src/branch_and_bound.rs b/src/branch_and_bound.rs index 74f047a..4004313 100644 --- a/src/branch_and_bound.rs +++ b/src/branch_and_bound.rs @@ -328,8 +328,6 @@ mod tests { use crate::tests::{assert_proptest_bnb, build_utxo, Utxo, UtxoPool}; use crate::WeightedUtxo; - const TX_IN_BASE_WEIGHT: u64 = 160; - #[derive(Debug)] pub struct ParamsStr<'a> { target: &'a str, @@ -350,7 +348,7 @@ mod tests { let mut pool = vec![]; for a in amts { - let utxo = build_utxo(a, Weight::ZERO, Weight::ZERO); + let utxo = build_utxo(a, Weight::ZERO); pool.push(utxo); } @@ -393,7 +391,7 @@ mod tests { .weighted_utxos .iter() .map(|s| Amount::from_str(s).unwrap()) - .map(|a| build_utxo(a, Weight::ZERO, Weight::ZERO)) + .map(|a| build_utxo(a, Weight::from_wu(160))) .collect(); let iter = select_coins_bnb(target, cost_of_change, fee_rate, lt_fee_rate, &w_utxos); @@ -427,8 +425,6 @@ mod tests { } fn calculate_max_fee_rate(amount: Amount, weight: Weight) -> Option { - let weight = weight + Weight::from_wu(TX_IN_BASE_WEIGHT); - let mut result = None; if let Some(fee_rate) = amount.checked_div_by_weight_floor(weight) { if fee_rate > FeeRate::ZERO { @@ -672,7 +668,7 @@ mod tests { .map(|a| Amount::from_sat(a as u64)) .collect(); - let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO, Weight::ZERO)).collect(); + let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO)).collect(); let list = select_coins_bnb(target, Amount::ONE_SAT, FeeRate::ZERO, FeeRate::ZERO, &pool); @@ -691,7 +687,7 @@ mod tests { }); let amts: Vec<_> = vals.map(Amount::from_sat).collect(); - let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO, Weight::ZERO)).collect(); + let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO)).collect(); let list = select_coins_bnb( Amount::from_sat(target), @@ -720,7 +716,7 @@ mod tests { // Add a value that will match the target before iteration exhaustion occurs. amts.push(Amount::from_sat(target)); - let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO, Weight::ZERO)).collect(); + let pool: Vec<_> = amts.into_iter().map(|a| build_utxo(a, Weight::ZERO)).collect(); let mut list = select_coins_bnb( Amount::from_sat(target), @@ -742,7 +738,7 @@ mod tests { arbtest(|u| { let amount = arb_amount_in_range(u, minimal_non_dust..=effective_value_max); - let utxo = build_utxo(amount, Weight::ZERO, Weight::ZERO); + let utxo = build_utxo(amount, Weight::ZERO); let pool: Vec = vec![utxo.clone()]; let coins: Vec = @@ -765,12 +761,12 @@ mod tests { let utxo = u.choose(&utxos)?; - let max_fee_rate = calculate_max_fee_rate(utxo.value(), utxo.satisfaction_weight()); + let max_fee_rate = calculate_max_fee_rate(utxo.value(), utxo.weight()); if let Some(f) = max_fee_rate { let fee_rate = arb_fee_rate_in_range(u, 1..=f.to_sat_per_kwu()); let target_effective_value = - effective_value(fee_rate, utxo.satisfaction_weight(), utxo.value()).unwrap(); + effective_value(fee_rate, utxo.weight() - Weight::from_wu(160), utxo.value()).unwrap(); if let Ok(target) = target_effective_value.to_unsigned() { let result = select_coins_bnb(target, Amount::ZERO, fee_rate, fee_rate, &utxos); @@ -778,7 +774,7 @@ mod tests { if let Some(r) = result { let sum: SignedAmount = r .map(|u| { - effective_value(fee_rate, u.satisfaction_weight(), u.value()) + effective_value(fee_rate, u.weight() - Weight::from_wu(160), u.value()) .unwrap() }) .sum(); @@ -794,7 +790,7 @@ mod tests { } Ok(()) - }); + }).seed(0xe26be2f200000020); } #[test] @@ -819,7 +815,7 @@ mod tests { let mut fee_rates: Vec = target_selection .iter() .map(|u| { - calculate_max_fee_rate(u.value(), u.satisfaction_weight()) + calculate_max_fee_rate(u.value(), u.weight()) .unwrap_or(FeeRate::ZERO) }) .collect(); @@ -831,7 +827,7 @@ mod tests { let effective_values: Vec = target_selection .iter() .map(|u| { - let e = effective_value(fee_rate, u.satisfaction_weight(), u.value()); + let e = effective_value(fee_rate, u.weight(), u.value()); e.unwrap_or(SignedAmount::ZERO) }) @@ -847,7 +843,7 @@ mod tests { if let Some(r) = result { let effective_value_sum: Amount = r .map(|u| { - effective_value(fee_rate, u.satisfaction_weight(), u.value()) + effective_value(fee_rate, u.weight(), u.value()) .unwrap() .to_unsigned() .unwrap() diff --git a/src/coin_grinder.rs b/src/coin_grinder.rs index 17b5701..fcc2256 100644 --- a/src/coin_grinder.rs +++ b/src/coin_grinder.rs @@ -272,26 +272,19 @@ mod tests { .map(|s| { let v: Vec<_> = s.split("/").collect(); match v.len() { - 3 => { - let a = Amount::from_str(v[0]).unwrap(); - let w = Weight::from_wu(v[1].parse().unwrap()); - let s = Weight::from_wu(v[2].parse().unwrap()); - (a, w, s) - } 2 => { let a = Amount::from_str(v[0]).unwrap(); let w = Weight::from_wu(v[1].parse().unwrap()); - let s = w - Weight::from_wu(160); - (a, w, s) + (a, w) } 1 => { let a = Amount::from_str(v[0]).unwrap(); - (a, Weight::ZERO, Weight::ZERO) + (a, Weight::ZERO) } _ => panic!(), } }) - .map(|(a, w, s)| build_utxo(a, w, s)) + .map(|(a, w)| build_utxo(a, w)) .collect() } @@ -326,10 +319,10 @@ mod tests { #[test] fn min_tail_weight() { let weighted_utxos = vec![ - "10 sats/8/8", - "7 sats/4/4", - "5 sats/4/4", - "4 sats/8/8" + "10 sats/8", + "7 sats/4", + "5 sats/4", + "4 sats/8" ]; let utxos: Vec<_> = build_utxos(weighted_utxos); @@ -348,10 +341,10 @@ mod tests { #[test] fn lookahead() { let weighted_utxos = vec![ - "10 sats/8/8", - "7 sats/4/4", - "5 sats/4/4", - "4 sats/8/8" + "10 sats/8", + "7 sats/4", + "5 sats/4", + "4 sats/8" ]; let utxos: Vec<_> = build_utxos(weighted_utxos); @@ -377,10 +370,10 @@ mod tests { max_weight: "100", fee_rate: "0", //from sat per vb weighted_utxos: vec![ - "10 sats/8/8", - "7 sats/4/4", - "5 sats/4/4", - "4 sats/8/8" + "10 sats/8", + "7 sats/4", + "5 sats/4", + "4 sats/8" ] }; @@ -395,8 +388,8 @@ mod tests { max_weight: "10000", fee_rate: "0", weighted_utxos: vec![ - "1 BTC/0/0", - "2 BTC/0/0", + "1 BTC/0", + "2 BTC/0", ] }; diff --git a/src/lib.rs b/src/lib.rs index afdf360..ad9fb02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,25 +25,9 @@ pub use crate::single_random_draw::select_coins_srd; // https://github.com/bitcoin/bitcoin/blob/f722a9bd132222d9d5cd503b5af25c905b205cdb/src/wallet/coinselection.h#L20 const CHANGE_LOWER: Amount = Amount::from_sat(50_000); -// https://github.com/rust-bitcoin/rust-bitcoin/blob/35202ba51bef3236e6ed1007a0d2111265b6498c/bitcoin/src/blockdata/transaction.rs#L357 -const SEQUENCE_SIZE: u64 = 4; - -// https://github.com/rust-bitcoin/rust-bitcoin/blob/35202ba51bef3236e6ed1007a0d2111265b6498c/bitcoin/src/blockdata/transaction.rs#L92 -const OUT_POINT_SIZE: u64 = 32 + 4; - -// https://github.com/rust-bitcoin/rust-bitcoin/blob/35202ba51bef3236e6ed1007a0d2111265b6498c/bitcoin/src/blockdata/transaction.rs#L249 -const BASE_WEIGHT: Weight = Weight::from_vb_unwrap(OUT_POINT_SIZE + SEQUENCE_SIZE); - /// Behavior needed for coin-selection. pub trait WeightedUtxo { - /// The weight of the witness data and `scriptSig` which is used to then calculate the fee on - /// a per `UTXO` basis. - /// - /// see also: - /// - fn satisfaction_weight(&self) -> Weight; - - /// The weight + /// Total UTXO weight. fn weight(&self) -> Weight; /// The UTXO value. @@ -69,8 +53,7 @@ pub trait WeightedUtxo { /// /// The fee is calculated as: fee rate * (satisfaction_weight + the base weight). fn calculate_fee(&self, fee_rate: FeeRate) -> Option { - let weight = self.satisfaction_weight().checked_add(BASE_WEIGHT)?; - fee_rate.checked_mul_by_weight(weight) + fee_rate.checked_mul_by_weight(self.weight()) } /// Computes how wastefull it is to spend this `Utxo` @@ -139,8 +122,7 @@ mod tests { .map(|a| { let amt = Amount::from_sat(*a); let weight = Weight::ZERO; - let satisfaction_weight = Weight::ZERO; - build_utxo(amt, weight, satisfaction_weight) + build_utxo(amt, weight) }) .collect(); @@ -157,12 +139,11 @@ mod tests { pub struct Utxo { pub output: TxOut, pub weight: Weight, - pub satisfaction_weight: Weight, } - pub fn build_utxo(amt: Amount, weight: Weight, satisfaction_weight: Weight) -> Utxo { + pub fn build_utxo(amt: Amount, weight: Weight) -> Utxo { let output = TxOut { value: amt, script_pubkey: ScriptBuf::new() }; - Utxo { output, weight, satisfaction_weight } + Utxo { output, weight } } impl<'a> Arbitrary<'a> for UtxoPool { @@ -185,7 +166,6 @@ mod tests { } impl WeightedUtxo for Utxo { - fn satisfaction_weight(&self) -> Weight { self.satisfaction_weight } fn weight(&self) -> Weight { self.weight } fn value(&self) -> Amount { self.output.value } } @@ -253,7 +233,7 @@ mod tests { let subset = gen.gen_subset(&pool.utxos).collect::>(); let effective_values_sum = subset .iter() - .filter_map(|u| effective_value(fee_rate, u.satisfaction_weight(), u.value())) + .filter_map(|u| effective_value(fee_rate, u.weight(), u.value())) .checked_sum(); if let Some(s) = effective_values_sum { @@ -278,7 +258,7 @@ mod tests { let subset = gen.gen_subset(&pool.utxos).collect::>(); let effective_values_sum = subset .iter() - .filter_map(|u| effective_value(fee_rate, u.satisfaction_weight(), u.value())) + .filter_map(|u| effective_value(fee_rate, u.weight(), u.value())) .checked_sum(); if let Some(eff_sum) = effective_values_sum { @@ -310,7 +290,7 @@ mod tests { if let Some(r) = result { let utxo_sum: Amount = r .map(|u| { - effective_value(fee_rate, u.satisfaction_weight(), u.value()) + effective_value(fee_rate, u.weight(), u.value()) .unwrap() .to_unsigned() .unwrap() @@ -338,7 +318,7 @@ mod tests { if let Some(r) = result { let utxo_sum: Amount = r .map(|u| { - effective_value(fee_rate, u.satisfaction_weight(), u.value()) + effective_value(fee_rate, u.weight(), u.value()) .unwrap() .to_unsigned() .unwrap() @@ -369,7 +349,7 @@ mod tests { if let Some(r) = result { let utxo_sum: Amount = r .map(|u| { - effective_value(fee_rate, u.satisfaction_weight(), u.value()) + effective_value(fee_rate, u.weight(), u.value()) .unwrap() .to_unsigned() .unwrap() diff --git a/src/single_random_draw.rs b/src/single_random_draw.rs index d0cfc73..bda401f 100644 --- a/src/single_random_draw.rs +++ b/src/single_random_draw.rs @@ -61,7 +61,7 @@ pub fn select_coins_srd<'a, R: rand::Rng + ?Sized, Utxo: WeightedUtxo>( for w_utxo in origin { let utxo_value = w_utxo.value(); - let utxo_weight = w_utxo.satisfaction_weight(); + let utxo_weight = w_utxo.weight(); let effective_value = effective_value(fee_rate, utxo_weight, utxo_value); if let Some(e) = effective_value { @@ -95,7 +95,6 @@ mod tests { use crate::WeightedUtxo; const FEE_RATE: FeeRate = FeeRate::from_sat_per_kwu(10); - const SATISFACTION_WEIGHT: Weight = Weight::from_wu(204); #[derive(Debug)] pub struct ParamsStr<'a> { @@ -110,7 +109,7 @@ mod tests { let mut pool = vec![]; for a in amts { - let utxo = build_utxo(a, Weight::ZERO, SATISFACTION_WEIGHT); + let utxo = build_utxo(a, Weight::ZERO); pool.push(utxo); } @@ -163,7 +162,7 @@ mod tests { _ => panic!(), } }) - .map(|(a, w)| build_utxo(a, Weight::ZERO, w)) + .map(|(a, w)| build_utxo(a, w)) .collect(); let result = select_coins_srd(target, fee_rate, &w_utxos, &mut get_rng());