diff --git a/bitcoin/src/blockdata/locktime/absolute.rs b/bitcoin/src/blockdata/locktime/absolute.rs index a8e9a993..1c4b58f0 100644 --- a/bitcoin/src/blockdata/locktime/absolute.rs +++ b/bitcoin/src/blockdata/locktime/absolute.rs @@ -7,17 +7,17 @@ //! use core::cmp::Ordering; -use core::{fmt, mem}; +use core::fmt; use io::{BufRead, Write}; #[cfg(all(test, mutate))] use mutagen::mutate; use units::parse; -use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError}; #[cfg(doc)] use crate::absolute; +use crate::consensus::encode::{self, Decodable, Encodable}; +use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] @@ -170,22 +170,21 @@ impl LockTime { /// Returns true if both lock times use the same unit i.e., both height based or both time based. #[inline] - pub fn is_same_unit(&self, other: LockTime) -> bool { - mem::discriminant(self) == mem::discriminant(&other) + pub const fn is_same_unit(&self, other: LockTime) -> bool { + matches!( + (self, other), + (LockTime::Blocks(_), LockTime::Blocks(_)) + | (LockTime::Seconds(_), LockTime::Seconds(_)) + ) } /// Returns true if this lock time value is a block height. #[inline] - pub fn is_block_height(&self) -> bool { - match *self { - LockTime::Blocks(_) => true, - LockTime::Seconds(_) => false, - } - } + pub const fn is_block_height(&self) -> bool { matches!(*self, LockTime::Blocks(_)) } /// Returns true if this lock time value is a block time (UNIX timestamp). #[inline] - pub fn is_block_time(&self) -> bool { !self.is_block_height() } + pub const fn is_block_time(&self) -> bool { !self.is_block_height() } /// Returns true if this timelock constraint is satisfied by the respective `height`/`time`. /// diff --git a/bitcoin/src/blockdata/locktime/relative.rs b/bitcoin/src/blockdata/locktime/relative.rs index 92e55f17..f574f7fb 100644 --- a/bitcoin/src/blockdata/locktime/relative.rs +++ b/bitcoin/src/blockdata/locktime/relative.rs @@ -6,13 +6,14 @@ //! whether bit 22 of the `u32` consensus value is set. //! -use core::fmt; +use core::{cmp, convert, fmt}; #[cfg(all(test, mutate))] use mutagen::mutate; #[cfg(doc)] use crate::relative; +use crate::Sequence; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] @@ -20,8 +21,13 @@ pub use units::locktime::relative::{Height, Time, TimeOverflowError}; /// A relative lock time value, representing either a block height or time (512 second intervals). /// -/// The `relative::LockTime` type does not have any constructors, this is by design, please use -/// `Sequence::to_relative_lock_time` to create a relative lock time. +/// Used for sequence numbers (`nSequence` in Bitcoin Core and [`crate::TxIn::sequence`] +/// in this library) and also for the argument to opcode 'OP_CHECKSEQUENCEVERIFY`. +/// +/// ### Note on ordering +/// +/// Because 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`]. /// /// ### Relevant BIPs /// @@ -38,6 +44,120 @@ pub enum LockTime { } 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::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 @@ -108,6 +228,20 @@ impl LockTime { } } + /// 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 @@ -131,7 +265,7 @@ impl LockTime { match *self { Blocks(ref h) => Ok(h.value() <= height.value()), - Time(time) => Err(IncompatibleHeightError { height, time }) + Time(time) => Err(IncompatibleHeightError { height, time }), } } @@ -158,7 +292,7 @@ impl LockTime { match *self { Time(ref t) => Ok(t.value() <= time.value()), - Blocks(height) => Err(IncompatibleTimeError { time, height }) + Blocks(height) => Err(IncompatibleTimeError { time, height }), } } } @@ -173,6 +307,19 @@ impl From