use core::cmp::Ordering;
use core::fmt;
use core::str::FromStr;
use io::{Read, Write};
#[cfg(all(test, mutate))]
use mutagen::mutate;
use units::parse::{self, ParseIntError};
#[cfg(doc)]
use crate::absolute;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError};
use crate::prelude::{Box, String};
#[rustfmt::skip] #[doc(inline)]
pub use units::locktime::absolute::{
Height, Time, LOCK_TIME_THRESHOLD, ConversionError, ParseHeightError, ParseTimeError,
};
#[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;
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let stripped = if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else if let Some(stripped) = s.strip_prefix("0X") {
stripped
} else {
return Err(MissingPrefixError::new(s).into());
};
let lock_time = parse::hex_u32(stripped)?;
Ok(Self::from_consensus(lock_time))
}
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
if s.starts_with("0x") || s.starts_with("0X") {
return Err(ContainsPrefixError::new(s).into());
}
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
#[inline]
pub fn from_consensus(n: u32) -> Self {
if units::locktime::absolute::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, ConversionError> {
let height = Height::from_consensus(n)?;
Ok(LockTime::Blocks(height))
}
#[inline]
pub fn from_time(n: u32) -> Result<Self, ConversionError> {
let time = Time::from_consensus(n)?;
Ok(LockTime::Seconds(time))
}
#[inline]
pub const fn is_same_unit(&self, other: LockTime) -> bool {
matches!(
(self, other),
(LockTime::Blocks(_), LockTime::Blocks(_))
| (LockTime::Seconds(_), LockTime::Seconds(_))
)
}
#[inline]
pub const fn is_block_height(&self) -> bool { matches!(*self, LockTime::Blocks(_)) }
#[inline]
pub const 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 FromStr for LockTime {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse::int::<u32, &str>(s).map(LockTime::from_consensus)
}
}
impl TryFrom<&str> for LockTime {
type Error = ParseIntError;
fn try_from(s: &str) -> Result<Self, Self::Error> { LockTime::from_str(s) }
}
impl TryFrom<String> for LockTime {
type Error = ParseIntError;
fn try_from(s: String) -> Result<Self, Self::Error> { LockTime::from_str(&s) }
}
impl TryFrom<Box<str>> for LockTime {
type Error = ParseIntError;
fn try_from(s: Box<str>) -> Result<Self, Self::Error> { LockTime::from_str(&s) }
}
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 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<'de> serde::de::Visitor<'de> 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> {
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> {
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)
}
}
#[cfg(feature = "ordered")]
impl ordered::ArbitraryOrd for LockTime {
fn arbitrary_cmp(&self, other: &Self) -> Ordering {
use LockTime::*;
match (self, other) {
(Blocks(_), Seconds(_)) => Ordering::Less,
(Seconds(_), Blocks(_)) => Ordering::Greater,
(Blocks(this), Blocks(that)) => this.cmp(that),
(Seconds(this), Seconds(that)) => this.cmp(that),
}
}
}
#[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 lock_time_from_hex_lower() {
let lock = LockTime::from_hex("0x6289c350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_hex_upper() {
let lock = LockTime::from_hex("0X6289C350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_unprefixed_hex_lower() {
let lock = LockTime::from_unprefixed_hex("6289c350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_unprefixed_hex_upper() {
let lock = LockTime::from_unprefixed_hex("6289C350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_invalid_hex_should_err() {
let hex = "0xzb93";
let result = LockTime::from_hex(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)));
}
}