use std::cmp::Ordering;
use std::fmt::{self, Display, Formatter};
use std::num::ParseIntError;
use std::str::FromStr;
use chrono::Utc;
use crate::LIB_NAME_BITCOIN;
pub const LOCKTIME_THRESHOLD: u32 = 500_000_000;
pub const SEQ_NO_CSV_DISABLE_MASK: u32 = 0x80000000;
pub const SEQ_NO_CSV_TYPE_MASK: u32 = 0x00400000;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display("invalid timelock value {0}")]
pub struct InvalidTimelock(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, From, Display)]
#[display(doc_comments)]
pub enum TimelockParseError {
#[from]
InvalidNumber(ParseIntError),
InvalidHeight(u32),
InvalidTimestamp(u32),
InvalidDescriptor(String),
NoRand,
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
pub struct LockTime(u32);
impl PartialOrd for LockTime {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.is_height_based() != other.is_height_based() {
None
} else {
Some(self.0.cmp(&other.0))
}
}
}
impl LockTime {
pub const ZERO: Self = Self(0);
#[inline]
#[deprecated(since = "0.10.8", note = "use LockTime::ZERO")]
pub const fn zero() -> Self { Self(0) }
#[inline]
pub const fn from_height(height: u32) -> Option<Self> {
if height < LOCKTIME_THRESHOLD {
Some(Self(height))
} else {
None
}
}
#[inline]
pub const fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
if timestamp < LOCKTIME_THRESHOLD {
None
} else {
Some(Self(timestamp))
}
}
#[inline]
pub const fn from_consensus_u32(lock_time: u32) -> Self { LockTime(lock_time) }
#[inline]
pub const fn to_consensus_u32(&self) -> u32 { self.0 }
#[inline]
pub const fn into_consensus_u32(self) -> u32 { self.0 }
#[inline]
pub const fn is_height_based(self) -> bool { self.0 < LOCKTIME_THRESHOLD }
#[inline]
pub const fn is_time_based(self) -> bool { !self.is_height_based() }
}
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
pub struct LockTimestamp(u32);
impl From<LockTimestamp> for u32 {
fn from(lock_timestamp: LockTimestamp) -> Self { lock_timestamp.into_consensus_u32() }
}
impl From<LockTimestamp> for LockTime {
fn from(lock: LockTimestamp) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
}
impl TryFrom<u32> for LockTimestamp {
type Error = InvalidTimelock;
fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
}
impl TryFrom<LockTime> for LockTimestamp {
type Error = InvalidTimelock;
fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
Self::try_from_lock_time(lock_time)
}
}
impl LockTimestamp {
#[inline]
pub fn anytime() -> Self { Self(0) }
#[cfg(feature = "chrono")]
pub fn since_now() -> Self {
let now = Utc::now();
LockTimestamp::from_unix_timestamp(now.timestamp() as u32)
.expect("we are too far in the future")
}
#[inline]
pub fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
if timestamp < LOCKTIME_THRESHOLD {
None
} else {
Some(Self(timestamp))
}
}
#[inline]
pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
Self::try_from_consensus_u32(lock_time.into_consensus_u32())
}
#[inline]
pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
if !LockTime::from_consensus_u32(lock_time).is_time_based() {
return Err(InvalidTimelock(lock_time));
}
Ok(Self(lock_time))
}
#[inline]
pub const fn to_consensus_u32(&self) -> u32 { self.0 }
#[inline]
pub const fn into_consensus_u32(self) -> u32 { self.0 }
#[inline]
pub fn into_lock_time(self) -> LockTime { self.into() }
#[inline]
pub fn to_lock_time(self) -> LockTime { self.into_lock_time() }
}
impl Display for LockTimestamp {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("time(")?;
Display::fmt(&self.0, f)?;
f.write_str(")")
}
}
impl FromStr for LockTimestamp {
type Err = TimelockParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
if s == "0" || s == "none" {
Ok(LockTimestamp::anytime())
} else if s.starts_with("time(") && s.ends_with(')') {
let no = s[5..].trim_end_matches(')').parse()?;
LockTimestamp::try_from(no).map_err(|_| TimelockParseError::InvalidTimestamp(no))
} else {
Err(TimelockParseError::InvalidDescriptor(s))
}
}
}
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
pub struct LockHeight(u32);
impl From<LockHeight> for u32 {
fn from(lock_height: LockHeight) -> Self { lock_height.into_consensus_u32() }
}
impl From<LockHeight> for LockTime {
fn from(lock: LockHeight) -> Self { LockTime::from_consensus_u32(lock.into_consensus_u32()) }
}
impl TryFrom<u32> for LockHeight {
type Error = InvalidTimelock;
fn try_from(value: u32) -> Result<Self, Self::Error> { Self::try_from_consensus_u32(value) }
}
impl TryFrom<LockTime> for LockHeight {
type Error = InvalidTimelock;
fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
Self::try_from_lock_time(lock_time)
}
}
impl LockHeight {
#[inline]
pub fn anytime() -> Self { Self(0) }
#[inline]
pub fn from_height(height: u32) -> Option<Self> {
if height < LOCKTIME_THRESHOLD {
Some(Self(height))
} else {
None
}
}
#[inline]
pub const fn try_from_lock_time(lock_time: LockTime) -> Result<Self, InvalidTimelock> {
Self::try_from_consensus_u32(lock_time.into_consensus_u32())
}
#[inline]
pub const fn try_from_consensus_u32(lock_time: u32) -> Result<Self, InvalidTimelock> {
if !LockTime::from_consensus_u32(lock_time).is_height_based() {
return Err(InvalidTimelock(lock_time));
}
Ok(Self(lock_time))
}
#[inline]
pub const fn to_consensus_u32(&self) -> u32 { self.0 }
#[inline]
pub const fn into_consensus_u32(self) -> u32 { self.0 }
#[inline]
pub fn to_lock_time(&self) -> LockTime { self.into_lock_time() }
#[inline]
pub fn into_lock_time(self) -> LockTime { self.into() }
}
impl Display for LockHeight {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str("height(")?;
Display::fmt(&self.0, f)?;
f.write_str(")")
}
}
impl FromStr for LockHeight {
type Err = TimelockParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
if s == "0" || s == "none" {
Ok(LockHeight::anytime())
} else if s.starts_with("height(") && s.ends_with(')') {
let no = s[7..].trim_end_matches(')').parse()?;
LockHeight::try_from(no).map_err(|_| TimelockParseError::InvalidHeight(no))
} else {
Err(TimelockParseError::InvalidDescriptor(s))
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
pub struct SeqNo(u32);
impl SeqNo {
pub const ZERO: SeqNo = SeqNo(0);
#[inline]
pub const fn from_consensus_u32(lock_time: u32) -> Self { SeqNo(lock_time) }
#[inline]
pub const fn to_consensus_u32(&self) -> u32 { self.0 }
#[inline]
pub const fn from_height(blocks: u16) -> SeqNo { SeqNo(blocks as u32) }
#[inline]
pub const fn from_intervals(intervals: u16) -> SeqNo {
SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK)
}
pub const fn time_lock_interval(self) -> Option<TimeLockInterval> {
if self.0 & SEQ_NO_CSV_DISABLE_MASK != 0 {
None
} else if self.0 & SEQ_NO_CSV_TYPE_MASK != 0 {
Some(TimeLockInterval::Time((self.0 & 0xFFFF) as u16))
} else {
Some(TimeLockInterval::Height((self.0 & 0xFFFF) as u16))
}
}
pub const fn is_timelock(self) -> bool { self.0 & SEQ_NO_CSV_DISABLE_MASK > 1 }
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_BITCOIN, tags = order)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub enum TimeLockInterval {
#[display("height({0})")]
Height(u16),
#[display("time({0})")]
Time(u16),
}
impl Default for TimeLockInterval {
fn default() -> Self { TimeLockInterval::Height(default!()) }
}