pub mod error;
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use internals::error::InputString;
use self::error::ParseError;
#[cfg(doc)]
use crate::absolute;
use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
#[rustfmt::skip] #[doc(no_inline)]
pub use self::error::{
ConversionError, IncompatibleHeightError, IncompatibleTimeError, ParseHeightError, ParseTimeError,
};
#[cfg(feature = "encoding")]
pub use self::error::LockTimeDecoderError;
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum LockTime {
Blocks(Height),
Seconds(MedianTimePast),
}
impl LockTime {
pub const ZERO: Self = Self::Blocks(Height::ZERO);
pub const SIZE: usize = 4;
#[inline]
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let lock_time = parse_int::hex_u32_prefixed(s)?;
Ok(Self::from_consensus(lock_time))
}
#[inline]
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
let lock_time = parse_int::hex_u32_unprefixed(s)?;
Ok(Self::from_consensus(lock_time))
}
#[inline]
#[allow(clippy::missing_panics_doc)]
pub fn from_consensus(n: u32) -> Self {
if crate::locktime::absolute::is_block_height(n) {
Self::Blocks(Height::from_u32(n).expect("n is valid"))
} else {
Self::Seconds(MedianTimePast::from_u32(n).expect("n is valid"))
}
}
#[inline]
pub fn from_height(n: u32) -> Result<Self, ConversionError> {
let height = Height::from_u32(n)?;
Ok(Self::Blocks(height))
}
#[inline]
#[deprecated(since = "1.0.0-rc.0", note = "use `from_mtp` instead")]
#[doc(hidden)]
pub fn from_time(n: u32) -> Result<Self, ConversionError> { Self::from_mtp(n) }
#[inline]
pub fn from_mtp(n: u32) -> Result<Self, ConversionError> {
let time = MedianTimePast::from_u32(n)?;
Ok(Self::Seconds(time))
}
#[inline]
pub const fn is_same_unit(self, other: Self) -> bool {
matches!(
(self, other),
(Self::Blocks(_), Self::Blocks(_)) | (Self::Seconds(_), Self::Seconds(_))
)
}
#[inline]
pub const fn is_block_height(self) -> bool { matches!(self, Self::Blocks(_)) }
#[inline]
pub const fn is_block_time(self) -> bool { !self.is_block_height() }
#[inline]
pub fn is_satisfied_by(self, height: Height, mtp: MedianTimePast) -> bool {
match self {
Self::Blocks(blocks) => blocks.is_satisfied_by(height),
Self::Seconds(time) => time.is_satisfied_by(mtp),
}
}
#[inline]
pub fn is_satisfied_by_height(self, height: Height) -> Result<bool, IncompatibleHeightError> {
match self {
Self::Blocks(blocks) => Ok(blocks.is_satisfied_by(height)),
Self::Seconds(time) =>
Err(IncompatibleHeightError { lock: time, incompatible: height }),
}
}
#[inline]
pub fn is_satisfied_by_time(self, mtp: MedianTimePast) -> Result<bool, IncompatibleTimeError> {
match self {
Self::Seconds(time) => Ok(time.is_satisfied_by(mtp)),
Self::Blocks(blocks) => Err(IncompatibleTimeError { lock: blocks, incompatible: mtp }),
}
}
#[inline]
pub fn is_implied_by(self, other: Self) -> bool {
match (self, other) {
(Self::Blocks(this), Self::Blocks(other)) => this <= other,
(Self::Seconds(this), Self::Seconds(other)) => this <= other,
_ => false, }
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
match self {
Self::Blocks(ref h) => h.to_u32(),
Self::Seconds(ref t) => t.to_u32(),
}
}
}
parse_int::impl_parse_str_from_int_infallible!(LockTime, u32, from_consensus);
#[cfg(feature = "encoding")]
encoding::encoder_newtype_exact! {
pub struct LockTimeEncoder<'e>(encoding::ArrayEncoder<4>);
}
#[cfg(feature = "encoding")]
impl encoding::Encodable for LockTime {
type Encoder<'e> = LockTimeEncoder<'e>;
fn encoder(&self) -> Self::Encoder<'_> {
LockTimeEncoder::new(encoding::ArrayEncoder::without_length_prefix(
self.to_consensus_u32().to_le_bytes(),
))
}
}
#[cfg(feature = "encoding")]
pub struct LockTimeDecoder(encoding::ArrayDecoder<4>);
#[cfg(feature = "encoding")]
impl LockTimeDecoder {
pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
}
#[cfg(feature = "encoding")]
impl Default for LockTimeDecoder {
fn default() -> Self { Self::new() }
}
#[cfg(feature = "encoding")]
impl encoding::Decoder for LockTimeDecoder {
type Output = LockTime;
type Error = LockTimeDecoderError;
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
Ok(self.0.push_bytes(bytes).map_err(LockTimeDecoderError)?)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
let n = u32::from_le_bytes(self.0.end().map_err(LockTimeDecoderError)?);
Ok(LockTime::from_consensus(n))
}
#[inline]
fn read_limit(&self) -> usize { self.0.read_limit() }
}
#[cfg(feature = "encoding")]
impl encoding::Decodable for LockTime {
type Decoder = LockTimeDecoder;
fn decoder() -> Self::Decoder { LockTimeDecoder(encoding::ArrayDecoder::<4>::new()) }
}
impl From<Height> for LockTime {
#[inline]
fn from(h: Height) -> Self { Self::Blocks(h) }
}
impl From<MedianTimePast> for LockTime {
#[inline]
fn from(t: MedianTimePast) -> Self { Self::Seconds(t) }
}
impl fmt::Debug for LockTime {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Blocks(ref h) => write!(f, "{} blocks", h),
Self::Seconds(ref t) => write!(f, "{} seconds", t),
}
}
}
impl fmt::Display for LockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
match *self {
Self::Blocks(ref h) => write!(f, "block-height {}", h),
Self::Seconds(ref t) => write!(f, "block-time {} (seconds since epoch)", t),
}
} else {
match *self {
Self::Blocks(ref h) => fmt::Display::fmt(h, f),
Self::Seconds(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for LockTime {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.to_consensus_u32().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LockTime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
u32::deserialize(deserializer).map(Self::from_consensus)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Height(u32);
impl Height {
pub const ZERO: Self = Self(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Self(LOCK_TIME_THRESHOLD - 1);
pub fn from_hex(s: &str) -> Result<Self, ParseHeightError> { parse_hex(s, Self::from_u32) }
#[deprecated(since = "1.0.0-rc.0", note = "use `from_u32` instead")]
#[doc(hidden)]
pub const fn from_consensus(n: u32) -> Result<Self, ConversionError> { Self::from_u32(n) }
#[deprecated(since = "1.0.0-rc.0", note = "use `to_u32` instead")]
#[doc(hidden)]
pub const fn to_consensus_u32(self) -> u32 { self.to_u32() }
#[inline]
pub const fn from_u32(n: u32) -> Result<Self, ConversionError> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n))
}
}
#[inline]
pub const fn to_u32(self) -> u32 { self.0 }
#[inline]
pub fn is_satisfied_by(self, height: Self) -> bool {
let next_block_height = u64::from(height.to_u32()) + 1;
u64::from(self.to_u32()) <= next_block_height
}
}
crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(Height);
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
parse_int::impl_parse_str!(Height, ParseHeightError, parser(Height::from_u32));
#[deprecated(since = "1.0.0-rc.0", note = "use `MedianTimePast` instead")]
#[doc(hidden)]
pub type Time = MedianTimePast;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MedianTimePast(u32);
impl MedianTimePast {
pub const MIN: Self = Self(LOCK_TIME_THRESHOLD);
pub const MAX: Self = Self(u32::MAX);
pub fn new(timestamps: [crate::BlockTime; 11]) -> Result<Self, ConversionError> {
crate::BlockMtp::new(timestamps).try_into()
}
pub fn from_hex(s: &str) -> Result<Self, ParseTimeError> { parse_hex(s, Self::from_u32) }
#[deprecated(since = "1.0.0-rc.0", note = "use `from_u32` instead")]
#[doc(hidden)]
pub const fn from_consensus(n: u32) -> Result<Self, ConversionError> { Self::from_u32(n) }
#[deprecated(since = "1.0.0-rc.0", note = "use `to_u32` instead")]
#[doc(hidden)]
pub const fn to_consensus_u32(self) -> u32 { self.to_u32() }
#[inline]
pub const fn from_u32(n: u32) -> Result<Self, ConversionError> {
if is_block_time(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_time(n))
}
}
#[inline]
pub const fn to_u32(self) -> u32 { self.0 }
#[inline]
pub fn is_satisfied_by(self, time: Self) -> bool {
self <= time
}
}
crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(MedianTimePast);
impl fmt::Display for MedianTimePast {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
parse_int::impl_parse_str!(MedianTimePast, ParseTimeError, parser(MedianTimePast::from_u32));
fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
where
E: From<ParseError>,
S: AsRef<str> + Into<InputString>,
F: FnOnce(u32) -> Result<T, ConversionError>,
{
move |s| {
let n = s.as_ref().parse::<i64>().map_err(ParseError::invalid_int(s))?;
let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
f(n).map_err(ParseError::from).map_err(Into::into)
}
}
fn parse_hex<T, E, S, F>(s: S, f: F) -> Result<T, E>
where
E: From<ParseError>,
S: AsRef<str> + Into<InputString>,
F: FnOnce(u32) -> Result<T, ConversionError>,
{
let n = i64::from_str_radix(parse_int::hex_remove_optional_prefix(s.as_ref()), 16)
.map_err(ParseError::invalid_int(s))?;
let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
f(n).map_err(ParseError::from).map_err(Into::into)
}
pub const fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
pub const fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for LockTime {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let l = u32::arbitrary(u)?;
Ok(Self::from_consensus(l))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Height {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?;
match choice {
0 => Ok(Self::MIN),
1 => Ok(Self::MAX),
_ => {
let min = Self::MIN.to_u32();
let max = Self::MAX.to_u32();
let h = u.int_in_range(min..=max)?;
Ok(Self::from_u32(h).unwrap())
}
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for MedianTimePast {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?;
match choice {
0 => Ok(Self::MIN),
1 => Ok(Self::MAX),
_ => {
let min = Self::MIN.to_u32();
let max = Self::MAX.to_u32();
let t = u.int_in_range(min..=max)?;
Ok(Self::from_u32(t).unwrap())
}
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, format, string::String};
use super::*;
#[test]
#[cfg(feature = "alloc")]
fn display_and_alternate() {
let lock_by_height = LockTime::from_height(741_521).unwrap();
let lock_by_time = LockTime::from_mtp(1_653_195_600).unwrap();
assert_eq!(format!("{}", lock_by_height), "741521");
assert_eq!(format!("{:#}", lock_by_height), "block-height 741521");
assert!(!format!("{:?}", lock_by_height).is_empty());
assert_eq!(format!("{}", lock_by_time), "1653195600");
assert_eq!(format!("{:#}", lock_by_time), "block-time 1653195600 (seconds since epoch)");
assert!(!format!("{:?}", lock_by_time).is_empty());
}
#[test]
fn roundtrips() {
let lock_by_height = LockTime::from_consensus(741_521);
let lock_by_time = LockTime::from_consensus(1_653_195_600);
assert_eq!(lock_by_height.to_consensus_u32(), 741_521);
assert_eq!(lock_by_time.to_consensus_u32(), 1_653_195_600);
}
#[test]
fn lock_time_from_hex_lower() {
let lock_by_time = LockTime::from_hex("0x6289c350").unwrap();
assert_eq!(lock_by_time, LockTime::from_consensus(0x6289_C350));
}
#[test]
fn lock_time_from_hex_upper() {
let lock_by_time = LockTime::from_hex("0X6289C350").unwrap();
assert_eq!(lock_by_time, LockTime::from_consensus(0x6289_C350));
}
#[test]
fn lock_time_from_unprefixed_hex_lower() {
let lock_by_time = LockTime::from_unprefixed_hex("6289c350").unwrap();
assert_eq!(lock_by_time, LockTime::from_consensus(0x6289_C350));
}
#[test]
fn lock_time_from_unprefixed_hex_upper() {
let lock_by_time = LockTime::from_unprefixed_hex("6289C350").unwrap();
assert_eq!(lock_by_time, LockTime::from_consensus(0x6289_C350));
}
#[test]
fn invalid_hex() {
assert!(LockTime::from_hex("0xzb93").is_err());
assert!(LockTime::from_unprefixed_hex("zb93").is_err());
}
#[test]
fn invalid_locktime_type() {
assert!(LockTime::from_height(499_999_999).is_ok()); assert!(LockTime::from_height(500_000_000).is_err()); assert!(LockTime::from_height(500_000_001).is_err());
assert!(LockTime::from_mtp(499_999_999).is_err()); assert!(LockTime::from_mtp(500_000_000).is_ok()); assert!(LockTime::from_mtp(500_000_001).is_ok()); }
#[test]
fn parses_correctly_to_height_or_time() {
let lock_by_height = LockTime::from_consensus(750_000);
assert!(lock_by_height.is_block_height());
assert!(!lock_by_height.is_block_time());
let t: u32 = 1_653_195_600; let lock_by_time = LockTime::from_consensus(t);
assert!(!lock_by_time.is_block_height());
assert!(lock_by_time.is_block_time());
assert!(lock_by_height.is_same_unit(LockTime::from_consensus(800_000)));
assert!(!lock_by_height.is_same_unit(lock_by_time));
assert!(lock_by_time.is_same_unit(LockTime::from_consensus(1_653_282_000)));
assert!(!lock_by_time.is_same_unit(lock_by_height));
}
#[test]
fn satisfied_by_height() {
let height_below = Height::from_u32(700_000).unwrap();
let height = Height::from_u32(750_000).unwrap();
let height_above = Height::from_u32(800_000).unwrap();
let lock_by_height = LockTime::from(height);
let t: u32 = 1_653_195_600; let time = MedianTimePast::from_u32(t).unwrap();
assert!(!lock_by_height.is_satisfied_by(height_below, time));
assert!(lock_by_height.is_satisfied_by(height, time));
assert!(lock_by_height.is_satisfied_by(height_above, time));
}
#[test]
fn satisfied_by_time() {
let time_before = MedianTimePast::from_u32(1_653_109_200).unwrap(); let time = MedianTimePast::from_u32(1_653_195_600).unwrap(); let time_after = MedianTimePast::from_u32(1_653_282_000).unwrap();
let lock_by_time = LockTime::from(time);
let height = Height::from_u32(800_000).unwrap();
assert!(!lock_by_time.is_satisfied_by(height, time_before));
assert!(lock_by_time.is_satisfied_by(height, time));
assert!(lock_by_time.is_satisfied_by(height, time_after));
}
#[test]
fn height_correctly_implies() {
let lock_by_height = LockTime::from_consensus(750_005);
assert!(!lock_by_height.is_implied_by(LockTime::from_consensus(750_004)));
assert!(lock_by_height.is_implied_by(LockTime::from_consensus(750_005)));
assert!(lock_by_height.is_implied_by(LockTime::from_consensus(750_006)));
}
#[test]
fn time_correctly_implies() {
let t: u32 = 1_700_000_005;
let lock_by_time = LockTime::from_consensus(t);
assert!(!lock_by_time.is_implied_by(LockTime::from_consensus(1_700_000_004)));
assert!(lock_by_time.is_implied_by(LockTime::from_consensus(1_700_000_005)));
assert!(lock_by_time.is_implied_by(LockTime::from_consensus(1_700_000_006)));
}
#[test]
fn incorrect_units_do_not_imply() {
let lock_by_height = LockTime::from_consensus(750_005);
assert!(!lock_by_height.is_implied_by(LockTime::from_consensus(1_700_000_004)));
}
#[test]
fn time_from_str_hex_happy_path() {
let actual = MedianTimePast::from_hex("0x6289C350").unwrap();
let expected = MedianTimePast::from_u32(0x6289_C350).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn time_from_str_hex_no_prefix_happy_path() {
let time = MedianTimePast::from_hex("6289C350").unwrap();
assert_eq!(time, MedianTimePast(0x6289_C350));
}
#[test]
fn time_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = MedianTimePast::from_hex(hex);
assert!(result.is_err());
}
#[test]
fn height_from_str_hex_happy_path() {
let actual = Height::from_hex("0xBA70D").unwrap();
let expected = Height(0xBA70D);
assert_eq!(actual, expected);
}
#[test]
fn height_from_str_hex_no_prefix_happy_path() {
let height = Height::from_hex("BA70D").unwrap();
assert_eq!(height, Height(0xBA70D));
}
#[test]
fn height_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Height::from_hex(hex);
assert!(result.is_err());
}
#[test]
fn height_try_from_stringlike_happy_path() {
let want = Height::from_u32(10).unwrap();
assert_eq!("10".parse::<Height>().unwrap(), want);
assert_eq!(Height::try_from("10").unwrap(), want);
#[cfg(feature = "alloc")]
{
assert_eq!(Height::try_from(String::from("10")).unwrap(), want);
assert_eq!(Height::try_from(Box::<str>::from("10")).unwrap(), want);
}
}
#[test]
fn height_try_from_stringlike_hex_error_path() {
assert!("0xab".parse::<Height>().is_err());
assert!(Height::try_from("0xab").is_err());
#[cfg(feature = "alloc")]
{
assert!(Height::try_from(String::from("0xab")).is_err());
assert!(Height::try_from(Box::<str>::from("0xab")).is_err());
}
}
#[test]
fn height_try_from_stringlike_decimal_error_path() {
assert!("10.123".parse::<Height>().is_err());
assert!(Height::try_from("10.123").is_err());
#[cfg(feature = "alloc")]
{
assert!(Height::try_from(String::from("10.123")).is_err());
assert!(Height::try_from(Box::<str>::from("10.123")).is_err());
}
}
#[test]
fn is_block_height_or_time() {
assert!(is_block_height(499_999_999));
assert!(!is_block_height(500_000_000));
assert!(!is_block_time(499_999_999));
assert!(is_block_time(500_000_000));
}
#[test]
fn valid_chain_computes_mtp() {
use crate::BlockTime;
let mut timestamps = [
BlockTime::from_u32(500_000_010),
BlockTime::from_u32(500_000_003),
BlockTime::from_u32(500_000_005),
BlockTime::from_u32(500_000_008),
BlockTime::from_u32(500_000_001),
BlockTime::from_u32(500_000_004),
BlockTime::from_u32(500_000_006),
BlockTime::from_u32(500_000_009),
BlockTime::from_u32(500_000_002),
BlockTime::from_u32(500_000_007),
BlockTime::from_u32(500_000_000),
];
assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.reverse();
assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.sort();
assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.reverse();
assert_eq!(MedianTimePast::new(timestamps).unwrap().to_u32(), 500_000_005);
}
#[test]
fn height_is_satisfied_by() {
let chain_tip = Height::from_u32(100).unwrap();
let locktime = Height::from_u32(100).unwrap();
assert!(locktime.is_satisfied_by(chain_tip));
let locktime = Height::from_u32(101).unwrap();
assert!(locktime.is_satisfied_by(chain_tip));
let locktime = Height::from_u32(102).unwrap();
assert!(!locktime.is_satisfied_by(chain_tip));
}
#[test]
fn median_time_past_is_satisfied_by() {
let mtp = MedianTimePast::from_u32(500_000_001).unwrap();
let locktime = MedianTimePast::from_u32(500_000_000).unwrap();
assert!(locktime.is_satisfied_by(mtp));
let locktime = MedianTimePast::from_u32(500_000_001).unwrap();
assert!(locktime.is_satisfied_by(mtp));
let locktime = MedianTimePast::from_u32(500_000_002).unwrap();
assert!(!locktime.is_satisfied_by(mtp));
}
}