logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Transaction LockTime.

use std::{convert::TryInto, io};

use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, TimeZone, Utc};

use crate::block::{self, Height};
use crate::serialization::{SerializationError, ZcashDeserialize, ZcashSerialize};

/// A Bitcoin-style `locktime`, representing either a block height or an epoch
/// time.
///
/// # Invariants
///
/// Users should not construct a [`LockTime`] with:
///   - a [`block::Height`] greater than [`LockTime::MAX_HEIGHT`],
///   - a timestamp before 6 November 1985
///     (Unix timestamp less than [`LockTime::MIN_TIMESTAMP`]), or
///   - a timestamp after 5 February 2106
///     (Unix timestamp greater than [`LockTime::MAX_TIMESTAMP`]).
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum LockTime {
    /// The transaction can only be included in a block if the block height is
    /// strictly greater than this height
    Height(block::Height),
    /// The transaction can only be included in a block if the block time is
    /// strictly greater than this timestamp
    Time(DateTime<Utc>),
}

impl LockTime {
    /// The minimum [`LockTime::Time`], as a Unix timestamp in seconds.
    ///
    /// Users should not construct [`LockTime`]s with [`LockTime::Time`] lower
    /// than [`LockTime::MIN_TIMESTAMP`].
    ///
    /// If a [`LockTime`] is supposed to be lower than
    /// [`LockTime::MIN_TIMESTAMP`], then a particular [`LockTime::Height`]
    /// applies instead, as described in the spec.
    pub const MIN_TIMESTAMP: i64 = 500_000_000;

    /// The maximum [`LockTime::Time`], as a timestamp in seconds.
    ///
    /// Users should not construct lock times with timestamps greater than
    /// [`LockTime::MAX_TIMESTAMP`]. LockTime is [`u32`] in the spec, so times
    /// are limited to [`u32::MAX`].
    pub const MAX_TIMESTAMP: i64 = u32::MAX as i64;

    /// The maximum [`LockTime::Height`], as a block height.
    ///
    /// Users should not construct lock times with a block height greater than
    /// [`LockTime::MAX_TIMESTAMP`].
    ///
    /// If a [`LockTime`] is supposed to be greater than
    /// [`LockTime::MAX_HEIGHT`], then a particular [`LockTime::Time`] applies
    /// instead, as described in the spec.
    pub const MAX_HEIGHT: Height = Height((Self::MIN_TIMESTAMP - 1) as u32);

    /// Returns a [`LockTime`] that is always unlocked.
    ///
    /// The lock time is set to the block height of the genesis block.
    pub fn unlocked() -> Self {
        LockTime::Height(block::Height(0))
    }

    /// Returns the minimum [`LockTime::Time`], as a [`LockTime`].
    ///
    /// Users should not construct lock times with timestamps lower than the
    /// value returned by this function.
    //
    // TODO: replace Utc.timestamp with DateTime32 (#2211)
    pub fn min_lock_time_timestamp() -> LockTime {
        LockTime::Time(Utc.timestamp(Self::MIN_TIMESTAMP, 0))
    }

    /// Returns the maximum [`LockTime::Time`], as a [`LockTime`].
    ///
    /// Users should not construct lock times with timestamps greater than the
    /// value returned by this function.
    //
    // TODO: replace Utc.timestamp with DateTime32 (#2211)
    pub fn max_lock_time_timestamp() -> LockTime {
        LockTime::Time(Utc.timestamp(Self::MAX_TIMESTAMP, 0))
    }
}

impl ZcashSerialize for LockTime {
    #[allow(clippy::unwrap_in_result)]
    fn zcash_serialize<W: io::Write>(&self, mut writer: W) -> Result<(), io::Error> {
        // This implementation does not check the invariants on `LockTime` so that the
        // serialization is fallible only if the underlying writer is. This ensures that
        // we can always compute a hash of a transaction object.
        match self {
            LockTime::Height(block::Height(n)) => writer.write_u32::<LittleEndian>(*n)?,
            LockTime::Time(t) => writer
                .write_u32::<LittleEndian>(t.timestamp().try_into().expect("time is in range"))?,
        }
        Ok(())
    }
}

impl ZcashDeserialize for LockTime {
    #[allow(clippy::unwrap_in_result)]
    fn zcash_deserialize<R: io::Read>(mut reader: R) -> Result<Self, SerializationError> {
        let n = reader.read_u32::<LittleEndian>()?;
        if n < Self::MIN_TIMESTAMP.try_into().expect("fits in u32") {
            Ok(LockTime::Height(block::Height(n)))
        } else {
            // This can't panic, because all u32 values are valid `Utc.timestamp`s.
            Ok(LockTime::Time(Utc.timestamp(n.into(), 0)))
        }
    }
}