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
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.

View File

@ -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)"

View File

@ -80,7 +80,7 @@ impl Amount {
#[must_use]
pub const fn checked_div_by_fee_rate_floor(self, fee_rate: FeeRate) -> Option<Weight> {
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<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) {
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<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 Ok(ret) = Amount::from_sat(round_up / 1_000) {
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).
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<Self> {
// 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<Self> {
// 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<Self> {
// 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<Self> {
// 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<FeeRate> 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<FeeRate> 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<NonZeroU64> 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<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
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]
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);
}

View File

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