From 429b65ca4432c3b0d1408ed9af9f899e13c70424 Mon Sep 17 00:00:00 2001 From: vmammal Date: Sun, 22 Oct 2023 22:29:57 -0400 Subject: [PATCH] refactor(bdk)!: drop `FeeRate` from bdk::types instead adopting `bitcoin::FeeRate` throughout --- crates/bdk/src/error.rs | 8 +- crates/bdk/src/psbt/mod.rs | 5 +- crates/bdk/src/types.rs | 167 +----------------------- crates/bdk/src/wallet/coin_selection.rs | 90 +++++++------ crates/bdk/src/wallet/hardwaresigner.rs | 2 +- crates/bdk/src/wallet/mod.rs | 42 +++--- crates/bdk/src/wallet/tx_builder.rs | 25 ++-- crates/bdk/tests/psbt.rs | 30 +++-- crates/bdk/tests/wallet.rs | 77 ++++++----- 9 files changed, 153 insertions(+), 293 deletions(-) diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index fcb5a6f7b1..03694eca94 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -49,8 +49,8 @@ pub enum Error { IrreplaceableTransaction, /// When bumping a tx the fee rate requested is lower than required FeeRateTooLow { - /// Required fee rate (satoshi/vbyte) - required: crate::types::FeeRate, + /// Required fee rate + required: bitcoin::FeeRate, }, /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee FeeTooLow { @@ -137,8 +137,8 @@ impl fmt::Display for Error { Self::IrreplaceableTransaction => write!(f, "Transaction can't be replaced"), Self::FeeRateTooLow { required } => write!( f, - "Fee rate too low: required {} sat/vbyte", - required.as_sat_per_vb() + "Fee rate too low: required {} sat/kwu", + required.to_sat_per_kwu() ), Self::FeeTooLow { required } => write!(f, "Fee to low: required {} sat", required), Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"), diff --git a/crates/bdk/src/psbt/mod.rs b/crates/bdk/src/psbt/mod.rs index bc6ce85897..6add0fae36 100644 --- a/crates/bdk/src/psbt/mod.rs +++ b/crates/bdk/src/psbt/mod.rs @@ -11,9 +11,10 @@ //! Additional functions on the `rust-bitcoin` `PartiallySignedTransaction` structure. -use crate::FeeRate; use alloc::vec::Vec; use bitcoin::psbt::PartiallySignedTransaction as Psbt; +use bitcoin::Amount; +use bitcoin::FeeRate; use bitcoin::TxOut; // TODO upstream the functions here to `rust-bitcoin`? @@ -73,7 +74,7 @@ impl PsbtUtils for Psbt { let fee_amount = self.fee_amount(); fee_amount.map(|fee| { let weight = self.clone().extract_tx().weight(); - FeeRate::from_wu(fee, weight) + Amount::from_sat(fee) / weight }) } } diff --git a/crates/bdk/src/types.rs b/crates/bdk/src/types.rs index 2a88cc374f..7f3711fa72 100644 --- a/crates/bdk/src/types.rs +++ b/crates/bdk/src/types.rs @@ -11,11 +11,10 @@ use alloc::boxed::Box; use core::convert::AsRef; -use core::ops::Sub; use bdk_chain::ConfirmationTime; use bitcoin::blockdata::transaction::{OutPoint, TxOut}; -use bitcoin::{psbt, Weight}; +use bitcoin::psbt; use serde::{Deserialize, Serialize}; @@ -47,103 +46,6 @@ impl AsRef<[u8]> for KeychainKind { } } -/// Fee rate -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] -// Internally stored as satoshi/vbyte -pub struct FeeRate(f32); - -impl FeeRate { - /// Create a new instance checking the value provided - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - fn new_checked(value: f32) -> Self { - assert!(value.is_normal() || value == 0.0); - assert!(value.is_sign_positive()); - - FeeRate(value) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kwu - pub fn from_sat_per_kwu(sat_per_kwu: f32) -> Self { - FeeRate::new_checked(sat_per_kwu / 250.0_f32) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in sats/kvb - pub fn from_sat_per_kvb(sat_per_kvb: f32) -> Self { - FeeRate::new_checked(sat_per_kvb / 1000.0_f32) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in btc/kvbytes - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - pub fn from_btc_per_kvb(btc_per_kvb: f32) -> Self { - FeeRate::new_checked(btc_per_kvb * 1e5) - } - - /// Create a new instance of [`FeeRate`] given a float fee rate in satoshi/vbyte - /// - /// ## Panics - /// - /// Panics if the value is not [normal](https://doc.rust-lang.org/std/primitive.f32.html#method.is_normal) (except if it's a positive zero) or negative. - pub fn from_sat_per_vb(sat_per_vb: f32) -> Self { - FeeRate::new_checked(sat_per_vb) - } - - /// Create a new [`FeeRate`] with the default min relay fee value - pub const fn default_min_relay_fee() -> Self { - FeeRate(1.0) - } - - /// Calculate fee rate from `fee` and weight units (`wu`). - pub fn from_wu(fee: u64, wu: Weight) -> FeeRate { - Self::from_vb(fee, wu.to_vbytes_ceil() as usize) - } - - /// Calculate fee rate from `fee` and `vbytes`. - pub fn from_vb(fee: u64, vbytes: usize) -> FeeRate { - let rate = fee as f32 / vbytes as f32; - Self::from_sat_per_vb(rate) - } - - /// Return the value as satoshi/vbyte - pub fn as_sat_per_vb(&self) -> f32 { - self.0 - } - - /// Return the value as satoshi/kwu - pub fn sat_per_kwu(&self) -> f32 { - self.0 * 250.0_f32 - } - - /// Calculate absolute fee in Satoshis using size in weight units. - pub fn fee_wu(&self, wu: Weight) -> u64 { - self.fee_vb(wu.to_vbytes_ceil() as usize) - } - - /// Calculate absolute fee in Satoshis using size in virtual bytes. - pub fn fee_vb(&self, vbytes: usize) -> u64 { - (self.as_sat_per_vb() * vbytes as f32).ceil() as u64 - } -} - -impl Default for FeeRate { - fn default() -> Self { - FeeRate::default_min_relay_fee() - } -} - -impl Sub for FeeRate { - type Output = Self; - - fn sub(self, other: FeeRate) -> Self::Output { - FeeRate(self.0 - other.0) - } -} - /// Trait implemented by types that can be used to measure weight units. pub trait Vbytes { /// Convert weight units to virtual bytes. @@ -236,70 +138,7 @@ impl Utxo { #[cfg(test)] mod tests { - use super::*; - - #[test] - fn can_store_feerate_in_const() { - const _MIN_RELAY: FeeRate = FeeRate::default_min_relay_fee(); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_neg_zero() { - let _ = FeeRate::from_sat_per_vb(-0.0); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_neg_value() { - let _ = FeeRate::from_sat_per_vb(-5.0); - } + //use super::*; - #[test] - #[should_panic] - fn test_invalid_feerate_nan() { - let _ = FeeRate::from_sat_per_vb(f32::NAN); - } - - #[test] - #[should_panic] - fn test_invalid_feerate_inf() { - let _ = FeeRate::from_sat_per_vb(f32::INFINITY); - } - - #[test] - fn test_valid_feerate_pos_zero() { - let _ = FeeRate::from_sat_per_vb(0.0); - } - - #[test] - fn test_fee_from_btc_per_kvb() { - let fee = FeeRate::from_btc_per_kvb(1e-5); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_vbyte() { - let fee = FeeRate::from_sat_per_vb(1.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_default_min_relay_fee() { - let fee = FeeRate::default_min_relay_fee(); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_kvb() { - let fee = FeeRate::from_sat_per_kvb(1000.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - } - - #[test] - fn test_fee_from_sat_per_kwu() { - let fee = FeeRate::from_sat_per_kwu(250.0); - assert!((fee.as_sat_per_vb() - 1.0).abs() < f32::EPSILON); - assert_eq!(fee.sat_per_kwu(), 250.0); - } + // TODO: decide what goes here } diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index c3e84af2ba..4c1679125b 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -38,7 +38,7 @@ //! &self, //! required_utxos: Vec, //! optional_utxos: Vec, -//! fee_rate: bdk::FeeRate, +//! fee_rate: FeeRate, //! target_amount: u64, //! drain_script: &Script, //! ) -> Result { @@ -58,7 +58,7 @@ //! }, //! ) //! .collect::>(); -//! let additional_fees = fee_rate.fee_wu(additional_weight); +//! let additional_fees = (fee_rate * additional_weight).to_sat(); //! let amount_needed_with_fees = additional_fees + target_amount; //! if selected_amount < amount_needed_with_fees { //! return Err(bdk::Error::InsufficientFunds { @@ -97,10 +97,10 @@ //! # Ok::<(), bdk::Error>(()) //! ``` -use crate::types::FeeRate; use crate::wallet::utils::IsDust; use crate::WeightedUtxo; use crate::{error::Error, Utxo}; +use bitcoin::FeeRate; use alloc::vec::Vec; use bitcoin::consensus::encode::serialize; @@ -276,7 +276,7 @@ impl CoinSelectionAlgorithm for OldestFirstCoinSelection { pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Script) -> Excess { // drain_output_len = size(len(script_pubkey)) + len(script_pubkey) + size(output_value) let drain_output_len = serialize(drain_script).len() + 8usize; - let change_fee = fee_rate.fee_vb(drain_output_len); + let change_fee = (fee_rate * Weight::from_vb_unchecked(drain_output_len as u64)).to_sat(); let drain_val = remaining_amount.saturating_sub(change_fee); if drain_val.is_dust(drain_script) { @@ -307,9 +307,12 @@ fn select_sorted_utxos( (&mut selected_amount, &mut fee_amount), |(selected_amount, fee_amount), (must_use, weighted_utxo)| { if must_use || **selected_amount < target_amount + **fee_amount { - **fee_amount += fee_rate.fee_wu(Weight::from_wu( - (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, - )); + **fee_amount += (fee_rate + * Weight::from_wu( + (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, + )) + .to_sat(); + **selected_amount += weighted_utxo.utxo.txout().value; log::debug!( @@ -357,9 +360,10 @@ struct OutputGroup { impl OutputGroup { fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { - let fee = fee_rate.fee_wu(Weight::from_wu( - (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, - )); + let fee = (fee_rate + * Weight::from_wu((TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64)) + .to_sat(); + let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64; OutputGroup { weighted_utxo, @@ -426,7 +430,7 @@ impl CoinSelectionAlgorithm for BranchAndBoundCoinSelection { .iter() .fold(0, |acc, x| acc + x.effective_value); - let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = (Weight::from_vb_unchecked(self.size_of_change) * fee_rate).to_sat(); // `curr_value` and `curr_available_value` are both the sum of *effective_values* of // the UTXOs. For the optional UTXOs (curr_available_value) we filter out UTXOs with @@ -517,7 +521,7 @@ impl BranchAndBoundCoinSelection { mut curr_value: i64, mut curr_available_value: i64, target_amount: i64, - cost_of_change: f32, + cost_of_change: u64, drain_script: &Script, fee_rate: FeeRate, ) -> Result { @@ -840,7 +844,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -861,7 +865,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -882,7 +886,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -904,7 +908,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -922,7 +926,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -939,7 +943,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -960,7 +964,7 @@ mod test { .coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -981,7 +985,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1003,7 +1007,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1022,7 +1026,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -1043,7 +1047,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1064,7 +1068,7 @@ mod test { .coin_select( utxos.clone(), utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1085,7 +1089,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1122,7 +1126,7 @@ mod test { .coin_select( required, optional, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1144,7 +1148,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1162,7 +1166,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1000.0), + FeeRate::from_sat_per_vb_unchecked(1000), target_amount, &drain_script, ) @@ -1179,7 +1183,7 @@ mod test { .coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(1.0), + FeeRate::from_sat_per_vb_unchecked(1), target_amount, &drain_script, ) @@ -1205,7 +1209,7 @@ mod test { .coin_select( vec![], optional_utxos, - FeeRate::from_sat_per_vb(0.0), + FeeRate::ZERO, target_amount, &drain_script, ) @@ -1217,7 +1221,7 @@ mod test { #[test] #[should_panic(expected = "BnBNoExactMatch")] fn test_bnb_function_no_exact_match() { - let fee_rate = FeeRate::from_sat_per_vb(10.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); let utxos: Vec = get_test_utxos() .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1226,7 +1230,7 @@ mod test { let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat(); let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; @@ -1247,7 +1251,7 @@ mod test { #[test] #[should_panic(expected = "BnBTotalTriesExceeded")] fn test_bnb_function_tries_exceeded() { - let fee_rate = FeeRate::from_sat_per_vb(10.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(10); let utxos: Vec = generate_same_value_utxos(100_000, 100_000) .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1256,7 +1260,7 @@ mod test { let curr_available_value = utxos.iter().fold(0, |acc, x| acc + x.effective_value); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat(); let target_amount = 20_000 + FEE_AMOUNT; let drain_script = ScriptBuf::default(); @@ -1278,9 +1282,9 @@ mod test { // The match won't be exact but still in the range #[test] fn test_bnb_function_almost_exact_match_with_fees() { - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let size_of_change = 31; - let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); + let cost_of_change = (Weight::from_vb_unchecked(size_of_change) * fee_rate).to_sat(); let utxos: Vec<_> = generate_same_value_utxos(50_000, 10) .into_iter() @@ -1293,7 +1297,7 @@ mod test { // 2*(value of 1 utxo) - 2*(1 utxo fees with 1.0sat/vbyte fee rate) - // cost_of_change + 5. - let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5; + let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change as i64 + 5; let drain_script = ScriptBuf::default(); @@ -1318,7 +1322,7 @@ mod test { fn test_bnb_function_exact_match_more_utxos() { let seed = [0; 32]; let mut rng: StdRng = SeedableRng::from_seed(seed); - let fee_rate = FeeRate::from_sat_per_vb(0.0); + let fee_rate = FeeRate::ZERO; for _ in 0..200 { let optional_utxos: Vec<_> = generate_random_utxos(&mut rng, 40) @@ -1344,7 +1348,7 @@ mod test { curr_value, curr_available_value, target_amount, - 0.0, + 0, &drain_script, fee_rate, ) @@ -1360,7 +1364,7 @@ mod test { let mut utxos = generate_random_utxos(&mut rng, 300); let target_amount = sum_random_utxos(&mut rng, &mut utxos) + FEE_AMOUNT; - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let utxos: Vec = utxos .into_iter() .map(|u| OutputGroup::new(u, fee_rate)) @@ -1389,7 +1393,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( vec![], utxos, - FeeRate::from_sat_per_vb(10.0), + FeeRate::from_sat_per_vb_unchecked(10), 500_000, &drain_script, ); @@ -1415,7 +1419,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( required, optional, - FeeRate::from_sat_per_vb(10.0), + FeeRate::from_sat_per_vb_unchecked(10), 500_000, &drain_script, ); @@ -1437,7 +1441,7 @@ mod test { let selection = BranchAndBoundCoinSelection::default().coin_select( utxos, vec![], - FeeRate::from_sat_per_vb(10_000.0), + FeeRate::from_sat_per_vb_unchecked(10_000), 500_000, &drain_script, ); diff --git a/crates/bdk/src/wallet/hardwaresigner.rs b/crates/bdk/src/wallet/hardwaresigner.rs index aec49297c2..8b11e784a8 100644 --- a/crates/bdk/src/wallet/hardwaresigner.rs +++ b/crates/bdk/src/wallet/hardwaresigner.rs @@ -18,7 +18,7 @@ //! # use bdk::signer::SignerOrdering; //! # use bdk::wallet::hardwaresigner::HWISigner; //! # use bdk::wallet::AddressIndex::New; -//! # use bdk::{FeeRate, KeychainKind, SignOptions, Wallet}; +//! # use bdk::{KeychainKind, SignOptions, Wallet}; //! # use hwi::HWIClient; //! # use std::sync::Arc; //! # diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index bc5093ce3b..e5990602d3 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -33,8 +33,8 @@ use bitcoin::psbt; use bitcoin::secp256k1::Secp256k1; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::{ - absolute, Address, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxOut, Txid, - Weight, Witness, + absolute, Address, FeeRate, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxOut, + Txid, Weight, Witness, }; use core::fmt; use core::ops::Deref; @@ -581,10 +581,8 @@ impl Wallet { /// ``` /// [`insert_txout`]: Self::insert_txout pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result { - self.calculate_fee(tx).map(|fee| { - let weight = tx.weight(); - FeeRate::from_wu(fee, weight) - }) + self.calculate_fee(tx) + .map(|fee| bitcoin::Amount::from_sat(fee) / tx.weight()) } /// Computes total input value going from script pubkeys in the index (sent) and the total output @@ -1019,33 +1017,33 @@ impl Wallet { (Some(rbf), _) => rbf.get_value(), }; - let (fee_rate, mut fee_amount) = match params - .fee_policy - .as_ref() - .unwrap_or(&FeePolicy::FeeRate(FeeRate::default())) - { + let (fee_rate, mut fee_amount) = match params.fee_policy { //FIXME: see https://github.com/bitcoindevkit/bdk/issues/256 - FeePolicy::FeeAmount(fee) => { + Some(FeePolicy::FeeAmount(fee)) => { if let Some(previous_fee) = params.bumping_fee { - if *fee < previous_fee.absolute { + if fee < previous_fee.absolute { return Err(Error::FeeTooLow { required: previous_fee.absolute, }); } } - (FeeRate::from_sat_per_vb(0.0), *fee) + (FeeRate::ZERO, fee) } - FeePolicy::FeeRate(rate) => { + Some(FeePolicy::FeeRate(rate)) => { if let Some(previous_fee) = params.bumping_fee { - let required_feerate = FeeRate::from_sat_per_vb(previous_fee.rate + 1.0); - if *rate < required_feerate { + let required_feerate = FeeRate::from_sat_per_kwu( + previous_fee.rate.to_sat_per_kwu() + + FeeRate::BROADCAST_MIN.to_sat_per_kwu(), // +1 sat/vb + ); + if rate < required_feerate { return Err(Error::FeeRateTooLow { required: required_feerate, }); } } - (*rate, 0) + (rate, 0) } + None => (FeeRate::BROADCAST_MIN, 0), }; let mut tx = Transaction { @@ -1087,7 +1085,7 @@ impl Wallet { outgoing += value; } - fee_amount += fee_rate.fee_wu(tx.weight()); + fee_amount += (fee_rate * tx.weight()).to_sat(); // Segwit transactions' header is 2WU larger than legacy txs' header, // as they contain a witness marker (1WU) and a witness flag (1WU) (see BIP144). @@ -1098,7 +1096,7 @@ impl Wallet { // end up with a transaction with a slightly higher fee rate than the requested one. // If, instead, we undershoot, we may end up with a feerate lower than the requested one // - we might come up with non broadcastable txs! - fee_amount += fee_rate.fee_wu(Weight::from_wu(2)); + fee_amount += (fee_rate * Weight::from_wu(2)).to_sat(); if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed && internal_descriptor.is_none() @@ -1241,7 +1239,7 @@ impl Wallet { /// let mut psbt = { /// let mut builder = wallet.build_fee_bump(tx.txid())?; /// builder - /// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)); + /// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); /// builder.finish()? /// }; /// @@ -1368,7 +1366,7 @@ impl Wallet { utxos: original_utxos, bumping_fee: Some(tx_builder::PreviousFee { absolute: fee, - rate: fee_rate.as_sat_per_vb(), + rate: fee_rate, }), ..Default::default() }; diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 37e85a1240..ac537d018a 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -27,7 +27,7 @@ //! // Create a transaction with one output to `to_address` of 50_000 satoshi //! .add_recipient(to_address.script_pubkey(), 50_000) //! // With a custom fee rate of 5.0 satoshi/vbyte -//! .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)) +//! .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)) //! // Only spend non-change outputs //! .do_not_spend_change() //! // Turn on RBF signaling @@ -44,11 +44,12 @@ use core::cell::RefCell; use core::marker::PhantomData; use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt}; +use bitcoin::FeeRate; use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transaction}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::ChangeSet; -use crate::types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo}; +use crate::types::{KeychainKind, LocalUtxo, WeightedUtxo}; use crate::{Error, Utxo, Wallet}; /// Context in which the [`TxBuilder`] is valid pub trait TxBuilderContext: core::fmt::Debug + Default + Clone {} @@ -152,7 +153,7 @@ pub(crate) struct TxParams { #[derive(Clone, Copy, Debug)] pub(crate) struct PreviousFee { pub absolute: u64, - pub rate: f32, + pub rate: FeeRate, } #[derive(Debug, Clone, Copy)] @@ -163,7 +164,7 @@ pub(crate) enum FeePolicy { impl Default for FeePolicy { fn default() -> Self { - FeePolicy::FeeRate(FeeRate::default_min_relay_fee()) + FeePolicy::FeeRate(FeeRate::BROADCAST_MIN) } } @@ -180,14 +181,12 @@ impl<'a, D, Cs: Clone, Ctx> Clone for TxBuilder<'a, D, Cs, Ctx> { // methods supported by both contexts, for any CoinSelectionAlgorithm impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D, Cs, Ctx> { - /// Set a custom fee rate - /// The fee_rate method sets the mining fee paid by the transaction as a rate on its size. - /// This means that the total fee paid is equal to this rate * size of the transaction in virtual Bytes (vB) or Weigth Unit (wu). - /// This rate is internally expressed in satoshis-per-virtual-bytes (sats/vB) using FeeRate::from_sat_per_vb, but can also be set by: - /// * sats/kvB (1000 sats/kvB == 1 sats/vB) using FeeRate::from_sat_per_kvb - /// * btc/kvB (0.00001000 btc/kvB == 1 sats/vB) using FeeRate::from_btc_per_kvb - /// * sats/kwu (250 sats/kwu == 1 sats/vB) using FeeRate::from_sat_per_kwu - /// Default is 1 sat/vB (see min_relay_fee) + /// Set a custom fee rate. + /// + /// This method sets the mining fee paid by the transaction as a rate on its size. + /// This means that the total fee paid is equal to `fee_rate` times the size + /// of the transaction. Default is 1 sat/vB in accordance with Bitcoin Core's default + /// relay policy. pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self { self.params.fee_policy = Some(FeePolicy::FeeRate(fee_rate)); self @@ -652,7 +651,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> { /// .drain_wallet() /// // Send the excess (which is all the coins minus the fee) to this address. /// .drain_to(to_address.script_pubkey()) - /// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)) + /// .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)) /// .enable_rbf(); /// let psbt = tx_builder.finish()?; /// # Ok::<(), bdk::Error>(()) diff --git a/crates/bdk/tests/psbt.rs b/crates/bdk/tests/psbt.rs index 3c4968bfeb..e3ace9db00 100644 --- a/crates/bdk/tests/psbt.rs +++ b/crates/bdk/tests/psbt.rs @@ -1,7 +1,7 @@ use bdk::bitcoin::TxIn; use bdk::wallet::AddressIndex; use bdk::wallet::AddressIndex::New; -use bdk::{psbt, FeeRate, SignOptions}; +use bdk::{psbt, SignOptions}; use bitcoin::psbt::PartiallySignedTransaction as Psbt; use core::str::FromStr; mod common; @@ -10,6 +10,12 @@ use common::*; // from bip 174 const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA"; +fn feerate_unchecked(sat_vb: f64) -> bitcoin::FeeRate { + // 1 sat_vb / 4wu_vb * 1000kwu_wu = 250 sat_kwu + let sat_kwu = (sat_vb * 250.0).ceil() as u64; + bitcoin::FeeRate::from_sat_per_kwu(sat_kwu) +} + #[test] #[should_panic(expected = "InputIndexOutOfRange")] fn test_psbt_malformed_psbt_input_legacy() { @@ -82,13 +88,13 @@ fn test_psbt_sign_with_finalized() { fn test_psbt_fee_rate_with_witness_utxo() { use psbt::PsbtUtils; - let expected_fee_rate = 1.2345; + let expected_fee_rate = feerate_unchecked(1.2345); let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(expected_fee_rate); let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); @@ -99,21 +105,21 @@ fn test_psbt_fee_rate_with_witness_utxo() { assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate.as_sat_per_vb() >= expected_fee_rate); - assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb()); + assert!(finalized_fee_rate >= expected_fee_rate); + assert!(finalized_fee_rate < unfinalized_fee_rate); } #[test] fn test_psbt_fee_rate_with_nonwitness_utxo() { use psbt::PsbtUtils; - let expected_fee_rate = 1.2345; + let expected_fee_rate = feerate_unchecked(1.2345); let (mut wallet, _) = get_funded_wallet("pkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); let mut builder = wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(expected_fee_rate); let mut psbt = builder.finish().unwrap(); let fee_amount = psbt.fee_amount(); assert!(fee_amount.is_some()); @@ -123,21 +129,21 @@ fn test_psbt_fee_rate_with_nonwitness_utxo() { assert!(finalized); let finalized_fee_rate = psbt.fee_rate().unwrap(); - assert!(finalized_fee_rate.as_sat_per_vb() >= expected_fee_rate); - assert!(finalized_fee_rate.as_sat_per_vb() < unfinalized_fee_rate.as_sat_per_vb()); + assert!(finalized_fee_rate >= expected_fee_rate); + assert!(finalized_fee_rate < unfinalized_fee_rate); } #[test] fn test_psbt_fee_rate_with_missing_txout() { use psbt::PsbtUtils; - let expected_fee_rate = 1.2345; + let expected_fee_rate = feerate_unchecked(1.2345); let (mut wpkh_wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wpkh_wallet.get_address(New); let mut builder = wpkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(expected_fee_rate); let mut wpkh_psbt = builder.finish().unwrap(); wpkh_psbt.inputs[0].witness_utxo = None; @@ -149,7 +155,7 @@ fn test_psbt_fee_rate_with_missing_txout() { let addr = pkh_wallet.get_address(New); let mut builder = pkh_wallet.build_tx(); builder.drain_to(addr.script_pubkey()).drain_wallet(); - builder.fee_rate(FeeRate::from_sat_per_vb(expected_fee_rate)); + builder.fee_rate(expected_fee_rate); let mut pkh_psbt = builder.finish().unwrap(); pkh_psbt.inputs[0].non_witness_utxo = None; diff --git a/crates/bdk/tests/wallet.rs b/crates/bdk/tests/wallet.rs index aad8c2db25..d60ec7e913 100644 --- a/crates/bdk/tests/wallet.rs +++ b/crates/bdk/tests/wallet.rs @@ -5,11 +5,13 @@ use bdk::signer::{SignOptions, SignerError}; use bdk::wallet::coin_selection::LargestFirstCoinSelection; use bdk::wallet::AddressIndex::*; use bdk::wallet::{AddressIndex, AddressInfo, Balance, Wallet}; -use bdk::{Error, FeeRate, KeychainKind}; +use bdk::{Error, KeychainKind}; use bdk_chain::COINBASE_MATURITY; use bdk_chain::{BlockId, ConfirmationTime}; use bitcoin::hashes::Hash; use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; +use bitcoin::Amount; +use bitcoin::FeeRate; use bitcoin::ScriptBuf; use bitcoin::{ absolute, script::PushBytesBuf, taproot::TapNodeHash, Address, OutPoint, Sequence, Transaction, @@ -18,7 +20,6 @@ use bitcoin::{ use bitcoin::{psbt, Network}; use bitcoin::{BlockHash, Txid}; use core::str::FromStr; - mod common; use common::*; @@ -52,6 +53,12 @@ fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint { receive_output(wallet, value, height) } +fn feerate_unchecked(sat_vb: f64) -> FeeRate { + // 1 sat_vb / 4wu_vb * 1000kwu_wu = 250 sat_kwu + let sat_kwu = (sat_vb * 250.0).ceil() as u64; + FeeRate::from_sat_per_kwu(sat_kwu) +} + // The satisfaction size of a P2WPKH is 112 WU = // 1 (elements in witness) + 1 (OP_PUSH) + 33 (pk) + 1 (OP_PUSH) + 72 (signature + sighash) + 1*4 (script len) // On the witness itself, we have to push once for the pk (33WU) and once for signature + sighash (72WU), for @@ -134,9 +141,11 @@ fn test_get_funded_wallet_tx_fee_rate() { // to a foreign address and one returning 50_000 back to the wallet as change. The remaining 1000 // sats are the transaction fee. - // tx weight = 452 bytes, as vbytes = (452+3)/4 = 113 - // fee rate (sats per vbyte) = fee / vbytes = 1000 / 113 = 8.8495575221 rounded to 8.849558 - assert_eq!(tx_fee_rate.as_sat_per_vb(), 8.849558); + // tx weight = 452 wu, as vbytes = (452 + 3) / 4 = 113 + // fee_rate (sats per kwu) = fee / weight = 1000sat / 0.452kwu = 2212 + // fee_rate (sats per vbyte ceil) = fee / vsize = 1000sat / 113vb = 9 + assert_eq!(tx_fee_rate.to_sat_per_kwu(), 2212); + assert_eq!(tx_fee_rate.to_sat_per_vb_ceil(), 9); } macro_rules! assert_fee_rate { @@ -171,11 +180,15 @@ macro_rules! assert_fee_rate { assert_eq!(fee_amount, $fees); - let tx_fee_rate = FeeRate::from_wu($fees, tx.weight()); - let fee_rate = $fee_rate; + let tx_fee_rate = (Amount::from_sat(fee_amount) / tx.weight()) + .to_sat_per_kwu(); + let fee_rate = $fee_rate.to_sat_per_kwu(); + let half_default = FeeRate::BROADCAST_MIN.checked_div(2) + .unwrap() + .to_sat_per_kwu(); if !dust_change { - assert!(tx_fee_rate >= fee_rate && (tx_fee_rate - fee_rate).as_sat_per_vb().abs() < 0.5, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); + assert!(tx_fee_rate >= fee_rate && tx_fee_rate - fee_rate < half_default, "Expected fee rate of {:?}, the tx has {:?}", fee_rate, tx_fee_rate); } else { assert!(tx_fee_rate >= fee_rate, "Expected fee rate of at least {:?}, the tx has {:?}", fee_rate, tx_fee_rate); } @@ -523,7 +536,7 @@ fn test_create_tx_default_fee_rate() { let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::default(), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::BROADCAST_MIN, @add_signature); } #[test] @@ -533,11 +546,11 @@ fn test_create_tx_custom_fee_rate() { let mut builder = wallet.build_tx(); builder .add_recipient(addr.script_pubkey(), 25_000) - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let fee = check_fee!(wallet, psbt); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -629,7 +642,7 @@ fn test_create_tx_drain_to_dust_amount() { builder .drain_to(addr.script_pubkey()) .drain_wallet() - .fee_rate(FeeRate::from_sat_per_vb(453.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(499)); builder.finish().unwrap(); } @@ -1376,7 +1389,7 @@ fn test_bump_fee_low_fee_rate() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(1.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(1)); builder.finish().unwrap(); } @@ -1446,7 +1459,7 @@ fn test_bump_fee_reduce_change() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(2.5)).enable_rbf(); + builder.fee_rate(feerate_unchecked(2.5)).enable_rbf(); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1477,7 +1490,7 @@ fn test_bump_fee_reduce_change() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), feerate_unchecked(2.5), @add_signature); let mut builder = wallet.build_fee_bump(txid).unwrap(); builder.fee_absolute(200); @@ -1542,7 +1555,7 @@ fn test_bump_fee_reduce_single_recipient() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder - .fee_rate(FeeRate::from_sat_per_vb(2.5)) + .fee_rate(feerate_unchecked(2.5)) .allow_shrinking(addr.script_pubkey()) .unwrap(); let psbt = builder.finish().unwrap(); @@ -1556,7 +1569,7 @@ fn test_bump_fee_reduce_single_recipient() { assert_eq!(tx.output.len(), 1); assert_eq!(tx.output[0].value + fee.unwrap_or(0), sent_received.0); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(2.5), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), feerate_unchecked(2.5), @add_signature); } #[test] @@ -1651,7 +1664,7 @@ fn test_bump_fee_drain_wallet() { .drain_wallet() .allow_shrinking(addr.script_pubkey()) .unwrap() - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.extract_tx()); @@ -1714,7 +1727,7 @@ fn test_bump_fee_remove_output_manually_selected_only() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder .manually_selected_only() - .fee_rate(FeeRate::from_sat_per_vb(255.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(255)); builder.finish().unwrap(); } @@ -1755,7 +1768,7 @@ fn test_bump_fee_add_input() { .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1782,7 +1795,7 @@ fn test_bump_fee_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -1865,7 +1878,7 @@ fn test_bump_fee_no_change_add_input_and_change() { // now bump the fees without using `allow_shrinking`. the wallet should add an // extra input and a change output, and leave the original output untouched let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(50.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(50)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1897,7 +1910,7 @@ fn test_bump_fee_no_change_add_input_and_change() { 75_000 - original_send_all_amount - fee.unwrap_or(0) ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(50.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(50), @add_signature); } #[test] @@ -1942,7 +1955,7 @@ fn test_bump_fee_add_input_change_dust() { // two inputs (50k, 25k) and one output (45k) - epsilon // We use epsilon here to avoid asking for a slightly too high feerate let fee_abs = 50_000 + 25_000 - 45_000 - 10; - builder.fee_rate(FeeRate::from_wu(fee_abs, new_tx_weight)); + builder.fee_rate(Amount::from_sat(fee_abs) / new_tx_weight); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -1965,7 +1978,7 @@ fn test_bump_fee_add_input_change_dust() { 45_000 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(140.0), @dust_change, @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(140), @dust_change, @add_signature); } #[test] @@ -1996,7 +2009,7 @@ fn test_bump_fee_force_add_input() { builder .add_utxo(incoming_op) .unwrap() - .fee_rate(FeeRate::from_sat_per_vb(5.0)); + .fee_rate(FeeRate::from_sat_per_vb_unchecked(5)); let psbt = builder.finish().unwrap(); let sent_received = wallet.sent_and_received(&psbt.clone().extract_tx()); let fee = check_fee!(wallet, psbt); @@ -2024,7 +2037,7 @@ fn test_bump_fee_force_add_input() { sent_received.1 ); - assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb(5.0), @add_signature); + assert_fee_rate!(psbt, fee.unwrap_or(0), FeeRate::from_sat_per_vb_unchecked(5), @add_signature); } #[test] @@ -2120,7 +2133,7 @@ fn test_bump_fee_unconfirmed_inputs_only() { .insert_tx(tx, ConfirmationTime::Unconfirmed { last_seen: 0 }) .unwrap(); let mut builder = wallet.build_fee_bump(txid).unwrap(); - builder.fee_rate(FeeRate::from_sat_per_vb(25.0)); + builder.fee_rate(FeeRate::from_sat_per_vb_unchecked(25)); builder.finish().unwrap(); } @@ -2155,7 +2168,7 @@ fn test_bump_fee_unconfirmed_input() { let mut builder = wallet.build_fee_bump(txid).unwrap(); builder - .fee_rate(FeeRate::from_sat_per_vb(15.0)) + .fee_rate(FeeRate::from_sat_per_vb_unchecked(15)) .allow_shrinking(addr.script_pubkey()) .unwrap(); builder.finish().unwrap(); @@ -2175,7 +2188,7 @@ fn test_fee_amount_negative_drain_val() { let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt") .unwrap() .assume_checked(); - let fee_rate = FeeRate::from_sat_per_vb(2.01); + let fee_rate = feerate_unchecked(2.01); let incoming_op = receive_output_in_latest_block(&mut wallet, 8859); let mut builder = wallet.build_tx(); @@ -3369,7 +3382,7 @@ fn test_fee_rate_sign_no_grinding_high_r() { // alright. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let mut builder = wallet.build_tx(); let mut data = PushBytesBuf::try_from(vec![0]).unwrap(); builder @@ -3435,7 +3448,7 @@ fn test_fee_rate_sign_grinding_low_r() { // signature is 70 bytes. let (mut wallet, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)"); let addr = wallet.get_address(New); - let fee_rate = FeeRate::from_sat_per_vb(1.0); + let fee_rate = FeeRate::from_sat_per_vb_unchecked(1); let mut builder = wallet.build_tx(); builder .drain_to(addr.script_pubkey())