From da95c3a489c18399d6c3170a0c8af0b5fa8d7cf9 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 16 Aug 2022 13:14:24 +1000 Subject: [PATCH] Add newtype `relative::LockTime` We recently added the `Sequence` type and we explicitly did not include relative lock time logic. Add a new module `relative` and new type `LockTime` to represent relative lock times as defined by BIP 68. --- src/blockdata/locktime/mod.rs | 3 +- src/blockdata/locktime/relative.rs | 289 +++++++++++++++++++++++++++++ src/blockdata/transaction.rs | 60 +++--- src/lib.rs | 2 +- 4 files changed, 322 insertions(+), 32 deletions(-) create mode 100644 src/blockdata/locktime/relative.rs diff --git a/src/blockdata/locktime/mod.rs b/src/blockdata/locktime/mod.rs index c4635e68..5100d702 100644 --- a/src/blockdata/locktime/mod.rs +++ b/src/blockdata/locktime/mod.rs @@ -1,7 +1,8 @@ // Rust Bitcoin Library - Written by the rust-bitcoin developers. // SPDX-License-Identifier: CC0-1.0 -//! Provides absolute locktime. +//! Provides absolute and relative locktimes. //! pub mod absolute; +pub mod relative; diff --git a/src/blockdata/locktime/relative.rs b/src/blockdata/locktime/relative.rs new file mode 100644 index 00000000..7502c969 --- /dev/null +++ b/src/blockdata/locktime/relative.rs @@ -0,0 +1,289 @@ +// Rust Bitcoin Library - Written by the rust-bitcoin developers. +// 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. +//! + +use core::fmt; +use core::convert::TryFrom; + +#[cfg(docsrs)] +use crate::relative; + +/// 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. +/// +/// ### 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) +#[allow(clippy::derive_ord_xor_partial_ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub enum LockTime { + /// A block height lock time value. + Blocks(Height), + /// A 512 second time interval value. + Time(Time), +} + +impl LockTime { + /// Returns true if this [`relative::LockTime`] is satisfied by either height or time. + /// + /// # Examples + /// + /// ```rust + /// # use bitcoin::Sequence; + /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// + /// # let height = 100; // 100 blocks. + /// # let intervals = 70; // Approx 10 hours. + /// # let current_height = || Height::from(height + 10); + /// # let current_time = || Time::from_512_second_intervals(intervals + 10); + /// # let lock = Sequence::from_height(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. + /// let height_and_time = (current_time(), current_height()); // tuple order does not matter. + /// assert!(lock.is_satisfied_by(current_height(), current_time())); + /// ``` + pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool { + if let Ok(true) = self.is_satisfied_by_height(h) { + true + } else if let Ok(true) = self.is_satisfied_by_time(t) { + true + } else { + false + } + } + + /// Returns true if this [`relative::LockTime`] is satisfied by `other` lock. + /// + /// 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 [`LockTime`] is satisfied by the newly created lock. + /// + /// # Examples + /// + /// ```rust + /// # use bitcoin::Sequence; + /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// + /// # let height = 100; // 100 blocks. + /// # let lock = Sequence::from_height(height).to_relative_lock_time().expect("valid height"); + /// # let test_sequence = Sequence::from_height(height + 10); + /// + /// let satisfied = match test_sequence.to_relative_lock_time() { + /// None => false, // Handle non-lock-time case. + /// Some(test_lock) => lock.is_satisfied_by_lock(test_lock), + /// }; + /// assert!(satisfied); + /// ``` + pub fn is_satisfied_by_lock(&self, other: LockTime) -> bool { + use LockTime::*; + + match (*self, other) { + (Blocks(n), Blocks(m)) => n.value() <= m.value(), + (Time(n), Time(m)) => n.value() <= m.value(), + _ => false, // Not the same units. + } + } + + /// 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::Sequence; + /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// + /// let height: u16 = 100; + /// let lock = Sequence::from_height(height).to_relative_lock_time().expect("valid height"); + /// assert!(lock.is_satisfied_by_height(Height::from(height+1)).expect("a height")); + /// ``` + #[inline] + pub fn is_satisfied_by_height(&self, h: Height) -> Result { + use LockTime::*; + + match *self { + Blocks(ref height) => Ok(height.value() <= h.value()), + Time(ref time) => Err(Error::IncompatibleTime(*self, *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::Sequence; + /// # use bitcoin::locktime::relative::{LockTime, Height, 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] + pub fn is_satisfied_by_time(&self, t: Time) -> Result { + use LockTime::*; + + match *self { + Time(ref time) => Ok(time.value() <= t.value()), + Blocks(ref height) => Err(Error::IncompatibleHeight(*self, *height)), + } + } +} + +impl From for LockTime { + #[inline] + fn from(h: Height) -> Self { + LockTime::Blocks(h) + } +} + +impl From