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.
This commit is contained in:
Tobin C. Harding 2025-05-21 05:29:25 +10:00
parent 64098e4578
commit b929022d56
No known key found for this signature in database
GPG Key ID: 0AEF0A899E41F7DD
5 changed files with 34 additions and 27 deletions

View File

@ -290,7 +290,7 @@ crate::internal_macros::define_extension_trait! {
/// ///
/// [`minimal_non_dust`]: Script::minimal_non_dust /// [`minimal_non_dust`]: Script::minimal_non_dust
fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option<Amount> { fn minimal_non_dust_custom(&self, dust_relay_fee: FeeRate) -> Option<Amount> {
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. /// Counts the sigops for this Script using accurate counting.

View File

@ -1178,8 +1178,11 @@ impl fmt::Display for ExtractTxError {
use ExtractTxError::*; use ExtractTxError::*;
match *self { match *self {
AbsurdFeeRate { fee_rate, .. } => AbsurdFeeRate { fee_rate, .. } => write!(
write!(f, "an absurdly high fee rate of {} sat/kwu", fee_rate.to_sat_per_kwu()), f,
"an absurdly high fee rate of {} sat/kwu",
fee_rate.to_sat_per_kwu_floor()
),
MissingInputValue { .. } => write!( MissingInputValue { .. } => write!(
f, f,
"one of the inputs lacked value information (witness_utxo or non_witness_utxo)" "one of the inputs lacked value information (witness_utxo or non_witness_utxo)"

View File

@ -80,7 +80,7 @@ impl Amount {
#[must_use] #[must_use]
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> { pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
match self.to_sat().checked_mul(1000) { 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)), Some(wu) => Some(Weight::from_wu(wu)),
None => None, None => None,
}, },
@ -96,7 +96,8 @@ impl Amount {
/// Returns [`None`] if overflow occurred or if `fee_rate` is zero. /// Returns [`None`] if overflow occurred or if `fee_rate` is zero.
#[must_use] #[must_use]
pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> { pub const fn checked_div_by_fee_rate_ceil(self, fee_rate: FeeRate) -> Option<Weight> {
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) { match self.to_sat().checked_mul(1000) {
Some(amount_msats) => match rate.checked_sub(1) { Some(amount_msats) => match rate.checked_sub(1) {
Some(rate_minus_one) => match amount_msats.checked_add(rate_minus_one) { Some(rate_minus_one) => match amount_msats.checked_add(rate_minus_one) {
@ -151,7 +152,7 @@ impl FeeRate {
/// ///
/// Returns [`NumOpResult::Error`] if overflow occurred. /// Returns [`NumOpResult::Error`] if overflow occurred.
pub const fn checked_mul_by_weight(self, weight: Weight) -> NumOpResult<Amount> { pub const fn checked_mul_by_weight(self, weight: Weight) -> NumOpResult<Amount> {
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 Some(round_up) = fee.checked_add(999) {
if let Ok(ret) = Amount::from_sat(round_up / 1_000) { if let Ok(ret) = Amount::from_sat(round_up / 1_000) {
return NumOpResult::Valid(ret); return NumOpResult::Valid(ret);

View File

@ -64,15 +64,18 @@ impl FeeRate {
/// Constructs a new [`FeeRate`] from satoshis per kilo virtual bytes (1,000 vbytes). /// 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) } pub const fn from_sat_per_kvb(sat_kvb: u64) -> Self { FeeRate::from_sat_per_kwu(sat_kvb / 4) }
/// Returns raw fee rate. /// Converts to sat/kwu rounding down.
pub const fn to_sat_per_kwu(self) -> u64 { self.0 } 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. /// 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. /// Converts to sat/vB rounding up.
pub const fn to_sat_per_vb_ceil(self) -> u64 { 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. /// Checked multiplication.
@ -81,7 +84,7 @@ impl FeeRate {
#[must_use] #[must_use]
pub const fn checked_mul(self, rhs: u64) -> Option<Self> { pub const fn checked_mul(self, rhs: u64) -> Option<Self> {
// No `map()` in const context. // 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)), Some(res) => Some(Self::from_sat_per_kwu(res)),
None => None, None => None,
} }
@ -93,7 +96,7 @@ impl FeeRate {
#[must_use] #[must_use]
pub const fn checked_div(self, rhs: u64) -> Option<Self> { pub const fn checked_div(self, rhs: u64) -> Option<Self> {
// No `map()` in const context. // 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)), Some(res) => Some(Self::from_sat_per_kwu(res)),
None => None, None => None,
} }
@ -105,7 +108,7 @@ impl FeeRate {
#[must_use] #[must_use]
pub const fn checked_add(self, rhs: FeeRate) -> Option<Self> { pub const fn checked_add(self, rhs: FeeRate) -> Option<Self> {
// No `map()` in const context. // 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)), Some(res) => Some(Self::from_sat_per_kwu(res)),
None => None, None => None,
} }
@ -117,7 +120,7 @@ impl FeeRate {
#[must_use] #[must_use]
pub const fn checked_sub(self, rhs: FeeRate) -> Option<Self> { pub const fn checked_sub(self, rhs: FeeRate) -> Option<Self> {
// No `map()` in const context. // 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)), Some(res) => Some(Self::from_sat_per_kwu(res)),
None => None, None => None,
} }
@ -128,19 +131,19 @@ crate::internal_macros::impl_op_for_references! {
impl ops::Add<FeeRate> for FeeRate { impl ops::Add<FeeRate> for FeeRate {
type Output = 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<FeeRate> for FeeRate { impl ops::Sub<FeeRate> for FeeRate {
type Output = 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<NonZeroU64> for FeeRate { impl ops::Div<NonZeroU64> for FeeRate {
type Output = 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); crate::internal_macros::impl_add_assign!(FeeRate);
@ -151,7 +154,7 @@ impl core::iter::Sum for FeeRate {
where where
I: Iterator<Item = Self>, I: Iterator<Item = Self>,
{ {
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 where
I: Iterator<Item = &'a FeeRate>, I: Iterator<Item = &'a FeeRate>,
{ {
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] #[test]
fn fee_rate_const() { fn fee_rate_const() {
assert_eq!(FeeRate::ZERO.to_sat_per_kwu(), 0); assert_eq!(FeeRate::ZERO.to_sat_per_kwu_floor(), 0);
assert_eq!(FeeRate::MIN.to_sat_per_kwu(), u64::MIN); assert_eq!(FeeRate::MIN.to_sat_per_kwu_floor(), u64::MIN);
assert_eq!(FeeRate::MAX.to_sat_per_kwu(), u64::MAX); assert_eq!(FeeRate::MAX.to_sat_per_kwu_floor(), u64::MAX);
assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu(), 250); assert_eq!(FeeRate::BROADCAST_MIN.to_sat_per_kwu_floor(), 250);
assert_eq!(FeeRate::DUST.to_sat_per_kwu(), 750); assert_eq!(FeeRate::DUST.to_sat_per_kwu_floor(), 750);
} }
#[test] #[test]
@ -304,7 +307,7 @@ mod tests {
#[test] #[test]
fn raw_feerate() { fn raw_feerate() {
let fee_rate = FeeRate::from_sat_per_kwu(749); 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_floor(), 2);
assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3); assert_eq!(fee_rate.to_sat_per_vb_ceil(), 3);
} }

View File

@ -36,7 +36,7 @@ pub mod as_sat_per_kwu {
use crate::FeeRate; use crate::FeeRate;
pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> { pub fn serialize<S: Serializer>(f: &FeeRate, s: S) -> Result<S::Ok, S::Error> {
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<FeeRate, D::Error> { pub fn deserialize<'d, D: Deserializer<'d>>(d: D) -> Result<FeeRate, D::Error> {
@ -57,7 +57,7 @@ pub mod as_sat_per_kwu {
#[allow(clippy::ref_option)] // API forced by serde. #[allow(clippy::ref_option)] // API forced by serde.
pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> { pub fn serialize<S: Serializer>(f: &Option<FeeRate>, s: S) -> Result<S::Ok, S::Error> {
match *f { 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(), None => s.serialize_none(),
} }
} }