use core::cmp::Ordering;
use core::fmt::{self, Display, Formatter};
use core::num::ParseIntError;
use core::str::FromStr;
use chrono::Utc;
pub const SEQ_NO_MAX_VALUE: u32 = 0xFFFFFFFF;
pub const SEQ_NO_SUBMAX_VALUE: u32 = 0xFFFFFFFE;
pub const SEQ_NO_CSV_DISABLE_MASK: u32 = 0x80000000;
pub const SEQ_NO_CSV_TYPE_MASK: u32 = 0x00400000;
pub const LOCKTIME_THRESHOLD: u32 = 500000000;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[derive(StrictEncode, StrictDecode)]
pub enum TimeLockInterval {
#[display("height({0})")]
Height(u16),
#[display("time({0})")]
Time(u16),
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub enum SeqNoClass {
Unencumbered,
RbfOnly,
RelativeTime,
RelativeHeight,
}
#[derive(Debug, Clone, PartialEq, Eq, From, Display)]
#[display(doc_comments)]
pub enum ParseError {
#[from]
InvalidNumber(ParseIntError),
InvalidHeight(u32),
InvalidTimestamp(u32),
InvalidDescriptor(String),
NoRand,
}
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseError::InvalidNumber(err) => Some(err),
_ => None,
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct SeqNo(#[from] u32);
impl From<SeqNo> for u32 {
fn from(seqno: SeqNo) -> Self {
seqno.into_consensus()
}
}
impl Default for SeqNo {
#[inline]
fn default() -> Self {
SeqNo(SEQ_NO_MAX_VALUE)
}
}
impl PartialOrd for SeqNo {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self.classify() != other.classify() {
None
} else {
Some(self.0.cmp(&other.0))
}
}
}
impl SeqNo {
#[inline]
pub fn unencumbered(max: bool) -> SeqNo {
SeqNo(if max {
SEQ_NO_MAX_VALUE
} else {
SEQ_NO_SUBMAX_VALUE
})
}
#[inline]
pub fn from_rbf(order: u16) -> SeqNo {
SeqNo(order as u32 | SEQ_NO_CSV_DISABLE_MASK)
}
#[inline]
pub fn rbf() -> SeqNo {
SeqNo(SEQ_NO_SUBMAX_VALUE - 1)
}
#[inline]
pub fn from_height(blocks: u16) -> SeqNo {
SeqNo(blocks as u32)
}
#[inline]
pub fn from_intervals(intervals: u16) -> SeqNo {
SeqNo(intervals as u32 | SEQ_NO_CSV_TYPE_MASK)
}
#[inline]
pub fn from_consensus(consensus: u32) -> SeqNo {
SeqNo(consensus)
}
#[inline]
pub fn classify(self) -> SeqNoClass {
match self.0 {
SEQ_NO_MAX_VALUE | SEQ_NO_SUBMAX_VALUE => SeqNoClass::Unencumbered,
no if no & SEQ_NO_CSV_DISABLE_MASK != 0 => SeqNoClass::RbfOnly,
no if no & SEQ_NO_CSV_TYPE_MASK != 0 => SeqNoClass::RelativeTime,
_ => SeqNoClass::RelativeHeight,
}
}
#[inline]
pub fn is_rbf(self) -> bool {
self.0 < SEQ_NO_SUBMAX_VALUE
}
#[inline]
pub fn is_timelock(self) -> bool {
self.0 & SEQ_NO_CSV_DISABLE_MASK > 1
}
#[inline]
pub fn into_consensus(self) -> u32 {
self.0
}
pub 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))
}
}
}
impl Display for SeqNo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.classify() {
SeqNoClass::Unencumbered if self.0 == SEQ_NO_MAX_VALUE => {
f.write_str("final(0xFFFFFFFF)")
}
SeqNoClass::Unencumbered if self.0 == SEQ_NO_SUBMAX_VALUE => {
f.write_str("non-rbf(0xFFFFFFFE)")
}
SeqNoClass::Unencumbered => unreachable!(),
SeqNoClass::RbfOnly => {
f.write_str("rbf(")?;
Display::fmt(&(self.0 ^ SEQ_NO_CSV_DISABLE_MASK), f)?;
f.write_str(")")
}
_ if self.0 >> 16 & 0xFFBF > 0 => Display::fmt(&self.0, f),
SeqNoClass::RelativeTime => {
let value = self.0 & 0xFFFF;
f.write_str("time(")?;
Display::fmt(&value, f)?;
f.write_str(")")
}
SeqNoClass::RelativeHeight => {
let value = self.0 & 0xFFFF;
f.write_str("height(")?;
Display::fmt(&value, f)?;
f.write_str(")")
}
}
}
}
impl FromStr for SeqNo {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
if s == "rbf" {
#[cfg(feature = "rand")]
{
Ok(SeqNo::rbf())
}
#[cfg(not(feature = "rand"))]
{
Err(ParseError::NoRand)
}
} else if s.starts_with("rbf(") && s.ends_with(')') {
let no = s[4..].trim_end_matches(')').parse()?;
Ok(SeqNo::from_rbf(no))
} else if s.starts_with("time(") && s.ends_with(')') {
let no = s[5..].trim_end_matches(')').parse()?;
Ok(SeqNo::from_intervals(no))
} else if s.starts_with("height(") && s.ends_with(')') {
let no = s[7..].trim_end_matches(')').parse()?;
Ok(SeqNo::from_height(no))
} else {
let no = s.parse()?;
Ok(SeqNo(no))
}
}
}
#[derive(
Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error
)]
#[display("invalid timelock value")]
pub struct InvalidTimelock;
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct LockTimestamp(u32);
impl From<LockTimestamp> for u32 {
fn from(lock_timestamp: LockTimestamp) -> Self {
lock_timestamp.into_consensus()
}
}
impl TryFrom<u32> for LockTimestamp {
type Error = InvalidTimelock;
fn try_from(value: u32) -> Result<Self, Self::Error> {
LockTime::from_consensus(value).try_into()
}
}
impl TryFrom<LockTime> for LockTimestamp {
type Error = InvalidTimelock;
fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
if !lock_time.is_time_based() {
return Err(InvalidTimelock);
}
Ok(Self(lock_time.into_consensus()))
}
}
impl LockTimestamp {
#[inline]
pub fn anytime() -> Self {
Self(0)
}
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 fn into_consensus(self) -> u32 {
self.0
}
#[inline]
pub fn into_locktime(self) -> LockTime {
self.into()
}
}
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 = ParseError;
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(|_| ParseError::InvalidTimestamp(no))
} else {
Err(ParseError::InvalidDescriptor(s))
}
}
}
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Hash, Debug, Default)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct LockHeight(u32);
impl From<LockHeight> for u32 {
fn from(lock_height: LockHeight) -> Self {
lock_height.into_consensus()
}
}
impl TryFrom<u32> for LockHeight {
type Error = InvalidTimelock;
fn try_from(value: u32) -> Result<Self, Self::Error> {
LockTime::from_consensus(value).try_into()
}
}
impl TryFrom<LockTime> for LockHeight {
type Error = InvalidTimelock;
fn try_from(lock_time: LockTime) -> Result<Self, Self::Error> {
if !lock_time.is_height_based() {
return Err(InvalidTimelock);
}
Ok(Self(lock_time.into_consensus()))
}
}
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 fn into_consensus(self) -> u32 {
self.0
}
#[inline]
pub fn into_locktime(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 = ParseError;
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(|_| ParseError::InvalidHeight(no))
} else {
Err(ParseError::InvalidDescriptor(s))
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, From, Default)]
#[derive(StrictEncode, StrictDecode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct LockTime(
#[from]
#[from(LockTimestamp)]
#[from(LockHeight)]
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 From<LockTime> for u32 {
fn from(lock_time: LockTime) -> Self {
lock_time.into_consensus()
}
}
impl LockTime {
#[inline]
pub fn anytime() -> Self {
Self(0)
}
pub fn since_now() -> Self {
let now = Utc::now();
LockTime::from_unix_timestamp(now.timestamp() as u32).expect("we are too far in the future")
}
#[inline]
pub fn from_height(height: u32) -> Option<Self> {
if height < LOCKTIME_THRESHOLD {
Some(Self(height))
} else {
None
}
}
#[inline]
pub fn from_unix_timestamp(timestamp: u32) -> Option<Self> {
if timestamp < LOCKTIME_THRESHOLD {
None
} else {
Some(Self(timestamp))
}
}
pub fn from_consensus(value: u32) -> Self {
Self(value)
}
#[inline]
pub fn is_height_based(self) -> bool {
self.0 < LOCKTIME_THRESHOLD
}
#[inline]
pub fn is_time_based(self) -> bool {
!self.is_height_based()
}
#[inline]
pub fn into_consensus(self) -> u32 {
self.0
}
}
impl Display for LockTime {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if self.is_height_based() {
f.write_str("height(")?;
Display::fmt(&self.0, f)?;
f.write_str(")")
} else {
f.write_str("time(")?;
Display::fmt(&self.0, f)?;
f.write_str(")")
}
}
}
impl FromStr for LockTime {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let s = s.to_lowercase();
if s == "0" || s == "none" {
Ok(LockTime::anytime())
} else if s.starts_with("height(") && s.ends_with(')') {
let no = s[7..].trim_end_matches(')').parse()?;
LockTime::from_height(no).ok_or(ParseError::InvalidHeight(no))
} else if s.starts_with("time(") && s.ends_with(')') {
let no = s[5..].trim_end_matches(')').parse()?;
LockTime::from_height(no).ok_or(ParseError::InvalidTimestamp(no))
} else {
Err(ParseError::InvalidDescriptor(s))
}
}
}