use core::cmp::{Ordering, PartialOrd};
use core::{fmt, mem};
use internals::write_err;
#[cfg(all(test, mutate))]
use mutagen::mutate;
#[cfg(doc)]
use crate::absolute;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::error::ParseIntError;
use crate::io::{self, Read, Write};
use crate::parse::{impl_parse_str_from_int_fallible, impl_parse_str_from_int_infallible};
use crate::prelude::*;
use crate::string::FromHexStr;
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum LockTime {
Blocks(Height),
Seconds(Time),
}
impl LockTime {
pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
pub const SIZE: usize = 4;
#[inline]
pub fn from_consensus(n: u32) -> Self {
if is_block_height(n) {
Self::Blocks(Height::from_consensus(n).expect("n is valid"))
} else {
Self::Seconds(Time::from_consensus(n).expect("n is valid"))
}
}
#[inline]
pub fn from_height(n: u32) -> Result<Self, Error> {
let height = Height::from_consensus(n)?;
Ok(LockTime::Blocks(height))
}
#[inline]
pub fn from_time(n: u32) -> Result<Self, Error> {
let time = Time::from_consensus(n)?;
Ok(LockTime::Seconds(time))
}
#[inline]
pub fn is_same_unit(&self, other: LockTime) -> bool {
mem::discriminant(self) == mem::discriminant(&other)
}
#[inline]
pub fn is_block_height(&self) -> bool {
match *self {
LockTime::Blocks(_) => true,
LockTime::Seconds(_) => false,
}
}
#[inline]
pub fn is_block_time(&self) -> bool { !self.is_block_height() }
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool {
use LockTime::*;
match *self {
Blocks(n) => n <= height,
Seconds(n) => n <= time,
}
}
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_implied_by(&self, other: LockTime) -> bool {
use LockTime::*;
match (*self, other) {
(Blocks(this), Blocks(other)) => this <= other,
(Seconds(this), Seconds(other)) => this <= other,
_ => false, }
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
match self {
LockTime::Blocks(ref h) => h.to_consensus_u32(),
LockTime::Seconds(ref t) => t.to_consensus_u32(),
}
}
}
impl_parse_str_from_int_infallible!(LockTime, u32, from_consensus);
impl From<Height> for LockTime {
#[inline]
fn from(h: Height) -> Self { LockTime::Blocks(h) }
}
impl From<Time> for LockTime {
#[inline]
fn from(t: Time) -> Self { LockTime::Seconds(t) }
}
impl PartialOrd for LockTime {
#[inline]
fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
use LockTime::*;
match (*self, *other) {
(Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
(Seconds(ref a), Seconds(ref b)) => a.partial_cmp(b),
(_, _) => None,
}
}
}
impl fmt::Debug for LockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTime::*;
match *self {
Blocks(ref h) => write!(f, "{} blocks", h),
Seconds(ref t) => write!(f, "{} seconds", t),
}
}
}
impl fmt::Display for LockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTime::*;
if f.alternate() {
match *self {
Blocks(ref h) => write!(f, "block-height {}", h),
Seconds(ref t) => write!(f, "block-time {} (seconds since epoch)", t),
}
} else {
match *self {
Blocks(ref h) => fmt::Display::fmt(h, f),
Seconds(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
impl FromHexStr for LockTime {
type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let packed_lock_time = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(packed_lock_time))
}
}
impl Encodable for LockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let v = self.to_consensus_u32();
v.consensus_encode(w)
}
}
impl Decodable for LockTime {
#[inline]
fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
u32::consensus_decode(r).map(LockTime::from_consensus)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for LockTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u32(self.to_consensus_u32())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LockTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = u32;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a u32") }
fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<u32, E> {
use core::convert::TryInto;
v.try_into().map_err(|_| {
E::invalid_value(serde::de::Unexpected::Unsigned(v), &"a 32-bit number")
})
}
fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<u32, E> {
use core::convert::TryInto;
v.try_into().map_err(|_| {
E::invalid_value(serde::de::Unexpected::Signed(v), &"a 32-bit number")
})
}
}
deserializer.deserialize_u32(Visitor).map(LockTime::from_consensus)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Height(u32);
impl Height {
pub const ZERO: Self = Height(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
#[deprecated(since = "0.31.0", note = "Use Self::MIN instead")]
pub const fn min_value() -> Self { Self::MIN }
#[deprecated(since = "0.31.0", note = "Use Self::MAX instead")]
pub const fn max_value() -> Self { Self::MAX }
#[inline]
pub fn from_consensus(n: u32) -> Result<Height, Error> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n).into())
}
}
#[inline]
pub fn to_consensus_u32(self) -> u32 { self.0 }
}
impl_parse_str_from_int_fallible!(Height, u32, from_consensus, Error);
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
impl FromHexStr for Height {
type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let height = crate::parse::hex_u32(s)?;
Self::from_consensus(height)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Time(u32);
impl Time {
pub const MIN: Self = Time(LOCK_TIME_THRESHOLD);
pub const MAX: Self = Time(u32::max_value());
#[deprecated(since = "0.31.0", note = "Use Self::MIN instead")]
pub const fn min_value() -> Self { Self::MIN }
#[deprecated(since = "0.31.0", note = "Use Self::MAX instead")]
pub const fn max_value() -> Self { Self::MAX }
#[inline]
pub fn from_consensus(n: u32) -> Result<Time, Error> {
if is_block_time(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_time(n).into())
}
}
#[inline]
pub fn to_consensus_u32(self) -> u32 { self.0 }
}
impl_parse_str_from_int_fallible!(Time, u32, from_consensus, Error);
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
impl FromHexStr for Time {
type Error = Error;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let time = crate::parse::hex_u32(s)?;
Time::from_consensus(time)
}
}
fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Conversion(ConversionError),
Operation(OperationError),
Parse(ParseIntError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Error::*;
match *self {
Conversion(ref e) => write_err!(f, "error converting lock time value"; e),
Operation(ref e) => write_err!(f, "error during lock time operation"; e),
Parse(ref e) => write_err!(f, "failed to parse lock time from string"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use Error::*;
match *self {
Conversion(ref e) => Some(e),
Operation(ref e) => Some(e),
Parse(ref e) => Some(e),
}
}
}
impl From<ConversionError> for Error {
#[inline]
fn from(e: ConversionError) -> Self { Error::Conversion(e) }
}
impl From<OperationError> for Error {
#[inline]
fn from(e: OperationError) -> Self { Error::Operation(e) }
}
impl From<ParseIntError> for Error {
#[inline]
fn from(e: ParseIntError) -> Self { Error::Parse(e) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversionError {
unit: LockTimeUnit,
input: u32,
}
impl ConversionError {
fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } }
fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } }
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConversionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
Blocks,
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTimeUnit::*;
match *self {
Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum OperationError {
InvalidComparison,
}
impl fmt::Display for OperationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use OperationError::*;
match *self {
InvalidComparison =>
f.write_str("cannot compare different lock units (height vs time)"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for OperationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use OperationError::*;
match *self {
InvalidComparison => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_alternate() {
let n = LockTime::from_consensus(741521);
let s = format!("{}", n);
assert_eq!(&s, "741521");
let got = format!("{:#}", n);
assert_eq!(got, "block-height 741521");
}
#[test]
fn time_from_str_hex_happy_path() {
let actual = Time::from_hex_str("0x6289C350").unwrap();
let expected = Time::from_consensus(0x6289C350).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn time_from_str_hex_no_prefix_happy_path() {
let time = Time::from_hex_str_no_prefix("6289C350").unwrap();
assert_eq!(time, Time(0x6289C350));
}
#[test]
fn time_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Time::from_hex_str(hex);
assert!(result.is_err());
}
#[test]
fn packed_lock_time_from_str_hex_happy_path() {
let actual = LockTime::from_hex_str("0xBA70D").unwrap();
let expected = LockTime::from_consensus(0xBA70D);
assert_eq!(actual, expected);
}
#[test]
fn packed_lock_time_from_str_hex_no_prefix_happy_path() {
let lock_time = LockTime::from_hex_str_no_prefix("BA70D").unwrap();
assert_eq!(lock_time, LockTime::from_consensus(0xBA70D));
}
#[test]
fn packed_lock_time_from_str_hex_invalid_hex_should_ergr() {
let hex = "0xzb93";
let result = LockTime::from_hex_str(hex);
assert!(result.is_err());
}
#[test]
fn height_from_str_hex_happy_path() {
let actual = Height::from_hex_str("0xBA70D").unwrap();
let expected = Height(0xBA70D);
assert_eq!(actual, expected);
}
#[test]
fn height_from_str_hex_no_prefix_happy_path() {
let height = Height::from_hex_str_no_prefix("BA70D").unwrap();
assert_eq!(height, Height(0xBA70D));
}
#[test]
fn height_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Height::from_hex_str(hex);
assert!(result.is_err());
}
#[test]
fn parses_correctly_to_height_or_time() {
let lock = LockTime::from_consensus(750_000);
assert!(lock.is_block_height());
assert!(!lock.is_block_time());
let t: u32 = 1653195600; let lock = LockTime::from_consensus(t);
assert!(!lock.is_block_height());
assert!(lock.is_block_time());
}
#[test]
fn satisfied_by_height() {
let lock = LockTime::from_consensus(750_000);
let height = Height::from_consensus(800_000).expect("failed to parse height");
let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_time() {
let lock = LockTime::from_consensus(1053195600);
let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
let height = Height::from_consensus(800_000).expect("failed to parse height");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_same_height() {
let h = 750_000;
let lock = LockTime::from_consensus(h);
let height = Height::from_consensus(h).expect("failed to parse height");
let t: u32 = 1653195600; let time = Time::from_consensus(t).expect("invalid time value");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn satisfied_by_same_time() {
let t: u32 = 1653195600; let lock = LockTime::from_consensus(t);
let time = Time::from_consensus(t).expect("invalid time value");
let height = Height::from_consensus(800_000).expect("failed to parse height");
assert!(lock.is_satisfied_by(height, time))
}
#[test]
fn height_correctly_implies() {
let lock = LockTime::from_consensus(750_005);
assert!(!lock.is_implied_by(LockTime::from_consensus(750_004)));
assert!(lock.is_implied_by(LockTime::from_consensus(750_005)));
assert!(lock.is_implied_by(LockTime::from_consensus(750_006)));
}
#[test]
fn time_correctly_implies() {
let t: u32 = 1700000005;
let lock = LockTime::from_consensus(t);
assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
assert!(lock.is_implied_by(LockTime::from_consensus(1700000005)));
assert!(lock.is_implied_by(LockTime::from_consensus(1700000006)));
}
#[test]
fn incorrect_units_do_not_imply() {
let lock = LockTime::from_consensus(750_005);
assert!(!lock.is_implied_by(LockTime::from_consensus(1700000004)));
}
}