From b929022d56d1441f842d61d06858255857975093 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 21 May 2025 05:29:25 +1000 Subject: [PATCH] Add floor/ceil versions of to_sat_per_kwu In preparation for changing the inner representation of `FeeRate` add floor and ceil versions of the getter function `to_sat_per_kwu`. For now both functions return the same thing but still call the correct one so that when we change the representation we do not need to re-visit them. --- bitcoin/src/blockdata/script/borrowed.rs | 2 +- bitcoin/src/psbt/mod.rs | 7 ++-- units/src/fee.rs | 7 ++-- units/src/fee_rate/mod.rs | 41 +++++++++++++----------- units/src/fee_rate/serde.rs | 4 +-- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 7cc5fca7d..41a89e4a7 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -290,7 +290,7 @@ crate::internal_macros::define_extension_trait! { /// /// [`minimal_non_dust`]: Script::minimal_non_dust fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option { - self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu() * 4) + self.minimal_non_dust_internal(dust_relay_fee.to_sat_per_kwu_ceil() * 4) } /// Counts the sigops for this Script using accurate counting. diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 3e5c9c2da..b8f518898 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -1178,8 +1178,11 @@ impl fmt::Display for ExtractTxError { use ExtractTxError::*; match *self { - AbsurdFeeRate { fee_rate, .. } => - write!(f, "an absurdly high fee rate of {} sat/kwu", fee_rate.to_sat_per_kwu()), + AbsurdFeeRate { fee_rate, .. } => write!( + f, + "an absurdly high fee rate of {} sat/kwu", + fee_rate.to_sat_per_kwu_floor() + ), MissingInputValue { .. } => write!( f, "one of the inputs lacked value information (witness_utxo or non_witness_utxo)" diff --git a/units/src/fee.rs b/units/src/fee.rs index 4dc6c4b56..418db80ce 100644 --- a/units/src/fee.rs +++ b/units/src/fee.rs @@ -80,7 +80,7 @@ impl Amount { #[must_use] pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option { match self.to_sat().checked_mul(1000) { - Some(amount_msats) => match amount_msats.checked_div(fee_rate.to_sat_per_kwu()) { + Some(amount_msats) => match amount_msats.checked_div(fee_rate.to_sat_per_kwu_ceil()) { Some(wu) => Some(Weight::from_wu(wu)), None => None, }, @@ -96,7 +96,8 @@ impl Amount { /// Returns [`None`] if overflow occurred or if `fee_rate` is zero. #[must_use] pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option { - let rate = fee_rate.to_sat_per_kwu(); + // Use ceil because result is used as the divisor. + let rate = fee_rate.to_sat_per_kwu_ceil(); match self.to_sat().checked_mul(1000) { Some(amount_msats) => match rate.checked_sub(1) { Some(rate_minus_one) => match amount_msats.checked_add(rate_minus_one) { @@ -151,7 +152,7 @@ impl FeeRate { /// /// Returns [`NumOpResult::Error`] if overflow occurred. pub const fn checked_mul_by_weight(self, weight: Weight) -> NumOpResult { - if let Some(fee) = self.to_sat_per_kwu().checked_mul(weight.to_wu()) { + if let Some(fee) = self.to_sat_per_kwu_floor().checked_mul(weight.to_wu()) { if let Some(round_up) = fee.checked_add(999) { if let Ok(ret) = Amount::from_sat(round_up / 1_000) { return NumOpResult::Valid(ret); diff --git a/units/src/fee_rate/mod.rs b/units/src/fee_rate/mod.rs index dd2cbb007..31fae382d 100644 --- a/units/src/fee_rate/mod.rs +++ b/units/src/fee_rate/mod.rs @@ -64,15 +64,18 @@ impl FeeRate { /// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes). pub const fn from_sat_per_kvb(sat_kvb: u64) -> Self { FeeRate::from_sat_per_kwu(sat_kvb / 4) } - /// Returns raw fee rate. - pub const fn to_sat_per_kwu(self) -> u64 { self.0 } + /// Converts to sat/kwu rounding down. + pub const fn to_sat_per_kwu_floor(self) -> u64 { self.0 } + + /// Converts to sat/kwu rounding up. + pub const fn to_sat_per_kwu_ceil(self) -> u64 { self.0 } /// Converts to sat/vB rounding down. - pub const fn to_sat_per_vb_floor(self) -> u64 { self.to_sat_per_kwu() / (1000 / 4) } + pub const fn to_sat_per_vb_floor(self) -> u64 { self.to_sat_per_kwu_floor() / (1000 / 4) } /// Converts to sat/vB rounding up. pub const fn to_sat_per_vb_ceil(self) -> u64 { - (self.to_sat_per_kwu() + (1000 / 4 - 1)) / (1000 / 4) + (self.to_sat_per_kwu_floor() + (1000 / 4 - 1)) / (1000 / 4) } /// Checked multiplication. @@ -81,7 +84,7 @@ impl FeeRate { #[must_use] pub const fn checked_mul(self, rhs: u64) -> Option { // No `map()` in const context. - match self.to_sat_per_kwu().checked_mul(rhs) { + match self.to_sat_per_kwu_floor().checked_mul(rhs) { Some(res) => Some(Self::from_sat_per_kwu(res)), None => None, } @@ -93,7 +96,7 @@ impl FeeRate { #[must_use] pub const fn checked_div(self, rhs: u64) -> Option { // No `map()` in const context. - match self.to_sat_per_kwu().checked_div(rhs) { + match self.to_sat_per_kwu_floor().checked_div(rhs) { Some(res) => Some(Self::from_sat_per_kwu(res)), None => None, } @@ -105,7 +108,7 @@ impl FeeRate { #[must_use] pub const fn checked_add(self, rhs: FeeRate) -> Option { // No `map()` in const context. - match self.to_sat_per_kwu().checked_add(rhs.to_sat_per_kwu()) { + match self.to_sat_per_kwu_floor().checked_add(rhs.to_sat_per_kwu_floor()) { Some(res) => Some(Self::from_sat_per_kwu(res)), None => None, } @@ -117,7 +120,7 @@ impl FeeRate { #[must_use] pub const fn checked_sub(self, rhs: FeeRate) -> Option { // No `map()` in const context. - match self.to_sat_per_kwu().checked_sub(rhs.to_sat_per_kwu()) { + match self.to_sat_per_kwu_floor().checked_sub(rhs.to_sat_per_kwu_floor()) { Some(res) => Some(Self::from_sat_per_kwu(res)), None => None, } @@ -128,19 +131,19 @@ crate::internal_macros::impl_op_for_references! { impl ops::Add for FeeRate { type Output = FeeRate; - fn add(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu() + rhs.to_sat_per_kwu()) } + fn add(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu_floor() + rhs.to_sat_per_kwu_floor()) } } impl ops::Sub for FeeRate { type Output = FeeRate; - fn sub(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu() - rhs.to_sat_per_kwu()) } + fn sub(self, rhs: FeeRate) -> Self::Output { FeeRate::from_sat_per_kwu(self.to_sat_per_kwu_floor() - rhs.to_sat_per_kwu_floor()) } } impl ops::Div for FeeRate { type Output = FeeRate; - fn div(self, rhs: NonZeroU64) -> Self::Output{ Self::from_sat_per_kwu(self.to_sat_per_kwu() / rhs.get()) } + fn div(self, rhs: NonZeroU64) -> Self::Output{ Self::from_sat_per_kwu(self.to_sat_per_kwu_floor() / rhs.get()) } } } crate::internal_macros::impl_add_assign!(FeeRate); @@ -151,7 +154,7 @@ impl core::iter::Sum for FeeRate { where I: Iterator, { - FeeRate::from_sat_per_kwu(iter.map(FeeRate::to_sat_per_kwu).sum()) + FeeRate::from_sat_per_kwu(iter.map(FeeRate::to_sat_per_kwu_floor).sum()) } } @@ -160,7 +163,7 @@ impl<'a> core::iter::Sum<&'a FeeRate> for FeeRate { where I: Iterator, { - FeeRate::from_sat_per_kwu(iter.map(|f| FeeRate::to_sat_per_kwu(*f)).sum()) + FeeRate::from_sat_per_kwu(iter.map(|f| FeeRate::to_sat_per_kwu_floor(*f)).sum()) } } @@ -266,11 +269,11 @@ mod tests { #[test] fn fee_rate_const() { - assert_eq!(FeeRate::ZERO.to_sat_per_kwu(), 0); - assert_eq!(FeeRate::MIN.to_sat_per_kwu(), u64::MIN); - assert_eq!(FeeRate::MAX.to_sat_per_kwu(), u64::MAX); - assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu(), 250); - assert_eq!(FeeRate::DUST.to_sat_per_kwu(), 750); + assert_eq!(FeeRate::ZERO.to_sat_per_kwu_floor(), 0); + assert_eq!(FeeRate::MIN.to_sat_per_kwu_floor(), u64::MIN); + assert_eq!(FeeRate::MAX.to_sat_per_kwu_floor(), u64::MAX); + assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu_floor(), 250); + assert_eq!(FeeRate::DUST.to_sat_per_kwu_floor(), 750); } #[test] @@ -304,7 +307,7 @@ mod tests { #[test] fn raw_feerate() { let fee_rate = FeeRate::from_sat_per_kwu(749); - assert_eq!(fee_rate.to_sat_per_kwu(), 749); + assert_eq!(fee_rate.to_sat_per_kwu_floor(), 749); assert_eq!(fee_rate.to_sat_per_vb_floor(), 2); assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3); } diff --git a/units/src/fee_rate/serde.rs b/units/src/fee_rate/serde.rs index ae2e2fb8f..473d1ba15 100644 --- a/units/src/fee_rate/serde.rs +++ b/units/src/fee_rate/serde.rs @@ -36,7 +36,7 @@ pub mod as_sat_per_kwu { use crate::FeeRate; pub fn serialize(f: &FeeRate, s: S) -> Result { - u64::serialize(&f.to_sat_per_kwu(), s) + u64::serialize(&f.to_sat_per_kwu_floor(), s) } pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result { @@ -57,7 +57,7 @@ pub mod as_sat_per_kwu { #[allow(clippy::ref_option)] // API forced by serde. pub fn serialize(f: &Option, s: S) -> Result { match *f { - Some(f) => s.serialize_some(&f.to_sat_per_kwu()), + Some(f) => s.serialize_some(&f.to_sat_per_kwu_floor()), None => s.serialize_none(), } }