// SPDX-License-Identifier: CC0-1.0 //! Provides [`NumberOfBlocks`] and [`NumberOf512Seconds`] types used by the `rust-bitcoin` `relative::LockTime` type. use core::fmt; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")] #[doc(hidden)] pub type Height = NumberOfBlocks; /// A relative lock time lock-by-blockheight value. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NumberOfBlocks(u16); impl NumberOfBlocks { /// Relative block height 0, can be included in any block. pub const ZERO: Self = Self(0); /// The minimum relative block height (0), can be included in any block. pub const MIN: Self = Self::ZERO; /// The maximum relative block height. pub const MAX: Self = Self(u16::MAX); /// Constructs a new [`NumberOfBlocks`] using a count of blocks. #[inline] pub const fn from_height(blocks: u16) -> Self { Self(blocks) } /// Express the [`Height`] as a count of blocks. #[inline] #[must_use] pub const fn to_height(self) -> u16 { self.0 } /// Returns the inner `u16` value. #[inline] #[must_use] #[deprecated(since = "TBD", note = "use `to_height` instead")] #[doc(hidden)] pub const fn value(self) -> u16 { self.0 } /// Returns the `u32` value used to encode this locktime in an nSequence field or /// argument to `OP_CHECKSEQUENCEVERIFY`. #[deprecated( since = "TBD", note = "use `LockTime::from` followed by `to_consensus_u32` instead" )] pub const fn to_consensus_u32(self) -> u32 { self.0 as u32 // cast safety: u32 is wider than u16 on all architectures } /// Determines whether a relative‐height locktime has matured, taking into account /// both the chain tip and the height at which the UTXO was confirmed. /// /// If you have two height intervals `x` and `y`, and want to know whether `x` /// is satisfied by `y`, use `x >= y`. /// /// # Parameters /// - `self` – the relative block‐height delay (`h`) required after confirmation. /// - `chain_tip` – the height of the current chain tip /// - `utxo_mined_at` – the height of the UTXO’s confirmation block /// /// # Returns /// - `true` if a UTXO locked by `self` can be spent in a block after `chain_tip`. /// - `false` if the UTXO is still locked at `chain_tip`. pub fn is_satisfied_by( self, chain_tip: crate::BlockHeight, utxo_mined_at: crate::BlockHeight, ) -> bool { chain_tip .checked_sub(utxo_mined_at) .and_then(|diff: crate::BlockHeightInterval| diff.try_into().ok()) .map_or(false, |diff: Self| diff >= self) } } impl From for NumberOfBlocks { #[inline] fn from(value: u16) -> Self { NumberOfBlocks(value) } } crate::impl_parse_str_from_int_infallible!(NumberOfBlocks, u16, from); impl fmt::Display for NumberOfBlocks { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[deprecated(since = "TBD", note = "use `NumberOf512Seconds` instead")] #[doc(hidden)] pub type Time = NumberOf512Seconds; /// A relative lock time lock-by-blocktime value. /// /// For BIP 68 relative lock-by-blocktime locks, time is measured in 512 second intervals. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NumberOf512Seconds(u16); impl NumberOf512Seconds { /// Relative block time 0, can be included in any block. pub const ZERO: Self = NumberOf512Seconds(0); /// The minimum relative block time (0), can be included in any block. pub const MIN: Self = NumberOf512Seconds::ZERO; /// The maximum relative block time (33,554,432 seconds or approx 388 days). pub const MAX: Self = NumberOf512Seconds(u16::MAX); /// Constructs a new [`NumberOf512Seconds`] using time intervals where each interval is equivalent to 512 seconds. /// /// Encoding finer granularity of time for relative lock-times is not supported in Bitcoin. #[inline] pub const fn from_512_second_intervals(intervals: u16) -> Self { NumberOf512Seconds(intervals) } /// Express the [`NumberOf512Seconds`] as an integer number of 512-second intervals. #[inline] #[must_use] pub const fn to_512_second_intervals(self) -> u16 { self.0 } /// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second interval with /// truncating division. /// /// # Errors /// /// Will return an error if the input cannot be encoded in 16 bits. #[inline] #[rustfmt::skip] // moves comments to unrelated code pub const fn from_seconds_floor(seconds: u32) -> Result { let interval = seconds / 512; if interval <= u16::MAX as u32 { // infallible cast, needed by const code Ok(NumberOf512Seconds::from_512_second_intervals(interval as u16)) // Cast checked above, needed by const code. } else { Err(TimeOverflowError { seconds }) } } /// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second intervals with /// ceiling division. /// /// # Errors /// /// Will return an error if the input cannot be encoded in 16 bits. #[inline] #[rustfmt::skip] // moves comments to unrelated code pub const fn from_seconds_ceil(seconds: u32) -> Result { if seconds <= u16::MAX as u32 * 512 { let interval = (seconds + 511) / 512; Ok(NumberOf512Seconds::from_512_second_intervals(interval as u16)) // Cast checked above, needed by const code. } else { Err(TimeOverflowError { seconds }) } } /// Represents the [`NumberOf512Seconds`] 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] #[deprecated(since = "TBD", note = "use `to_512_second_intervals` instead")] #[doc(hidden)] pub const fn value(self) -> u16 { self.0 } /// Returns the `u32` value used to encode this locktime in an nSequence field or /// argument to `OP_CHECKSEQUENCEVERIFY`. #[deprecated( since = "TBD", note = "use `LockTime::from` followed by `to_consensus_u32` instead" )] pub const fn to_consensus_u32(self) -> u32 { (1u32 << 22) | self.0 as u32 // cast safety: u32 is wider than u16 on all architectures } /// Determines whether a relative‑time lock has matured, taking into account both /// the UTXO’s Median Time Past at confirmation and the required delay. /// /// If you have two MTP intervals `x` and `y`, and want to know whether `x` /// is satisfied by `y`, use `x >= y`. /// /// # Parameters /// - `self` – the relative time delay (`t`) in 512‑second intervals. /// - `chain_tip` – the MTP of the current chain tip /// - `utxo_mined_at` – the MTP of the UTXO’s confirmation block /// /// # Returns /// - `true` if the relative‐time lock has expired by the tip’s MTP /// - `false` if the lock has not yet expired by the tip’s MTP pub fn is_satisfied_by( self, chain_tip: crate::BlockMtp, utxo_mined_at: crate::BlockMtp, ) -> bool { chain_tip .checked_sub(utxo_mined_at) .and_then(|diff: crate::BlockMtpInterval| diff.to_relative_mtp_interval_floor().ok()) .map_or(false, |diff: Self| diff >= self) } } crate::impl_parse_str_from_int_infallible!(NumberOf512Seconds, u16, from_512_second_intervals); impl fmt::Display for NumberOf512Seconds { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } /// Error returned when the input time in seconds was too large to be encoded to a 16 bit 512 second interval. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TimeOverflowError { /// Time interval value in seconds that overflowed. // Private because we maintain an invariant that the `seconds` value does actually overflow. pub(crate) seconds: u32, } impl TimeOverflowError { /// Constructs a new `TimeOverflowError` using `seconds`. /// /// # Panics /// /// If `seconds` would not actually overflow a `u16`. pub fn new(seconds: u32) -> Self { assert!(u16::try_from((seconds + 511) / 512).is_err()); Self { seconds } } } impl fmt::Display for TimeOverflowError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "{} seconds is too large to be encoded to a 16 bit 512 second interval", self.seconds ) } } #[cfg(feature = "std")] impl std::error::Error for TimeOverflowError {} #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for NumberOfBlocks { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { let choice = u.int_in_range(0..=2)?; match choice { 0 => Ok(NumberOfBlocks::MIN), 1 => Ok(NumberOfBlocks::MAX), _ => Ok(NumberOfBlocks::from_height(u16::arbitrary(u)?)), } } } #[cfg(feature = "arbitrary")] impl<'a> Arbitrary<'a> for NumberOf512Seconds { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { let choice = u.int_in_range(0..=2)?; match choice { 0 => Ok(NumberOf512Seconds::MIN), 1 => Ok(NumberOf512Seconds::MAX), _ => Ok(NumberOf512Seconds::from_512_second_intervals(u16::arbitrary(u)?)), } } } #[cfg(test)] mod tests { #[cfg(feature = "serde")] use internals::serde_round_trip; use super::*; use crate::BlockTime; const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512; #[test] #[allow(deprecated_in_future)] fn sanity_check() { assert_eq!(NumberOfBlocks::MAX.to_consensus_u32(), u32::from(u16::MAX)); assert_eq!(NumberOf512Seconds::from_512_second_intervals(100).value(), 100u16); assert_eq!( NumberOf512Seconds::from_512_second_intervals(100).to_consensus_u32(), 4_194_404u32 ); // 0x400064 } #[test] fn from_seconds_ceil_success() { let actual = NumberOf512Seconds::from_seconds_ceil(100).unwrap(); let expected = NumberOf512Seconds(1_u16); assert_eq!(actual, expected); } #[test] fn from_seconds_ceil_with_maximum_encodable_seconds_success() { let actual = NumberOf512Seconds::from_seconds_ceil(MAXIMUM_ENCODABLE_SECONDS).unwrap(); let expected = NumberOf512Seconds(u16::MAX); assert_eq!(actual, expected); } #[test] fn from_seconds_ceil_causes_time_overflow_error() { let result = NumberOf512Seconds::from_seconds_ceil(MAXIMUM_ENCODABLE_SECONDS + 1); assert!(result.is_err()); } #[test] fn from_seconds_floor_success() { let actual = NumberOf512Seconds::from_seconds_floor(100).unwrap(); let expected = NumberOf512Seconds(0_u16); assert_eq!(actual, expected); } #[test] fn from_seconds_floor_with_exact_interval() { let actual = NumberOf512Seconds::from_seconds_floor(512).unwrap(); let expected = NumberOf512Seconds(1_u16); assert_eq!(actual, expected); } #[test] fn from_seconds_floor_with_maximum_encodable_seconds_success() { let actual = NumberOf512Seconds::from_seconds_floor(MAXIMUM_ENCODABLE_SECONDS + 511).unwrap(); let expected = NumberOf512Seconds(u16::MAX); assert_eq!(actual, expected); } #[test] fn from_seconds_floor_causes_time_overflow_error() { let result = NumberOf512Seconds::from_seconds_floor(MAXIMUM_ENCODABLE_SECONDS + 512); assert!(result.is_err()); } #[test] #[cfg(feature = "serde")] pub fn encode_decode_height() { serde_round_trip!(NumberOfBlocks::ZERO); serde_round_trip!(NumberOfBlocks::MIN); serde_round_trip!(NumberOfBlocks::MAX); } #[test] #[cfg(feature = "serde")] pub fn encode_decode_time() { serde_round_trip!(NumberOf512Seconds::ZERO); serde_round_trip!(NumberOf512Seconds::MIN); serde_round_trip!(NumberOf512Seconds::MAX); } fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] { let mut timestamps = [BlockTime::from_u32(0); 11]; for (i, ts) in timestamps.iter_mut().enumerate() { *ts = BlockTime::from_u32(start.saturating_sub((step * i as u16).into())); } timestamps } #[test] fn test_time_chain_state() { use crate::BlockMtp; let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200); let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200); let timestamps2: [BlockTime; 11] = generate_timestamps(1_599_995_119, 200); let utxo_timestamps2: [BlockTime; 11] = generate_timestamps(1_599_990_000, 200); let timestamps3: [BlockTime; 11] = generate_timestamps(1_600_050_000, 200); let utxo_timestamps3: [BlockTime; 11] = generate_timestamps(1_599_990_000, 200); // Test case 1: Satisfaction (current_mtp >= utxo_mtp + required_seconds) // 10 intervals × 512 seconds = 5120 seconds let time_lock = NumberOf512Seconds::from_512_second_intervals(10); let chain_state1 = BlockMtp::new(timestamps); let utxo_state1 = BlockMtp::new(utxo_timestamps); assert!(time_lock.is_satisfied_by(chain_state1, utxo_state1)); // Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds) let chain_state2 = BlockMtp::new(timestamps2); let utxo_state2 = BlockMtp::new(utxo_timestamps2); assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2)); // Test case 3: Test with a larger value (100 intervals = 51200 seconds) let larger_lock = NumberOf512Seconds::from_512_second_intervals(100); let chain_state3 = BlockMtp::new(timestamps3); let utxo_state3 = BlockMtp::new(utxo_timestamps3); assert!(larger_lock.is_satisfied_by(chain_state3, utxo_state3)); // Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully let max_time_lock = NumberOf512Seconds::MAX; let chain_state4 = BlockMtp::new(timestamps); let utxo_state4 = BlockMtp::new(utxo_timestamps); assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4)); } #[test] fn test_height_chain_state() { use crate::BlockHeight; let height_lock = NumberOfBlocks(10); // Test case 1: Satisfaction (current_height >= utxo_height + required) let chain_state1 = BlockHeight::from_u32(100); let utxo_state1 = BlockHeight::from_u32(80); assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1)); // Test case 2: Not satisfied (current_height < utxo_height + required) let chain_state2 = BlockHeight::from_u32(89); let utxo_state2 = BlockHeight::from_u32(80); assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2)); // Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully let max_height_lock = NumberOfBlocks::MAX; let chain_state3 = BlockHeight::from_u32(1000); let utxo_state3 = BlockHeight::from_u32(80); assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3)); } }