Merge rust-bitcoin/rust-bitcoin#4468: Improve lock times - fix off-by-one bug

4ccecf5dec Fix stale Height type link (Tobin C. Harding)
caebb1bf73 units: relative: Do minor rustdocs fixes (Tobin C. Harding)
40bb177bc2 Put is_satisfied_by functions together (Tobin C. Harding)
480a2cd62a Favour new function `from_mtp` over deprecated (Tobin C. Harding)
f9d6453d5b Shorten locktime type term (Tobin C. Harding)
727047bd39 Fix off-by-one-bug in absolute locktime (Tobin C. Harding)
3ffdc54ca5 Fix off-by-one bug in relative locktime (Tobin C. Harding)
a2ff8ddbbb Improve relative::LockTime is_satisfied_by_{height, time} (Tobin C. Harding)

Pull request description:

  Make the APIs uniform in relative and absolute locktimes in relation to the `is_satisfied_by` functions. In doing so improve the API and fix an off-by-one bug when checking satisfaction of locks by height.

  Done in three patches but maybe should be squashed? Probably easiest to review by looking at all the `is_satisfied_by*` functions and convincing yourself we got it right.

  EDIT: Now has 5 cleanup patches also (mostly docs cleanups).

ACKs for top commit:
  apoelstra:
    ACK 4ccecf5decfead9818b74fbdee73115c349e2f3e; successfully ran local tests

Tree-SHA512: 9206cb464a06647510a35a7d564062823117e75df60251969be458616f4f5d04acf0aada53dbf7d493a2a2a72d26b3a300417a6499e45413d5f2a011538b7826
This commit is contained in:
merge-script 2025-05-31 15:48:29 +00:00
commit 6d8299e8b8
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
9 changed files with 425 additions and 287 deletions

View File

@ -32,7 +32,7 @@ pub mod locktime {
pub mod absolute { pub mod absolute {
//! Provides type [`LockTime`] that implements the logic around nLockTime/OP_CHECKLOCKTIMEVERIFY. //! Provides type [`LockTime`] that implements the logic around nLockTime/OP_CHECKLOCKTIMEVERIFY.
//! //!
//! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by
//! whether `LockTime < LOCKTIME_THRESHOLD`. //! whether `LockTime < LOCKTIME_THRESHOLD`.
use io::{BufRead, Write}; use io::{BufRead, Write};
@ -66,12 +66,12 @@ pub mod locktime {
pub mod relative { pub mod relative {
//! Provides type [`LockTime`] that implements the logic around nSequence/OP_CHECKSEQUENCEVERIFY. //! 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 //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by
//! whether bit 22 of the `u32` consensus value is set. //! whether bit 22 of the `u32` consensus value is set.
/// Re-export everything from the `primitives::locktime::relative` module. /// Re-export everything from the `primitives::locktime::relative` module.
pub use primitives::locktime::relative::{ pub use primitives::locktime::relative::{
DisabledLockTimeError, IncompatibleHeightError, IncompatibleTimeError, LockTime, DisabledLockTimeError, InvalidHeightError, InvalidTimeError, LockTime,
NumberOf512Seconds, NumberOfBlocks, TimeOverflowError, NumberOf512Seconds, NumberOfBlocks, TimeOverflowError,
}; };

View File

@ -63,7 +63,7 @@ fn serde_regression_absolute_lock_time_height() {
#[test] #[test]
fn serde_regression_absolute_lock_time_time() { fn serde_regression_absolute_lock_time_time() {
let seconds: u32 = 1653195600; // May 22nd, 5am UTC. let seconds: u32 = 1653195600; // May 22nd, 5am UTC.
let t = absolute::LockTime::from_time(seconds).expect("valid time"); let t = absolute::LockTime::from_mtp(seconds).expect("valid time");
let got = serialize(&t).unwrap(); let got = serialize(&t).unwrap();
let want = include_bytes!("data/serde/absolute_lock_time_seconds_bincode") as &[_]; let want = include_bytes!("data/serde/absolute_lock_time_seconds_bincode") as &[_];

View File

@ -2,7 +2,7 @@
//! Provides type [`LockTime`] that implements the logic around `nLockTime`/`OP_CHECKLOCKTIMEVERIFY`. //! Provides type [`LockTime`] that implements the logic around `nLockTime`/`OP_CHECKLOCKTIMEVERIFY`.
//! //!
//! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by
//! whether `LockTime < LOCKTIME_THRESHOLD`. //! whether `LockTime < LOCKTIME_THRESHOLD`.
use core::fmt; use core::fmt;
@ -79,7 +79,7 @@ pub enum LockTime {
/// use bitcoin_primitives::absolute; /// use bitcoin_primitives::absolute;
/// ///
/// let seconds: u32 = 1653195600; // May 22nd, 5am UTC. /// let seconds: u32 = 1653195600; // May 22nd, 5am UTC.
/// let n = absolute::LockTime::from_time(seconds).expect("valid time"); /// let n = absolute::LockTime::from_mtp(seconds).expect("valid time");
/// assert!(n.is_block_time()); /// assert!(n.is_block_time());
/// assert_eq!(n.to_consensus_u32(), seconds); /// assert_eq!(n.to_consensus_u32(), seconds);
/// ``` /// ```
@ -197,8 +197,8 @@ impl LockTime {
/// ///
/// ```rust /// ```rust
/// # use bitcoin_primitives::absolute; /// # use bitcoin_primitives::absolute;
/// assert!(absolute::LockTime::from_time(1653195600).is_ok()); /// assert!(absolute::LockTime::from_mtp(1653195600).is_ok());
/// assert!(absolute::LockTime::from_time(741521).is_err()); /// assert!(absolute::LockTime::from_mtp(741521).is_err());
/// ``` /// ```
#[inline] #[inline]
pub fn from_mtp(n: u32) -> Result<Self, ConversionError> { pub fn from_mtp(n: u32) -> Result<Self, ConversionError> {
@ -233,13 +233,21 @@ impl LockTime {
/// is satisfied if a transaction with `nLockTime` ([`Transaction::lock_time`]) set to /// is satisfied if a transaction with `nLockTime` ([`Transaction::lock_time`]) set to
/// `height`/`time` is valid. /// `height`/`time` is valid.
/// ///
/// If `height` and `mtp` represent the current chain tip then a transaction with this
/// locktime can be broadcast for inclusion in the next block.
///
/// If you do not have, or do not wish to calculate, both parameters consider using:
///
/// * [`is_satisfied_by_height()`](absolute::LockTime::is_satisfied_by_height)
/// * [`is_satisfied_by_time()`](absolute::LockTime::is_satisfied_by_time)
///
/// # Examples /// # Examples
/// ///
/// ```no_run /// ```no_run
/// # use bitcoin_primitives::absolute; /// # use bitcoin_primitives::absolute;
/// // Can be implemented if block chain data is available. /// // Can be implemented if block chain data is available.
/// fn get_height() -> absolute::Height { todo!("return the current block height") } /// fn get_height() -> absolute::Height { todo!("return the current block height") }
/// fn get_time() -> absolute::MedianTimePast { todo!("return the current block time") } /// fn get_time() -> absolute::MedianTimePast { todo!("return the current block MTP") }
/// ///
/// let n = absolute::LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`. /// let n = absolute::LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`.
/// if n.is_satisfied_by(get_height(), get_time()) { /// if n.is_satisfied_by(get_height(), get_time()) {
@ -247,12 +255,43 @@ impl LockTime {
/// } /// }
/// ```` /// ````
#[inline] #[inline]
pub fn is_satisfied_by(self, height: Height, time: MedianTimePast) -> bool { pub fn is_satisfied_by(self, height: Height, mtp: MedianTimePast) -> bool {
match self {
LockTime::Blocks(blocks) => blocks.is_satisfied_by(height),
LockTime::Seconds(time) => time.is_satisfied_by(mtp),
}
}
/// Returns true if a transaction with this locktime can be spent in the next block.
///
/// If `height` is the current block height of the chain then a transaction with this locktime
/// can be broadcast for inclusion in the next block.
///
/// # Errors
///
/// Returns an error if this lock is not lock-by-height.
#[inline]
pub fn is_satisfied_by_height(self, height: Height) -> Result<bool, IncompatibleHeightError> {
use LockTime as L; use LockTime as L;
match self { match self {
L::Blocks(n) => n <= height, L::Blocks(blocks) => Ok(blocks.is_satisfied_by(height)),
L::Seconds(n) => n <= time, L::Seconds(time) => Err(IncompatibleHeightError { lock: time, incompatible: height }),
}
}
/// Returns true if a transaction with this locktime can be included in the next block.
///
/// # Errors
///
/// Returns an error if this lock is not lock-by-time.
#[inline]
pub fn is_satisfied_by_time(self, mtp: MedianTimePast) -> Result<bool, IncompatibleTimeError> {
use LockTime as L;
match self {
L::Seconds(time) => Ok(time.is_satisfied_by(mtp)),
L::Blocks(blocks) => Err(IncompatibleTimeError { lock: blocks, incompatible: mtp }),
} }
} }
@ -407,6 +446,68 @@ impl<'de> serde::Deserialize<'de> for LockTime {
} }
} }
/// Tried to satisfy a lock-by-time lock using a height value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleHeightError {
/// The inner value of the lock-by-time lock.
lock: MedianTimePast,
/// Attempted to satisfy a lock-by-time lock with this height.
incompatible: Height,
}
impl IncompatibleHeightError {
/// Returns the value of the lock-by-time lock.
pub fn lock(&self) -> MedianTimePast { self.lock }
/// Returns the height that was erroneously used to try and satisfy a lock-by-time lock.
pub fn incompatible(&self) -> Height { self.incompatible }
}
impl fmt::Display for IncompatibleHeightError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-time lock {} with height: {}",
self.lock, self.incompatible
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleHeightError {}
/// Tried to satisfy a lock-by-height lock using a height value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleTimeError {
/// The inner value of the lock-by-height lock.
lock: Height,
/// Attempted to satisfy a lock-by-height lock with this MTP.
incompatible: MedianTimePast,
}
impl IncompatibleTimeError {
/// Returns the value of the lock-by-height lock.
pub fn lock(&self) -> Height { self.lock }
/// Returns the MTP that was erroneously used to try and satisfy a lock-by-height lock.
pub fn incompatible(&self) -> MedianTimePast { self.incompatible }
}
impl fmt::Display for IncompatibleTimeError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-height lock {} with MTP: {}",
self.lock, self.incompatible
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleTimeError {}
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for LockTime { impl<'a> Arbitrary<'a> for LockTime {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {

View File

@ -2,18 +2,20 @@
//! Provides type [`LockTime`] that implements the logic around `nSequence`/`OP_CHECKSEQUENCEVERIFY`. //! 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 //! There are two types of lock time: lock-by-height and lock-by-time, distinguished by
//! whether bit 22 of the `u32` consensus value is set. //! whether bit 22 of the `u32` consensus value is set.
use core::{convert, fmt}; use core::{convert, fmt};
use internals::write_err;
use crate::Sequence; use crate::Sequence;
#[cfg(all(doc, feature = "alloc"))] #[cfg(all(doc, feature = "alloc"))]
use crate::{relative, TxIn}; use crate::{relative, TxIn};
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)] #[doc(inline)]
pub use units::locktime::relative::{NumberOfBlocks, NumberOf512Seconds, TimeOverflowError}; pub use units::locktime::relative::{NumberOfBlocks, NumberOf512Seconds, TimeOverflowError, InvalidHeightError, InvalidTimeError};
use units::{BlockHeight, BlockMtp}; use units::{BlockHeight, BlockMtp};
#[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")] #[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")]
@ -39,40 +41,6 @@ pub type Time = NumberOf512Seconds;
/// ///
/// * [BIP 68 Relative lock-time using consensus-enforced sequence numbers](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki) /// * [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) /// * [BIP 112 CHECKSEQUENCEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki)
///
/// # Examples
///
/// ```
/// use bitcoin_primitives::relative;
/// use bitcoin_primitives::{BlockHeight, BlockMtp, BlockTime};
/// let lock_by_height = relative::LockTime::from_height(144); // 144 blocks, approx 24h.
/// assert!(lock_by_height.is_block_height());
///
/// let lock_by_time = relative::LockTime::from_512_second_intervals(168); // 168 time intervals, approx 24h.
/// assert!(lock_by_time.is_block_time());
///
/// 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
/// }
/// // time extracted from BlockHeader
/// let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
/// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
///
/// let current_height = BlockHeight::from(100);
/// let current_mtp = BlockMtp::new(timestamps);
///
/// let utxo_height = BlockHeight::from(80);
/// let utxo_mtp = BlockMtp::new(utxo_timestamps);
///
/// let locktime = relative::LockTime::Time(relative::NumberOf512Seconds::from_512_second_intervals(10));
///
/// // Check if locktime is satisfied
/// assert!(locktime.is_satisfied_by(current_height, current_mtp, utxo_height, utxo_mtp));
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LockTime { pub enum LockTime {
@ -221,45 +189,75 @@ impl LockTime {
pub const 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 [`relative::LockTime`] is satisfied by the given chain state. /// Returns true if this [`relative::LockTime`] is satisfied by the given chain state.
/// # Examples
/// ///
/// ```rust /// If this function returns true then an output with this locktime can be spent in the next
/// # use bitcoin_primitives::relative::Time; /// block.
/// # use bitcoin_primitives::{BlockHeight, BlockMtp, BlockTime};
/// # use bitcoin_primitives::relative::LockTime;
/// ///
/// fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] { /// # Errors
/// 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
/// }
/// // time extracted from BlockHeader
/// let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
/// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
/// ///
/// let current_height = BlockHeight::from_u32(100); /// If `chain_tip` as not _after_ `utxo_mined_at` i.e., if you get the args mixed up.
/// let current_mtp = BlockMtp::new(timestamps);
/// let utxo_height = BlockHeight::from_u32(80);
/// let utxo_mtp = BlockMtp::new(utxo_timestamps);
///
/// let locktime = LockTime::Time(Time::from_512_second_intervals(10));
///
/// // Check if locktime is satisfied
/// assert!(locktime.is_satisfied_by(current_height, current_mtp, utxo_height, utxo_mtp));
/// ```
pub fn is_satisfied_by( pub fn is_satisfied_by(
self, self,
chain_tip_height: BlockHeight, chain_tip_height: BlockHeight,
chain_tip_mtp: BlockMtp, chain_tip_mtp: BlockMtp,
utxo_mined_at_height: BlockHeight, utxo_mined_at_height: BlockHeight,
utxo_mined_at_mtp: BlockMtp, utxo_mined_at_mtp: BlockMtp,
) -> bool { ) -> Result<bool, IsSatisfiedByError> {
match self { match self {
LockTime::Blocks(blocks) => LockTime::Blocks(blocks) => blocks
blocks.is_satisfied_by(chain_tip_height, utxo_mined_at_height), .is_satisfied_by(chain_tip_height, utxo_mined_at_height)
LockTime::Time(time) => time.is_satisfied_by(chain_tip_mtp, utxo_mined_at_mtp), .map_err(IsSatisfiedByError::Blocks),
LockTime::Time(time) => time
.is_satisfied_by(chain_tip_mtp, utxo_mined_at_mtp)
.map_err(IsSatisfiedByError::Time),
}
}
/// Returns true if an output with this locktime can be spent in the next block.
///
/// If this function returns true then an output with this locktime can be spent in the next
/// block.
///
/// # Errors
///
/// Returns an error if this lock is not lock-by-height.
#[inline]
pub fn is_satisfied_by_height(
self,
chain_tip: BlockHeight,
utxo_mined_at: BlockHeight,
) -> Result<bool, IsSatisfiedByHeightError> {
use LockTime as L;
match self {
L::Blocks(blocks) => blocks
.is_satisfied_by(chain_tip, utxo_mined_at)
.map_err(IsSatisfiedByHeightError::Satisfaction),
L::Time(time) => Err(IsSatisfiedByHeightError::Incompatible(time)),
}
}
/// Returns true if an output with this locktime can be spent in the next block.
///
/// If this function returns true then an output with this locktime can be spent in the next
/// block.
///
/// # Errors
///
/// Returns an error if this lock is not lock-by-time.
#[inline]
pub fn is_satisfied_by_time(
self,
chain_tip: BlockMtp,
utxo_mined_at: BlockMtp,
) -> Result<bool, IsSatisfiedByTimeError> {
use LockTime as L;
match self {
L::Time(time) => time
.is_satisfied_by(chain_tip, utxo_mined_at)
.map_err(IsSatisfiedByTimeError::Satisfaction),
L::Blocks(blocks) => Err(IsSatisfiedByTimeError::Incompatible(blocks)),
} }
} }
@ -331,64 +329,6 @@ impl LockTime {
false false
} }
} }
/// Returns true if this [`relative::LockTime`] is satisfied by [`NumberOfBlocks`].
///
/// # Errors
///
/// Returns an error if this lock is not lock-by-height.
///
/// # Examples
///
/// ```rust
/// # use bitcoin_primitives::Sequence;
/// # use bitcoin_primitives::relative;
///
/// 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(relative::NumberOfBlocks::from(required_height + 1)).expect("a height"));
/// ```
#[inline]
pub fn is_satisfied_by_height(
self,
height: NumberOfBlocks,
) -> Result<bool, IncompatibleHeightError> {
use LockTime as L;
match self {
L::Blocks(required_height) => Ok(required_height <= height),
L::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::relative;
///
/// 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(relative::Time::from_512_second_intervals(intervals + 10)).expect("a time"));
/// ```
#[inline]
pub fn is_satisfied_by_time(
self,
time: NumberOf512Seconds,
) -> Result<bool, IncompatibleTimeError> {
use LockTime as L;
match self {
L::Time(ref t) => Ok(t.to_512_second_intervals() <= time.to_512_second_intervals()),
L::Blocks(height) => Err(IncompatibleTimeError { time, height }),
}
}
} }
impl From<NumberOfBlocks> for LockTime { impl From<NumberOfBlocks> for LockTime {
@ -454,67 +394,108 @@ impl fmt::Display for DisabledLockTimeError {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for DisabledLockTimeError {} impl std::error::Error for DisabledLockTimeError {}
/// Tried to satisfy a lock-by-blocktime lock using a height value. /// Error returned when attempting to satisfy lock fails.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct IncompatibleHeightError { pub enum IsSatisfiedByError {
/// Attempted to satisfy a lock-by-blocktime lock with this height. /// Error when attempting to satisfy lock by height.
height: NumberOfBlocks, Blocks(InvalidHeightError),
/// The inner time value of the lock-by-blocktime lock. /// Error when attempting to satisfy lock by time.
time: NumberOf512Seconds, Time(InvalidTimeError),
} }
impl IncompatibleHeightError { impl fmt::Display for IsSatisfiedByError {
/// Returns the height that was erroneously used to try and satisfy a lock-by-blocktime lock.
pub fn incompatible(&self) -> NumberOfBlocks { self.height }
/// Returns the time value of the lock-by-blocktime lock.
pub fn expected(&self) -> NumberOf512Seconds { self.time }
}
impl fmt::Display for IncompatibleHeightError {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( use IsSatisfiedByError as E;
f,
"tried to satisfy a lock-by-blocktime lock {} with height: {}", match *self {
self.time, self.height E::Blocks(ref e) => write_err!(f, "blocks"; e),
) E::Time(ref e) => write_err!(f, "time"; e),
}
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for IncompatibleHeightError {} impl std::error::Error for IsSatisfiedByError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use IsSatisfiedByError as E;
/// Tried to satisfy a lock-by-blockheight lock using a time value. match *self {
E::Blocks(ref e) => Some(e),
E::Time(ref e) => Some(e),
}
}
}
/// Error returned when `is_satisfied_by_height` fails.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleTimeError { pub enum IsSatisfiedByHeightError {
/// Attempted to satisfy a lock-by-blockheight lock with this time. /// Satisfaction of the lock height value failed.
time: NumberOf512Seconds, Satisfaction(InvalidHeightError),
/// The inner height value of the lock-by-blockheight lock. /// Tried to satisfy a lock-by-height locktime using seconds.
height: NumberOfBlocks, // TODO: Hide inner value in a new struct error type.
Incompatible(NumberOf512Seconds),
} }
impl IncompatibleTimeError { impl fmt::Display for IsSatisfiedByHeightError {
/// Returns the time that was erroneously used to try and satisfy a lock-by-blockheight lock.
pub fn incompatible(&self) -> NumberOf512Seconds { self.time }
/// Returns the height value of the lock-by-blockheight lock.
pub fn expected(&self) -> NumberOfBlocks { self.height }
}
impl fmt::Display for IncompatibleTimeError {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( use IsSatisfiedByHeightError as E;
f,
"tried to satisfy a lock-by-blockheight lock {} with time: {}", match *self {
self.height, self.time E::Satisfaction(ref e) => write_err!(f, "satisfaction"; e),
) E::Incompatible(time) =>
write!(f, "tried to satisfy a lock-by-height locktime using seconds {}", time),
}
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for IncompatibleTimeError {} impl std::error::Error for IsSatisfiedByHeightError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use IsSatisfiedByHeightError as E;
match *self {
E::Satisfaction(ref e) => Some(e),
E::Incompatible(_) => None,
}
}
}
/// Error returned when `is_satisfied_by_time` fails.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IsSatisfiedByTimeError {
/// Satisfaction of the lock time value failed.
Satisfaction(InvalidTimeError),
/// Tried to satisfy a lock-by-time locktime using number of blocks.
// TODO: Hide inner value in a new struct error type.
Incompatible(NumberOfBlocks),
}
impl fmt::Display for IsSatisfiedByTimeError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use IsSatisfiedByTimeError as E;
match *self {
E::Satisfaction(ref e) => write_err!(f, "satisfaction"; e),
E::Incompatible(blocks) =>
write!(f, "tried to satisfy a lock-by-height locktime using blocks {}", blocks),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for IsSatisfiedByTimeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use IsSatisfiedByTimeError as E;
match *self {
E::Satisfaction(ref e) => Some(e),
E::Incompatible(_) => None,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -664,25 +645,29 @@ mod tests {
#[test] #[test]
fn incompatible_height_error() { fn incompatible_height_error() {
let height = NumberOfBlocks::from(10); // This is an error test these values are not used in the error path.
let time = NumberOf512Seconds::from_512_second_intervals(70); let mined_at = BlockHeight::from_u32(700_000);
let lock_by_time = LockTime::from(time); let chain_tip = BlockHeight::from_u32(800_000);
let err = lock_by_time.is_satisfied_by_height(height).unwrap_err();
assert_eq!(err.incompatible(), height); let lock_by_time = LockTime::from_512_second_intervals(70); // Arbitrary value.
assert_eq!(err.expected(), time); let err = lock_by_time.is_satisfied_by_height(chain_tip, mined_at).unwrap_err();
let expected_time = NumberOf512Seconds::from_512_second_intervals(70);
assert_eq!(err, IsSatisfiedByHeightError::Incompatible(expected_time));
assert!(!format!("{}", err).is_empty()); assert!(!format!("{}", err).is_empty());
} }
#[test] #[test]
fn incompatible_time_error() { fn incompatible_time_error() {
let height = NumberOfBlocks::from(10); // This is an error test these values are not used in the error path.
let time = NumberOf512Seconds::from_512_second_intervals(70); let mined_at = BlockMtp::from_u32(1_234_567_890);
let lock_by_height = LockTime::from(height); let chain_tip = BlockMtp::from_u32(1_600_000_000);
let err = lock_by_height.is_satisfied_by_time(time).unwrap_err();
assert_eq!(err.incompatible(), time); let lock_by_height = LockTime::from_height(10); // Arbitrary value.
assert_eq!(err.expected(), height); let err = lock_by_height.is_satisfied_by_time(chain_tip, mined_at).unwrap_err();
let expected_height = NumberOfBlocks::from(10);
assert_eq!(err, IsSatisfiedByTimeError::Incompatible(expected_height));
assert!(!format!("{}", err).is_empty()); assert!(!format!("{}", err).is_empty());
} }
@ -705,49 +690,46 @@ mod tests {
let utxo_mtp = BlockMtp::new(utxo_timestamps); let utxo_mtp = BlockMtp::new(utxo_timestamps);
let lock1 = LockTime::Blocks(NumberOfBlocks::from(10)); let lock1 = LockTime::Blocks(NumberOfBlocks::from(10));
assert!(lock1.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(lock1.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp).unwrap());
let lock2 = LockTime::Blocks(NumberOfBlocks::from(21)); let lock2 = LockTime::Blocks(NumberOfBlocks::from(21));
assert!(!lock2.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(lock2.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp).unwrap());
let lock3 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(10)); let lock3 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(10));
assert!(lock3.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(lock3.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp).unwrap());
let lock4 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(20000)); let lock4 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(20000));
assert!(!lock4.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(!lock4.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp).unwrap());
assert!(LockTime::ZERO.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(LockTime::ZERO
assert!(LockTime::from_512_second_intervals(0).is_satisfied_by( .is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)
chain_height, .unwrap());
chain_mtp, assert!(LockTime::from_512_second_intervals(0)
utxo_height, .is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)
utxo_mtp .unwrap());
));
let lock6 = LockTime::from_seconds_floor(5000).unwrap(); let lock6 = LockTime::from_seconds_floor(5000).unwrap();
assert!(lock6.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(lock6.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp).unwrap());
let max_height_lock = LockTime::Blocks(NumberOfBlocks::MAX); let max_height_lock = LockTime::Blocks(NumberOfBlocks::MAX);
assert!(!max_height_lock.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(!max_height_lock
.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)
.unwrap());
let max_time_lock = LockTime::Time(NumberOf512Seconds::MAX); let max_time_lock = LockTime::Time(NumberOf512Seconds::MAX);
assert!(!max_time_lock.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)); assert!(!max_time_lock
.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp)
.unwrap());
let max_chain_height = BlockHeight::from_u32(u32::MAX); let max_chain_height = BlockHeight::from_u32(u32::MAX);
let max_chain_mtp = BlockMtp::new(generate_timestamps(u32::MAX, 100)); let max_chain_mtp = BlockMtp::new(generate_timestamps(u32::MAX, 100));
let max_utxo_height = BlockHeight::MAX; let max_utxo_height = BlockHeight::MAX;
let max_utxo_mtp = max_chain_mtp; let max_utxo_mtp = max_chain_mtp;
assert!(!max_height_lock.is_satisfied_by( assert!(!max_height_lock
max_chain_height, .is_satisfied_by(max_chain_height, max_chain_mtp, max_utxo_height, max_utxo_mtp)
max_chain_mtp, .unwrap());
max_utxo_height, assert!(!max_time_lock
max_utxo_mtp .is_satisfied_by(max_chain_height, max_chain_mtp, max_utxo_height, max_utxo_mtp)
)); .unwrap());
assert!(!max_time_lock.is_satisfied_by(
max_chain_height,
max_chain_mtp,
max_utxo_height,
max_utxo_mtp
));
} }
} }

View File

@ -163,14 +163,12 @@ struct Default {
#[derive(Debug, Clone, PartialEq, Eq)] // All public types implement Debug (C-DEBUG). #[derive(Debug, Clone, PartialEq, Eq)] // All public types implement Debug (C-DEBUG).
struct Errors { struct Errors {
a: transaction::ParseOutPointError, a: transaction::ParseOutPointError,
b: relative::IncompatibleHeightError, b: relative::DisabledLockTimeError,
c: relative::IncompatibleTimeError, c: relative::IsSatisfiedByError,
d: relative::IncompatibleHeightError, d: relative::IsSatisfiedByHeightError,
e: relative::IncompatibleTimeError, e: relative::IsSatisfiedByTimeError,
f: relative::DisabledLockTimeError, f: script::RedeemScriptSizeError,
g: relative::DisabledLockTimeError, g: script::WitnessScriptSizeError,
h: script::RedeemScriptSizeError,
i: script::WitnessScriptSizeError,
} }
#[test] #[test]
@ -212,7 +210,7 @@ fn api_can_use_types_from_crate_root() {
#[test] #[test]
fn api_can_use_all_types_from_module_locktime() { fn api_can_use_all_types_from_module_locktime() {
use bitcoin_primitives::locktime::relative::{ use bitcoin_primitives::locktime::relative::{
DisabledLockTimeError, IncompatibleHeightError, IncompatibleTimeError, LockTime, DisabledLockTimeError, InvalidHeightError, InvalidTimeError, LockTime,
}; };
use bitcoin_primitives::locktime::{absolute, relative}; use bitcoin_primitives::locktime::{absolute, relative};
} }

View File

@ -124,7 +124,7 @@ impl_u32_wrapper! {
/// Block interval is an integer type representing a difference between the heights of two blocks. /// Block interval is an integer type representing a difference between the heights of two blocks.
/// ///
/// This type is not meant for constructing relative height based timelocks. It is a general /// This type is not meant for constructing relative height based timelocks. It is a general
/// purpose block interval abstraction. For locktimes please see [`locktime::relative::Height`]. /// purpose block interval abstraction. For locktimes please see [`locktime::relative::NumberOfBlocks`].
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BlockHeightInterval(u32); pub struct BlockHeightInterval(u32);

View File

@ -83,6 +83,17 @@ impl Height {
/// Converts this [`Height`] to a raw `u32` value. /// Converts this [`Height`] to a raw `u32` value.
#[inline] #[inline]
pub const fn to_u32(self) -> u32 { self.0 } 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 { impl fmt::Display for Height {
@ -217,6 +228,18 @@ impl MedianTimePast {
/// Converts this [`MedianTimePast`] to a raw `u32` value. /// Converts this [`MedianTimePast`] to a raw `u32` value.
#[inline] #[inline]
pub const fn to_u32(self) -> u32 { self.0 } 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 { impl fmt::Display for MedianTimePast {
@ -326,7 +349,7 @@ impl std::error::Error for ConversionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
} }
/// Describes the two types of locking, lock-by-blockheight and lock-by-blocktime. /// Describes the two types of locking, lock-by-height and lock-by-time.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit { enum LockTimeUnit {
/// Lock by blockheight. /// Lock by blockheight.
@ -341,9 +364,9 @@ impl fmt::Display for LockTimeUnit {
match *self { match *self {
L::Blocks => L::Blocks =>
write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD), write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
L::Seconds => L::Seconds =>
write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD), write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
} }
} }
} }
@ -553,8 +576,8 @@ mod tests {
let blocks = LockTimeUnit::Blocks; let blocks = LockTimeUnit::Blocks;
let seconds = LockTimeUnit::Seconds; let seconds = LockTimeUnit::Seconds;
assert_eq!(format!("{}", blocks), "expected lock-by-blockheight (must be < 500000000)"); assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
assert_eq!(format!("{}", seconds), "expected lock-by-blocktime (must be >= 500000000)"); assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
} }
#[test] #[test]

View File

@ -14,7 +14,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[doc(hidden)] #[doc(hidden)]
pub type Height = NumberOfBlocks; pub type Height = NumberOfBlocks;
/// A relative lock time lock-by-blockheight value. /// A relative lock time lock-by-height value.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumberOfBlocks(u16); pub struct NumberOfBlocks(u16);
@ -54,29 +54,27 @@ impl NumberOfBlocks {
self.0 as u32 // cast safety: u32 is wider than u16 on all architectures self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
} }
/// Determines whether a relativeheight locktime has matured, taking into account /// Returns true if an output locked by height can be spent in the next block.
/// 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` /// # Errors
/// is satisfied by `y`, use `x >= y`.
/// ///
/// # Parameters /// If `chain_tip` as not _after_ `utxo_mined_at` i.e., if you get the args mixed up.
/// - `self` the relative blockheight delay (`h`) required after confirmation.
/// - `chain_tip` the height of the current chain tip
/// - `utxo_mined_at` the height of the UTXOs 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( pub fn is_satisfied_by(
self, self,
chain_tip: crate::BlockHeight, chain_tip: crate::BlockHeight,
utxo_mined_at: crate::BlockHeight, utxo_mined_at: crate::BlockHeight,
) -> bool { ) -> Result<bool, InvalidHeightError> {
chain_tip match chain_tip.checked_sub(utxo_mined_at) {
.checked_sub(utxo_mined_at) Some(diff) => {
.and_then(|diff: crate::BlockHeightInterval| diff.try_into().ok()) if diff.to_u32() == u32::MAX {
.map_or(false, |diff: Self| diff >= self) // Weird but ok none the less - protects against overflow below.
return Ok(true);
}
// +1 because the next block will have height 1 higher than `chain_tip`.
Ok(u32::from(self.to_height()) <= diff.to_u32() + 1)
}
None => Err(InvalidHeightError { chain_tip, utxo_mined_at }),
}
} }
} }
@ -117,9 +115,9 @@ impl<'de> Deserialize<'de> for NumberOfBlocks {
#[doc(hidden)] #[doc(hidden)]
pub type Time = NumberOf512Seconds; pub type Time = NumberOf512Seconds;
/// A relative lock time lock-by-blocktime value. /// A relative lock time lock-by-time value.
/// ///
/// For BIP 68 relative lock-by-blocktime locks, time is measured in 512 second intervals. /// For BIP 68 relative lock-by-time locks, time is measured in 512 second intervals.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumberOf512Seconds(u16); pub struct NumberOf512Seconds(u16);
@ -133,7 +131,8 @@ impl NumberOf512Seconds {
/// The maximum relative block time (33,554,432 seconds or approx 388 days). /// The maximum relative block time (33,554,432 seconds or approx 388 days).
pub const MAX: Self = NumberOf512Seconds(u16::MAX); pub const MAX: Self = NumberOf512Seconds(u16::MAX);
/// Constructs a new [`NumberOf512Seconds`] using time intervals where each interval is equivalent to 512 seconds. /// 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. /// Encoding finer granularity of time for relative lock-times is not supported in Bitcoin.
#[inline] #[inline]
@ -144,8 +143,8 @@ impl NumberOf512Seconds {
#[must_use] #[must_use]
pub const fn to_512_second_intervals(self) -> u16 { self.0 } pub const fn to_512_second_intervals(self) -> u16 { self.0 }
/// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second /// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into a 512
/// interval with truncating division. /// second interval using truncating division.
/// ///
/// # Errors /// # Errors
/// ///
@ -161,8 +160,8 @@ impl NumberOf512Seconds {
} }
} }
/// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second /// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into a 512
/// intervals with ceiling division. /// second interval using ceiling division.
/// ///
/// # Errors /// # Errors
/// ///
@ -201,29 +200,24 @@ impl NumberOf512Seconds {
(1u32 << 22) | self.0 as u32 // cast safety: u32 is wider than u16 on all architectures (1u32 << 22) | self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
} }
/// Determines whether a relativetime lock has matured, taking into account both /// Returns true if an output locked by time can be spent in the next block.
/// the UTXOs Median Time Past at confirmation and the required delay.
/// ///
/// If you have two MTP intervals `x` and `y`, and want to know whether `x` /// # Errors
/// is satisfied by `y`, use `x >= y`.
/// ///
/// # Parameters /// If `chain_tip` as not _after_ `utxo_mined_at` i.e., if you get the args mixed up.
/// - `self` the relative time delay (`t`) in 512second intervals.
/// - `chain_tip` the MTP of the current chain tip
/// - `utxo_mined_at` the MTP of the UTXOs confirmation block
///
/// # Returns
/// - `true` if the relativetime lock has expired by the tips MTP
/// - `false` if the lock has not yet expired by the tips MTP
pub fn is_satisfied_by( pub fn is_satisfied_by(
self, self,
chain_tip: crate::BlockMtp, chain_tip: crate::BlockMtp,
utxo_mined_at: crate::BlockMtp, utxo_mined_at: crate::BlockMtp,
) -> bool { ) -> Result<bool, InvalidTimeError> {
chain_tip match chain_tip.checked_sub(utxo_mined_at) {
.checked_sub(utxo_mined_at) Some(diff) => {
.and_then(|diff: crate::BlockMtpInterval| diff.to_relative_mtp_interval_floor().ok()) // The locktime check in Core during block validation uses the MTP of the previous
.map_or(false, |diff: Self| diff >= self) // block - which is `chain_tip` here.
Ok(self.to_seconds() <= diff.to_u32())
}
None => Err(InvalidTimeError { chain_tip, utxo_mined_at }),
}
} }
} }
@ -288,6 +282,44 @@ impl fmt::Display for TimeOverflowError {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl std::error::Error for TimeOverflowError {} impl std::error::Error for TimeOverflowError {}
/// Error returned when `NumberOfBlocks::is_satisfied_by` is incorrectly called.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidHeightError {
/// The `chain_tip` argument.
pub(crate) chain_tip: crate::BlockHeight,
/// The `utxo_mined_at` argument.
pub(crate) utxo_mined_at: crate::BlockHeight,
}
impl fmt::Display for InvalidHeightError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "is_satisfied_by arguments invalid (probably the wrong way around) chain_tip: {} utxo_mined_at: {}", self.chain_tip, self.utxo_mined_at
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidHeightError {}
/// Error returned when `NumberOf512Seconds::is_satisfied_by` is incorrectly called.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidTimeError {
/// The `chain_tip` argument.
pub(crate) chain_tip: crate::BlockMtp,
/// The `utxo_mined_at` argument.
pub(crate) utxo_mined_at: crate::BlockMtp,
}
impl fmt::Display for InvalidTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "is_satisfied_by arguments invalid (probably the wrong way around) chain_tip: {} utxo_mined_at: {}", self.chain_tip, self.utxo_mined_at
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidTimeError {}
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for NumberOfBlocks { impl<'a> Arbitrary<'a> for NumberOfBlocks {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
@ -426,24 +458,24 @@ mod tests {
let time_lock = NumberOf512Seconds::from_512_second_intervals(10); let time_lock = NumberOf512Seconds::from_512_second_intervals(10);
let chain_state1 = BlockMtp::new(timestamps); let chain_state1 = BlockMtp::new(timestamps);
let utxo_state1 = BlockMtp::new(utxo_timestamps); let utxo_state1 = BlockMtp::new(utxo_timestamps);
assert!(time_lock.is_satisfied_by(chain_state1, utxo_state1)); assert!(time_lock.is_satisfied_by(chain_state1, utxo_state1).unwrap());
// Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds) // Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds)
let chain_state2 = BlockMtp::new(timestamps2); let chain_state2 = BlockMtp::new(timestamps2);
let utxo_state2 = BlockMtp::new(utxo_timestamps2); let utxo_state2 = BlockMtp::new(utxo_timestamps2);
assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2)); assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2).unwrap());
// Test case 3: Test with a larger value (100 intervals = 51200 seconds) // Test case 3: Test with a larger value (100 intervals = 51200 seconds)
let larger_lock = NumberOf512Seconds::from_512_second_intervals(100); let larger_lock = NumberOf512Seconds::from_512_second_intervals(100);
let chain_state3 = BlockMtp::new(timestamps3); let chain_state3 = BlockMtp::new(timestamps3);
let utxo_state3 = BlockMtp::new(utxo_timestamps3); let utxo_state3 = BlockMtp::new(utxo_timestamps3);
assert!(larger_lock.is_satisfied_by(chain_state3, utxo_state3)); assert!(larger_lock.is_satisfied_by(chain_state3, utxo_state3).unwrap());
// Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully // Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_time_lock = NumberOf512Seconds::MAX; let max_time_lock = NumberOf512Seconds::MAX;
let chain_state4 = BlockMtp::new(timestamps); let chain_state4 = BlockMtp::new(timestamps);
let utxo_state4 = BlockMtp::new(utxo_timestamps); let utxo_state4 = BlockMtp::new(utxo_timestamps);
assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4)); assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4).unwrap());
} }
#[test] #[test]
@ -453,19 +485,19 @@ mod tests {
let height_lock = NumberOfBlocks(10); let height_lock = NumberOfBlocks(10);
// Test case 1: Satisfaction (current_height >= utxo_height + required) // Test case 1: Satisfaction (current_height >= utxo_height + required)
let chain_state1 = BlockHeight::from_u32(100); let chain_state1 = BlockHeight::from_u32(89);
let utxo_state1 = BlockHeight::from_u32(80); let utxo_state1 = BlockHeight::from_u32(80);
assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1)); assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1).unwrap());
// Test case 2: Not satisfied (current_height < utxo_height + required) // Test case 2: Not satisfied (current_height < utxo_height + required)
let chain_state2 = BlockHeight::from_u32(89); let chain_state2 = BlockHeight::from_u32(88);
let utxo_state2 = BlockHeight::from_u32(80); let utxo_state2 = BlockHeight::from_u32(80);
assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2)); assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2).unwrap());
// Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully // Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_height_lock = NumberOfBlocks::MAX; let max_height_lock = NumberOfBlocks::MAX;
let chain_state3 = BlockHeight::from_u32(1000); let chain_state3 = BlockHeight::from_u32(1000);
let utxo_state3 = BlockHeight::from_u32(80); let utxo_state3 = BlockHeight::from_u32(80);
assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3)); assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3).unwrap());
} }
} }

View File

@ -139,11 +139,13 @@ struct Errors {
x: locktime::absolute::ConversionError, x: locktime::absolute::ConversionError,
y: locktime::absolute::Height, y: locktime::absolute::Height,
z: locktime::absolute::ParseHeightError, z: locktime::absolute::ParseHeightError,
_a: locktime::absolute::ParseTimeError, aa: locktime::absolute::ParseTimeError,
_b: locktime::relative::TimeOverflowError, ab: locktime::relative::TimeOverflowError,
_e: parse::ParseIntError, ac: locktime::relative::InvalidHeightError,
_f: parse::PrefixedHexError, ad: locktime::relative::InvalidTimeError,
_g: parse::UnprefixedHexError, ae: parse::ParseIntError,
af: parse::PrefixedHexError,
ag: parse::UnprefixedHexError,
} }
#[test] #[test]