diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 4265be8db..9df6b25f5 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -134,7 +134,7 @@ pub use primitives::{ #[doc(inline)] pub use units::{ amount::{Amount, Denomination, SignedAmount}, - block::{BlockHeight, BlockInterval}, + block::{BlockHeight, BlockInterval, BlockMtp}, fee_rate::FeeRate, time::{self, BlockTime}, weight::Weight, diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index f9dafc05a..f47749c01 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -50,7 +50,7 @@ pub mod witness; #[doc(inline)] pub use units::{ amount::{self, Amount, SignedAmount}, - block::{BlockHeight, BlockInterval}, + block::{BlockHeight, BlockInterval, BlockMtp}, fee_rate::{self, FeeRate}, time::{self, BlockTime}, weight::{self, Weight}, diff --git a/units/src/block.rs b/units/src/block.rs index 589cabcb5..d3fdf66ee 100644 --- a/units/src/block.rs +++ b/units/src/block.rs @@ -93,8 +93,7 @@ impl From for BlockHeight { /// Converts a [`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. + /// minus one, while [`BlockHeight`] may take the full range of `u32`. fn from(h: absolute::Height) -> Self { Self::from_u32(h.to_u32()) } } @@ -104,8 +103,7 @@ impl TryFrom for absolute::Height { /// Converts a [`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. + /// minus one, while [`BlockHeight`] may take the full range of `u32`. fn try_from(h: BlockHeight) -> Result { absolute::Height::from_u32(h.to_u32()) } @@ -149,6 +147,49 @@ impl TryFrom for relative::HeightInterval { } } +impl_u32_wrapper! { + /// The median timestamp of 11 consecutive blocks. + /// + /// This type is not meant for constructing time-based timelocks. It is a general purpose + /// MTP abstraction. For locktimes please see [`locktime::absolute::Mtp`]. + /// + /// 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 BlockMtp(pub u32); +} + +impl BlockMtp { + /// Constructs a [`BlockMtp`] by computing the median‐time‐past from the last 11 block timestamps + /// + /// Because block timestamps are not monotonic, this function internally sorts them; + /// it is therefore not important what order they appear in the array; use whatever + /// is most convenient. + pub fn new(mut timestamps: [crate::BlockTime; 11]) -> Self { + timestamps.sort_unstable(); + Self::from_u32(u32::from(timestamps[5])) + } +} + +impl From for BlockMtp { + /// Converts a [`locktime::absolute::Mtp`] to a [`BlockMtp`]. + /// + /// An absolute locktime MTP has a minimum value of [`absolute::LOCK_TIME_THRESHOLD`], + /// while [`BlockMtp`] may take the full range of `u32`. + fn from(h: absolute::Mtp) -> Self { Self::from_u32(h.to_u32()) } +} + +impl TryFrom for absolute::Mtp { + type Error = absolute::ConversionError; + + /// Converts a [`BlockHeight`] to a [`locktime::absolute::Height`]. + /// + /// An absolute locktime MTP has a minimum value of [`absolute::LOCK_TIME_THRESHOLD`], + /// while [`BlockMtp`] may take the full range of `u32`. + fn try_from(h: BlockMtp) -> Result { absolute::Mtp::from_u32(h.to_u32()) } +} + /// 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); diff --git a/units/src/lib.rs b/units/src/lib.rs index 41c33b017..f559592ab 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -57,7 +57,7 @@ pub mod weight; #[rustfmt::skip] pub use self::{ amount::{Amount, SignedAmount}, - block::{BlockHeight, BlockInterval}, + block::{BlockHeight, BlockInterval, BlockMtp}, fee_rate::FeeRate, result::{NumOpError, NumOpResult, MathOp}, time::BlockTime, diff --git a/units/tests/api.rs b/units/tests/api.rs index 41b39bb4b..94913229b 100644 --- a/units/tests/api.rs +++ b/units/tests/api.rs @@ -14,7 +14,7 @@ use arbitrary::{Arbitrary, Unstructured}; // These imports test "typical" usage by user code. use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`. use bitcoin_units::{ - amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, + amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, BlockMtp, BlockTime, FeeRate, SignedAmount, Weight, }; @@ -43,6 +43,7 @@ struct Structs { j: relative::Time, k: Weight, l: BlockTime, + m: BlockMtp, } impl Structs { @@ -60,6 +61,7 @@ impl Structs { j: relative::Time::MAX, k: Weight::MAX, l: BlockTime::from_u32(u32::MAX), + m: BlockMtp::MAX, } } } @@ -90,6 +92,7 @@ struct CommonTraits { j: relative::Time, k: Weight, l: BlockTime, + m: BlockMtp, } /// A struct that includes all types that implement `Default`. @@ -147,7 +150,7 @@ fn api_can_use_modules_from_crate_root() { #[test] fn api_can_use_types_from_crate_root() { use bitcoin_units::{ - Amount, BlockHeight, BlockInterval, BlockTime, FeeRate, SignedAmount, Weight, + Amount, BlockHeight, BlockInterval, BlockMtp, BlockTime, FeeRate, SignedAmount, Weight, }; } @@ -298,6 +301,7 @@ impl<'a> Arbitrary<'a> for Structs { j: relative::Time::arbitrary(u)?, k: Weight::arbitrary(u)?, l: BlockTime::arbitrary(u)?, + m: BlockMtp::arbitrary(u)?, }; Ok(a) }