use core::fmt;
use core::convert::TryFrom;
use crate::parse::impl_parse_str_from_int_infallible;
use crate::prelude::*;
#[cfg(doc)]
use crate::relative;
#[allow(clippy::derive_ord_xor_partial_ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub enum LockTime {
Blocks(Height),
Time(Time),
}
impl LockTime {
#[inline]
pub fn is_satisfied_by(&self, h: Height, t: Time) -> bool {
if let Ok(true) = self.is_satisfied_by_height(h) {
true
} else if let Ok(true) = self.is_satisfied_by_time(t) {
true
} else {
false
}
}
#[inline]
pub fn is_implied_by(&self, other: LockTime) -> bool {
use LockTime::*;
match (*self, other) {
(Blocks(this), Blocks(other)) => this.value() <= other.value(),
(Time(this), Time(other)) => this.value() <= other.value(),
_ => false, }
}
#[inline]
pub fn is_satisfied_by_height(&self, h: Height) -> Result<bool, Error> {
use LockTime::*;
match *self {
Blocks(ref height) => Ok(height.value() <= h.value()),
Time(ref time) => Err(Error::IncompatibleTime(*self, *time)),
}
}
#[inline]
pub fn is_satisfied_by_time(&self, t: Time) -> Result<bool, Error> {
use LockTime::*;
match *self {
Time(ref time) => Ok(time.value() <= t.value()),
Blocks(ref height) => Err(Error::IncompatibleHeight(*self, *height)),
}
}
}
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::Time(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),
Time(ref t) => write!(f, "block-time {} (512 second intervals)", t),
}
} else {
match *self {
Blocks(ref h) => fmt::Display::fmt(h, f),
Time(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
#[derive(Debug, Default, 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(u16);
impl Height {
pub const ZERO: Self = Height(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Height(u16::max_value());
pub const fn min_value() -> Self { Self::MIN }
pub const fn max_value() -> Self { Self::MAX }
#[inline]
pub fn value(self) -> u16 {
self.0
}
}
impl From<u16> for Height {
#[inline]
fn from(value: u16) -> Self {
Height(value)
}
}
impl_parse_str_from_int_infallible!(Height, u16, from);
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, Default, 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(u16);
impl Time {
pub const ZERO: Self = Time(0);
pub const MIN: Self = Time::ZERO;
pub const MAX: Self = Time(u16::max_value());
pub const fn min_value() -> Self { Self::MIN }
pub const fn max_value() -> Self { Self::MAX }
#[inline]
pub fn from_512_second_intervals(intervals: u16) -> Self {
Time(intervals)
}
#[inline]
pub fn from_seconds_ceil(seconds: u32) -> Result<Self, Error> {
if let Ok(interval) = u16::try_from((seconds + 511) / 512) {
Ok(Time::from_512_second_intervals(interval))
} else {
Err(Error::IntegerOverflow(seconds))
}
}
#[inline]
pub fn value(self) -> u16 {
self.0
}
}
impl_parse_str_from_int_infallible!(Time, u16, from_512_second_intervals);
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Error {
IntegerOverflow(u32),
IncompatibleHeight(LockTime, Height),
IncompatibleTime(LockTime, Time),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::IntegerOverflow(val) => write!(f, "{} seconds is too large to be encoded to a 16 bit 512 second interval", val),
Self::IncompatibleHeight(lock, height) => write!(f, "tried to satisfy lock {} with height: {}", lock, height),
Self::IncompatibleTime(lock, time) => write!(f, "tried to satisfy lock {} with time: {}", lock, time),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use self::Error::*;
match *self {
IntegerOverflow(_) | IncompatibleHeight(_, _) | IncompatibleTime(_, _) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn satisfied_by_height() {
let height = Height::from(10);
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(height);
assert!(!lock.is_satisfied_by(Height::from(9), time));
assert!(lock.is_satisfied_by(Height::from(10), time));
assert!(lock.is_satisfied_by(Height::from(11), time));
}
#[test]
fn satisfied_by_time() {
let height = Height::from(10);
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(time);
assert!(!lock.is_satisfied_by(height, Time::from_512_second_intervals(69)));
assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(70)));
assert!(lock.is_satisfied_by(height, Time::from_512_second_intervals(71)));
}
#[test]
fn height_correctly_implies() {
let height = Height::from(10);
let lock = LockTime::from(height);
assert!(!lock.is_implied_by(LockTime::from(Height::from(9))));
assert!(lock.is_implied_by(LockTime::from(Height::from(10))));
assert!(lock.is_implied_by(LockTime::from(Height::from(11))));
}
#[test]
fn time_correctly_implies() {
let time = Time::from_512_second_intervals(70);
let lock = LockTime::from(time);
assert!(!lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(69))));
assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(70))));
assert!(lock.is_implied_by(LockTime::from(Time::from_512_second_intervals(71))));
}
#[test]
fn incorrect_units_do_not_imply() {
let time = Time::from_512_second_intervals(70);
let height = Height::from(10);
let lock = LockTime::from(time);
assert!(!lock.is_implied_by(LockTime::from(height)));
}
}