zebra_chain/transaction/lock_time.rs
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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
//! Transaction LockTime.
use std::io;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use chrono::{DateTime, TimeZone, Utc};
use crate::{
block::{self, Height},
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_opt(Self::MIN_TIMESTAMP, 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
)
}
/// 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_opt(Self::MAX_TIMESTAMP, 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
)
}
/// Returns `true` if this lock time is a [`LockTime::Time`], or `false` if it is a [`LockTime::Height`].
pub fn is_time(&self) -> bool {
matches!(self, LockTime::Time(_))
}
}
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_opt(n.into(), 0)
.single()
.expect("in-range number of seconds and valid nanosecond"),
))
}
}
}