zebra_chain/transaction/lock_time.rs
1//! Transaction LockTime.
2
3use std::io;
4
5use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
6use chrono::{DateTime, TimeZone, Utc};
7
8use crate::{
9 block::{self, Height},
10 serialization::{SerializationError, ZcashDeserialize, ZcashSerialize},
11};
12
13/// A Bitcoin-style `locktime`, representing either a block height or an epoch
14/// time.
15///
16/// # Invariants
17///
18/// Users should not construct a [`LockTime`] with:
19/// - a [`block::Height`] greater than [`LockTime::MAX_HEIGHT`],
20/// - a timestamp before 6 November 1985
21/// (Unix timestamp less than [`LockTime::MIN_TIMESTAMP`]), or
22/// - a timestamp after 5 February 2106
23/// (Unix timestamp greater than [`LockTime::MAX_TIMESTAMP`]).
24#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
25pub enum LockTime {
26 /// The transaction can only be included in a block if the block height is
27 /// strictly greater than this height
28 Height(block::Height),
29 /// The transaction can only be included in a block if the block time is
30 /// strictly greater than this timestamp
31 Time(DateTime<Utc>),
32}
33
34impl LockTime {
35 /// The minimum [`LockTime::Time`], as a Unix timestamp in seconds.
36 ///
37 /// Users should not construct [`LockTime`]s with [`LockTime::Time`] lower
38 /// than [`LockTime::MIN_TIMESTAMP`].
39 ///
40 /// If a [`LockTime`] is supposed to be lower than
41 /// [`LockTime::MIN_TIMESTAMP`], then a particular [`LockTime::Height`]
42 /// applies instead, as described in the spec.
43 pub const MIN_TIMESTAMP: i64 = 500_000_000;
44
45 /// The maximum [`LockTime::Time`], as a timestamp in seconds.
46 ///
47 /// Users should not construct lock times with timestamps greater than
48 /// [`LockTime::MAX_TIMESTAMP`]. LockTime is [`u32`] in the spec, so times
49 /// are limited to [`u32::MAX`].
50 pub const MAX_TIMESTAMP: i64 = u32::MAX as i64;
51
52 /// The maximum [`LockTime::Height`], as a block height.
53 ///
54 /// Users should not construct lock times with a block height greater than
55 /// [`LockTime::MAX_TIMESTAMP`].
56 ///
57 /// If a [`LockTime`] is supposed to be greater than
58 /// [`LockTime::MAX_HEIGHT`], then a particular [`LockTime::Time`] applies
59 /// instead, as described in the spec.
60 pub const MAX_HEIGHT: Height = Height((Self::MIN_TIMESTAMP - 1) as u32);
61
62 /// Returns a [`LockTime`] that is always unlocked.
63 ///
64 /// The lock time is set to the block height of the genesis block.
65 pub fn unlocked() -> Self {
66 LockTime::Height(block::Height(0))
67 }
68
69 /// Returns the minimum [`LockTime::Time`], as a [`LockTime`].
70 ///
71 /// Users should not construct lock times with timestamps lower than the
72 /// value returned by this function.
73 //
74 // TODO: replace Utc.timestamp with DateTime32 (#2211)
75 pub fn min_lock_time_timestamp() -> LockTime {
76 LockTime::Time(
77 Utc.timestamp_opt(Self::MIN_TIMESTAMP, 0)
78 .single()
79 .expect("in-range number of seconds and valid nanosecond"),
80 )
81 }
82
83 /// Returns the maximum [`LockTime::Time`], as a [`LockTime`].
84 ///
85 /// Users should not construct lock times with timestamps greater than the
86 /// value returned by this function.
87 //
88 // TODO: replace Utc.timestamp with DateTime32 (#2211)
89 pub fn max_lock_time_timestamp() -> LockTime {
90 LockTime::Time(
91 Utc.timestamp_opt(Self::MAX_TIMESTAMP, 0)
92 .single()
93 .expect("in-range number of seconds and valid nanosecond"),
94 )
95 }
96
97 /// Returns `true` if this lock time is a [`LockTime::Time`], or `false` if it is a [`LockTime::Height`].
98 pub fn is_time(&self) -> bool {
99 matches!(self, LockTime::Time(_))
100 }
101}
102
103impl ZcashSerialize for LockTime {
104 #[allow(clippy::unwrap_in_result)]
105 fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
106 // This implementation does not check the invariants on `LockTime` so that the
107 // serialization is fallible only if the underlying writer is. This ensures that
108 // we can always compute a hash of a transaction object.
109 match self {
110 LockTime::Height(block::Height(n)) => writer.write_u32::<LittleEndian>(*n)?,
111 LockTime::Time(t) => writer
112 .write_u32::<LittleEndian>(t.timestamp().try_into().expect("time is in range"))?,
113 }
114 Ok(())
115 }
116}
117
118impl ZcashDeserialize for LockTime {
119 #[allow(clippy::unwrap_in_result)]
120 fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
121 let n = reader.read_u32::<LittleEndian>()?;
122 if n < Self::MIN_TIMESTAMP.try_into().expect("fits in u32") {
123 Ok(LockTime::Height(block::Height(n)))
124 } else {
125 // This can't panic, because all u32 values are valid `Utc.timestamp`s.
126 Ok(LockTime::Time(
127 Utc.timestamp_opt(n.into(), 0)
128 .single()
129 .expect("in-range number of seconds and valid nanosecond"),
130 ))
131 }
132 }
133}