// SPDX-License-Identifier: CC0-1.0 //! Provides type [`LockTime`] that implements the logic around nSequence/OP_CHECKSEQUENCEVERIFY. //! //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by //! whether bit 22 of the `u32` consensus value is set. #[cfg(feature = "ordered")] use core::cmp::Ordering; use core::{cmp, convert, fmt}; #[cfg(all(test, mutate))] use mutagen::mutate; use crate::Sequence; #[cfg(all(doc, feature = "alloc"))] use crate::{relative, TxIn}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] pub use units::locktime::relative::{Height, Time, TimeOverflowError}; /// A relative lock time value, representing either a block height or time (512 second intervals). /// /// Used for sequence numbers (`nSequence` in Bitcoin Core and [`TxIn::sequence`] /// in this library) and also for the argument to opcode 'OP_CHECKSEQUENCEVERIFY`. /// /// ### Note on ordering /// /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total /// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`]. We also /// implement [`ordered::ArbitraryOrd`] if the "ordered" feature is enabled. /// /// ### Relevant BIPs /// /// * [BIP 68 Relative lock-time using consensus-enforced sequence numbers](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) /// * [BIP 112 CHECKSEQUENCEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum LockTime { /// A block height lock time value. Blocks(Height), /// A 512 second time interval value. Time(Time), } impl LockTime { /// A relative locktime of 0 is always valid, and is assumed valid for inputs that /// are not yet confirmed. pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO); /// The number of bytes that the locktime contributes to the size of a transaction. pub const SIZE: usize = 4; // Serialized length of a u32. /// Constructs a `LockTime` from an nSequence value or the argument to OP_CHECKSEQUENCEVERIFY. /// /// This method will **not** round-trip with [`Self::to_consensus_u32`], because relative /// locktimes only use some bits of the underlying `u32` value and discard the rest. If /// you want to preserve the full value, you should use the [`Sequence`] type instead. /// /// # Examples /// /// ```rust /// # use bitcoin_primitives::relative::LockTime; /// /// // `from_consensus` roundtrips with `to_consensus_u32` for small values. /// let n_lock_time: u32 = 7000; /// let lock_time = LockTime::from_consensus(n_lock_time).unwrap(); /// assert_eq!(lock_time.to_consensus_u32(), n_lock_time); /// ``` pub fn from_consensus(n: u32) -> Result { let sequence = crate::Sequence::from_consensus(n); sequence.to_relative_lock_time().ok_or(DisabledLockTimeError(n)) } /// Returns the `u32` value used to encode this locktime in an nSequence field or /// argument to `OP_CHECKSEQUENCEVERIFY`. /// /// # Warning /// /// Locktimes are not ordered by the natural ordering on `u32`. If you want to /// compare locktimes, use [`Self::is_implied_by`] or similar methods. #[inline] pub fn to_consensus_u32(&self) -> u32 { match self { LockTime::Blocks(ref h) => h.to_consensus_u32(), LockTime::Time(ref t) => t.to_consensus_u32(), } } /// Constructs a `LockTime` from the sequence number of a Bitcoin input. /// /// This method will **not** round-trip with [`Self::to_sequence`]. See the /// docs for [`Self::from_consensus`] for more information. #[inline] pub fn from_sequence(n: Sequence) -> Result { Self::from_consensus(n.to_consensus_u32()) } /// Encodes the locktime as a sequence number. #[inline] pub fn to_sequence(&self) -> Sequence { Sequence::from_consensus(self.to_consensus_u32()) } /// Constructs a `LockTime` from `n`, expecting `n` to be a 16-bit count of blocks. #[inline] pub const fn from_height(n: u16) -> Self { LockTime::Blocks(Height::from_height(n)) } /// Constructs a `LockTime` from `n`, expecting `n` to be a count of 512-second intervals. /// /// This function is a little awkward to use, and users may wish to instead use /// [`Self::from_seconds_floor`] or [`Self::from_seconds_ceil`]. #[inline] pub const fn from_512_second_intervals(intervals: u16) -> Self { LockTime::Time(Time::from_512_second_intervals(intervals)) } /// Create a [`LockTime`] 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] pub const fn from_seconds_floor(seconds: u32) -> Result { match Time::from_seconds_floor(seconds) { Ok(time) => Ok(LockTime::Time(time)), Err(e) => Err(e), } } /// Create a [`LockTime`] from seconds, converting the seconds into 512 second interval /// with ceiling division. /// /// # Errors /// /// Will return an error if the input cannot be encoded in 16 bits. #[inline] pub const fn from_seconds_ceil(seconds: u32) -> Result { match Time::from_seconds_ceil(seconds) { Ok(time) => Ok(LockTime::Time(time)), Err(e) => Err(e), } } /// Returns true if both lock times use the same unit i.e., both height based or both time based. #[inline] pub const fn is_same_unit(&self, other: LockTime) -> bool { matches!( (self, other), (LockTime::Blocks(_), LockTime::Blocks(_)) | (LockTime::Time(_), LockTime::Time(_)) ) } /// Returns true if this lock time value is in units of block height. #[inline] pub const fn is_block_height(&self) -> bool { matches!(*self, LockTime::Blocks(_)) } /// Returns true if this lock time value is in units of time. #[inline] pub const fn is_block_time(&self) -> bool { !self.is_block_height() } /// Returns true if this [`relative::LockTime`] is satisfied by either height or time. /// /// # Examples /// /// ```rust /// # use bitcoin_primitives::Sequence; /// # use bitcoin_primitives::locktime::relative::{Height, Time}; /// /// # let required_height = 100; // 100 blocks. /// # let intervals = 70; // Approx 10 hours. /// # let current_height = || Height::from(required_height + 10); /// # let current_time = || Time::from_512_second_intervals(intervals + 10); /// # let lock = Sequence::from_height(required_height).to_relative_lock_time().expect("valid height"); /// /// // Users that have chain data can get the current height and time to check against a lock. /// assert!(lock.is_satisfied_by(current_height(), current_time())); /// ``` #[inline] #[cfg_attr(all(test, mutate), mutate)] pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool { if let Ok(true) = self.is_satisfied_by_height(h) { true } else { matches!(self.is_satisfied_by_time(t), Ok(true)) } } /// Returns true if satisfaction of `other` lock time implies satisfaction of this /// [`relative::LockTime`]. /// /// A lock time can only be satisfied by n blocks being mined or n seconds passing. If you have /// two lock times (same unit) then the larger lock time being satisfied implies (in a /// mathematical sense) the smaller one being satisfied. /// /// This function is useful when checking sequence values against a lock, first one checks the /// sequence represents a relative lock time by converting to `LockTime` then use this function /// to see if satisfaction of the newly created lock time would imply satisfaction of `self`. /// /// Can also be used to remove the smaller value of two `OP_CHECKSEQUENCEVERIFY` operations /// within one branch of the script. /// /// # Examples /// /// ```rust /// # use bitcoin_primitives::Sequence; /// /// # let required_height = 100; // 100 blocks. /// # let lock = Sequence::from_height(required_height).to_relative_lock_time().expect("valid height"); /// # let test_sequence = Sequence::from_height(required_height + 10); /// /// let satisfied = match test_sequence.to_relative_lock_time() { /// None => false, // Handle non-lock-time case. /// Some(test_lock) => lock.is_implied_by(test_lock), /// }; /// assert!(satisfied); /// ``` #[inline] #[cfg_attr(all(test, mutate), mutate)] pub fn is_implied_by(&self, other: LockTime) -> bool { use LockTime::*; match (*self, other) { (Blocks(this), Blocks(other)) => this.value() <= other.value(), (Time(this), Time(other)) => this.value() <= other.value(), _ => false, // Not the same units. } } /// Returns true if satisfaction of the sequence number implies satisfaction of this lock time. /// /// When deciding whether an instance of ` CHECKSEQUENCEVERIFY` will pass, this /// method can be used by parsing `n` as a [`LockTime`] and calling this method /// with the sequence number of the input which spends the script. #[inline] pub fn is_implied_by_sequence(&self, other: Sequence) -> bool { if let Ok(other) = LockTime::from_sequence(other) { self.is_implied_by(other) } else { false } } /// Returns true if this [`relative::LockTime`] is satisfied by [`Height`]. /// /// # Errors /// /// Returns an error if this lock is not lock-by-height. /// /// # Examples /// /// ```rust /// # use bitcoin_primitives::Sequence; /// # use bitcoin_primitives::locktime::relative::Height; /// /// let required_height: u16 = 100; /// let lock = Sequence::from_height(required_height).to_relative_lock_time().expect("valid height"); /// assert!(lock.is_satisfied_by_height(Height::from(required_height + 1)).expect("a height")); /// ``` #[inline] #[cfg_attr(all(test, mutate), mutate)] pub fn is_satisfied_by_height(&self, height: Height) -> Result { use LockTime::*; match *self { Blocks(ref required_height) => Ok(required_height.value() <= height.value()), Time(time) => Err(IncompatibleHeightError { height, time }), } } /// Returns true if this [`relative::LockTime`] is satisfied by [`Time`]. /// /// # Errors /// /// Returns an error if this lock is not lock-by-time. /// /// # Examples /// /// ```rust /// # use bitcoin_primitives::Sequence; /// # use bitcoin_primitives::locktime::relative::Time; /// /// let intervals: u16 = 70; // approx 10 hours; /// let lock = Sequence::from_512_second_intervals(intervals).to_relative_lock_time().expect("valid time"); /// assert!(lock.is_satisfied_by_time(Time::from_512_second_intervals(intervals + 10)).expect("a time")); /// ``` #[inline] #[cfg_attr(all(test, mutate), mutate)] pub fn is_satisfied_by_time(&self, time: Time) -> Result { use LockTime::*; match *self { Time(ref t) => Ok(t.value() <= time.value()), Blocks(height) => Err(IncompatibleTimeError { time, height }), } } } impl From for LockTime { #[inline] fn from(h: Height) -> Self { LockTime::Blocks(h) } } impl From