rust-bitcoin-unsafe-fast/units/src/locktime/relative.rs

428 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: CC0-1.0
//! Provides [`NumberOfBlocks`] and [`NumberOf512Seconds`] types used by the `rust-bitcoin` `relative::LockTime` type.
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")]
#[doc(hidden)]
pub type Height = NumberOfBlocks;
/// A relative lock time lock-by-blockheight value.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NumberOfBlocks(u16);
impl NumberOfBlocks {
/// Relative block height 0, can be included in any block.
pub const ZERO: Self = Self(0);
/// The minimum relative block height (0), can be included in any block.
pub const MIN: Self = Self::ZERO;
/// The maximum relative block height.
pub const MAX: Self = Self(u16::MAX);
/// Constructs a new [`NumberOfBlocks`] using a count of blocks.
#[inline]
pub const fn from_height(blocks: u16) -> Self { Self(blocks) }
/// Express the [`Height`] as a count of blocks.
#[inline]
#[must_use]
pub const fn to_height(self) -> u16 { self.0 }
/// Returns the inner `u16` value.
#[inline]
#[must_use]
#[deprecated(since = "TBD", note = "use `to_height` instead")]
#[doc(hidden)]
pub const fn value(self) -> u16 { self.0 }
/// Returns the `u32` value used to encode this locktime in an nSequence field or
/// argument to `OP_CHECKSEQUENCEVERIFY`.
#[deprecated(
since = "TBD",
note = "use `LockTime::from` followed by `to_consensus_u32` instead"
)]
pub const fn to_consensus_u32(self) -> u32 {
self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
}
/// Determines whether a relativeheight locktime has matured, taking into account
/// both the chain tip and the height at which the UTXO was confirmed.
///
/// If you have two height intervals `x` and `y`, and want to know whether `x`
/// is satisfied by `y`, use `x >= y`.
///
/// # Parameters
/// - `self` the relative blockheight delay (`h`) required after confirmation.
/// - `chain_tip` the height of the current chain tip
/// - `utxo_mined_at` the height of the UTXOs confirmation block
///
/// # Returns
/// - `true` if a UTXO locked by `self` can be spent in a block after `chain_tip`.
/// - `false` if the UTXO is still locked at `chain_tip`.
pub fn is_satisfied_by(
self,
chain_tip: crate::BlockHeight,
utxo_mined_at: crate::BlockHeight,
) -> bool {
chain_tip
.checked_sub(utxo_mined_at)
.and_then(|diff: crate::BlockHeightInterval| diff.try_into().ok())
.map_or(false, |diff: Self| diff >= self)
}
}
impl From<u16> for NumberOfBlocks {
#[inline]
fn from(value: u16) -> Self { NumberOfBlocks(value) }
}
crate::impl_parse_str_from_int_infallible!(NumberOfBlocks, u16, from);
impl fmt::Display for NumberOfBlocks {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
#[deprecated(since = "TBD", note = "use `NumberOf512Seconds` instead")]
#[doc(hidden)]
pub type Time = NumberOf512Seconds;
/// A relative lock time lock-by-blocktime value.
///
/// For BIP 68 relative lock-by-blocktime locks, time is measured in 512 second intervals.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NumberOf512Seconds(u16);
impl NumberOf512Seconds {
/// Relative block time 0, can be included in any block.
pub const ZERO: Self = NumberOf512Seconds(0);
/// The minimum relative block time (0), can be included in any block.
pub const MIN: Self = NumberOf512Seconds::ZERO;
/// The maximum relative block time (33,554,432 seconds or approx 388 days).
pub const MAX: Self = NumberOf512Seconds(u16::MAX);
/// Constructs a new [`NumberOf512Seconds`] using time intervals where each interval is equivalent to 512 seconds.
///
/// Encoding finer granularity of time for relative lock-times is not supported in Bitcoin.
#[inline]
pub const fn from_512_second_intervals(intervals: u16) -> Self { NumberOf512Seconds(intervals) }
/// Express the [`NumberOf512Seconds`] as an integer number of 512-second intervals.
#[inline]
#[must_use]
pub const fn to_512_second_intervals(self) -> u16 { self.0 }
/// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second interval with
/// truncating division.
///
/// # Errors
///
/// Will return an error if the input cannot be encoded in 16 bits.
#[inline]
#[rustfmt::skip] // moves comments to unrelated code
pub const fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
let interval = seconds / 512;
if interval <= u16::MAX as u32 { // infallible cast, needed by const code
Ok(NumberOf512Seconds::from_512_second_intervals(interval as u16)) // Cast checked above, needed by const code.
} else {
Err(TimeOverflowError { seconds })
}
}
/// Constructs a new [`NumberOf512Seconds`] from seconds, converting the seconds into 512 second intervals with
/// ceiling division.
///
/// # Errors
///
/// Will return an error if the input cannot be encoded in 16 bits.
#[inline]
#[rustfmt::skip] // moves comments to unrelated code
pub const fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
if seconds <= u16::MAX as u32 * 512 {
let interval = (seconds + 511) / 512;
Ok(NumberOf512Seconds::from_512_second_intervals(interval as u16)) // Cast checked above, needed by const code.
} else {
Err(TimeOverflowError { seconds })
}
}
/// Represents the [`NumberOf512Seconds`] as an integer number of seconds.
#[inline]
pub const fn to_seconds(self) -> u32 {
self.0 as u32 * 512 // u16->u32 cast ok, const context
}
/// Returns the inner `u16` value.
#[inline]
#[must_use]
#[deprecated(since = "TBD", note = "use `to_512_second_intervals` instead")]
#[doc(hidden)]
pub const fn value(self) -> u16 { self.0 }
/// Returns the `u32` value used to encode this locktime in an nSequence field or
/// argument to `OP_CHECKSEQUENCEVERIFY`.
#[deprecated(
since = "TBD",
note = "use `LockTime::from` followed by `to_consensus_u32` instead"
)]
pub const fn to_consensus_u32(self) -> u32 {
(1u32 << 22) | self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
}
/// Determines whether a relativetime lock has matured, taking into account both
/// the UTXOs Median Time Past at confirmation and the required delay.
///
/// If you have two MTP intervals `x` and `y`, and want to know whether `x`
/// is satisfied by `y`, use `x >= y`.
///
/// # Parameters
/// - `self` the relative time delay (`t`) in 512second intervals.
/// - `chain_tip` the MTP of the current chain tip
/// - `utxo_mined_at` the MTP of the UTXOs confirmation block
///
/// # Returns
/// - `true` if the relativetime lock has expired by the tips MTP
/// - `false` if the lock has not yet expired by the tips MTP
pub fn is_satisfied_by(
self,
chain_tip: crate::BlockMtp,
utxo_mined_at: crate::BlockMtp,
) -> bool {
chain_tip
.checked_sub(utxo_mined_at)
.and_then(|diff: crate::BlockMtpInterval| diff.to_relative_mtp_interval_floor().ok())
.map_or(false, |diff: Self| diff >= self)
}
}
crate::impl_parse_str_from_int_infallible!(NumberOf512Seconds, u16, from_512_second_intervals);
impl fmt::Display for NumberOf512Seconds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
/// Error returned when the input time in seconds was too large to be encoded to a 16 bit 512 second interval.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimeOverflowError {
/// Time interval value in seconds that overflowed.
// Private because we maintain an invariant that the `seconds` value does actually overflow.
pub(crate) seconds: u32,
}
impl TimeOverflowError {
/// Constructs a new `TimeOverflowError` using `seconds`.
///
/// # Panics
///
/// If `seconds` would not actually overflow a `u16`.
pub fn new(seconds: u32) -> Self {
assert!(u16::try_from((seconds + 511) / 512).is_err());
Self { seconds }
}
}
impl fmt::Display for TimeOverflowError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{} seconds is too large to be encoded to a 16 bit 512 second interval",
self.seconds
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for TimeOverflowError {}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for NumberOfBlocks {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?;
match choice {
0 => Ok(NumberOfBlocks::MIN),
1 => Ok(NumberOfBlocks::MAX),
_ => Ok(NumberOfBlocks::from_height(u16::arbitrary(u)?)),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for NumberOf512Seconds {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?;
match choice {
0 => Ok(NumberOf512Seconds::MIN),
1 => Ok(NumberOf512Seconds::MAX),
_ => Ok(NumberOf512Seconds::from_512_second_intervals(u16::arbitrary(u)?)),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "serde")]
use internals::serde_round_trip;
use super::*;
use crate::BlockTime;
const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512;
#[test]
#[allow(deprecated_in_future)]
fn sanity_check() {
assert_eq!(NumberOfBlocks::MAX.to_consensus_u32(), u32::from(u16::MAX));
assert_eq!(NumberOf512Seconds::from_512_second_intervals(100).value(), 100u16);
assert_eq!(
NumberOf512Seconds::from_512_second_intervals(100).to_consensus_u32(),
4_194_404u32
); // 0x400064
}
#[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());
}
#[test]
#[cfg(feature = "serde")]
pub fn encode_decode_height() {
serde_round_trip!(NumberOfBlocks::ZERO);
serde_round_trip!(NumberOfBlocks::MIN);
serde_round_trip!(NumberOfBlocks::MAX);
}
#[test]
#[cfg(feature = "serde")]
pub fn encode_decode_time() {
serde_round_trip!(NumberOf512Seconds::ZERO);
serde_round_trip!(NumberOf512Seconds::MIN);
serde_round_trip!(NumberOf512Seconds::MAX);
}
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 test_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);
// Test case 1: Satisfaction (current_mtp >= utxo_mtp + required_seconds)
// 10 intervals × 512 seconds = 5120 seconds
let time_lock = 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(chain_state1, utxo_state1));
// Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds)
let chain_state2 = BlockMtp::new(timestamps2);
let utxo_state2 = BlockMtp::new(utxo_timestamps2);
assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2));
// Test case 3: Test with a larger value (100 intervals = 51200 seconds)
let larger_lock = 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(chain_state3, utxo_state3));
// Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_time_lock = NumberOf512Seconds::MAX;
let chain_state4 = BlockMtp::new(timestamps);
let utxo_state4 = BlockMtp::new(utxo_timestamps);
assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4));
}
#[test]
fn test_height_chain_state() {
use crate::BlockHeight;
let height_lock = NumberOfBlocks(10);
// Test case 1: Satisfaction (current_height >= utxo_height + required)
let chain_state1 = BlockHeight::from_u32(100);
let utxo_state1 = BlockHeight::from_u32(80);
assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1));
// Test case 2: Not satisfied (current_height < utxo_height + required)
let chain_state2 = BlockHeight::from_u32(89);
let utxo_state2 = BlockHeight::from_u32(80);
assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2));
// Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_height_lock = NumberOfBlocks::MAX;
let chain_state3 = BlockHeight::from_u32(1000);
let utxo_state3 = BlockHeight::from_u32(80);
assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3));
}
}