units: add global `BlockMtpInterval` type
See the previous commit message for justification; for sensible arithmetic on block timestamps we need the ability to do MTP calculations on arbitrary MTPs and arbitrary intervals between them. However, the absolute::Mtp and relative::MtpInterval types are severely limited in both range and precision. Also adds a bunch of arithmetic ops to match the existing ops for BlockHeight and BlockInterval. These panic on overflow, just like the underlying std arithmetic, which I think is reasonable behavior for types which are documented as being thin wrappers around u32. We may want to add checked_add, checked_sub and maybe checked_sum methods, but that's out of scope for this PR.
This commit is contained in:
parent
4e3af5162f
commit
cb882c5ce1
|
@ -190,6 +190,62 @@ impl TryFrom<BlockMtp> for absolute::Mtp {
|
|||
fn try_from(h: BlockMtp) -> Result<Self, Self::Error> { absolute::Mtp::from_u32(h.to_u32()) }
|
||||
}
|
||||
|
||||
impl_u32_wrapper! {
|
||||
/// An unsigned difference between two [`BlockMtp`]s.
|
||||
///
|
||||
/// This type is not meant for constructing time-based timelocks. It is a general purpose
|
||||
/// MTP abstraction. For locktimes please see [`locktime::relative::MtpInterval`].
|
||||
///
|
||||
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
// Public to try and make it really clear that there are no invariants.
|
||||
pub struct BlockMtpInterval(pub u32);
|
||||
}
|
||||
|
||||
impl BlockMtpInterval {
|
||||
/// Converts a [`BlockMtpInterval`] to a [`locktime::relative::MtpInterval`], rounding down.
|
||||
///
|
||||
/// Relative timelock MTP intervals have a resolution of 512 seconds, while
|
||||
/// [`BlockMtpInterval`], like all block timestamp types, has a one-second resolution.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if the MTP is out-of-range (in excess of 512 times `u16::MAX` seconds, or about
|
||||
/// 388 days) for a time-based relative locktime.
|
||||
#[inline]
|
||||
pub const fn to_relative_mtp_interval_floor(
|
||||
self,
|
||||
) -> Result<relative::MtpInterval, relative::TimeOverflowError> {
|
||||
relative::MtpInterval::from_seconds_floor(self.to_u32())
|
||||
}
|
||||
|
||||
/// Converts a [`BlockMtpInterval`] to a [`locktime::relative::MtpInterval`], rounding up.
|
||||
///
|
||||
/// Relative timelock MTP intervals have a resolution of 512 seconds, while
|
||||
/// [`BlockMtpInterval`], like all block timestamp types, has a one-second resolution.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if the MTP is out-of-range (in excess of 512 times `u16::MAX` seconds, or about
|
||||
/// 388 days) for a time-based relative locktime.
|
||||
#[inline]
|
||||
pub const fn to_relative_mtp_interval_ceil(
|
||||
self,
|
||||
) -> Result<relative::MtpInterval, relative::TimeOverflowError> {
|
||||
relative::MtpInterval::from_seconds_ceil(self.to_u32())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<relative::MtpInterval> for BlockMtpInterval {
|
||||
/// Converts a [`locktime::relative::MtpInterval`] to a [`BlockMtpInterval `].
|
||||
///
|
||||
/// A relative locktime MTP interval has a resolution of 512 seconds, and a maximum value
|
||||
/// of `u16::MAX` 512-second intervals. [`BlockMtpInterval`] may take the full range of
|
||||
/// `u32`.
|
||||
fn from(h: relative::MtpInterval) -> Self { Self::from_u32(h.to_seconds()) }
|
||||
}
|
||||
|
||||
/// Error returned when the block interval is too big to be used as a relative lock time.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TooBigForRelativeBlockHeightIntervalError(u32);
|
||||
|
@ -258,10 +314,62 @@ crate::internal_macros::impl_op_for_references! {
|
|||
BlockInterval::from_u32(height)
|
||||
}
|
||||
}
|
||||
|
||||
// height - height = interval
|
||||
impl ops::Sub<BlockMtp> for BlockMtp {
|
||||
type Output = BlockMtpInterval;
|
||||
|
||||
fn sub(self, rhs: BlockMtp) -> Self::Output {
|
||||
let interval = self.to_u32() - rhs.to_u32();
|
||||
BlockMtpInterval::from_u32(interval)
|
||||
}
|
||||
}
|
||||
|
||||
// height + interval = height
|
||||
impl ops::Add<BlockMtpInterval> for BlockMtp {
|
||||
type Output = BlockMtp;
|
||||
|
||||
fn add(self, rhs: BlockMtpInterval) -> Self::Output {
|
||||
let height = self.to_u32() + rhs.to_u32();
|
||||
BlockMtp::from_u32(height)
|
||||
}
|
||||
}
|
||||
|
||||
// height - interval = height
|
||||
impl ops::Sub<BlockMtpInterval> for BlockMtp {
|
||||
type Output = BlockMtp;
|
||||
|
||||
fn sub(self, rhs: BlockMtpInterval) -> Self::Output {
|
||||
let height = self.to_u32() - rhs.to_u32();
|
||||
BlockMtp::from_u32(height)
|
||||
}
|
||||
}
|
||||
|
||||
// interval + interval = interval
|
||||
impl ops::Add<BlockMtpInterval> for BlockMtpInterval {
|
||||
type Output = BlockMtpInterval;
|
||||
|
||||
fn add(self, rhs: BlockMtpInterval) -> Self::Output {
|
||||
let height = self.to_u32() + rhs.to_u32();
|
||||
BlockMtpInterval::from_u32(height)
|
||||
}
|
||||
}
|
||||
|
||||
// interval - interval = interval
|
||||
impl ops::Sub<BlockMtpInterval> for BlockMtpInterval {
|
||||
type Output = BlockMtpInterval;
|
||||
|
||||
fn sub(self, rhs: BlockMtpInterval) -> Self::Output {
|
||||
let height = self.to_u32() - rhs.to_u32();
|
||||
BlockMtpInterval::from_u32(height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::internal_macros::impl_add_assign!(BlockInterval);
|
||||
crate::internal_macros::impl_sub_assign!(BlockInterval);
|
||||
crate::internal_macros::impl_add_assign!(BlockMtpInterval);
|
||||
crate::internal_macros::impl_sub_assign!(BlockMtpInterval);
|
||||
|
||||
impl core::iter::Sum for BlockInterval {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
|
@ -280,6 +388,23 @@ impl<'a> core::iter::Sum<&'a BlockInterval> for BlockInterval {
|
|||
}
|
||||
}
|
||||
|
||||
impl core::iter::Sum for BlockMtpInterval {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
let sum = iter.map(|interval| interval.0).sum();
|
||||
BlockMtpInterval::from_u32(sum)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> core::iter::Sum<&'a BlockMtpInterval> for BlockMtpInterval {
|
||||
fn sum<I>(iter: I) -> Self
|
||||
where
|
||||
I: Iterator<Item = &'a BlockMtpInterval>,
|
||||
{
|
||||
let sum = iter.map(|interval| interval.0).sum();
|
||||
BlockMtpInterval::from_u32(sum)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -57,7 +57,7 @@ pub mod weight;
|
|||
#[rustfmt::skip]
|
||||
pub use self::{
|
||||
amount::{Amount, SignedAmount},
|
||||
block::{BlockHeight, BlockInterval, BlockMtp},
|
||||
block::{BlockHeight, BlockInterval, BlockMtp, BlockMtpInterval},
|
||||
fee_rate::FeeRate,
|
||||
result::{NumOpError, NumOpResult, MathOp},
|
||||
time::BlockTime,
|
||||
|
|
|
@ -154,6 +154,12 @@ impl MtpInterval {
|
|||
}
|
||||
}
|
||||
|
||||
/// Represents the [`MtpInterval`] as an integer number of seconds.
|
||||
#[inline]
|
||||
pub const fn to_seconds(self) -> u32 {
|
||||
self.0 as u32 * 512 // u16->u32 cast ok, const context
|
||||
}
|
||||
|
||||
/// Returns the inner `u16` value.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
|
|
|
@ -15,7 +15,7 @@ use arbitrary::{Arbitrary, Unstructured};
|
|||
use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`.
|
||||
use bitcoin_units::{
|
||||
amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, BlockMtp,
|
||||
BlockTime, FeeRate, SignedAmount, Weight,
|
||||
BlockMtpInterval, BlockTime, FeeRate, SignedAmount, Weight,
|
||||
};
|
||||
|
||||
/// A struct that includes all public non-error enums.
|
||||
|
@ -44,6 +44,7 @@ struct Structs {
|
|||
k: Weight,
|
||||
l: BlockTime,
|
||||
m: BlockMtp,
|
||||
n: BlockMtpInterval,
|
||||
}
|
||||
|
||||
impl Structs {
|
||||
|
@ -62,6 +63,7 @@ impl Structs {
|
|||
k: Weight::MAX,
|
||||
l: BlockTime::from_u32(u32::MAX),
|
||||
m: BlockMtp::MAX,
|
||||
n: BlockMtpInterval::MAX,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +95,7 @@ struct CommonTraits {
|
|||
k: Weight,
|
||||
l: BlockTime,
|
||||
m: BlockMtp,
|
||||
n: BlockMtpInterval,
|
||||
}
|
||||
|
||||
/// A struct that includes all types that implement `Default`.
|
||||
|
@ -103,6 +106,7 @@ struct Default {
|
|||
c: BlockInterval,
|
||||
d: relative::Height,
|
||||
e: relative::Time,
|
||||
f: BlockMtpInterval,
|
||||
}
|
||||
|
||||
/// A struct that includes all public error types.
|
||||
|
@ -150,7 +154,8 @@ fn api_can_use_modules_from_crate_root() {
|
|||
#[test]
|
||||
fn api_can_use_types_from_crate_root() {
|
||||
use bitcoin_units::{
|
||||
Amount, BlockHeight, BlockInterval, BlockMtp, BlockTime, FeeRate, SignedAmount, Weight,
|
||||
Amount, BlockHeight, BlockInterval, BlockMtp, BlockMtpInterval, BlockTime, FeeRate,
|
||||
SignedAmount, Weight,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -260,6 +265,7 @@ fn regression_default() {
|
|||
c: BlockInterval::ZERO,
|
||||
d: relative::Height::ZERO,
|
||||
e: relative::Time::ZERO,
|
||||
f: BlockMtpInterval::ZERO,
|
||||
};
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
@ -302,6 +308,7 @@ impl<'a> Arbitrary<'a> for Structs {
|
|||
k: Weight::arbitrary(u)?,
|
||||
l: BlockTime::arbitrary(u)?,
|
||||
m: BlockMtp::arbitrary(u)?,
|
||||
n: BlockMtpInterval::arbitrary(u)?,
|
||||
};
|
||||
Ok(a)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue