pub mod error;
use core::{convert, fmt};
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use internals::const_casts;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
#[cfg(doc)]
use crate::relative;
use crate::{BlockHeight, BlockMtp, Sequence};
#[rustfmt::skip] #[doc(no_inline)]
pub use self::error::{
DisabledLockTimeError, IncompatibleHeightError, IncompatibleTimeError, InvalidHeightError,
InvalidTimeError, IsSatisfiedByError, IsSatisfiedByHeightError, IsSatisfiedByTimeError,
TimeOverflowError,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LockTime {
Blocks(NumberOfBlocks),
Time(NumberOf512Seconds),
}
impl LockTime {
pub const ZERO: Self = Self::Blocks(NumberOfBlocks::ZERO);
pub const SIZE: usize = 4;
#[inline]
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 {
Self::Blocks(ref h) => u32::from(h.to_count()),
Self::Time(ref t) => Sequence::LOCK_TYPE_MASK | u32::from(t.to_512_second_intervals()),
}
}
#[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_block_count(n: u16) -> Self { Self::Blocks(NumberOfBlocks::from_count(n)) }
#[inline]
pub const fn from_512_second_intervals(intervals: u16) -> Self {
Self::Time(NumberOf512Seconds::from_512_second_intervals(intervals))
}
#[inline]
pub const fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
match NumberOf512Seconds::from_seconds_floor(seconds) {
Ok(time) => Ok(Self::Time(time)),
Err(e) => Err(e),
}
}
#[inline]
pub const fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
match NumberOf512Seconds::from_seconds_ceil(seconds) {
Ok(time) => Ok(Self::Time(time)),
Err(e) => Err(e),
}
}
#[inline]
pub const fn is_same_unit(self, other: Self) -> bool {
matches!((self, other), (Self::Blocks(_), Self::Blocks(_)) | (Self::Time(_), Self::Time(_)))
}
#[inline]
pub const fn is_block_count(self) -> bool { matches!(self, Self::Blocks(_)) }
#[inline]
pub const fn is_block_time(self) -> bool { !self.is_block_count() }
#[inline]
pub fn is_satisfied_by(
self,
chain_tip_height: BlockHeight,
chain_tip_mtp: BlockMtp,
utxo_mined_at_height: Option<BlockHeight>,
utxo_mined_at_mtp: Option<BlockMtp>,
) -> Result<bool, IsSatisfiedByError> {
match self {
Self::Blocks(blocks) => blocks
.is_satisfied_by(chain_tip_height, utxo_mined_at_height)
.map_err(IsSatisfiedByError::Blocks),
Self::Time(time) => time
.is_satisfied_by(chain_tip_mtp, utxo_mined_at_mtp)
.map_err(IsSatisfiedByError::Time),
}
}
#[inline]
pub fn is_satisfied_by_height(
self,
chain_tip: BlockHeight,
utxo_mined_at: Option<BlockHeight>,
) -> Result<bool, IsSatisfiedByHeightError> {
match self {
Self::Blocks(blocks) => blocks
.is_satisfied_by(chain_tip, utxo_mined_at)
.map_err(IsSatisfiedByHeightError::Satisfaction),
Self::Time(time) =>
Err(IncompatibleHeightError(time)).map_err(IsSatisfiedByHeightError::Incompatible),
}
}
#[inline]
pub fn is_satisfied_by_time(
self,
chain_tip: BlockMtp,
utxo_mined_at: Option<BlockMtp>,
) -> Result<bool, IsSatisfiedByTimeError> {
match self {
Self::Time(time) => time
.is_satisfied_by(chain_tip, utxo_mined_at)
.map_err(IsSatisfiedByTimeError::Satisfaction),
Self::Blocks(blocks) =>
Err(IncompatibleTimeError(blocks)).map_err(IsSatisfiedByTimeError::Incompatible),
}
}
#[inline]
pub fn is_implied_by(self, other: Self) -> bool {
match (self, other) {
(Self::Blocks(this), Self::Blocks(other)) => this <= other,
(Self::Time(this), Self::Time(other)) => this <= other,
_ => false, }
}
#[inline]
pub fn is_implied_by_sequence(self, other: Sequence) -> bool {
Self::from_sequence(other).is_ok_and(|other| self.is_implied_by(other))
}
}
impl From<NumberOfBlocks> for LockTime {
#[inline]
fn from(h: NumberOfBlocks) -> Self { Self::Blocks(h) }
}
impl From<NumberOf512Seconds> for LockTime {
#[inline]
fn from(t: NumberOf512Seconds) -> Self { Self::Time(t) }
}
impl fmt::Display for LockTime {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
match *self {
Self::Blocks(ref h) => write!(f, "block-count {}", h),
Self::Time(ref t) => write!(f, "block-time {} (512 second intervals)", t),
}
} else {
match *self {
Self::Blocks(ref h) => fmt::Display::fmt(h, f),
Self::Time(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
impl convert::TryFrom<Sequence> for LockTime {
type Error = DisabledLockTimeError;
#[inline]
fn try_from(seq: Sequence) -> Result<Self, DisabledLockTimeError> { Self::from_sequence(seq) }
}
impl From<LockTime> for Sequence {
#[inline]
fn from(lt: LockTime) -> Self { lt.to_sequence() }
}
#[cfg(feature = "serde")]
impl serde::Serialize for LockTime {
#[inline]
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 {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
u32::deserialize(deserializer)
.and_then(|n| Self::from_consensus(n).map_err(serde::de::Error::custom))
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumberOfBlocks(u16);
impl NumberOfBlocks {
pub const ZERO: Self = Self(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Self(u16::MAX);
#[inline]
pub const fn from_count(blocks: u16) -> Self { Self(blocks) }
#[inline]
#[must_use]
pub const fn to_count(self) -> u16 { self.0 }
#[inline]
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let block_count = parse_int::hex_u16_prefixed(s)?;
Ok(Self::from_count(block_count))
}
#[inline]
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
let block_count = parse_int::hex_u16_unprefixed(s)?;
Ok(Self::from_count(block_count))
}
#[inline]
pub fn is_satisfied_by(
self,
chain_tip: crate::BlockHeight,
utxo_mined_at: Option<crate::BlockHeight>,
) -> Result<bool, InvalidHeightError> {
utxo_mined_at.map_or(
Ok(self == Self::ZERO),
|mined_at| {
chain_tip
.checked_sub(mined_at)
.ok_or(InvalidHeightError { chain_tip, utxo_mined_at: mined_at })
.map(|diff| u32::from(self.to_count()).saturating_sub(1) <= diff.to_u32())
},
)
}
}
crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(NumberOfBlocks);
parse_int::impl_parse_str_from_int_infallible!(NumberOfBlocks, u16, from_count);
impl fmt::Display for NumberOfBlocks {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
#[cfg(feature = "serde")]
impl Serialize for NumberOfBlocks {
#[inline]
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
u16::serialize(&self.to_count(), s)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for NumberOfBlocks {
#[inline]
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from_count(u16::deserialize(d)?))
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumberOf512Seconds(u16);
impl NumberOf512Seconds {
pub const ZERO: Self = Self(0);
pub const MIN: Self = Self::ZERO;
pub const MAX: Self = Self(u16::MAX);
#[inline]
pub const fn from_512_second_intervals(intervals: u16) -> Self { Self(intervals) }
#[inline]
#[must_use]
pub const fn to_512_second_intervals(self) -> u16 { self.0 }
#[inline]
#[rustfmt::skip] pub const fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
let interval = seconds / 512;
if interval <= u16::MAX as u32 { Ok(Self::from_512_second_intervals(interval as u16)) } else {
Err(TimeOverflowError { seconds })
}
}
#[inline]
#[rustfmt::skip] pub const fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
if seconds <= u16::MAX as u32 * 512 {
let interval = seconds.div_ceil(512);
Ok(Self::from_512_second_intervals(interval as u16)) } else {
Err(TimeOverflowError { seconds })
}
}
#[inline]
pub const fn to_seconds(self) -> u32 { const_casts::u16_to_u32(self.0) * 512 }
#[inline]
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let block_count = parse_int::hex_u16_prefixed(s)?;
Ok(Self::from_512_second_intervals(block_count))
}
#[inline]
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
let block_count = parse_int::hex_u16_unprefixed(s)?;
Ok(Self::from_512_second_intervals(block_count))
}
#[inline]
pub fn is_satisfied_by(
self,
chain_tip: crate::BlockMtp,
utxo_mined_at: Option<crate::BlockMtp>,
) -> Result<bool, InvalidTimeError> {
utxo_mined_at.map_or(
Ok(self == Self::ZERO),
|mined_at| {
chain_tip
.checked_sub(mined_at)
.ok_or(InvalidTimeError { chain_tip, utxo_mined_at: mined_at })
.map(|diff| self.to_seconds() <= diff.to_u32())
},
)
}
}
crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(NumberOf512Seconds);
parse_int::impl_parse_str_from_int_infallible!(NumberOf512Seconds, u16, from_512_second_intervals);
impl fmt::Display for NumberOf512Seconds {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
#[cfg(feature = "serde")]
impl Serialize for NumberOf512Seconds {
#[inline]
fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
u16::serialize(&self.to_512_second_intervals(), s)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for NumberOf512Seconds {
#[inline]
fn deserialize<D>(d: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self::from_512_second_intervals(u16::deserialize(d)?))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for LockTime {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=1)?;
match choice {
0 => Ok(Self::Blocks(NumberOfBlocks::arbitrary(u)?)),
_ => Ok(Self::Time(NumberOf512Seconds::arbitrary(u)?)),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for NumberOfBlocks {
#[inline]
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),
_ => Ok(Self::from_count(u16::arbitrary(u)?)),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for NumberOf512Seconds {
#[inline]
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),
_ => Ok(Self::from_512_second_intervals(u16::arbitrary(u)?)),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::format;
use super::*;
use crate::{BlockHeight, BlockTime};
const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512;
#[test]
#[cfg(feature = "alloc")]
fn display_and_alternate() {
let lock_by_height = LockTime::from_block_count(10);
let lock_by_time = LockTime::from_512_second_intervals(70);
assert_eq!(format!("{}", lock_by_height), "10");
assert_eq!(format!("{:#}", lock_by_height), "block-count 10");
assert!(!format!("{:?}", lock_by_height).is_empty());
assert_eq!(format!("{}", lock_by_time), "70");
assert_eq!(format!("{:#}", lock_by_time), "block-time 70 (512 second intervals)");
assert!(!format!("{:?}", lock_by_time).is_empty());
}
#[test]
fn from_seconds_ceil_and_floor() {
let time = 70 * 512 + 1;
let lock_by_time = LockTime::from_seconds_ceil(time).unwrap();
assert_eq!(lock_by_time, LockTime::from_512_second_intervals(71));
let lock_by_time = LockTime::from_seconds_floor(time).unwrap();
assert_eq!(lock_by_time, LockTime::from_512_second_intervals(70));
let mut max_time = 0xffff * 512;
assert_eq!(LockTime::from_seconds_ceil(max_time), LockTime::from_seconds_floor(max_time));
max_time += 512;
assert!(LockTime::from_seconds_ceil(max_time).is_err());
assert!(LockTime::from_seconds_floor(max_time).is_err());
}
#[test]
fn parses_correctly_to_height_or_time() {
let height1 = NumberOfBlocks::from_count(10);
let height2 = NumberOfBlocks::from_count(11);
let time1 = NumberOf512Seconds::from_512_second_intervals(70);
let time2 = NumberOf512Seconds::from_512_second_intervals(71);
let lock_by_height1 = LockTime::from(height1);
let lock_by_height2 = LockTime::from(height2);
let lock_by_time1 = LockTime::from(time1);
let lock_by_time2 = LockTime::from(time2);
assert!(lock_by_height1.is_block_count());
assert!(!lock_by_height1.is_block_time());
assert!(!lock_by_time1.is_block_count());
assert!(lock_by_time1.is_block_time());
assert!(lock_by_height1.is_same_unit(lock_by_height2));
assert!(!lock_by_height1.is_same_unit(lock_by_time1));
assert!(lock_by_time1.is_same_unit(lock_by_time2));
assert!(!lock_by_time1.is_same_unit(lock_by_height1));
}
#[test]
fn height_correctly_implies() {
let height = NumberOfBlocks::from_count(10);
let lock_by_height = LockTime::from(height);
assert!(!lock_by_height.is_implied_by(LockTime::from(NumberOfBlocks::from_count(9))));
assert!(lock_by_height.is_implied_by(LockTime::from(NumberOfBlocks::from_count(10))));
assert!(lock_by_height.is_implied_by(LockTime::from(NumberOfBlocks::from_count(11))));
}
#[test]
fn time_correctly_implies() {
let time = NumberOf512Seconds::from_512_second_intervals(70);
let lock_by_time = LockTime::from(time);
assert!(!lock_by_time
.is_implied_by(LockTime::from(NumberOf512Seconds::from_512_second_intervals(69))));
assert!(lock_by_time
.is_implied_by(LockTime::from(NumberOf512Seconds::from_512_second_intervals(70))));
assert!(lock_by_time
.is_implied_by(LockTime::from(NumberOf512Seconds::from_512_second_intervals(71))));
}
#[test]
fn sequence_correctly_implies() {
let height = NumberOfBlocks::from_count(10);
let time = NumberOf512Seconds::from_512_second_intervals(70);
let lock_by_height = LockTime::from(height);
let lock_by_time = LockTime::from(time);
let seq_height = Sequence::from(lock_by_height);
let seq_time = Sequence::from(lock_by_time);
assert!(lock_by_height.is_implied_by_sequence(seq_height));
assert!(!lock_by_height.is_implied_by_sequence(seq_time));
assert!(lock_by_time.is_implied_by_sequence(seq_time));
assert!(!lock_by_time.is_implied_by_sequence(seq_height));
let disabled_sequence = Sequence::from_consensus(1 << 31);
assert!(!lock_by_height.is_implied_by_sequence(disabled_sequence));
assert!(!lock_by_time.is_implied_by_sequence(disabled_sequence));
}
#[test]
fn incorrect_units_do_not_imply() {
let time = NumberOf512Seconds::from_512_second_intervals(70);
let height = NumberOfBlocks::from_count(10);
let lock_by_time = LockTime::from(time);
assert!(!lock_by_time.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);
}
}
#[test]
#[cfg(feature = "alloc")]
fn disabled_locktime_error() {
let disabled_sequence = Sequence::from_consensus(1 << 31);
let err = LockTime::try_from(disabled_sequence).unwrap_err();
assert_eq!(err.disabled_locktime_value(), 1 << 31);
assert!(!format!("{}", err).is_empty());
}
#[test]
#[cfg(feature = "alloc")]
fn incompatible_height_error() {
let mined_at = BlockHeight::from_u32(700_000);
let chain_tip = BlockHeight::from_u32(800_000);
let lock_by_time = LockTime::from_512_second_intervals(70); let err = lock_by_time.is_satisfied_by_height(chain_tip, Some(mined_at)).unwrap_err();
let expected_time = NumberOf512Seconds::from_512_second_intervals(70);
assert_eq!(
err,
IsSatisfiedByHeightError::Incompatible(IncompatibleHeightError(expected_time))
);
assert!(!format!("{}", err).is_empty());
}
#[test]
#[cfg(feature = "alloc")]
fn invalid_height_error() {
let mined_at = BlockHeight::from_u32(900_000);
let chain_tip = BlockHeight::from_u32(800_000);
let block_count = NumberOfBlocks::from_count(70); let err = block_count.is_satisfied_by(chain_tip, Some(mined_at)).unwrap_err();
assert!(matches!(err, InvalidHeightError { chain_tip: _, utxo_mined_at: _ }));
}
#[test]
#[cfg(feature = "alloc")]
fn incompatible_time_error() {
let mined_at = BlockMtp::from_u32(1_234_567_890);
let chain_tip = BlockMtp::from_u32(1_600_000_000);
let lock_by_height = LockTime::from_block_count(10); let err = lock_by_height.is_satisfied_by_time(chain_tip, Some(mined_at)).unwrap_err();
let expected_height = NumberOfBlocks::from_count(10);
assert_eq!(
err,
IsSatisfiedByTimeError::Incompatible(IncompatibleTimeError(expected_height))
);
assert!(!format!("{}", err).is_empty());
}
#[test]
#[cfg(feature = "alloc")]
fn invalid_time_error() {
let mined_at = BlockMtp::from_u32(1_734_567_890);
let chain_tip = BlockMtp::from_u32(1_600_000_000);
let time_interval = NumberOf512Seconds::from_512_second_intervals(10); let err = time_interval.is_satisfied_by(chain_tip, Some(mined_at)).unwrap_err();
assert!(matches!(err, InvalidTimeError { chain_tip: _, utxo_mined_at: _ }));
}
#[test]
fn locktime_chain_state() {
fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] {
let mut timestamps = [BlockTime::from_u32(0); 11];
for (i, ts) in timestamps.iter_mut().enumerate() {
*ts = BlockTime::from_u32(start.saturating_sub((step * i as u16).into()));
}
timestamps
}
let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
let chain_height = BlockHeight::from_u32(100);
let chain_mtp = BlockMtp::new(timestamps);
let utxo_height = BlockHeight::from_u32(80);
let utxo_mtp = BlockMtp::new(utxo_timestamps);
let lock1 = LockTime::Blocks(NumberOfBlocks::from_count(10));
assert!(lock1
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let lock2 = LockTime::Blocks(NumberOfBlocks::from_count(21));
assert!(lock2
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let lock3 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(10));
assert!(lock3
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let lock4 = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(20000));
assert!(!lock4
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
assert!(LockTime::ZERO
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
assert!(LockTime::from_512_second_intervals(0)
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let lock6 = LockTime::from_seconds_floor(5000).unwrap();
assert!(lock6
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let max_height_lock = LockTime::Blocks(NumberOfBlocks::MAX);
assert!(!max_height_lock
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let max_time_lock = LockTime::Time(NumberOf512Seconds::MAX);
assert!(!max_time_lock
.is_satisfied_by(chain_height, chain_mtp, Some(utxo_height), Some(utxo_mtp))
.unwrap());
let max_chain_height = BlockHeight::from_u32(u32::MAX);
let max_chain_mtp = BlockMtp::new(generate_timestamps(u32::MAX, 100));
let max_utxo_height = BlockHeight::MAX;
let max_utxo_mtp = max_chain_mtp;
assert!(!max_height_lock
.is_satisfied_by(
max_chain_height,
max_chain_mtp,
Some(max_utxo_height),
Some(max_utxo_mtp)
)
.unwrap());
assert!(!max_time_lock
.is_satisfied_by(
max_chain_height,
max_chain_mtp,
Some(max_utxo_height),
Some(max_utxo_mtp)
)
.unwrap());
}
#[test]
fn sanity_check() {
assert_eq!(LockTime::from(NumberOfBlocks::MAX).to_consensus_u32(), u32::from(u16::MAX));
assert_eq!(
NumberOf512Seconds::from_512_second_intervals(100).to_512_second_intervals(),
100u16
);
assert_eq!(
LockTime::from(NumberOf512Seconds::from_512_second_intervals(100)).to_consensus_u32(),
4_194_404u32
); assert_eq!(NumberOf512Seconds::from_512_second_intervals(1).to_seconds(), 512);
}
#[test]
fn from_512_second_intervals_roundtrip() {
let intervals = 100_u16;
let locktime = NumberOf512Seconds::from_512_second_intervals(intervals);
assert_eq!(locktime.to_512_second_intervals(), intervals);
}
#[test]
fn from_seconds_ceil_success() {
let actual = NumberOf512Seconds::from_seconds_ceil(100).unwrap();
let expected = NumberOf512Seconds(1_u16);
assert_eq!(actual, expected);
}
#[test]
fn from_seconds_ceil_with_maximum_encodable_seconds_success() {
let actual = NumberOf512Seconds::from_seconds_ceil(MAXIMUM_ENCODABLE_SECONDS).unwrap();
let expected = NumberOf512Seconds(u16::MAX);
assert_eq!(actual, expected);
}
#[test]
fn from_seconds_ceil_causes_time_overflow_error() {
let result = NumberOf512Seconds::from_seconds_ceil(MAXIMUM_ENCODABLE_SECONDS + 1);
assert!(result.is_err());
}
#[test]
fn from_seconds_floor_success() {
let actual = NumberOf512Seconds::from_seconds_floor(100).unwrap();
let expected = NumberOf512Seconds(0_u16);
assert_eq!(actual, expected);
}
#[test]
fn from_seconds_floor_with_exact_interval() {
let actual = NumberOf512Seconds::from_seconds_floor(512).unwrap();
let expected = NumberOf512Seconds(1_u16);
assert_eq!(actual, expected);
}
#[test]
fn from_seconds_floor_with_maximum_encodable_seconds_success() {
let actual =
NumberOf512Seconds::from_seconds_floor(MAXIMUM_ENCODABLE_SECONDS + 511).unwrap();
let expected = NumberOf512Seconds(u16::MAX);
assert_eq!(actual, expected);
}
#[test]
fn from_seconds_floor_causes_time_overflow_error() {
let result = NumberOf512Seconds::from_seconds_floor(MAXIMUM_ENCODABLE_SECONDS + 512);
assert!(result.is_err());
}
fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] {
let mut timestamps = [BlockTime::from_u32(0); 11];
for (i, ts) in timestamps.iter_mut().enumerate() {
*ts = BlockTime::from_u32(start.saturating_sub((step * i as u16).into()));
}
timestamps
}
#[test]
fn time_chain_state() {
use crate::BlockMtp;
let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
let timestamps2: [BlockTime; 11] = generate_timestamps(1_599_995_119, 200);
let utxo_timestamps2: [BlockTime; 11] = generate_timestamps(1_599_990_000, 200);
let timestamps3: [BlockTime; 11] = generate_timestamps(1_600_050_000, 200);
let utxo_timestamps3: [BlockTime; 11] = generate_timestamps(1_599_990_000, 200);
let time_lock = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(10));
let chain_state1 = BlockMtp::new(timestamps);
let utxo_state1 = BlockMtp::new(utxo_timestamps);
assert!(time_lock.is_satisfied_by_time(chain_state1, Some(utxo_state1)).unwrap());
let chain_state2 = BlockMtp::new(timestamps2);
let utxo_state2 = BlockMtp::new(utxo_timestamps2);
assert!(!time_lock.is_satisfied_by_time(chain_state2, Some(utxo_state2)).unwrap());
let larger_lock = LockTime::Time(NumberOf512Seconds::from_512_second_intervals(100));
let chain_state3 = BlockMtp::new(timestamps3);
let utxo_state3 = BlockMtp::new(utxo_timestamps3);
assert!(larger_lock.is_satisfied_by_time(chain_state3, Some(utxo_state3)).unwrap());
let max_time_lock = LockTime::Time(NumberOf512Seconds::MAX);
let chain_state4 = BlockMtp::new(timestamps);
let utxo_state4 = BlockMtp::new(utxo_timestamps);
assert!(!max_time_lock.is_satisfied_by_time(chain_state4, Some(utxo_state4)).unwrap());
}
#[test]
fn height_chain_state() {
let height_lock = LockTime::Blocks(NumberOfBlocks(10));
let chain_state1 = BlockHeight::from_u32(89);
let utxo_state1 = BlockHeight::from_u32(80);
assert!(height_lock.is_satisfied_by_height(chain_state1, Some(utxo_state1)).unwrap());
let chain_state2 = BlockHeight::from_u32(88);
let utxo_state2 = BlockHeight::from_u32(80);
assert!(!height_lock.is_satisfied_by_height(chain_state2, Some(utxo_state2)).unwrap());
let max_height_lock = LockTime::Blocks(NumberOfBlocks::MAX);
let chain_state3 = BlockHeight::from_u32(1000);
let utxo_state3 = BlockHeight::from_u32(80);
assert!(!max_height_lock.is_satisfied_by_height(chain_state3, Some(utxo_state3)).unwrap());
}
#[test]
fn parent_child_can_be_mined_in_the_same_block_lock_by_count_if_zero() {
let height_lock = LockTime::Blocks(NumberOfBlocks::ZERO);
let chain_state = BlockHeight::from_u32(100);
assert!(height_lock.is_satisfied_by_height(chain_state, None).unwrap());
}
#[test]
fn parent_child_cannot_be_mined_in_the_same_block_lock_by_count_if_non_zero() {
let height_lock = LockTime::from_block_count(1);
let chain_state = BlockHeight::from_u32(100);
assert!(!height_lock.is_satisfied_by_height(chain_state, None).unwrap());
}
#[test]
fn parent_child_can_be_mined_in_the_same_block_lock_by_time_if_zero() {
let height_lock = LockTime::Time(NumberOf512Seconds::ZERO);
let chain_state = crate::BlockMtp::from_u32(83_176_862);
assert!(height_lock.is_satisfied_by_time(chain_state, None).unwrap());
}
#[test]
fn parent_child_cannot_be_mined_in_the_same_block_lock_by_time_if_non_zero() {
let height_lock = LockTime::from_512_second_intervals(1);
let chain_state = crate::BlockMtp::from_u32(83_176_862);
assert!(!height_lock.is_satisfied_by_time(chain_state, None).unwrap());
}
#[test]
fn satisfied_by_height_boundary() {
for n in [0, 2, 5, 9, 20, u16::MAX] {
let lock = NumberOfBlocks::from_count(n);
let mined_at = BlockHeight::from_u32(100);
let first_spendable_tip = BlockHeight::from_u32(100 + u32::from(n).saturating_sub(1));
assert!(
lock.is_satisfied_by(first_spendable_tip, Some(mined_at)).unwrap(),
"n={n} must be satisfied at tip {first_spendable_tip} ({n} confirmations)"
);
if n > 1 {
let too_early = BlockHeight::from_u32(100 + u32::from(n) - 2);
assert!(
!lock.is_satisfied_by(too_early, Some(mined_at)).unwrap(),
"n={n} must not be satisfied at tip {too_early}"
);
}
}
}
#[test]
fn max_height_satisfaction() {
let mined_at = BlockHeight::from_u32(u32::MIN);
let chain_tip = BlockHeight::from_u32(u32::MAX);
let block_height = NumberOfBlocks::from_count(10); assert!(block_height.is_satisfied_by(chain_tip, Some(mined_at)).unwrap());
}
}