diff --git a/units/src/block.rs b/units/src/block.rs new file mode 100644 index 000000000..bd3989f68 --- /dev/null +++ b/units/src/block.rs @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Block height and interval types. +//! +//! These types are thin wrappers around `u32`, no invariants implemented or implied. +//! +//! These are general types for abstracting over block heights, they are not designed to use with +//! lock times. If you are creating lock times you should be using the +//! [`locktime::absolute::Height`] and [`locktime::relative::Height`] types. +//! +//! The difference between these types and the locktime types is that these types are thin wrappers +//! whereas the locktime types contain more complex locktime specific abstractions. + +use core::{fmt, ops}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(doc)] +use crate::locktime; +use crate::locktime::{absolute, relative}; + +/// The block height, zero denotes the genesis block. +/// +/// This type is not meant for constructing height based timelocks, this is a general purpose block +/// height abstraction. For locktimes please see [`locktime::absolute::Height`]. +/// +/// This is a thin wrapper around a `u32` that may take on all values of a `u32`. +#[derive(Debug, 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 BlockHeight(pub u32); + +impl BlockHeight { + /// Block height 0, the genesis block. + pub const ZERO: Self = BlockHeight(0); + + /// The minimum block height (0), the genesis block. + pub const MIN: Self = Self::ZERO; + + /// The maximum block height. + pub const MAX: Self = BlockHeight(u32::MAX); + + /// Creates a block height from a `u32`. + // Because From is not const. + pub const fn from_u32(inner: u32) -> Self { Self(inner) } + + /// Returns block height as a `u32`. + // Because type inference doesn't always work using `Into`. + pub const fn to_u32(&self) -> u32 { self.0 } +} + +impl fmt::Display for BlockHeight { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} + +crate::impl_parse_str_from_int_infallible!(BlockHeight, u32, from); + +impl From for BlockHeight { + fn from(inner: u32) -> Self { Self::from_u32(inner) } +} + +impl From for u32 { + fn from(height: BlockHeight) -> Self { height.to_u32() } +} + +impl From for BlockHeight { + /// Converts [`locktime::absolute::Height`] to a [`BlockHeight`]. + /// + /// An absolute locktime block height has a maximum value of `absolute::LOCK_TIME_THRESHOLD` + /// (500,000,000) where as a `BlockHeight` is a thin wrapper around a `u32`, the two types are + /// not interchangeable. + fn from(h: absolute::Height) -> Self { Self::from_u32(h.to_consensus_u32()) } +} + +impl TryFrom for absolute::Height { + type Error = absolute::ConversionError; + + /// Converts [`BlockHeight`] to a [`locktime::absolute::Height`]. + /// + /// An absolute locktime block height has a maximum value of `absolute::LOCK_TIME_THRESHOLD` + /// (500,000,000) where as a `BlockHeight` is a thin wrapper around a `u32`, the two types are + /// not interchangeable. + fn try_from(h: BlockHeight) -> Result { + absolute::Height::from_consensus(h.to_u32()) + } +} + +/// The block interval. +/// +/// Block interval is an integer type denoting the number of blocks that has passed since some point +/// i.e., this type is meant for usage as a relative block measure. +/// +/// This type is not meant for constructing relative height based timelocks, this is a general +/// purpose block interval abstraction. For locktimes please see [`locktime::relative::Height`]. +#[derive(Debug, 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 BlockInterval(pub u32); + +impl BlockInterval { + /// Block interval 0 i.e., the current block. + pub const ZERO: Self = BlockInterval(0); + + /// The minimum block interval (0). + pub const MIN: Self = Self::ZERO; + + /// The maximum block interval. + pub const MAX: Self = BlockInterval(u32::MAX); + + /// Creates a block interval from a `u32`. + // Because From is not const. + pub const fn from_u32(inner: u32) -> Self { Self(inner) } + + /// Returns block interval as a `u32`. + // Because type inference doesn't always work using `Into`. + pub const fn to_u32(&self) -> u32 { self.0 } +} + +impl fmt::Display for BlockInterval { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} + +crate::impl_parse_str_from_int_infallible!(BlockInterval, u32, from); + +impl From for BlockInterval { + fn from(inner: u32) -> Self { Self::from_u32(inner) } +} + +impl From for u32 { + fn from(height: BlockInterval) -> Self { height.to_u32() } +} + +impl From for BlockInterval { + /// Converts [`locktime::relative::Height`] to a [`BlockInterval`]. + /// + /// A relative locktime block height has a maximum value of `u16::MAX` where as a + /// `BlockInterval` is a thin wrapper around a `u32`, the two types are not interchangeable. + fn from(h: relative::Height) -> Self { Self::from_u32(h.value().into()) } +} + +impl TryFrom for relative::Height { + type Error = TooBigForRelativeBlockHeightError; + + /// Converts [`BlockInterval`] to a [`locktime::relative::Height`]. + /// + /// A relative locktime block height has a maximum value of `u16::MAX` where as a + /// `BlockInterval` is a thin wrapper around a `u32`, the two types are not interchangeable. + fn try_from(h: BlockInterval) -> Result { + let h = h.to_u32(); + if h > u16::MAX as u32 { + return Err(TooBigForRelativeBlockHeightError(h)); + } + Ok(relative::Height::from(h as u16)) // Cast ok, value checked above + } +} + +/// The block interval is too big to be used as a relative lock time. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TooBigForRelativeBlockHeightError(u32); + +impl fmt::Display for TooBigForRelativeBlockHeightError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "block interval is too big to be used as a relative lock time: {} (max: {})", + self.0, + relative::Height::MAX + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TooBigForRelativeBlockHeightError {} + +// height - height = interval +impl ops::Sub for BlockHeight { + type Output = BlockInterval; + + fn sub(self, rhs: BlockHeight) -> Self::Output { + let interval = self.to_u32() - rhs.to_u32(); + BlockInterval::from_u32(interval) + } +} + +// height + interval = height +impl ops::Add for BlockHeight { + type Output = BlockHeight; + + fn add(self, rhs: BlockInterval) -> Self::Output { + let height = self.to_u32() + rhs.to_u32(); + BlockHeight::from_u32(height) + } +} + +// height - interval = height +impl ops::Sub for BlockHeight { + type Output = BlockHeight; + + fn sub(self, rhs: BlockInterval) -> Self::Output { + let height = self.to_u32() - rhs.to_u32(); + BlockHeight::from_u32(height) + } +} + +// interval + interval = interval +impl ops::Add for BlockInterval { + type Output = BlockInterval; + + fn add(self, rhs: BlockInterval) -> Self::Output { + let height = self.to_u32() + rhs.to_u32(); + BlockInterval::from_u32(height) + } +} + +impl ops::AddAssign for BlockInterval { + fn add_assign(&mut self, rhs: BlockInterval) { self.0 = self.to_u32() + rhs.to_u32(); } +} + +// interval - interval = interval +impl ops::Sub for BlockInterval { + type Output = BlockInterval; + + fn sub(self, rhs: BlockInterval) -> Self::Output { + let height = self.to_u32() - rhs.to_u32(); + BlockInterval::from_u32(height) + } +} + +impl ops::SubAssign for BlockInterval { + fn sub_assign(&mut self, rhs: BlockInterval) { self.0 = self.to_u32() - rhs.to_u32(); } +} + +#[cfg(test)] +mod tests { + use super::*; + + // These tests are supposed to comprise an exhaustive list of available operations. + #[test] + fn all_available_ops() { + // height - height = interval + assert!(BlockHeight(100) - BlockHeight(99) == BlockInterval(1)); + + // height + interval = height + assert!(BlockHeight(100) + BlockInterval(1) == BlockHeight(101)); + + // height - interval == height + assert!(BlockHeight(100) - BlockInterval(1) == BlockHeight(99)); + + // interval + interval = interval + assert!(BlockInterval(1) + BlockInterval(2) == BlockInterval(3)); + + // interval - interval = interval + assert!(BlockInterval(3) - BlockInterval(2) == BlockInterval(1)); + + // interval += interval + let mut int = BlockInterval(1); + int += BlockInterval(2); + assert_eq!(int, BlockInterval(3)); + + // interval -= interval + let mut int = BlockInterval(3); + int -= BlockInterval(2); + assert_eq!(int, BlockInterval(1)); + } +} diff --git a/units/src/lib.rs b/units/src/lib.rs index f123034ce..e3fbdf581 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -37,6 +37,8 @@ mod test_macros; pub mod amount; #[cfg(feature = "alloc")] +pub mod block; +#[cfg(feature = "alloc")] pub mod fee_rate; #[cfg(feature = "alloc")] pub mod locktime; @@ -51,6 +53,7 @@ pub use self::amount::{Amount, SignedAmount}; #[doc(inline)] #[rustfmt::skip] pub use self::{ + block::{BlockHeight, BlockInterval}, fee_rate::FeeRate, // ParseIntError is used by other modules, so we re-export it. parse::ParseIntError,