bitcoin/blockdata/locktime/relative.rs
1// SPDX-License-Identifier: CC0-1.0
2
3//! Provides type [`LockTime`] that implements the logic around nSequence/OP_CHECKSEQUENCEVERIFY.
4//!
5//! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by
6//! whether bit 22 of the `u32` consensus value is set.
7//!
8
9#[cfg(feature = "ordered")]
10use core::cmp::Ordering;
11use core::{cmp, convert, fmt};
12
13#[cfg(all(test, mutate))]
14use mutagen::mutate;
15
16#[cfg(doc)]
17use crate::relative;
18use crate::Sequence;
19
20#[rustfmt::skip] // Keep public re-exports separate.
21#[doc(inline)]
22pub use units::locktime::relative::{Height, Time, TimeOverflowError};
23
24/// A relative lock time value, representing either a block height or time (512 second intervals).
25///
26/// Used for sequence numbers (`nSequence` in Bitcoin Core and [`crate::TxIn::sequence`]
27/// in this library) and also for the argument to opcode 'OP_CHECKSEQUENCEVERIFY`.
28///
29/// ### Note on ordering
30///
31/// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total
32/// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`]. We also
33/// implement [`ordered::ArbitraryOrd`].
34///
35/// [`ordered::ArbitraryOrd`]: <https://docs.rs/ordered/latest/ordered/trait.ArbitraryOrd.html>
36///
37/// ### Relevant BIPs
38///
39/// * [BIP 68 Relative lock-time using consensus-enforced sequence numbers](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki)
40/// * [BIP 112 CHECKSEQUENCEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki)
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
44pub enum LockTime {
45 /// A block height lock time value.
46 Blocks(Height),
47 /// A 512 second time interval value.
48 Time(Time),
49}
50
51impl LockTime {
52 /// A relative locktime of 0 is always valid, and is assumed valid for inputs that
53 /// are not yet confirmed.
54 pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
55
56 /// The number of bytes that the locktime contributes to the size of a transaction.
57 pub const SIZE: usize = 4; // Serialized length of a u32.
58
59 /// Constructs a `LockTime` from an nSequence value or the argument to OP_CHECKSEQUENCEVERIFY.
60 ///
61 /// This method will **not** round-trip with [`Self::to_consensus_u32`], because relative
62 /// locktimes only use some bits of the underlying `u32` value and discard the rest. If
63 /// you want to preserve the full value, you should use the [`Sequence`] type instead.
64 ///
65 /// # Examples
66 ///
67 /// ```rust
68 /// # use bitcoin::relative::LockTime;
69 ///
70 /// // `from_consensus` roundtrips with `to_consensus_u32` for small values.
71 /// let n_lock_time: u32 = 7000;
72 /// let lock_time = LockTime::from_consensus(n_lock_time).unwrap();
73 /// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
74 /// ```
75 pub fn from_consensus(n: u32) -> Result<Self, DisabledLockTimeError> {
76 let sequence = crate::Sequence::from_consensus(n);
77 sequence.to_relative_lock_time().ok_or(DisabledLockTimeError(n))
78 }
79
80 /// Returns the `u32` value used to encode this locktime in an nSequence field or
81 /// argument to `OP_CHECKSEQUENCEVERIFY`.
82 ///
83 /// # Warning
84 ///
85 /// Locktimes are not ordered by the natural ordering on `u32`. If you want to
86 /// compare locktimes, use [`Self::is_implied_by`] or similar methods.
87 #[inline]
88 pub fn to_consensus_u32(&self) -> u32 {
89 match self {
90 LockTime::Blocks(ref h) => h.to_consensus_u32(),
91 LockTime::Time(ref t) => t.to_consensus_u32(),
92 }
93 }
94
95 /// Constructs a `LockTime` from the sequence number of a Bitcoin input.
96 ///
97 /// This method will **not** round-trip with [`Self::to_sequence`]. See the
98 /// docs for [`Self::from_consensus`] for more information.
99 #[inline]
100 pub fn from_sequence(n: Sequence) -> Result<Self, DisabledLockTimeError> {
101 Self::from_consensus(n.to_consensus_u32())
102 }
103
104 /// Encodes the locktime as a sequence number.
105 #[inline]
106 pub fn to_sequence(&self) -> Sequence { Sequence::from_consensus(self.to_consensus_u32()) }
107
108 /// Constructs a `LockTime` from `n`, expecting `n` to be a 16-bit count of blocks.
109 #[inline]
110 pub const fn from_height(n: u16) -> Self { LockTime::Blocks(Height::from_height(n)) }
111
112 /// Constructs a `LockTime` from `n`, expecting `n` to be a count of 512-second intervals.
113 ///
114 /// This function is a little awkward to use, and users may wish to instead use
115 /// [`Self::from_seconds_floor`] or [`Self::from_seconds_ceil`].
116 #[inline]
117 pub const fn from_512_second_intervals(intervals: u16) -> Self {
118 LockTime::Time(Time::from_512_second_intervals(intervals))
119 }
120
121 /// Create a [`LockTime`] from seconds, converting the seconds into 512 second interval
122 /// with truncating division.
123 ///
124 /// # Errors
125 ///
126 /// Will return an error if the input cannot be encoded in 16 bits.
127 #[inline]
128 pub const fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
129 match Time::from_seconds_floor(seconds) {
130 Ok(time) => Ok(LockTime::Time(time)),
131 Err(e) => Err(e),
132 }
133 }
134
135 /// Create a [`LockTime`] from seconds, converting the seconds into 512 second interval
136 /// with ceiling division.
137 ///
138 /// # Errors
139 ///
140 /// Will return an error if the input cannot be encoded in 16 bits.
141 #[inline]
142 pub const fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
143 match Time::from_seconds_ceil(seconds) {
144 Ok(time) => Ok(LockTime::Time(time)),
145 Err(e) => Err(e),
146 }
147 }
148
149 /// Returns true if both lock times use the same unit i.e., both height based or both time based.
150 #[inline]
151 pub const fn is_same_unit(&self, other: LockTime) -> bool {
152 matches!(
153 (self, other),
154 (LockTime::Blocks(_), LockTime::Blocks(_)) | (LockTime::Time(_), LockTime::Time(_))
155 )
156 }
157
158 /// Returns true if this lock time value is in units of block height.
159 #[inline]
160 pub const fn is_block_height(&self) -> bool { matches!(*self, LockTime::Blocks(_)) }
161
162 /// Returns true if this lock time value is in units of time.
163 #[inline]
164 pub const fn is_block_time(&self) -> bool { !self.is_block_height() }
165
166 /// Returns true if this [`relative::LockTime`] is satisfied by either height or time.
167 ///
168 /// # Examples
169 ///
170 /// ```rust
171 /// # use bitcoin::Sequence;
172 /// # use bitcoin::locktime::relative::{LockTime, Height, Time};
173 ///
174 /// # let height = 100; // 100 blocks.
175 /// # let intervals = 70; // Approx 10 hours.
176 /// # let current_height = || Height::from(height + 10);
177 /// # let current_time = || Time::from_512_second_intervals(intervals + 10);
178 /// # let lock = Sequence::from_height(height).to_relative_lock_time().expect("valid height");
179 ///
180 /// // Users that have chain data can get the current height and time to check against a lock.
181 /// let height_and_time = (current_time(), current_height()); // tuple order does not matter.
182 /// assert!(lock.is_satisfied_by(current_height(), current_time()));
183 /// ```
184 #[inline]
185 #[cfg_attr(all(test, mutate), mutate)]
186 pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool {
187 if let Ok(true) = self.is_satisfied_by_height(h) {
188 true
189 } else {
190 matches!(self.is_satisfied_by_time(t), Ok(true))
191 }
192 }
193
194 /// Returns true if satisfaction of `other` lock time implies satisfaction of this
195 /// [`relative::LockTime`].
196 ///
197 /// A lock time can only be satisfied by n blocks being mined or n seconds passing. If you have
198 /// two lock times (same unit) then the larger lock time being satisfied implies (in a
199 /// mathematical sense) the smaller one being satisfied.
200 ///
201 /// This function is useful when checking sequence values against a lock, first one checks the
202 /// sequence represents a relative lock time by converting to `LockTime` then use this function
203 /// to see if satisfaction of the newly created lock time would imply satisfaction of `self`.
204 ///
205 /// Can also be used to remove the smaller value of two `OP_CHECKSEQUENCEVERIFY` operations
206 /// within one branch of the script.
207 ///
208 /// # Examples
209 ///
210 /// ```rust
211 /// # use bitcoin::Sequence;
212 /// # use bitcoin::locktime::relative::{LockTime, Height, Time};
213 ///
214 /// # let height = 100; // 100 blocks.
215 /// # let lock = Sequence::from_height(height).to_relative_lock_time().expect("valid height");
216 /// # let test_sequence = Sequence::from_height(height + 10);
217 ///
218 /// let satisfied = match test_sequence.to_relative_lock_time() {
219 /// None => false, // Handle non-lock-time case.
220 /// Some(test_lock) => lock.is_implied_by(test_lock),
221 /// };
222 /// assert!(satisfied);
223 /// ```
224 #[inline]
225 #[cfg_attr(all(test, mutate), mutate)]
226 pub fn is_implied_by(&self, other: LockTime) -> bool {
227 use LockTime::*;
228
229 match (*self, other) {
230 (Blocks(this), Blocks(other)) => this.value() <= other.value(),
231 (Time(this), Time(other)) => this.value() <= other.value(),
232 _ => false, // Not the same units.
233 }
234 }
235
236 /// Returns true if satisfaction of the sequence number implies satisfaction of this lock time.
237 ///
238 /// When deciding whether an instance of `<n> CHECKSEQUENCEVERIFY` will pass, this
239 /// method can be used by parsing `n` as a [`LockTime`] and calling this method
240 /// with the sequence number of the input which spends the script.
241 #[inline]
242 pub fn is_implied_by_sequence(&self, other: Sequence) -> bool {
243 if let Ok(other) = LockTime::from_sequence(other) {
244 self.is_implied_by(other)
245 } else {
246 false
247 }
248 }
249
250 /// Returns true if this [`relative::LockTime`] is satisfied by [`Height`].
251 ///
252 /// # Errors
253 ///
254 /// Returns an error if this lock is not lock-by-height.
255 ///
256 /// # Examples
257 ///
258 /// ```rust
259 /// # use bitcoin::Sequence;
260 /// # use bitcoin::locktime::relative::{LockTime, Height, Time};
261 ///
262 /// let height: u16 = 100;
263 /// let lock = Sequence::from_height(height).to_relative_lock_time().expect("valid height");
264 /// assert!(lock.is_satisfied_by_height(Height::from(height+1)).expect("a height"));
265 /// ```
266 #[inline]
267 #[cfg_attr(all(test, mutate), mutate)]
268 pub fn is_satisfied_by_height(&self, height: Height) -> Result<bool, IncompatibleHeightError> {
269 use LockTime::*;
270
271 match *self {
272 Blocks(ref h) => Ok(h.value() <= height.value()),
273 Time(time) => Err(IncompatibleHeightError { height, time }),
274 }
275 }
276
277 /// Returns true if this [`relative::LockTime`] is satisfied by [`Time`].
278 ///
279 /// # Errors
280 ///
281 /// Returns an error if this lock is not lock-by-time.
282 ///
283 /// # Examples
284 ///
285 /// ```rust
286 /// # use bitcoin::Sequence;
287 /// # use bitcoin::locktime::relative::{LockTime, Height, Time};
288 ///
289 /// let intervals: u16 = 70; // approx 10 hours;
290 /// let lock = Sequence::from_512_second_intervals(intervals).to_relative_lock_time().expect("valid time");
291 /// assert!(lock.is_satisfied_by_time(Time::from_512_second_intervals(intervals + 10)).expect("a time"));
292 /// ```
293 #[inline]
294 #[cfg_attr(all(test, mutate), mutate)]
295 pub fn is_satisfied_by_time(&self, time: Time) -> Result<bool, IncompatibleTimeError> {
296 use LockTime::*;
297
298 match *self {
299 Time(ref t) => Ok(t.value() <= time.value()),
300 Blocks(height) => Err(IncompatibleTimeError { time, height }),
301 }
302 }
303}
304
305impl From<Height> for LockTime {
306 #[inline]
307 fn from(h: Height) -> Self { LockTime::Blocks(h) }
308}
309
310impl From<Time> for LockTime {
311 #[inline]
312 fn from(t: Time) -> Self { LockTime::Time(t) }
313}
314
315impl PartialOrd for LockTime {
316 #[inline]
317 fn partial_cmp(&self, other: &LockTime) -> Option<cmp::Ordering> {
318 use LockTime::*;
319
320 match (*self, *other) {
321 (Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
322 (Time(ref a), Time(ref b)) => a.partial_cmp(b),
323 (_, _) => None,
324 }
325 }
326}
327
328impl fmt::Display for LockTime {
329 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330 use LockTime::*;
331
332 if f.alternate() {
333 match *self {
334 Blocks(ref h) => write!(f, "block-height {}", h),
335 Time(ref t) => write!(f, "block-time {} (512 second intervals)", t),
336 }
337 } else {
338 match *self {
339 Blocks(ref h) => fmt::Display::fmt(h, f),
340 Time(ref t) => fmt::Display::fmt(t, f),
341 }
342 }
343 }
344}
345
346#[cfg(feature = "ordered")]
347impl ordered::ArbitraryOrd for LockTime {
348 fn arbitrary_cmp(&self, other: &Self) -> Ordering {
349 use LockTime::*;
350
351 match (self, other) {
352 (Blocks(_), Time(_)) => Ordering::Less,
353 (Time(_), Blocks(_)) => Ordering::Greater,
354 (Blocks(this), Blocks(that)) => this.cmp(that),
355 (Time(this), Time(that)) => this.cmp(that),
356 }
357 }
358}
359
360impl convert::TryFrom<Sequence> for LockTime {
361 type Error = DisabledLockTimeError;
362 fn try_from(seq: Sequence) -> Result<LockTime, DisabledLockTimeError> {
363 LockTime::from_sequence(seq)
364 }
365}
366
367impl From<LockTime> for Sequence {
368 fn from(lt: LockTime) -> Sequence { lt.to_sequence() }
369}
370
371/// Error returned when a sequence number is parsed as a lock time, but its
372/// "disable" flag is set.
373#[derive(Debug, Clone, Eq, PartialEq)]
374pub struct DisabledLockTimeError(u32);
375
376impl DisabledLockTimeError {
377 /// Accessor for the `u32` whose "disable" flag was set, preventing
378 /// it from being parsed as a relative locktime.
379 pub fn disabled_locktime_value(&self) -> u32 { self.0 }
380}
381
382impl fmt::Display for DisabledLockTimeError {
383 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
384 write!(f, "lock time 0x{:08x} has disable flag set", self.0)
385 }
386}
387
388#[cfg(feature = "std")]
389impl std::error::Error for DisabledLockTimeError {}
390
391/// Tried to satisfy a lock-by-blocktime lock using a height value.
392#[derive(Debug, Clone, PartialEq, Eq)]
393#[non_exhaustive]
394pub struct IncompatibleHeightError {
395 /// Attempted to satisfy a lock-by-blocktime lock with this height.
396 pub height: Height,
397 /// The inner time value of the lock-by-blocktime lock.
398 pub time: Time,
399}
400
401impl fmt::Display for IncompatibleHeightError {
402 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
403 write!(
404 f,
405 "tried to satisfy a lock-by-blocktime lock {} with height: {}",
406 self.time, self.height
407 )
408 }
409}
410
411#[cfg(feature = "std")]
412impl std::error::Error for IncompatibleHeightError {}
413
414/// Tried to satisfy a lock-by-blockheight lock using a time value.
415#[derive(Debug, Clone, PartialEq, Eq)]
416#[non_exhaustive]
417pub struct IncompatibleTimeError {
418 /// Attempted to satisfy a lock-by-blockheight lock with this time.
419 pub time: Time,
420 /// The inner height value of the lock-by-blockheight lock.
421 pub height: Height,
422}
423
424impl fmt::Display for IncompatibleTimeError {
425 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426 write!(
427 f,
428 "tried to satisfy a lock-by-blockheight lock {} with time: {}",
429 self.height, self.time
430 )
431 }
432}
433
434#[cfg(feature = "std")]
435impl std::error::Error for IncompatibleTimeError {}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn satisfied_by_height() {
443 let height = Height::from(10);
444 let time = Time::from_512_second_intervals(70);
445
446 let lock = LockTime::from(height);
447
448 assert!(!lock.is_satisfied_by(Height::from(9), time));
449 assert!(lock.is_satisfied_by(Height::from(10), time));
450 assert!(lock.is_satisfied_by(Height::from(11), time));
451 }
452
453 #[test]
454 fn satisfied_by_time() {
455 let height = Height::from(10);
456 let time = Time::from_512_second_intervals(70);
457
458 let lock = LockTime::from(time);
459
460 assert!(!lock.is_satisfied_by(height, Time::from_512_second_intervals(69)));
461 assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(70)));
462 assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(71)));
463 }
464
465 #[test]
466 fn height_correctly_implies() {
467 let height = Height::from(10);
468 let lock = LockTime::from(height);
469
470 assert!(!lock.is_implied_by(LockTime::from(Height::from(9))));
471 assert!(lock.is_implied_by(LockTime::from(Height::from(10))));
472 assert!(lock.is_implied_by(LockTime::from(Height::from(11))));
473 }
474
475 #[test]
476 fn time_correctly_implies() {
477 let time = Time::from_512_second_intervals(70);
478 let lock = LockTime::from(time);
479
480 assert!(!lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(69))));
481 assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(70))));
482 assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(71))));
483 }
484
485 #[test]
486 fn incorrect_units_do_not_imply() {
487 let time = Time::from_512_second_intervals(70);
488 let height = Height::from(10);
489
490 let lock = LockTime::from(time);
491 assert!(!lock.is_implied_by(LockTime::from(height)));
492 }
493
494 #[test]
495 fn consensus_round_trip() {
496 assert!(LockTime::from_consensus(1 << 31).is_err());
497 assert!(LockTime::from_consensus(1 << 30).is_ok());
498 // Relative locktimes do not care about bits 17 through 21.
499 assert_eq!(LockTime::from_consensus(65536), LockTime::from_consensus(0));
500
501 for val in [0u32, 1, 1000, 65535] {
502 let seq = Sequence::from_consensus(val);
503 let lt = LockTime::from_consensus(val).unwrap();
504 assert_eq!(lt.to_consensus_u32(), val);
505 assert_eq!(lt.to_sequence(), seq);
506 assert_eq!(LockTime::from_sequence(seq).unwrap().to_sequence(), seq);
507
508 let seq = Sequence::from_consensus(val + (1 << 22));
509 let lt = LockTime::from_consensus(val + (1 << 22)).unwrap();
510 assert_eq!(lt.to_consensus_u32(), val + (1 << 22));
511 assert_eq!(lt.to_sequence(), seq);
512 assert_eq!(LockTime::from_sequence(seq).unwrap().to_sequence(), seq);
513 }
514 }
515}