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

579 lines
21 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 [`Height`] and [`MedianTimePast`] types used by the `rust-bitcoin` `absolute::LockTime` type.
use core::convert::Infallible;
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use internals::error::InputString;
use crate::parse::{self, ParseIntError};
/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
///
/// `LockTime` values _below_ the threshold are interpreted as block heights, values _above_ (or
/// equal to) the threshold are interpreted as block times (UNIX timestamp, seconds since epoch).
///
/// Bitcoin is able to safely use this value because a block height greater than 500,000,000 would
/// never occur because it would represent a height in approximately 9500 years. Conversely, block
/// times under 500,000,000 will never happen because they would represent times before 1986 which
/// are, for obvious reasons, not useful within the Bitcoin network.
///
/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
/// An absolute block height, guaranteed to always contain a valid height value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Height(u32);
impl Height {
/// Absolute block height 0, the genesis block.
pub const ZERO: Self = Height(0);
/// The minimum absolute block height (0), the genesis block.
pub const MIN: Self = Self::ZERO;
/// The maximum absolute block height.
pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1);
/// Constructs a new [`Height`] from a hex string.
///
/// The input string may or may not contain a typical hex prefix e.g., `0x`.
///
/// # Errors
///
/// If the input string is not a valid hex representation of a block height.
pub fn from_hex(s: &str) -> Result<Self, ParseHeightError> { parse_hex(s, Self::from_u32) }
#[deprecated(since = "TBD", note = "use `from_u32` instead")]
#[doc(hidden)]
pub const fn from_consensus(n: u32) -> Result<Self, ConversionError> { Self::from_u32(n) }
#[deprecated(since = "TBD", note = "use `to_u32` instead")]
#[doc(hidden)]
pub const fn to_consensus_u32(self) -> u32 { self.to_u32() }
/// Constructs a new block height directly from a `u32` value.
///
/// # Errors
///
/// If `n` does not represent a block height within the valid range for a locktime:
/// `[0, 499_999_999]`.
///
/// # Examples
///
/// ```rust
/// use bitcoin_units::locktime::absolute::Height;
///
/// let h: u32 = 741521;
/// let height = Height::from_u32(h).expect("invalid height value");
/// assert_eq!(height.to_consensus_u32(), h);
/// ```
#[inline]
pub const fn from_u32(n: u32) -> Result<Height, ConversionError> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n))
}
}
/// Converts this [`Height`] to a raw `u32` value.
#[inline]
pub const fn to_u32(self) -> u32 { self.0 }
/// Returns true if a transaction with this locktime can be included in the next block.
///
/// `self` is value of the `LockTime` and if `height` is the current chain tip then
/// a transaction with this lock can be broadcast for inclusion in the next block.
#[inline]
pub fn is_satisfied_by(self, height: Height) -> bool {
// Use u64 so that there can be no overflow.
let next_block_height = u64::from(height.to_u32()) + 1;
u64::from(self.to_u32()) <= next_block_height
}
}
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_u32));
/// Error returned when parsing block height fails.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseHeightError(ParseError);
impl fmt::Display for ParseHeightError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseHeightError {
// To be consistent with `write_err` we need to **not** return source in case of overflow
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseHeightError {
fn from(value: ParseError) -> Self { Self(value) }
}
#[deprecated(since = "TBD", note = "use `MedianTimePast` instead")]
#[doc(hidden)]
pub type Time = MedianTimePast;
/// The median timestamp of 11 consecutive blocks, representing "the timestamp" of the
/// final block for locktime-checking purposes.
///
/// Time-based locktimes are not measured against the timestamps in individual block
/// headers, since these are not monotone and may be subject to miner manipulation.
/// Instead, locktimes use the "median-time-past" (MTP) of the most recent 11 blocks,
/// a quantity which is required by consensus to be monotone and which is difficult
/// for any individual miner to manipulate.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MedianTimePast(u32);
impl MedianTimePast {
/// The minimum MTP allowable in a locktime (Tue Nov 05 1985 00:53:20 GMT+0000).
pub const MIN: Self = MedianTimePast(LOCK_TIME_THRESHOLD);
/// The maximum MTP allowable in a locktime (Sun Feb 07 2106 06:28:15 GMT+0000).
pub const MAX: Self = MedianTimePast(u32::MAX);
/// Constructs an [`MedianTimePast`] by computing the mediantimepast from the last 11 block timestamps
///
/// Because block timestamps are not monotonic, this function internally sorts them;
/// it is therefore not important what order they appear in the array; use whatever
/// is most convenient.
///
/// # Errors
///
/// If the median block timestamp is not in the allowable range of MTPs in a
/// locktime: `[500_000_000, 2^32 - 1]`. Because there is a consensus rule that MTP
/// be monotonically increasing, and the MTP of the first 11 blocks exceeds `500_000_000`
/// for every real-life chain, this error typically cannot be hit in practice.
pub fn new(timestamps: [crate::BlockTime; 11]) -> Result<Self, ConversionError> {
crate::BlockMtp::new(timestamps).try_into()
}
/// Constructs a new [`MedianTimePast`] from a big-endian hex-encoded `u32`.
///
/// The input string may or may not contain a typical hex prefix e.g., `0x`.
///
/// # Errors
///
/// If the input string is not a valid hex representation of a block time.
pub fn from_hex(s: &str) -> Result<Self, ParseTimeError> { parse_hex(s, Self::from_u32) }
#[deprecated(since = "TBD", note = "use `from_u32` instead")]
#[doc(hidden)]
pub const fn from_consensus(n: u32) -> Result<Self, ConversionError> { Self::from_u32(n) }
#[deprecated(since = "TBD", note = "use `to_u32` instead")]
#[doc(hidden)]
pub const fn to_consensus_u32(self) -> u32 { self.to_u32() }
/// Constructs a new MTP directly from a `u32` value.
///
/// This function, with [`MedianTimePast::to_u32`], is used to obtain a raw MTP value. It is
/// **not** used to convert to or from a block timestamp, which is not a MTP.
///
/// # Errors
///
/// If `n` is not in the allowable range of MTPs in a locktime: `[500_000_000, 2^32 - 1]`.
///
/// # Examples
///
/// ```rust
/// use bitcoin_units::locktime::absolute::MedianTimePast;
///
/// let t: u32 = 1653195600; // May 22nd, 5am UTC.
/// let time = MedianTimePast::from_u32(t).expect("invalid time value");
/// assert_eq!(time.to_consensus_u32(), t);
/// ```
#[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))
}
}
/// Converts this [`MedianTimePast`] to a raw `u32` value.
#[inline]
pub const fn to_u32(self) -> u32 { self.0 }
/// Returns true if a transaction with this locktime can be included in the next block.
///
/// `self`is the value of the `LockTime` and if `time` is the median time past of the block at
/// the chain tip then a transaction with this lock can be broadcast for inclusion in the next
/// block.
#[inline]
pub fn is_satisfied_by(self, time: MedianTimePast) -> bool {
// The locktime check in Core during block validation uses the MTP
// of the previous block - which is the expected to be `time` here.
self <= time
}
}
impl fmt::Display for MedianTimePast {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
crate::impl_parse_str!(MedianTimePast, ParseTimeError, parser(MedianTimePast::from_u32));
/// Error returned when parsing block time fails.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseTimeError(ParseError);
impl fmt::Display for ParseTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseTimeError {
// To be consistent with `write_err` we need to **not** return source in case of overflow
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseTimeError {
fn from(value: ParseError) -> Self { Self(value) }
}
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::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)
}
/// Returns true if `n` is a block height i.e., less than 500,000,000.
pub const fn is_block_height(n: u32) -> bool { n < LOCK_TIME_THRESHOLD }
/// Returns true if `n` is a UNIX timestamp i.e., greater than or equal to 500,000,000.
pub const fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD }
/// Error returned when converting a `u32` to a lock time variant fails.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversionError {
/// The expected timelock unit, height (blocks) or time (seconds).
unit: LockTimeUnit,
/// The invalid input value.
input: u32,
}
impl ConversionError {
/// Constructs a new `ConversionError` from an invalid `n` when expecting a height value.
const fn invalid_height(n: u32) -> Self { Self { unit: LockTimeUnit::Blocks, input: n } }
/// Constructs a new `ConversionError` from an invalid `n` when expecting a time value.
const fn invalid_time(n: u32) -> Self { Self { unit: LockTimeUnit::Seconds, input: n } }
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConversionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
/// Describes the two types of locking, lock-by-height and lock-by-time.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
/// Lock by blockheight.
Blocks,
/// Lock by blocktime.
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTimeUnit as L;
match *self {
L::Blocks => write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
L::Seconds => write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
/// Internal - common representation for height and time.
#[derive(Debug, Clone, Eq, PartialEq)]
enum ParseError {
ParseInt(ParseIntError),
// unit implied by outer type
// we use i64 to have nicer messages for negative values
Conversion(i64),
}
impl From<Infallible> for ParseError {
fn from(never: Infallible) -> Self { match never {} }
}
impl ParseError {
fn invalid_int<S: Into<InputString>>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self {
move |source| {
Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
}
}
fn display(
&self,
f: &mut fmt::Formatter<'_>,
subject: &str,
lower_bound: u32,
upper_bound: u32,
) -> fmt::Result {
use core::num::IntErrorKind;
match self {
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::PosOverflow =>
{
// Outputs "failed to parse <input_string> as absolute Height/MedianTimePast (<subject> is above limit <upper_bound>)"
write!(
f,
"{} ({} is above limit {})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject,
upper_bound
)
}
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::NegOverflow =>
{
// Outputs "failed to parse <input_string> as absolute Height/MedianTimePast (<subject> is below limit <lower_bound>)"
write!(
f,
"{} ({} is below limit {})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject,
lower_bound
)
}
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
write!(
f,
"{} ({})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject
)
}
Self::Conversion(value) if *value < i64::from(lower_bound) => {
write!(f, "{} {} is below limit {}", subject, value, lower_bound)
}
Self::Conversion(value) => {
write!(f, "{} {} is above limit {}", subject, value, upper_bound)
}
}
}
// To be consistent with `write_err` we need to **not** return source in case of overflow
#[cfg(feature = "std")]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use core::num::IntErrorKind;
match self {
Self::ParseInt(ParseIntError { source, .. })
if *source.kind() == IntErrorKind::PosOverflow =>
None,
Self::ParseInt(ParseIntError { source, .. })
if *source.kind() == IntErrorKind::NegOverflow =>
None,
Self::ParseInt(ParseIntError { source, .. }) => Some(source),
Self::Conversion(_) => None,
}
}
}
impl From<ConversionError> for ParseError {
fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
}
#[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(Height::MIN),
1 => Ok(Height::MAX),
_ => {
let min = Height::MIN.to_u32();
let max = Height::MAX.to_u32();
let h = u.int_in_range(min..=max)?;
Ok(Height::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(MedianTimePast::MIN),
1 => Ok(MedianTimePast::MAX),
_ => {
let min = MedianTimePast::MIN.to_u32();
let max = MedianTimePast::MAX.to_u32();
let t = u.int_in_range(min..=max)?;
Ok(MedianTimePast::from_u32(t).unwrap())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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 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]
#[cfg(feature = "alloc")]
fn locktime_unit_display() {
use alloc::format;
let blocks = LockTimeUnit::Blocks;
let seconds = LockTimeUnit::Seconds;
assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
}
#[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),
];
// Try various reorderings
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();
// lock is satisfied if transaction can go in the next block (height <= chain_tip + 1).
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));
// It is not satisfied if the lock height is after the next block.
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();
// lock is satisfied if transaction can go in the next block (locktime <= mtp).
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));
// It is not satisfied if the lock time is after the median time past.
let locktime = MedianTimePast::from_u32(500_000_002).unwrap();
assert!(!locktime.is_satisfied_by(mtp));
}
}