// SPDX-License-Identifier: CC0-1.0 //! Provides [`Height`] and [`MedianTimePast`] types used by the `rust-bitcoin` `absolute::LockTime` type. use core::convert::Infallible; use core::fmt; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use internals::error::InputString; use crate::parse::{self, ParseIntError}; /// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]). /// /// `LockTime` values _below_ the threshold are interpreted as block heights, values _above_ (or /// equal to) the threshold are interpreted as block times (UNIX timestamp, seconds since epoch). /// /// Bitcoin is able to safely use this value because a block height greater than 500,000,000 would /// never occur because it would represent a height in approximately 9500 years. Conversely, block /// times under 500,000,000 will never happen because they would represent times before 1986 which /// are, for obvious reasons, not useful within the Bitcoin network. /// /// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39 pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000; /// An absolute block height, guaranteed to always contain a valid height value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Height(u32); impl Height { /// Absolute block height 0, the genesis block. pub const ZERO: Self = Height(0); /// The minimum absolute block height (0), the genesis block. pub const MIN: Self = Self::ZERO; /// The maximum absolute block height. pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1); /// Constructs a new [`Height`] from a hex string. /// /// The input string may or may not contain a typical hex prefix e.g., `0x`. /// /// # Errors /// /// If the input string is not a valid hex representation of a block height. pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_u32) } #[deprecated(since = "TBD", note = "use `from_u32` instead")] #[doc(hidden)] pub const fn from_consensus(n: u32) -> Result { Self::from_u32(n) } #[deprecated(since = "TBD", note = "use `to_u32` instead")] #[doc(hidden)] pub const fn to_consensus_u32(self) -> u32 { self.to_u32() } /// Constructs a new block height directly from a `u32` value. /// /// # Errors /// /// If `n` does not represent a block height within the valid range for a locktime: /// `[0, 499_999_999]`. /// /// # Examples /// /// ```rust /// use bitcoin_units::locktime::absolute::Height; /// /// let h: u32 = 741521; /// let height = Height::from_u32(h).expect("invalid height value"); /// assert_eq!(height.to_consensus_u32(), h); /// ``` #[inline] pub const fn from_u32(n: u32) -> Result { if is_block_height(n) { Ok(Self(n)) } else { Err(ConversionError::invalid_height(n)) } } /// Converts this [`Height`] to a raw `u32` value. #[inline] pub const fn to_u32(self) -> u32 { self.0 } /// Returns true if a transaction with this locktime can be included in the next block. /// /// `self` is value of the `LockTime` and if `height` is the current chain tip then /// a transaction with this lock can be broadcast for inclusion in the next block. #[inline] pub fn is_satisfied_by(self, height: Height) -> bool { // Use u64 so that there can be no overflow. let next_block_height = u64::from(height.to_u32()) + 1; u64::from(self.to_u32()) <= next_block_height } } impl fmt::Display for Height { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_u32)); /// Error returned when parsing block height fails. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ParseHeightError(ParseError); impl fmt::Display for ParseHeightError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1) } } #[cfg(feature = "std")] impl std::error::Error for ParseHeightError { // To be consistent with `write_err` we need to **not** return source in case of overflow fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } } impl From for ParseHeightError { fn from(value: ParseError) -> Self { Self(value) } } #[deprecated(since = "TBD", note = "use `MedianTimePast` instead")] #[doc(hidden)] pub type Time = MedianTimePast; /// The median timestamp of 11 consecutive blocks, representing "the timestamp" of the /// final block for locktime-checking purposes. /// /// Time-based locktimes are not measured against the timestamps in individual block /// headers, since these are not monotone and may be subject to miner manipulation. /// Instead, locktimes use the "median-time-past" (MTP) of the most recent 11 blocks, /// a quantity which is required by consensus to be monotone and which is difficult /// for any individual miner to manipulate. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MedianTimePast(u32); impl MedianTimePast { /// The minimum MTP allowable in a locktime (Tue Nov 05 1985 00:53:20 GMT+0000). pub const MIN: Self = MedianTimePast(LOCK_TIME_THRESHOLD); /// The maximum MTP allowable in a locktime (Sun Feb 07 2106 06:28:15 GMT+0000). pub const MAX: Self = MedianTimePast(u32::MAX); /// Constructs an [`MedianTimePast`] 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. /// /// # Errors /// /// If the median block timestamp is not in the allowable range of MTPs in a /// locktime: `[500_000_000, 2^32 - 1]`. Because there is a consensus rule that MTP /// be monotonically increasing, and the MTP of the first 11 blocks exceeds `500_000_000` /// for every real-life chain, this error typically cannot be hit in practice. pub fn new(timestamps: [crate::BlockTime; 11]) -> Result { crate::BlockMtp::new(timestamps).try_into() } /// Constructs a new [`MedianTimePast`] from a big-endian hex-encoded `u32`. /// /// The input string may or may not contain a typical hex prefix e.g., `0x`. /// /// # Errors /// /// If the input string is not a valid hex representation of a block time. pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_u32) } #[deprecated(since = "TBD", note = "use `from_u32` instead")] #[doc(hidden)] pub const fn from_consensus(n: u32) -> Result { Self::from_u32(n) } #[deprecated(since = "TBD", note = "use `to_u32` instead")] #[doc(hidden)] pub const fn to_consensus_u32(self) -> u32 { self.to_u32() } /// Constructs a new MTP directly from a `u32` value. /// /// This function, with [`MedianTimePast::to_u32`], is used to obtain a raw MTP value. It is /// **not** used to convert to or from a block timestamp, which is not a MTP. /// /// # Errors /// /// If `n` is not in the allowable range of MTPs in a locktime: `[500_000_000, 2^32 - 1]`. /// /// # Examples /// /// ```rust /// use bitcoin_units::locktime::absolute::MedianTimePast; /// /// let t: u32 = 1653195600; // May 22nd, 5am UTC. /// let time = MedianTimePast::from_u32(t).expect("invalid time value"); /// assert_eq!(time.to_consensus_u32(), t); /// ``` #[inline] pub const fn from_u32(n: u32) -> Result { if is_block_time(n) { Ok(Self(n)) } else { Err(ConversionError::invalid_time(n)) } } /// Converts this [`MedianTimePast`] to a raw `u32` value. #[inline] pub const fn to_u32(self) -> u32 { self.0 } /// Returns true if a transaction with this locktime can be included in the next block. /// /// `self`is the value of the `LockTime` and if `time` is the median time past of the block at /// the chain tip then a transaction with this lock can be broadcast for inclusion in the next /// block. #[inline] pub fn is_satisfied_by(self, time: MedianTimePast) -> bool { // The locktime check in Core during block validation uses the MTP // of the previous block - which is the expected to be `time` here. self <= time } } impl fmt::Display for MedianTimePast { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } crate::impl_parse_str!(MedianTimePast, ParseTimeError, parser(MedianTimePast::from_u32)); /// Error returned when parsing block time fails. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ParseTimeError(ParseError); impl fmt::Display for ParseTimeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX) } } #[cfg(feature = "std")] impl std::error::Error for ParseTimeError { // To be consistent with `write_err` we need to **not** return source in case of overflow fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } } impl From for ParseTimeError { fn from(value: ParseError) -> Self { Self(value) } } fn parser(f: F) -> impl FnOnce(S) -> Result where E: From, S: AsRef + Into, F: FnOnce(u32) -> Result, { move |s| { let n = s.as_ref().parse::().map_err(ParseError::invalid_int(s))?; let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; f(n).map_err(ParseError::from).map_err(Into::into) } } fn parse_hex(s: S, f: F) -> Result where E: From, S: AsRef + Into, F: FnOnce(u32) -> Result, { let n = i64::from_str_radix(parse::hex_remove_optional_prefix(s.as_ref()), 16) .map_err(ParseError::invalid_int(s))?; let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; f(n).map_err(ParseError::from).map_err(Into::into) } /// Returns true if `n` is a block height i.e., less than 500,000,000. pub const fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD } /// Returns true if `n` is a UNIX timestamp i.e., greater than or equal to 500,000,000. pub const fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD } /// Error returned when converting a `u32` to a lock time variant fails. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub struct ConversionError { /// The expected timelock unit, height (blocks) or time (seconds). unit: LockTimeUnit, /// The invalid input value. input: u32, } impl ConversionError { /// Constructs a new `ConversionError` from an invalid `n` when expecting a height value. const fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } } /// Constructs a new `ConversionError` from an invalid `n` when expecting a time value. const fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } } } impl fmt::Display for ConversionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "invalid lock time value {}, {}", self.input, self.unit) } } #[cfg(feature = "std")] impl std::error::Error for ConversionError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } /// Describes the two types of locking, lock-by-height and lock-by-time. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] enum LockTimeUnit { /// Lock by blockheight. Blocks, /// Lock by blocktime. Seconds, } impl fmt::Display for LockTimeUnit { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use LockTimeUnit as L; match *self { L::Blocks => write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD), L::Seconds => write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD), } } } /// Internal - common representation for height and time. #[derive(Debug, Clone, Eq, PartialEq)] enum ParseError { ParseInt(ParseIntError), // unit implied by outer type // we use i64 to have nicer messages for negative values Conversion(i64), } impl From for ParseError { fn from(never: Infallible) -> Self { match never {} } } impl ParseError { fn invalid_int>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self { move |source| { Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source }) } } fn display( &self, f: &mut fmt::Formatter<'_>, subject: &str, lower_bound: u32, upper_bound: u32, ) -> fmt::Result { use core::num::IntErrorKind; match self { Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::PosOverflow => { // Outputs "failed to parse as absolute Height/MedianTimePast ( is above limit )" write!( f, "{} ({} is above limit {})", input.display_cannot_parse("absolute Height/MedianTimePast"), subject, upper_bound ) } Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) if *source.kind() == IntErrorKind::NegOverflow => { // Outputs "failed to parse as absolute Height/MedianTimePast ( is below limit )" write!( f, "{} ({} is below limit {})", input.display_cannot_parse("absolute Height/MedianTimePast"), subject, lower_bound ) } Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => { write!( f, "{} ({})", input.display_cannot_parse("absolute Height/MedianTimePast"), subject ) } Self::Conversion(value) if *value < i64::from(lower_bound) => { write!(f, "{} {} is below limit {}", subject, value, lower_bound) } Self::Conversion(value) => { write!(f, "{} {} is above limit {}", subject, value, upper_bound) } } } // To be consistent with `write_err` we need to **not** return source in case of overflow #[cfg(feature = "std")] fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use core::num::IntErrorKind; match self { Self::ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::PosOverflow => None, Self::ParseInt(ParseIntError { source, .. }) if *source.kind() == IntErrorKind::NegOverflow => None, Self::ParseInt(ParseIntError { source, .. }) => Some(source), Self::Conversion(_) => None, } } } impl From for ParseError { fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) } } #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for Height { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { let choice = u.int_in_range(0..=2)?; match choice { 0 => Ok(Height::MIN), 1 => Ok(Height::MAX), _ => { let min = Height::MIN.to_u32(); let max = Height::MAX.to_u32(); let h = u.int_in_range(min..=max)?; Ok(Height::from_u32(h).unwrap()) } } } } #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for MedianTimePast { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { let choice = u.int_in_range(0..=2)?; match choice { 0 => Ok(MedianTimePast::MIN), 1 => Ok(MedianTimePast::MAX), _ => { let min = MedianTimePast::MIN.to_u32(); let max = MedianTimePast::MAX.to_u32(); let t = u.int_in_range(min..=max)?; Ok(MedianTimePast::from_u32(t).unwrap()) } } } } #[cfg(test)] mod tests { use super::*; #[test] fn time_from_str_hex_happy_path() { let actual = MedianTimePast::from_hex("0x6289C350").unwrap(); let expected = MedianTimePast::from_u32(0x6289_C350).unwrap(); assert_eq!(actual, expected); } #[test] fn time_from_str_hex_no_prefix_happy_path() { let time = MedianTimePast::from_hex("6289C350").unwrap(); assert_eq!(time, MedianTimePast(0x6289_C350)); } #[test] fn time_from_str_hex_invalid_hex_should_err() { let hex = "0xzb93"; let result = MedianTimePast::from_hex(hex); assert!(result.is_err()); } #[test] fn height_from_str_hex_happy_path() { let actual = Height::from_hex("0xBA70D").unwrap(); let expected = Height(0xBA70D); assert_eq!(actual, expected); } #[test] fn height_from_str_hex_no_prefix_happy_path() { let height = Height::from_hex("BA70D").unwrap(); assert_eq!(height, Height(0xBA70D)); } #[test] fn height_from_str_hex_invalid_hex_should_err() { let hex = "0xzb93"; let result = Height::from_hex(hex); assert!(result.is_err()); } #[test] fn is_block_height_or_time() { assert!(is_block_height(499_999_999)); assert!(!is_block_height(500_000_000)); assert!(!is_block_time(499_999_999)); assert!(is_block_time(500_000_000)); } #[test] #[cfg(feature = "alloc")] fn locktime_unit_display() { use alloc::format; let blocks = LockTimeUnit::Blocks; let seconds = LockTimeUnit::Seconds; assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)"); assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)"); } #[test] fn valid_chain_computes_mtp() { use crate::BlockTime; let mut timestamps = [ BlockTime::from_u32(500_000_010), BlockTime::from_u32(500_000_003), BlockTime::from_u32(500_000_005), BlockTime::from_u32(500_000_008), BlockTime::from_u32(500_000_001), BlockTime::from_u32(500_000_004), BlockTime::from_u32(500_000_006), BlockTime::from_u32(500_000_009), BlockTime::from_u32(500_000_002), BlockTime::from_u32(500_000_007), BlockTime::from_u32(500_000_000), ]; // Try various reorderings assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005); timestamps.reverse(); assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005); timestamps.sort(); assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005); timestamps.reverse(); assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005); } #[test] fn height_is_satisfied_by() { let chain_tip = Height::from_u32(100).unwrap(); // lock is satisfied if transaction can go in the next block (height <= chain_tip + 1). let locktime = Height::from_u32(100).unwrap(); assert!(locktime.is_satisfied_by(chain_tip)); let locktime = Height::from_u32(101).unwrap(); assert!(locktime.is_satisfied_by(chain_tip)); // It is not satisfied if the lock height is after the next block. let locktime = Height::from_u32(102).unwrap(); assert!(!locktime.is_satisfied_by(chain_tip)); } #[test] fn median_time_past_is_satisfied_by() { let mtp = MedianTimePast::from_u32(500_000_001).unwrap(); // lock is satisfied if transaction can go in the next block (locktime <= mtp). let locktime = MedianTimePast::from_u32(500_000_000).unwrap(); assert!(locktime.is_satisfied_by(mtp)); let locktime = MedianTimePast::from_u32(500_000_001).unwrap(); assert!(locktime.is_satisfied_by(mtp)); // It is not satisfied if the lock time is after the median time past. let locktime = MedianTimePast::from_u32(500_000_002).unwrap(); assert!(!locktime.is_satisfied_by(mtp)); } }