1use core::{mem, fmt};
11use core::cmp::{PartialOrd, Ordering};
12
13use bitcoin_internals::write_err;
14
15#[cfg(all(test, mutate))]
16use mutagen::mutate;
17
18use crate::consensus::encode::{self, Decodable, Encodable};
19use crate::error::ParseIntError;
20use crate::io::{self, Read, Write};
21use crate::parse::{impl_parse_str_from_int_infallible, impl_parse_str_from_int_fallible};
22use crate::prelude::*;
23use crate::string::FromHexStr;
24
25#[cfg(doc)]
26use crate::absolute;
27
28pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
40
41#[allow(clippy::derive_ord_xor_partial_ord)]
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74pub enum LockTime {
75 Blocks(Height),
87 Seconds(Time),
99}
100
101impl LockTime {
102 pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
105
106 #[inline]
119 pub fn from_consensus(n: u32) -> Self {
120 if is_block_height(n) {
121 Self::Blocks(Height::from_consensus(n).expect("n is valid"))
122 } else {
123 Self::Seconds(Time::from_consensus(n).expect("n is valid"))
124 }
125 }
126
127 #[inline]
138 pub fn from_height(n: u32) -> Result<Self, Error> {
139 let height = Height::from_consensus(n)?;
140 Ok(LockTime::Blocks(height))
141 }
142
143 #[inline]
154 pub fn from_time(n: u32) -> Result<Self, Error> {
155 let time = Time::from_consensus(n)?;
156 Ok(LockTime::Seconds(time))
157 }
158
159 #[inline]
161 pub fn is_same_unit(&self, other: LockTime) -> bool {
162 mem::discriminant(self) == mem::discriminant(&other)
163 }
164
165 #[inline]
167 pub fn is_block_height(&self) -> bool {
168 match *self {
169 LockTime::Blocks(_) => true,
170 LockTime::Seconds(_) => false,
171 }
172 }
173
174 #[inline]
176 pub fn is_block_time(&self) -> bool {
177 !self.is_block_height()
178 }
179
180 #[inline]
202 #[cfg_attr(all(test, mutate), mutate)]
203 pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool {
204 use LockTime::*;
205
206 match *self {
207 Blocks(n) => n <= height,
208 Seconds(n) => n <= time,
209 }
210 }
211
212 #[inline]
232 #[cfg_attr(all(test, mutate), mutate)]
233 pub fn is_implied_by(&self, other: LockTime) -> bool {
234 use LockTime::*;
235
236 match (*self, other) {
237 (Blocks(this), Blocks(other)) => this <= other,
238 (Seconds(this), Seconds(other)) => this <= other,
239 _ => false, }
241 }
242
243 #[inline]
269 pub fn to_consensus_u32(self) -> u32 {
270 match self {
271 LockTime::Blocks(ref h) => h.to_consensus_u32(),
272 LockTime::Seconds(ref t) => t.to_consensus_u32(),
273 }
274 }
275}
276
277impl_parse_str_from_int_infallible!(LockTime, u32, from_consensus);
278
279impl From<Height> for LockTime {
280 #[inline]
281 fn from(h: Height) -> Self {
282 LockTime::Blocks(h)
283 }
284}
285
286impl From<Time> for LockTime {
287 #[inline]
288 fn from(t: Time) -> Self {
289 LockTime::Seconds(t)
290 }
291}
292
293impl PartialOrd for LockTime {
294 #[inline]
295 fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
296 use LockTime::*;
297
298 match (*self, *other) {
299 (Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
300 (Seconds(ref a), Seconds(ref b)) => a.partial_cmp(b),
301 (_, _) => None,
302 }
303 }
304}
305
306impl fmt::Display for LockTime {
307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
308 use LockTime::*;
309
310 if f.alternate() {
311 match *self {
312 Blocks(ref h) => write!(f, "block-height {}", h),
313 Seconds(ref t) => write!(f, "block-time {} (seconds since epoch)", t),
314 }
315 } else {
316 match *self {
317 Blocks(ref h) => fmt::Display::fmt(h, f),
318 Seconds(ref t) => fmt::Display::fmt(t, f),
319 }
320 }
321 }
322}
323
324impl FromHexStr for LockTime {
325 type Error = Error;
326
327 #[inline]
328 fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
329 let packed_lock_time = crate::parse::hex_u32(s)?;
330 Ok(Self::from_consensus(packed_lock_time))
331 }
332}
333
334impl Encodable for LockTime {
335 #[inline]
336 fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
337 let v = self.to_consensus_u32();
338 v.consensus_encode(w)
339 }
340}
341
342impl Decodable for LockTime {
343 #[inline]
344 fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
345 u32::consensus_decode(r).map(LockTime::from_consensus)
346 }
347}
348
349#[cfg(feature = "serde")]
350impl serde::Serialize for LockTime {
351 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
352 where
353 S: serde::Serializer,
354 {
355 serializer.serialize_u32(self.to_consensus_u32())
356 }
357}
358
359#[cfg(feature = "serde")]
360impl<'de> serde::Deserialize<'de> for LockTime {
361 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
362 where
363 D: serde::Deserializer<'de>,
364 {
365 struct Visitor;
366 impl<'de> serde::de::Visitor<'de> for Visitor {
367 type Value = u32;
368 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a u32") }
369 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<u32, E> {
373 use core::convert::TryInto;
374 v.try_into().map_err(|_| E::invalid_value(serde::de::Unexpected::Unsigned(v), &"a 32-bit number"))
375 }
376 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<u32, E> {
378 use core::convert::TryInto;
379 v.try_into().map_err(|_| E::invalid_value(serde::de::Unexpected::Signed(v), &"a 32-bit number"))
380 }
381 }
382 deserializer.deserialize_u32(Visitor).map(LockTime::from_consensus)
383 }
384}
385
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
389#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
390#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
391pub struct Height(u32);
392
393impl Height {
394 pub const ZERO: Self = Height(0);
396
397 pub const MIN: Self = Self::ZERO;
399
400 pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
402
403 pub const fn min_value() -> Self { Self::MIN }
407
408 pub const fn max_value() -> Self { Self::MAX }
412
413 #[inline]
428 pub fn from_consensus(n: u32) -> Result<Height, Error> {
429 if is_block_height(n) {
430 Ok(Self(n))
431 } else {
432 Err(ConversionError::invalid_height(n).into())
433 }
434 }
435
436 #[inline]
447 pub fn to_consensus_u32(self) -> u32 {
448 self.0
449 }
450}
451
452impl_parse_str_from_int_fallible!(Height, u32, from_consensus, Error);
453
454impl fmt::Display for Height {
455 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
456 fmt::Display::fmt(&self.0, f)
457 }
458}
459
460impl FromHexStr for Height {
461 type Error = Error;
462
463 #[inline]
464 fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
465 let height = crate::parse::hex_u32(s)?;
466 Self::from_consensus(height)
467 }
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
476#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
477#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
478pub struct Time(u32);
479
480impl Time {
481 pub const MIN: Self = Time(LOCK_TIME_THRESHOLD);
483
484 pub const MAX: Self = Time(u32::max_value());
486
487 pub const fn min_value() -> Self { Self::MIN }
491
492 pub const fn max_value() -> Self { Self::MAX }
496
497 #[inline]
512 pub fn from_consensus(n: u32) -> Result<Time, Error> {
513 if is_block_time(n) {
514 Ok(Self(n))
515 } else {
516 Err(ConversionError::invalid_time(n).into())
517 }
518 }
519
520 #[inline]
531 pub fn to_consensus_u32(self) -> u32 {
532 self.0
533 }
534}
535
536impl_parse_str_from_int_fallible!(Time, u32, from_consensus, Error);
537
538impl fmt::Display for Time {
539 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
540 fmt::Display::fmt(&self.0, f)
541 }
542}
543
544impl FromHexStr for Time {
545 type Error = Error;
546
547 #[inline]
548 fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
549 let time = crate::parse::hex_u32(s)?;
550 Time::from_consensus(time)
551 }
552}
553
554fn is_block_height(n: u32) -> bool {
556 n < LOCK_TIME_THRESHOLD
557}
558
559fn is_block_time(n: u32) -> bool {
561 n >= LOCK_TIME_THRESHOLD
562}
563
564#[derive(Debug, Clone, PartialEq, Eq)]
566#[non_exhaustive]
567pub enum Error {
568 Conversion(ConversionError),
570 Operation(OperationError),
572 Parse(ParseIntError),
574}
575
576impl fmt::Display for Error {
577 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
578 use self::Error::*;
579
580 match *self {
581 Conversion(ref e) => write_err!(f, "error converting lock time value"; e),
582 Operation(ref e) => write_err!(f, "error during lock time operation"; e),
583 Parse(ref e) => write_err!(f, "failed to parse lock time from string"; e),
584 }
585 }
586}
587
588#[cfg(feature = "std")]
589#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
590impl std::error::Error for Error {
591 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
592 use self::Error::*;
593
594 match *self {
595 Conversion(ref e) => Some(e),
596 Operation(ref e) => Some(e),
597 Parse(ref e) => Some(e),
598 }
599 }
600}
601
602impl From<ConversionError> for Error {
603 #[inline]
604 fn from(e: ConversionError) -> Self {
605 Error::Conversion(e)
606 }
607}
608
609impl From<OperationError> for Error {
610 #[inline]
611 fn from(e: OperationError) -> Self {
612 Error::Operation(e)
613 }
614}
615
616impl From<ParseIntError> for Error {
617 #[inline]
618 fn from(e: ParseIntError) -> Self {
619 Error::Parse(e)
620 }
621}
622
623#[derive(Debug, Clone, Eq, PartialEq, Hash)]
625pub struct ConversionError {
626 unit: LockTimeUnit,
628 input: u32,
630}
631
632impl ConversionError {
633 fn invalid_height(n: u32) -> Self {
635 Self {
636 unit: LockTimeUnit::Blocks,
637 input: n,
638 }
639 }
640
641 fn invalid_time(n: u32) -> Self {
643 Self {
644 unit: LockTimeUnit::Seconds,
645 input: n,
646 }
647 }
648}
649
650impl fmt::Display for ConversionError {
651 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
652 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
653 }
654}
655
656#[cfg(feature = "std")]
657#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
658impl std::error::Error for ConversionError {}
659
660#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
662enum LockTimeUnit {
663 Blocks,
665 Seconds,
667}
668
669impl fmt::Display for LockTimeUnit {
670 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671 use LockTimeUnit::*;
672
673 match *self {
674 Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
675 Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
676 }
677 }
678}
679
680#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
682#[non_exhaustive]
683pub enum OperationError {
684 InvalidComparison,
686}
687
688impl fmt::Display for OperationError {
689 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
690 use self::OperationError::*;
691
692 match *self {
693 InvalidComparison => f.write_str("cannot compare different lock units (height vs time)"),
694 }
695 }
696}
697
698#[cfg(feature = "std")]
699#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
700impl std::error::Error for OperationError {}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705
706 #[test]
707 fn display_and_alternate() {
708 let n = LockTime::from_consensus(741521);
709 let s = format!("{}", n);
710 assert_eq!(&s, "741521");
711
712 let got = format!("{:#}", n);
713 assert_eq!(got, "block-height 741521");
714 }
715
716 #[test]
717 fn time_from_str_hex_happy_path() {
718 let actual = Time::from_hex_str("0x6289C350").unwrap();
719 let expected = Time::from_consensus(0x6289C350).unwrap();
720 assert_eq!(actual, expected);
721 }
722
723 #[test]
724 fn time_from_str_hex_no_prefix_happy_path() {
725 let time = Time::from_hex_str_no_prefix("6289C350").unwrap();
726 assert_eq!(time, Time(0x6289C350));
727 }
728
729 #[test]
730 fn time_from_str_hex_invalid_hex_should_err() {
731 let hex = "0xzb93";
732 let result = Time::from_hex_str(hex);
733 assert!(result.is_err());
734 }
735
736 #[test]
737 fn packed_lock_time_from_str_hex_happy_path() {
738 let actual = LockTime::from_hex_str("0xBA70D").unwrap();
739 let expected = LockTime::from_consensus(0xBA70D);
740 assert_eq!(actual, expected);
741 }
742
743 #[test]
744 fn packed_lock_time_from_str_hex_no_prefix_happy_path() {
745 let lock_time = LockTime::from_hex_str_no_prefix("BA70D").unwrap();
746 assert_eq!(lock_time, LockTime::from_consensus(0xBA70D));
747 }
748
749 #[test]
750 fn packed_lock_time_from_str_hex_invalid_hex_should_ergr() {
751 let hex = "0xzb93";
752 let result = LockTime::from_hex_str(hex);
753 assert!(result.is_err());
754 }
755
756 #[test]
757 fn height_from_str_hex_happy_path() {
758 let actual = Height::from_hex_str("0xBA70D").unwrap();
759 let expected = Height(0xBA70D);
760 assert_eq!(actual, expected);
761 }
762
763 #[test]
764 fn height_from_str_hex_no_prefix_happy_path() {
765 let height = Height::from_hex_str_no_prefix("BA70D").unwrap();
766 assert_eq!(height, Height(0xBA70D));
767 }
768
769 #[test]
770 fn height_from_str_hex_invalid_hex_should_err() {
771 let hex = "0xzb93";
772 let result = Height::from_hex_str(hex);
773 assert!(result.is_err());
774 }
775
776 #[test]
777 fn parses_correctly_to_height_or_time() {
778 let lock = LockTime::from_consensus(750_000);
779
780 assert!(lock.is_block_height());
781 assert!(!lock.is_block_time());
782
783 let t: u32 = 1653195600; let lock = LockTime::from_consensus(t);
785
786 assert!(!lock.is_block_height());
787 assert!(lock.is_block_time());
788 }
789
790 #[test]
791 fn satisfied_by_height() {
792 let lock = LockTime::from_consensus(750_000);
793
794 let height = Height::from_consensus(800_000).expect("failed to parse height");
795
796 let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
798
799 assert!(lock.is_satisfied_by(height, time))
800 }
801
802 #[test]
803 fn satisfied_by_time() {
804 let lock = LockTime::from_consensus(1053195600);
805
806 let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
808
809 let height = Height::from_consensus(800_000).expect("failed to parse height");
810
811 assert!(lock.is_satisfied_by(height, time))
812 }
813
814 #[test]
815 fn satisfied_by_same_height() {
816 let h = 750_000;
817 let lock = LockTime::from_consensus(h);
818 let height = Height::from_consensus(h).expect("failed to parse height");
819
820 let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
822
823 assert!(lock.is_satisfied_by(height, time))
824 }
825
826 #[test]
827 fn satisfied_by_same_time() {
828 let t: u32 = 1653195600; let lock = LockTime::from_consensus(t);
830 let time = Time::from_consensus(t).expect("invalid time value");
831
832 let height = Height::from_consensus(800_000).expect("failed to parse height");
833
834 assert!(lock.is_satisfied_by(height, time))
835 }
836
837 #[test]
838 fn height_correctly_implies() {
839 let lock = LockTime::from_consensus(750_005);
840
841 assert!(!lock.is_implied_by(LockTime::from_consensus(750_004)));
842 assert!(lock.is_implied_by(LockTime::from_consensus(750_005)));
843 assert!(lock.is_implied_by(LockTime::from_consensus(750_006)));
844 }
845
846 #[test]
847 fn time_correctly_implies() {
848 let t: u32 = 1700000005;
849 let lock = LockTime::from_consensus(t);
850
851 assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
852 assert!(lock.is_implied_by(LockTime::from_consensus(1700000005)));
853 assert!(lock.is_implied_by(LockTime::from_consensus(1700000006)));
854 }
855
856 #[test]
857 fn incorrect_units_do_not_imply() {
858 let lock = LockTime::from_consensus(750_005);
859 assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
860 }
861}