#[cfg(feature = "ordered")]
use core::cmp::Ordering;
use core::{cmp, convert, fmt};
#[cfg(all(test, mutate))]
use mutagen::mutate;
#[cfg(doc)]
use crate::relative;
use crate::Sequence;
#[rustfmt::skip] #[doc(inline)]
pub use units::locktime::relative::{Height, Time, TimeOverflowError};
#[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 {
pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
pub const SIZE: usize = 4;
pub fn from_consensus(n: u32) -> Result<Self, DisabledLockTimeError> {
let sequence = crate::Sequence::from_consensus(n);
sequence.to_relative_lock_time().ok_or(DisabledLockTimeError(n))
}
#[inline]
pub fn to_consensus_u32(&self) -> u32 {
match self {
LockTime::Blocks(ref h) => h.to_consensus_u32(),
LockTime::Time(ref t) => t.to_consensus_u32(),
}
}
#[inline]
pub fn from_sequence(n: Sequence) -> Result<Self, DisabledLockTimeError> {
Self::from_consensus(n.to_consensus_u32())
}
#[inline]
pub fn to_sequence(&self) -> Sequence { Sequence::from_consensus(self.to_consensus_u32()) }
#[inline]
pub const fn from_height(n: u16) -> Self { LockTime::Blocks(Height::from_height(n)) }
#[inline]
pub const fn from_512_second_intervals(intervals: u16) -> Self {
LockTime::Time(Time::from_512_second_intervals(intervals))
}
#[inline]
pub const fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
match Time::from_seconds_floor(seconds) {
Ok(time) => Ok(LockTime::Time(time)),
Err(e) => Err(e),
}
}
#[inline]
pub const fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
match Time::from_seconds_ceil(seconds) {
Ok(time) => Ok(LockTime::Time(time)),
Err(e) => Err(e),
}
}
#[inline]
pub const fn is_same_unit(&self, other: LockTime) -> bool {
matches!(
(self, other),
(LockTime::Blocks(_), LockTime::Blocks(_)) | (LockTime::Time(_), LockTime::Time(_))
)
}
#[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, h: Height, t: Time) -> bool {
if let Ok(true) = self.is_satisfied_by_height(h) {
true
} else {
matches!(self.is_satisfied_by_time(t), Ok(true))
}
}
#[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.value() <= other.value(),
(Time(this), Time(other)) => this.value() <= other.value(),
_ => false, }
}
#[inline]
pub fn is_implied_by_sequence(&self, other: Sequence) -> bool {
if let Ok(other) = LockTime::from_sequence(other) {
self.is_implied_by(other)
} else {
false
}
}
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by_height(&self, height: Height) -> Result<bool, IncompatibleHeightError> {
use LockTime::*;
match *self {
Blocks(ref h) => Ok(h.value() <= height.value()),
Time(time) => Err(IncompatibleHeightError { height, time }),
}
}
#[inline]
#[cfg_attr(all(test, mutate), mutate)]
pub fn is_satisfied_by_time(&self, time: Time) -> Result<bool, IncompatibleTimeError> {
use LockTime::*;
match *self {
Time(ref t) => Ok(t.value() <= time.value()),
Blocks(height) => Err(IncompatibleTimeError { time, 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 PartialOrd for LockTime {
#[inline]
fn partial_cmp(&self, other: &LockTime) -> Option<cmp::Ordering> {
use LockTime::*;
match (*self, *other) {
(Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
(Time(ref a), Time(ref b)) => a.partial_cmp(b),
(_, _) => None,
}
}
}
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),
}
}
}
}
#[cfg(feature = "ordered")]
impl ordered::ArbitraryOrd for LockTime {
fn arbitrary_cmp(&self, other: &Self) -> Ordering {
use LockTime::*;
match (self, other) {
(Blocks(_), Time(_)) => Ordering::Less,
(Time(_), Blocks(_)) => Ordering::Greater,
(Blocks(this), Blocks(that)) => this.cmp(that),
(Time(this), Time(that)) => this.cmp(that),
}
}
}
impl convert::TryFrom<Sequence> for LockTime {
type Error = DisabledLockTimeError;
fn try_from(seq: Sequence) -> Result<LockTime, DisabledLockTimeError> {
LockTime::from_sequence(seq)
}
}
impl From<LockTime> for Sequence {
fn from(lt: LockTime) -> Sequence { lt.to_sequence() }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct DisabledLockTimeError(u32);
impl DisabledLockTimeError {
pub fn disabled_locktime_value(&self) -> u32 { self.0 }
}
impl fmt::Display for DisabledLockTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "lock time 0x{:08x} has disable flag set", self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for DisabledLockTimeError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct IncompatibleHeightError {
pub height: Height,
pub time: Time,
}
impl fmt::Display for IncompatibleHeightError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-blocktime lock {} with height: {}",
self.time, self.height
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleHeightError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct IncompatibleTimeError {
pub time: Time,
pub height: Height,
}
impl fmt::Display for IncompatibleTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-blockheight lock {} with time: {}",
self.height, self.time
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleTimeError {}
#[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)));
}
#[test]
fn consensus_round_trip() {
assert!(LockTime::from_consensus(1 << 31).is_err());
assert!(LockTime::from_consensus(1 << 30).is_ok());
assert_eq!(LockTime::from_consensus(65536), LockTime::from_consensus(0));
for val in [0u32, 1, 1000, 65535] {
let seq = Sequence::from_consensus(val);
let lt = LockTime::from_consensus(val).unwrap();
assert_eq!(lt.to_consensus_u32(), val);
assert_eq!(lt.to_sequence(), seq);
assert_eq!(LockTime::from_sequence(seq).unwrap().to_sequence(), seq);
let seq = Sequence::from_consensus(val + (1 << 22));
let lt = LockTime::from_consensus(val + (1 << 22)).unwrap();
assert_eq!(lt.to_consensus_u32(), val + (1 << 22));
assert_eq!(lt.to_sequence(), seq);
assert_eq!(LockTime::from_sequence(seq).unwrap().to_sequence(), seq);
}
}
}