units: rename absolute::Time to absolute::Mtp

This is not a generic UNIX timestamp, but rather a MTP restricted to
have values between 500 million and u32::MAX. Most importantly, it is
*not* a blocktime, which is what is implied by its name and
constructors.
This commit is contained in:
Andrew Poelstra 2025-05-01 17:20:54 +00:00
parent 1aedc7e4d1
commit 8ffcd2cf30
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
6 changed files with 77 additions and 62 deletions

View File

@ -41,7 +41,11 @@ pub mod locktime {
/// Re-export everything from the `primitives::locktime::absolute` module. /// Re-export everything from the `primitives::locktime::absolute` module.
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
pub use primitives::locktime::absolute::{ConversionError, Height, LockTime, ParseHeightError, ParseTimeError, Time}; pub use primitives::locktime::absolute::{ConversionError, Height, LockTime, ParseHeightError, ParseTimeError, Mtp};
#[deprecated(since = "TBD", note = "use `Mtp` instead")]
#[doc(hidden)]
pub type Time = Mtp;
impl Encodable for LockTime { impl Encodable for LockTime {
#[inline] #[inline]

View File

@ -20,7 +20,7 @@ use primitives::Sequence;
use super::Weight; use super::Weight;
use crate::consensus::{self, encode, Decodable, Encodable}; use crate::consensus::{self, encode, Decodable, Encodable};
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
use crate::locktime::absolute::{self, Height, Time}; use crate::locktime::absolute::{self, Height, Mtp};
use crate::prelude::{Borrow, Vec}; use crate::prelude::{Borrow, Vec};
use crate::script::{Script, ScriptBuf, ScriptExt as _, ScriptExtPriv as _}; use crate::script::{Script, ScriptBuf, ScriptExt as _, ScriptExtPriv as _};
#[cfg(doc)] #[cfg(doc)]
@ -295,7 +295,7 @@ pub trait TransactionExt: sealed::Sealed {
/// By definition if the lock time is not enabled the transaction's absolute timelock is /// By definition if the lock time is not enabled the transaction's absolute timelock is
/// considered to be satisfied i.e., there are no timelock constraints restricting this /// considered to be satisfied i.e., there are no timelock constraints restricting this
/// transaction from being mined immediately. /// transaction from being mined immediately.
fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool; fn is_absolute_timelock_satisfied(&self, height: Height, time: Mtp) -> bool;
/// Returns `true` if this transactions nLockTime is enabled ([BIP-65]). /// Returns `true` if this transactions nLockTime is enabled ([BIP-65]).
/// ///
@ -393,7 +393,7 @@ impl TransactionExt for Transaction {
fn is_explicitly_rbf(&self) -> bool { self.input.iter().any(|input| input.sequence.is_rbf()) } fn is_explicitly_rbf(&self) -> bool { self.input.iter().any(|input| input.sequence.is_rbf()) }
fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool { fn is_absolute_timelock_satisfied(&self, height: Height, time: Mtp) -> bool {
if !self.is_lock_time_enabled() { if !self.is_lock_time_enabled() {
return true; return true;
} }

View File

@ -16,7 +16,11 @@ use crate::{absolute, Transaction};
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)] #[doc(inline)]
pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, ParseTimeError, Time, LOCK_TIME_THRESHOLD}; pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, ParseTimeError, Mtp, LOCK_TIME_THRESHOLD};
#[deprecated(since = "TBD", note = "use `Mtp` instead")]
#[doc(hidden)]
pub type Time = Mtp;
/// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds /// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds
/// since epoch). /// since epoch).
@ -79,7 +83,7 @@ pub enum LockTime {
/// assert!(n.is_block_time()); /// assert!(n.is_block_time());
/// assert_eq!(n.to_consensus_u32(), seconds); /// assert_eq!(n.to_consensus_u32(), seconds);
/// ``` /// ```
Seconds(Time), Seconds(Mtp),
} }
impl LockTime { impl LockTime {
@ -143,7 +147,7 @@ impl LockTime {
if units::locktime::absolute::is_block_height(n) { if units::locktime::absolute::is_block_height(n) {
Self::Blocks(Height::from_consensus(n).expect("n is valid")) Self::Blocks(Height::from_consensus(n).expect("n is valid"))
} else { } else {
Self::Seconds(Time::from_consensus(n).expect("n is valid")) Self::Seconds(Mtp::from_consensus(n).expect("n is valid"))
} }
} }
@ -192,7 +196,7 @@ impl LockTime {
/// ``` /// ```
#[inline] #[inline]
pub fn from_time(n: u32) -> Result<Self, ConversionError> { pub fn from_time(n: u32) -> Result<Self, ConversionError> {
let time = Time::from_consensus(n)?; let time = Mtp::from_consensus(n)?;
Ok(LockTime::Seconds(time)) Ok(LockTime::Seconds(time))
} }
@ -229,7 +233,7 @@ impl LockTime {
/// # use bitcoin_primitives::absolute; /// # use bitcoin_primitives::absolute;
/// // Can be implemented if block chain data is available. /// // Can be implemented if block chain data is available.
/// fn get_height() -> absolute::Height { todo!("return the current block height") } /// fn get_height() -> absolute::Height { todo!("return the current block height") }
/// fn get_time() -> absolute::Time { todo!("return the current block time") } /// fn get_time() -> absolute::Mtp { todo!("return the current block time") }
/// ///
/// let n = absolute::LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`. /// let n = absolute::LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`.
/// if n.is_satisfied_by(get_height(), get_time()) { /// if n.is_satisfied_by(get_height(), get_time()) {
@ -237,7 +241,7 @@ impl LockTime {
/// } /// }
/// ```` /// ````
#[inline] #[inline]
pub fn is_satisfied_by(self, height: Height, time: Time) -> bool { pub fn is_satisfied_by(self, height: Height, time: Mtp) -> bool {
use LockTime as L; use LockTime as L;
match self { match self {
@ -317,9 +321,9 @@ impl From<Height> for LockTime {
fn from(h: Height) -> Self { LockTime::Blocks(h) } fn from(h: Height) -> Self { LockTime::Blocks(h) }
} }
impl From<Time> for LockTime { impl From<Mtp> for LockTime {
#[inline] #[inline]
fn from(t: Time) -> Self { LockTime::Seconds(t) } fn from(t: Mtp) -> Self { LockTime::Seconds(t) }
} }
impl fmt::Debug for LockTime { impl fmt::Debug for LockTime {
@ -496,7 +500,7 @@ mod tests {
let lock_by_height = LockTime::from(height); let lock_by_height = LockTime::from(height);
let t: u32 = 1_653_195_600; // May 22nd, 5am UTC. let t: u32 = 1_653_195_600; // May 22nd, 5am UTC.
let time = Time::from_consensus(t).unwrap(); let time = Mtp::from_consensus(t).unwrap();
assert!(!lock_by_height.is_satisfied_by(height_below, time)); 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, time));
@ -505,9 +509,9 @@ mod tests {
#[test] #[test]
fn satisfied_by_time() { fn satisfied_by_time() {
let time_before = Time::from_consensus(1_653_109_200).unwrap(); // "May 21th 2022, 5am UTC. let time_before = Mtp::from_consensus(1_653_109_200).unwrap(); // "May 21th 2022, 5am UTC.
let time = Time::from_consensus(1_653_195_600).unwrap(); // "May 22nd 2022, 5am UTC. let time = Mtp::from_consensus(1_653_195_600).unwrap(); // "May 22nd 2022, 5am UTC.
let time_after = Time::from_consensus(1_653_282_000).unwrap(); // "May 23rd 2022, 5am UTC. let time_after = Mtp::from_consensus(1_653_282_000).unwrap(); // "May 23rd 2022, 5am UTC.
let lock_by_time = LockTime::from(time); let lock_by_time = LockTime::from(time);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: CC0-1.0 // SPDX-License-Identifier: CC0-1.0
//! Provides [`Height`] and [`Time`] types used by the `rust-bitcoin` `absolute::LockTime` type. //! Provides [`Height`] and [`Mtp`] types used by the `rust-bitcoin` `absolute::LockTime` type.
use core::convert::Infallible; use core::convert::Infallible;
use core::fmt; use core::fmt;
@ -125,22 +125,29 @@ impl serde::Serialize for Height {
} }
} }
/// A UNIX timestamp, seconds since epoch, guaranteed to always contain a valid time value. #[deprecated(since = "TBD", note = "use `Mtp` instead")]
#[doc(hidden)]
pub type Time = Mtp;
/// The median timestamp of 11 consecutive blocks, representing "the timestamp" of the
/// final block for locktime-checking purposes.
/// ///
/// Note that there is no manipulation of the inner value during construction or when using /// Time-based locktimes are not measured against the timestamps in individual block
/// `to_consensus_u32()`. Said another way, `Time(x)` means 'x seconds since epoch' _not_ '(x - /// headers, since these are not monotone and may be subject to miner manipulation.
/// threshold) seconds since epoch'. /// 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Time(u32); pub struct Mtp(u32);
impl Time { impl Mtp {
/// The minimum absolute block time (Tue Nov 05 1985 00:53:20 GMT+0000). /// The minimum MTP allowable in a locktime (Tue Nov 05 1985 00:53:20 GMT+0000).
pub const MIN: Self = Time(LOCK_TIME_THRESHOLD); pub const MIN: Self = Mtp(LOCK_TIME_THRESHOLD);
/// The maximum absolute block time (Sun Feb 07 2106 06:28:15 GMT+0000). /// The maximum MTP allowable in a locktime (Sun Feb 07 2106 06:28:15 GMT+0000).
pub const MAX: Self = Time(u32::MAX); pub const MAX: Self = Mtp(u32::MAX);
/// Constructs a new [`Time`] from a hex string. /// Constructs a new [`Mtp`] from a hex string.
/// ///
/// The input string may or may not contain a typical hex prefix e.g., `0x`. /// The input string may or may not contain a typical hex prefix e.g., `0x`.
/// ///
@ -158,14 +165,14 @@ impl Time {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use bitcoin_units::locktime::absolute::Time; /// use bitcoin_units::locktime::absolute::Mtp;
/// ///
/// let t: u32 = 1653195600; // May 22nd, 5am UTC. /// let t: u32 = 1653195600; // May 22nd, 5am UTC.
/// let time = Time::from_consensus(t).expect("invalid time value"); /// let time = Mtp::from_consensus(t).expect("invalid time value");
/// assert_eq!(time.to_consensus_u32(), t); /// assert_eq!(time.to_consensus_u32(), t);
/// ``` /// ```
#[inline] #[inline]
pub const fn from_consensus(n: u32) -> Result<Time, ConversionError> { pub const fn from_consensus(n: u32) -> Result<Mtp, ConversionError> {
if is_block_time(n) { if is_block_time(n) {
Ok(Self(n)) Ok(Self(n))
} else { } else {
@ -173,30 +180,30 @@ impl Time {
} }
} }
/// Converts this [`Time`] to its inner `u32` value. /// Converts this [`Mtp`] to its inner `u32` value.
#[inline] #[inline]
pub const fn to_consensus_u32(self) -> u32 { self.0 } pub const fn to_consensus_u32(self) -> u32 { self.0 }
} }
impl fmt::Display for Time { impl fmt::Display for Mtp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
} }
crate::impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus)); crate::impl_parse_str!(Mtp, ParseTimeError, parser(Mtp::from_consensus));
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Time { impl<'de> serde::Deserialize<'de> for Mtp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let u = u32::deserialize(deserializer)?; let u = u32::deserialize(deserializer)?;
Time::from_consensus(u).map_err(serde::de::Error::custom) Mtp::from_consensus(u).map_err(serde::de::Error::custom)
} }
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl serde::Serialize for Time { impl serde::Serialize for Mtp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,
@ -342,11 +349,11 @@ impl ParseError {
E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::PosOverflow => if *source.kind() == IntErrorKind::PosOverflow =>
{ {
// Outputs "failed to parse <input_string> as absolute Height/Time (<subject> is above limit <upper_bound>)" // Outputs "failed to parse <input_string> as absolute Height/Mtp (<subject> is above limit <upper_bound>)"
write!( write!(
f, f,
"{} ({} is above limit {})", "{} ({} is above limit {})",
input.display_cannot_parse("absolute Height/Time"), input.display_cannot_parse("absolute Height/Mtp"),
subject, subject,
upper_bound upper_bound
) )
@ -354,17 +361,17 @@ impl ParseError {
E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source }) E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::NegOverflow => if *source.kind() == IntErrorKind::NegOverflow =>
{ {
// Outputs "failed to parse <input_string> as absolute Height/Time (<subject> is below limit <lower_bound>)" // Outputs "failed to parse <input_string> as absolute Height/Mtp (<subject> is below limit <lower_bound>)"
write!( write!(
f, f,
"{} ({} is below limit {})", "{} ({} is below limit {})",
input.display_cannot_parse("absolute Height/Time"), input.display_cannot_parse("absolute Height/Mtp"),
subject, subject,
lower_bound lower_bound
) )
} }
E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => { E::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
write!(f, "{} ({})", input.display_cannot_parse("absolute Height/Time"), subject) write!(f, "{} ({})", input.display_cannot_parse("absolute Height/Mtp"), subject)
} }
E::Conversion(value) if *value < i64::from(lower_bound) => { E::Conversion(value) if *value < i64::from(lower_bound) => {
write!(f, "{} {} is below limit {}", subject, value, lower_bound) write!(f, "{} {} is below limit {}", subject, value, lower_bound)
@ -417,17 +424,17 @@ impl<'a> Arbitrary<'a> for Height {
} }
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Time { impl<'a> Arbitrary<'a> for Mtp {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?; let choice = u.int_in_range(0..=2)?;
match choice { match choice {
0 => Ok(Time::MIN), 0 => Ok(Mtp::MIN),
1 => Ok(Time::MAX), 1 => Ok(Mtp::MAX),
_ => { _ => {
let min = Time::MIN.to_consensus_u32(); let min = Mtp::MIN.to_consensus_u32();
let max = Time::MAX.to_consensus_u32(); let max = Mtp::MAX.to_consensus_u32();
let t = u.int_in_range(min..=max)?; let t = u.int_in_range(min..=max)?;
Ok(Time::from_consensus(t).unwrap()) Ok(Mtp::from_consensus(t).unwrap())
} }
} }
} }
@ -442,21 +449,21 @@ mod tests {
#[test] #[test]
fn time_from_str_hex_happy_path() { fn time_from_str_hex_happy_path() {
let actual = Time::from_hex("0x6289C350").unwrap(); let actual = Mtp::from_hex("0x6289C350").unwrap();
let expected = Time::from_consensus(0x6289_C350).unwrap(); let expected = Mtp::from_consensus(0x6289_C350).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn time_from_str_hex_no_prefix_happy_path() { fn time_from_str_hex_no_prefix_happy_path() {
let time = Time::from_hex("6289C350").unwrap(); let time = Mtp::from_hex("6289C350").unwrap();
assert_eq!(time, Time(0x6289_C350)); assert_eq!(time, Mtp(0x6289_C350));
} }
#[test] #[test]
fn time_from_str_hex_invalid_hex_should_err() { fn time_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93"; let hex = "0xzb93";
let result = Time::from_hex(hex); let result = Mtp::from_hex(hex);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -500,8 +507,8 @@ mod tests {
#[test] #[test]
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
pub fn encode_decode_time() { pub fn encode_decode_time() {
serde_round_trip!(Time::MIN); serde_round_trip!(Mtp::MIN);
serde_round_trip!(Time::MAX); serde_round_trip!(Mtp::MAX);
} }
#[test] #[test]

View File

@ -38,7 +38,7 @@ struct Structs {
e: BlockInterval, e: BlockInterval,
f: FeeRate, f: FeeRate,
g: absolute::Height, g: absolute::Height,
h: absolute::Time, h: absolute::Mtp,
i: relative::Height, i: relative::Height,
j: relative::Time, j: relative::Time,
k: Weight, k: Weight,
@ -55,7 +55,7 @@ impl Structs {
e: BlockInterval::MAX, e: BlockInterval::MAX,
f: FeeRate::MAX, f: FeeRate::MAX,
g: absolute::Height::MAX, g: absolute::Height::MAX,
h: absolute::Time::MAX, h: absolute::Mtp::MAX,
i: relative::Height::MAX, i: relative::Height::MAX,
j: relative::Time::MAX, j: relative::Time::MAX,
k: Weight::MAX, k: Weight::MAX,
@ -85,7 +85,7 @@ struct CommonTraits {
e: BlockInterval, e: BlockInterval,
f: FeeRate, f: FeeRate,
g: absolute::Height, g: absolute::Height,
h: absolute::Time, h: absolute::Mtp,
i: relative::Height, i: relative::Height,
j: relative::Time, j: relative::Time,
k: Weight, k: Weight,
@ -174,7 +174,7 @@ fn api_can_use_all_types_from_module_fee_rate() {
#[test] #[test]
fn api_can_use_all_types_from_module_locktime_absolute() { fn api_can_use_all_types_from_module_locktime_absolute() {
use bitcoin_units::locktime::absolute::{ use bitcoin_units::locktime::absolute::{
ConversionError, Height, ParseHeightError, ParseTimeError, Time, ConversionError, Height, Mtp, ParseHeightError, ParseTimeError,
}; };
} }
@ -291,7 +291,7 @@ impl<'a> Arbitrary<'a> for Structs {
e: BlockInterval::arbitrary(u)?, e: BlockInterval::arbitrary(u)?,
f: FeeRate::arbitrary(u)?, f: FeeRate::arbitrary(u)?,
g: absolute::Height::arbitrary(u)?, g: absolute::Height::arbitrary(u)?,
h: absolute::Time::arbitrary(u)?, h: absolute::Mtp::arbitrary(u)?,
i: relative::Height::arbitrary(u)?, i: relative::Height::arbitrary(u)?,
j: relative::Time::arbitrary(u)?, j: relative::Time::arbitrary(u)?,
k: Weight::arbitrary(u)?, k: Weight::arbitrary(u)?,

View File

@ -47,7 +47,7 @@ struct Serde {
a: BlockHeight, a: BlockHeight,
b: BlockInterval, b: BlockInterval,
c: absolute::Height, c: absolute::Height,
d: absolute::Time, d: absolute::Mtp,
e: relative::Height, e: relative::Height,
f: relative::Time, f: relative::Time,
g: Weight, g: Weight,
@ -80,7 +80,7 @@ impl Serde {
a: BlockHeight::MAX, a: BlockHeight::MAX,
b: BlockInterval::MAX, b: BlockInterval::MAX,
c: absolute::Height::MAX, c: absolute::Height::MAX,
d: absolute::Time::MAX, d: absolute::Mtp::MAX,
e: relative::Height::MAX, e: relative::Height::MAX,
f: relative::Time::MAX, f: relative::Time::MAX,
g: Weight::MAX, g: Weight::MAX,