Merge rust-bitcoin/rust-bitcoin#4458: locktimes: replace `MtpAndHeight` type with pair of `BlockMtp` and `BlockHeight`

47c77afaac units: delete MtpAndHeight type (Andrew Poelstra)
d82b8c0bcb primitives: stop using MtpAndHeight (Andrew Poelstra)
72d5fbad73 units: stop using MtpAndHeight in locktime::relative is_satisfied_by methods (Andrew Poelstra)
d933c754f5 units: change type of MtpHeight::to_mtp to BlockMtp (Andrew Poelstra)
dcbdb7ca8a units: add checked arithmetic to Block{Height,Mtp}{Interval,} (Andrew Poelstra)
4300271f0c units: add constructor for absolute::Mtp from timestamps (Andrew Poelstra)
4e4601b3d5 units: rename BlockInterval to BlockHeightInterval (Andrew Poelstra)
cb882c5ce1 units: add global `BlockMtpInterval` type (Andrew Poelstra)
4e3af5162f units: add global `BlockMtp` type (Andrew Poelstra)
a3228d4636 units: pull u32 conversions for BlockHeight/BlockInterval into macro (Andrew Poelstra)

Pull request description:

  This is a more involved PR than I'd expected but hopefully the individual commits make sense and are well-motivated. Essentially, my goal was to replace `MtpAndHeight` as used by relative locktimes with a pair of `Mtp` and `Height`.

  However, relative locktimes, when given a MTP/Height for the UTXO creation and the chain tip, are roughly modeled as "take a diff of MTPs to get a `relative::MtpInterval`, a diff of heights to get a `relative::HeightInterval`, and compare to the locktimes". *However*, we have no standalone MTP type to "take a diff of", and also there are failure modes when creating the diffs (e.g. if the diff would exceed the range of `MtpInterval` or `HeightInterval`).

  So I backed up and decided to use the existing `BlockHeight`/`BlockInterval` as the type to "take a diff of". I needed to introduce a `BlockMtp`/`BlockMtpInterval` to work with MTPs. These types have full-u32 range, unlike the similarly-named types in `units::locktimes::absolute`. I then needed to add some conversion methods. Along the way, I cleaned up the APIs and documentation, added checked arithmetic, etc., as needed.

  See the individual commit messages for more detail.

  I believe the resulting API is much more consistent and discoverable, even though it has more surface than the old API.

  I considered splitting this into 2 PRs but I think the first half of the changes aren't well-motivated with out the second half. Let me know.

ACKs for top commit:
  tcharding:
    ACK 47c77afaac

Tree-SHA512: ebe19a5b1684db8c2d913274347c994026aaa0dcdd79349c237920a82fe55560777278efdbbc7f1b1424c9391d9bbd891ae844db885deea75288000437a8a287
This commit is contained in:
merge-script 2025-05-07 22:32:16 +00:00
commit 4ca6cd6065
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
11 changed files with 530 additions and 314 deletions

View File

@ -30,7 +30,13 @@ use crate::transaction::{Transaction, TransactionExt as _, Wtxid};
#[doc(inline)] #[doc(inline)]
pub use primitives::block::{Block, Checked, Unchecked, Validation, Version, BlockHash, Header, WitnessCommitment}; pub use primitives::block::{Block, Checked, Unchecked, Validation, Version, BlockHash, Header, WitnessCommitment};
#[doc(inline)] #[doc(inline)]
pub use units::block::{BlockHeight, BlockInterval, TooBigForRelativeBlockHeightIntervalError}; pub use units::block::{
BlockHeight, BlockHeightInterval, TooBigForRelativeBlockHeightIntervalError,
};
#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")]
#[doc(hidden)]
pub type BlockInterval = BlockHeightInterval;
impl_hashencode!(BlockHash); impl_hashencode!(BlockHash);

View File

@ -135,12 +135,16 @@ pub use primitives::{
#[doc(inline)] #[doc(inline)]
pub use units::{ pub use units::{
amount::{Amount, Denomination, SignedAmount}, amount::{Amount, Denomination, SignedAmount},
block::{BlockHeight, BlockInterval}, block::{BlockHeight, BlockHeightInterval, BlockMtp},
fee_rate::FeeRate, fee_rate::FeeRate,
time::{self, BlockTime}, time::{self, BlockTime},
weight::Weight, weight::Weight,
}; };
#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")]
#[doc(hidden)]
pub type BlockInterval = BlockHeightInterval;
#[doc(inline)] #[doc(inline)]
pub use crate::{ pub use crate::{
address::{Address, AddressType, KnownHrp}, address::{Address, AddressType, KnownHrp},
@ -243,7 +247,7 @@ mod encode_impls {
//! Encodable/Decodable implementations. //! Encodable/Decodable implementations.
// While we are deprecating, re-exporting, and generally moving things around just put these here. // While we are deprecating, re-exporting, and generally moving things around just put these here.
use units::{BlockHeight, BlockInterval}; use units::{BlockHeight, BlockHeightInterval};
use crate::consensus::{encode, Decodable, Encodable}; use crate::consensus::{encode, Decodable, Encodable};
use crate::io::{BufRead, Write}; use crate::io::{BufRead, Write};
@ -275,5 +279,5 @@ mod encode_impls {
} }
impl_encodable_for_u32_wrapper!(BlockHeight); impl_encodable_for_u32_wrapper!(BlockHeight);
impl_encodable_for_u32_wrapper!(BlockInterval); impl_encodable_for_u32_wrapper!(BlockHeightInterval);
} }

View File

@ -67,7 +67,7 @@
//! # } //! # }
//! ``` //! ```
use units::{BlockHeight, BlockInterval}; use units::{BlockHeight, BlockHeightInterval};
use crate::network::Network; use crate::network::Network;
#[cfg(doc)] #[cfg(doc)]
@ -92,9 +92,9 @@ pub struct Params {
/// Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period, /// Minimum blocks including miner confirmation of the total of 2016 blocks in a retargeting period,
/// (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. /// (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments.
/// Examples: 1916 for 95%, 1512 for testchains. /// Examples: 1916 for 95%, 1512 for testchains.
pub rule_change_activation_threshold: BlockInterval, pub rule_change_activation_threshold: BlockHeightInterval,
/// Number of blocks with the same set of rules. /// Number of blocks with the same set of rules.
pub miner_confirmation_window: BlockInterval, pub miner_confirmation_window: BlockHeightInterval,
/// Proof of work limit value. It contains the lowest possible difficulty. /// Proof of work limit value. It contains the lowest possible difficulty.
#[deprecated(since = "0.32.0", note = "use `max_attainable_target` instead")] #[deprecated(since = "0.32.0", note = "use `max_attainable_target` instead")]
pub pow_limit: Target, pub pow_limit: Target,
@ -152,8 +152,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(227931), // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8 bip34_height: BlockHeight::from_u32(227931), // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
bip65_height: BlockHeight::from_u32(388381), // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 bip65_height: BlockHeight::from_u32(388381), // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
bip66_height: BlockHeight::from_u32(363725), // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 bip66_height: BlockHeight::from_u32(363725), // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931
rule_change_activation_threshold: BlockInterval::from_u32(1916), // 95% rule_change_activation_threshold: BlockHeightInterval::from_u32(1916), // 95%
miner_confirmation_window: BlockInterval::from_u32(2016), miner_confirmation_window: BlockHeightInterval::from_u32(2016),
pow_limit: Target::MAX_ATTAINABLE_MAINNET, pow_limit: Target::MAX_ATTAINABLE_MAINNET,
max_attainable_target: Target::MAX_ATTAINABLE_MAINNET, max_attainable_target: Target::MAX_ATTAINABLE_MAINNET,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.
@ -170,8 +170,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75%
miner_confirmation_window: BlockInterval::from_u32(2016), miner_confirmation_window: BlockHeightInterval::from_u32(2016),
pow_limit: Target::MAX_ATTAINABLE_TESTNET, pow_limit: Target::MAX_ATTAINABLE_TESTNET,
max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.
@ -187,8 +187,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 bip34_height: BlockHeight::from_u32(21111), // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 bip65_height: BlockHeight::from_u32(581885), // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 bip66_height: BlockHeight::from_u32(330776), // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75%
miner_confirmation_window: BlockInterval::from_u32(2016), miner_confirmation_window: BlockHeightInterval::from_u32(2016),
pow_limit: Target::MAX_ATTAINABLE_TESTNET, pow_limit: Target::MAX_ATTAINABLE_TESTNET,
max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.
@ -204,8 +204,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(1), bip34_height: BlockHeight::from_u32(1),
bip65_height: BlockHeight::from_u32(1), bip65_height: BlockHeight::from_u32(1),
bip66_height: BlockHeight::from_u32(1), bip66_height: BlockHeight::from_u32(1),
rule_change_activation_threshold: BlockInterval::from_u32(1512), // 75% rule_change_activation_threshold: BlockHeightInterval::from_u32(1512), // 75%
miner_confirmation_window: BlockInterval::from_u32(2016), miner_confirmation_window: BlockHeightInterval::from_u32(2016),
pow_limit: Target::MAX_ATTAINABLE_TESTNET, pow_limit: Target::MAX_ATTAINABLE_TESTNET,
max_attainable_target: Target::MAX_ATTAINABLE_TESTNET, max_attainable_target: Target::MAX_ATTAINABLE_TESTNET,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.
@ -221,8 +221,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(1), bip34_height: BlockHeight::from_u32(1),
bip65_height: BlockHeight::from_u32(1), bip65_height: BlockHeight::from_u32(1),
bip66_height: BlockHeight::from_u32(1), bip66_height: BlockHeight::from_u32(1),
rule_change_activation_threshold: BlockInterval::from_u32(1916), // 95% rule_change_activation_threshold: BlockHeightInterval::from_u32(1916), // 95%
miner_confirmation_window: BlockInterval::from_u32(2016), miner_confirmation_window: BlockHeightInterval::from_u32(2016),
pow_limit: Target::MAX_ATTAINABLE_SIGNET, pow_limit: Target::MAX_ATTAINABLE_SIGNET,
max_attainable_target: Target::MAX_ATTAINABLE_SIGNET, max_attainable_target: Target::MAX_ATTAINABLE_SIGNET,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.
@ -238,8 +238,8 @@ impl Params {
bip34_height: BlockHeight::from_u32(100000000), // not activated on regtest bip34_height: BlockHeight::from_u32(100000000), // not activated on regtest
bip65_height: BlockHeight::from_u32(1351), bip65_height: BlockHeight::from_u32(1351),
bip66_height: BlockHeight::from_u32(1251), // used only in rpc tests bip66_height: BlockHeight::from_u32(1251), // used only in rpc tests
rule_change_activation_threshold: BlockInterval::from_u32(108), // 75% rule_change_activation_threshold: BlockHeightInterval::from_u32(108), // 75%
miner_confirmation_window: BlockInterval::from_u32(144), miner_confirmation_window: BlockHeightInterval::from_u32(144),
pow_limit: Target::MAX_ATTAINABLE_REGTEST, pow_limit: Target::MAX_ATTAINABLE_REGTEST,
max_attainable_target: Target::MAX_ATTAINABLE_REGTEST, max_attainable_target: Target::MAX_ATTAINABLE_REGTEST,
pow_target_spacing: 10 * 60, // 10 minutes. pow_target_spacing: 10 * 60, // 10 minutes.

View File

@ -52,12 +52,16 @@ pub mod witness;
#[doc(inline)] #[doc(inline)]
pub use units::{ pub use units::{
amount::{self, Amount, SignedAmount}, amount::{self, Amount, SignedAmount},
block::{BlockHeight, BlockInterval}, block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInterval},
fee_rate::{self, FeeRate}, fee_rate::{self, FeeRate},
time::{self, BlockTime}, time::{self, BlockTime},
weight::{self, Weight}, weight::{self, Weight},
}; };
#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")]
#[doc(hidden)]
pub type BlockInterval = BlockHeightInterval;
#[doc(inline)] #[doc(inline)]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub use self::{ pub use self::{

View File

@ -14,7 +14,7 @@ use crate::{relative, TxIn};
#[rustfmt::skip] // Keep public re-exports separate. #[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)] #[doc(inline)]
pub use units::locktime::relative::{HeightInterval, MtpInterval, TimeOverflowError}; pub use units::locktime::relative::{HeightInterval, MtpInterval, TimeOverflowError};
use units::mtp_height::MtpAndHeight; use units::{BlockHeight, BlockMtp};
#[deprecated(since = "TBD", note = "use `Mtp` instead")] #[deprecated(since = "TBD", note = "use `Mtp` instead")]
#[doc(hidden)] #[doc(hidden)]
@ -44,9 +44,7 @@ pub type Time = MtpInterval;
/// ///
/// ``` /// ```
/// use bitcoin_primitives::relative; /// use bitcoin_primitives::relative;
/// use bitcoin_primitives::BlockTime; /// use bitcoin_primitives::{BlockHeight, BlockMtp, BlockTime};
/// use bitcoin_primitives::BlockHeight;
/// use units::mtp_height::MtpAndHeight;
/// let lock_by_height = relative::LockTime::from_height(144); // 144 blocks, approx 24h. /// let lock_by_height = relative::LockTime::from_height(144); // 144 blocks, approx 24h.
/// assert!(lock_by_height.is_block_height()); /// assert!(lock_by_height.is_block_height());
/// ///
@ -65,14 +63,15 @@ pub type Time = MtpInterval;
/// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200); /// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
/// ///
/// let current_height = BlockHeight::from(100); /// let current_height = BlockHeight::from(100);
/// let utxo_height = BlockHeight::from(80); /// let current_mtp = BlockMtp::new(timestamps);
///
/// let utxo_height = BlockHeight::from(80);
/// let utxo_mtp = BlockMtp::new(utxo_timestamps);
/// ///
/// let chain_tip = MtpAndHeight::new(current_height, timestamps);
/// let utxo_mined_at = MtpAndHeight::new(utxo_height, utxo_timestamps);
/// let locktime = relative::LockTime::Time(relative::MtpInterval::from_512_second_intervals(10)); /// let locktime = relative::LockTime::Time(relative::MtpInterval::from_512_second_intervals(10));
/// ///
/// // Check if locktime is satisfied /// // Check if locktime is satisfied
/// assert!(locktime.is_satisfied_by(chain_tip, utxo_mined_at)); /// assert!(locktime.is_satisfied_by(current_height, current_mtp, utxo_height, utxo_mtp));
/// ``` /// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -226,10 +225,8 @@ impl LockTime {
/// ///
/// ```rust /// ```rust
/// # use bitcoin_primitives::relative::Time; /// # use bitcoin_primitives::relative::Time;
/// # use units::mtp_height::MtpAndHeight; /// # use bitcoin_primitives::{BlockHeight, BlockMtp, BlockTime};
/// # use bitcoin_primitives::BlockHeight;
/// # use bitcoin_primitives::relative::LockTime; /// # use bitcoin_primitives::relative::LockTime;
/// # use bitcoin_primitives::BlockTime;
/// ///
/// fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] { /// fn generate_timestamps(start: u32, step: u16) -> [BlockTime; 11] {
/// let mut timestamps = [BlockTime::from_u32(0); 11]; /// let mut timestamps = [BlockTime::from_u32(0); 11];
@ -243,19 +240,26 @@ impl LockTime {
/// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200); /// let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
/// ///
/// let current_height = BlockHeight::from_u32(100); /// let current_height = BlockHeight::from_u32(100);
/// let current_mtp = BlockMtp::new(timestamps);
/// let utxo_height = BlockHeight::from_u32(80); /// let utxo_height = BlockHeight::from_u32(80);
/// let utxo_mtp = BlockMtp::new(utxo_timestamps);
/// ///
/// let chain_tip = MtpAndHeight::new(current_height, timestamps);
/// let utxo_mined_at = MtpAndHeight::new(utxo_height, utxo_timestamps);
/// let locktime = LockTime::Time(Time::from_512_second_intervals(10)); /// let locktime = LockTime::Time(Time::from_512_second_intervals(10));
/// ///
/// // Check if locktime is satisfied /// // Check if locktime is satisfied
/// assert!(locktime.is_satisfied_by(chain_tip, utxo_mined_at)); /// assert!(locktime.is_satisfied_by(current_height, current_mtp, utxo_height, utxo_mtp));
/// ``` /// ```
pub fn is_satisfied_by(self, chain_tip: MtpAndHeight, utxo_mined_at: MtpAndHeight) -> bool { pub fn is_satisfied_by(
self,
chain_tip_height: BlockHeight,
chain_tip_mtp: BlockMtp,
utxo_mined_at_height: BlockHeight,
utxo_mined_at_mtp: BlockMtp,
) -> bool {
match self { match self {
LockTime::Blocks(blocks) => blocks.is_satisfied_by(chain_tip, utxo_mined_at), LockTime::Blocks(blocks) =>
LockTime::Time(time) => time.is_satisfied_by(chain_tip, utxo_mined_at), blocks.is_satisfied_by(chain_tip_height, utxo_mined_at_height),
LockTime::Time(time) => time.is_satisfied_by(chain_tip_mtp, utxo_mined_at_mtp),
} }
} }
@ -695,38 +699,55 @@ mod tests {
let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200); let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200); let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
let chain_tip = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps); let chain_height = BlockHeight::from_u32(100);
let utxo_mined_at = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let chain_mtp = BlockMtp::new(timestamps);
let utxo_height = BlockHeight::from_u32(80);
let utxo_mtp = BlockMtp::new(utxo_timestamps);
let lock1 = LockTime::Blocks(HeightInterval::from(10)); let lock1 = LockTime::Blocks(HeightInterval::from(10));
assert!(lock1.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(lock1.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let lock2 = LockTime::Blocks(HeightInterval::from(21)); let lock2 = LockTime::Blocks(HeightInterval::from(21));
assert!(!lock2.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(!lock2.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let lock3 = LockTime::Time(MtpInterval::from_512_second_intervals(10)); let lock3 = LockTime::Time(MtpInterval::from_512_second_intervals(10));
assert!(lock3.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(lock3.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let lock4 = LockTime::Time(MtpInterval::from_512_second_intervals(20000)); let lock4 = LockTime::Time(MtpInterval::from_512_second_intervals(20000));
assert!(!lock4.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(!lock4.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
assert!(LockTime::ZERO.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(LockTime::ZERO.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
assert!(LockTime::from_512_second_intervals(0).is_satisfied_by(chain_tip, utxo_mined_at)); assert!(LockTime::from_512_second_intervals(0).is_satisfied_by(
chain_height,
chain_mtp,
utxo_height,
utxo_mtp
));
let lock6 = LockTime::from_seconds_floor(5000).unwrap(); let lock6 = LockTime::from_seconds_floor(5000).unwrap();
assert!(lock6.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(lock6.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let max_height_lock = LockTime::Blocks(HeightInterval::MAX); let max_height_lock = LockTime::Blocks(HeightInterval::MAX);
assert!(!max_height_lock.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(!max_height_lock.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let max_time_lock = LockTime::Time(MtpInterval::MAX); let max_time_lock = LockTime::Time(MtpInterval::MAX);
assert!(!max_time_lock.is_satisfied_by(chain_tip, utxo_mined_at)); assert!(!max_time_lock.is_satisfied_by(chain_height, chain_mtp, utxo_height, utxo_mtp));
let max_chain_tip = let max_chain_height = BlockHeight::from_u32(u32::MAX);
MtpAndHeight::new(BlockHeight::from_u32(u32::MAX), generate_timestamps(u32::MAX, 100)); let max_chain_mtp = BlockMtp::new(generate_timestamps(u32::MAX, 100));
let max_utxo_mined_at = let max_utxo_height = BlockHeight::MAX;
MtpAndHeight::new(BlockHeight::MAX, generate_timestamps(u32::MAX, 100)); let max_utxo_mtp = max_chain_mtp;
assert!(!max_height_lock.is_satisfied_by(max_chain_tip, max_utxo_mined_at)); assert!(!max_height_lock.is_satisfied_by(
assert!(!max_time_lock.is_satisfied_by(max_chain_tip, max_utxo_mined_at)); max_chain_height,
max_chain_mtp,
max_utxo_height,
max_utxo_mtp
));
assert!(!max_time_lock.is_satisfied_by(
max_chain_height,
max_chain_mtp,
max_utxo_height,
max_utxo_mtp
));
} }
} }

View File

@ -22,26 +22,23 @@ use serde::{Deserialize, Serialize};
use crate::locktime; use crate::locktime;
use crate::locktime::{absolute, relative}; use crate::locktime::{absolute, relative};
/// The block height, zero denotes the genesis block. macro_rules! impl_u32_wrapper {
/// {
/// This type is not meant for constructing height based timelocks, this is a general purpose block $(#[$($type_attrs:tt)*])*
/// height abstraction. For locktimes please see [`locktime::absolute::Height`]. $type_vis:vis struct $newtype:ident($inner_vis:vis u32);
/// } => {
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`. $(#[$($type_attrs)*])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] $type_vis struct $newtype($inner_vis u32);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockHeight(pub u32);
impl BlockHeight { impl $newtype {
/// Block height 0, the genesis block. /// Block height 0, the genesis block.
pub const ZERO: Self = BlockHeight(0); pub const ZERO: Self = Self(0);
/// The minimum block height (0), the genesis block. /// The minimum block height (0), the genesis block.
pub const MIN: Self = Self::ZERO; pub const MIN: Self = Self::ZERO;
/// The maximum block height. /// The maximum block height.
pub const MAX: Self = BlockHeight(u32::MAX); pub const MAX: Self = Self(u32::MAX);
/// Constructs a new block height from a `u32`. /// Constructs a new block height from a `u32`.
pub const fn from_u32(inner: u32) -> Self { Self(inner) } pub const fn from_u32(inner: u32) -> Self { Self(inner) }
@ -50,26 +47,65 @@ impl BlockHeight {
pub const fn to_u32(self) -> u32 { self.0 } pub const fn to_u32(self) -> u32 { self.0 }
} }
impl fmt::Display for BlockHeight { impl fmt::Display for $newtype {
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_from_int_infallible!(BlockHeight, u32, from); crate::impl_parse_str_from_int_infallible!($newtype, u32, from);
impl From<u32> for BlockHeight { impl From<u32> for $newtype {
fn from(inner: u32) -> Self { Self::from_u32(inner) } fn from(inner: u32) -> Self { Self::from_u32(inner) }
} }
impl From<BlockHeight> for u32 { impl From<$newtype> for u32 {
fn from(height: BlockHeight) -> Self { height.to_u32() } fn from(height: $newtype) -> Self { height.to_u32() }
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for $newtype {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=2)?;
match choice {
0 => Ok(Self::ZERO),
1 => Ok(Self::MIN),
2 => Ok(Self::MAX),
_ => Ok(Self::from_u32(u32::arbitrary(u)?)),
}
}
}
}
}
impl_u32_wrapper! {
/// A block height. Zero denotes the genesis block.
///
/// This type is not meant for constructing height based timelocks. It is a general purpose
/// blockheight abstraction. For locktimes please see [`locktime::absolute::Height`].
///
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockHeight(pub u32);
}
impl BlockHeight {
/// Attempt to subtract two [`BlockHeight`]s, returning `None` in case of overflow.
pub fn checked_sub(self, other: Self) -> Option<BlockHeightInterval> {
self.0.checked_sub(other.0).map(BlockHeightInterval)
}
/// Attempt to add an interval to this [`BlockHeight`], returning `None` in case of overflow.
pub fn checked_add(self, other: BlockHeightInterval) -> Option<Self> {
self.0.checked_add(other.0).map(Self)
}
} }
impl From<absolute::Height> for BlockHeight { impl From<absolute::Height> for BlockHeight {
/// Converts a [`locktime::absolute::Height`] to a [`BlockHeight`]. /// Converts a [`locktime::absolute::Height`] to a [`BlockHeight`].
/// ///
/// An absolute locktime block height has a maximum value of [`absolute::LOCK_TIME_THRESHOLD`] /// An absolute locktime block height has a maximum value of [`absolute::LOCK_TIME_THRESHOLD`]
/// (500,000,000) where as a [`BlockHeight`] is a thin wrapper around a `u32`, the two types are /// minus one, while [`BlockHeight`] may take the full range of `u32`.
/// not interchangeable.
fn from(h: absolute::Height) -> Self { Self::from_u32(h.to_u32()) } fn from(h: absolute::Height) -> Self { Self::from_u32(h.to_u32()) }
} }
@ -79,72 +115,49 @@ impl TryFrom<BlockHeight> for absolute::Height {
/// Converts a [`BlockHeight`] to a [`locktime::absolute::Height`]. /// Converts a [`BlockHeight`] to a [`locktime::absolute::Height`].
/// ///
/// An absolute locktime block height has a maximum value of [`absolute::LOCK_TIME_THRESHOLD`] /// An absolute locktime block height has a maximum value of [`absolute::LOCK_TIME_THRESHOLD`]
/// (500,000,000) where as a [`BlockHeight`] is a thin wrapper around a `u32`, the two types are /// minus one, while [`BlockHeight`] may take the full range of `u32`.
/// not interchangeable.
fn try_from(h: BlockHeight) -> Result<Self, Self::Error> { fn try_from(h: BlockHeight) -> Result<Self, Self::Error> {
absolute::Height::from_u32(h.to_u32()) absolute::Height::from_u32(h.to_u32())
} }
} }
/// The block interval. impl_u32_wrapper! {
/// An unsigned block interval.
/// ///
/// Block interval is an integer type denoting the number of blocks that has passed since some point /// Block interval is an integer type representing a difference between the heights of two blocks.
/// i.e., this type is meant for usage as a relative block measure.
/// ///
/// This type is not meant for constructing relative height based timelocks, this is a general /// This type is not meant for constructing relative height based timelocks. It is a general
/// purpose block interval abstraction. For locktimes please see [`locktime::relative::Height`]. /// purpose block interval abstraction. For locktimes please see [`locktime::relative::Height`].
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants. // Public to try and make it really clear that there are no invariants.
pub struct BlockInterval(pub u32); pub struct BlockHeightInterval(pub u32);
impl BlockInterval {
/// Block interval 0 i.e., the current block.
pub const ZERO: Self = BlockInterval(0);
/// The minimum block interval (0).
pub const MIN: Self = Self::ZERO;
/// The maximum block interval.
pub const MAX: Self = BlockInterval(u32::MAX);
/// Constructs a new block interval from a `u32`.
pub const fn from_u32(inner: u32) -> Self { Self(inner) }
/// Returns block interval as a `u32`.
pub const fn to_u32(self) -> u32 { self.0 }
} }
impl fmt::Display for BlockInterval { impl BlockHeightInterval {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } /// Attempt to subtract two [`BlockHeightInterval`]s, returning `None` in case of overflow.
pub fn checked_sub(self, other: Self) -> Option<Self> { self.0.checked_sub(other.0).map(Self) }
/// Attempt to add two [`BlockHeightInterval`]s, returning `None` in case of overflow.
pub fn checked_add(self, other: Self) -> Option<Self> { self.0.checked_add(other.0).map(Self) }
} }
crate::impl_parse_str_from_int_infallible!(BlockInterval, u32, from); impl From<relative::HeightInterval> for BlockHeightInterval {
/// Converts a [`locktime::relative::HeightInterval`] to a [`BlockHeightInterval`].
impl From<u32> for BlockInterval {
fn from(inner: u32) -> Self { Self::from_u32(inner) }
}
impl From<BlockInterval> for u32 {
fn from(height: BlockInterval) -> Self { height.to_u32() }
}
impl From<relative::HeightInterval> for BlockInterval {
/// Converts a [`locktime::relative::HeightInterval`] to a [`BlockInterval`].
/// ///
/// A relative locktime block height has a maximum value of `u16::MAX` where as a /// A relative locktime block height has a maximum value of `u16::MAX` where as a
/// [`BlockInterval`] is a thin wrapper around a `u32`, the two types are not interchangeable. /// [`BlockHeightInterval`] is a thin wrapper around a `u32`, the two types are not interchangeable.
fn from(h: relative::HeightInterval) -> Self { Self::from_u32(h.to_height().into()) } fn from(h: relative::HeightInterval) -> Self { Self::from_u32(h.to_height().into()) }
} }
impl TryFrom<BlockInterval> for relative::HeightInterval { impl TryFrom<BlockHeightInterval> for relative::HeightInterval {
type Error = TooBigForRelativeBlockHeightIntervalError; type Error = TooBigForRelativeBlockHeightIntervalError;
/// Converts a [`BlockInterval`] to a [`locktime::relative::HeightInterval`]. /// Converts a [`BlockHeightInterval`] to a [`locktime::relative::HeightInterval`].
/// ///
/// A relative locktime block height has a maximum value of `u16::MAX` where as a /// A relative locktime block height has a maximum value of `u16::MAX` where as a
/// [`BlockInterval`] is a thin wrapper around a `u32`, the two types are not interchangeable. /// [`BlockHeightInterval`] is a thin wrapper around a `u32`, the two types are not interchangeable.
fn try_from(h: BlockInterval) -> Result<Self, Self::Error> { fn try_from(h: BlockHeightInterval) -> Result<Self, Self::Error> {
let h = h.to_u32(); let h = h.to_u32();
if h > u32::from(u16::MAX) { if h > u32::from(u16::MAX) {
@ -154,6 +167,121 @@ impl TryFrom<BlockInterval> for relative::HeightInterval {
} }
} }
impl_u32_wrapper! {
/// The median timestamp of 11 consecutive blocks.
///
/// This type is not meant for constructing time-based timelocks. It is a general purpose
/// MTP abstraction. For locktimes please see [`locktime::absolute::Mtp`].
///
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockMtp(pub u32);
}
impl BlockMtp {
/// Constructs a [`BlockMtp`] 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.
pub fn new(mut timestamps: [crate::BlockTime; 11]) -> Self {
timestamps.sort_unstable();
Self::from_u32(u32::from(timestamps[5]))
}
/// Attempt to subtract two [`BlockMtp`]s, returning `None` in case of overflow.
pub fn checked_sub(self, other: Self) -> Option<BlockMtpInterval> {
self.0.checked_sub(other.0).map(BlockMtpInterval)
}
/// Attempt to add an interval to this [`BlockMtp`], returning `None` in case of overflow.
pub fn checked_add(self, other: BlockMtpInterval) -> Option<Self> {
self.0.checked_add(other.0).map(Self)
}
}
impl From<absolute::Mtp> for BlockMtp {
/// Converts a [`locktime::absolute::Mtp`] to a [`BlockMtp`].
///
/// An absolute locktime MTP has a minimum value of [`absolute::LOCK_TIME_THRESHOLD`],
/// while [`BlockMtp`] may take the full range of `u32`.
fn from(h: absolute::Mtp) -> Self { Self::from_u32(h.to_u32()) }
}
impl TryFrom<BlockMtp> for absolute::Mtp {
type Error = absolute::ConversionError;
/// Converts a [`BlockHeight`] to a [`locktime::absolute::Height`].
///
/// An absolute locktime MTP has a minimum value of [`absolute::LOCK_TIME_THRESHOLD`],
/// while [`BlockMtp`] may take the full range of `u32`.
fn try_from(h: BlockMtp) -> Result<Self, Self::Error> { absolute::Mtp::from_u32(h.to_u32()) }
}
impl_u32_wrapper! {
/// An unsigned difference between two [`BlockMtp`]s.
///
/// This type is not meant for constructing time-based timelocks. It is a general purpose
/// MTP abstraction. For locktimes please see [`locktime::relative::MtpInterval`].
///
/// This is a thin wrapper around a `u32` that may take on all values of a `u32`.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
// Public to try and make it really clear that there are no invariants.
pub struct BlockMtpInterval(pub u32);
}
impl BlockMtpInterval {
/// Converts a [`BlockMtpInterval`] to a [`locktime::relative::MtpInterval`], rounding down.
///
/// Relative timelock MTP intervals have a resolution of 512 seconds, while
/// [`BlockMtpInterval`], like all block timestamp types, has a one-second resolution.
///
/// # Errors
///
/// Errors if the MTP is out-of-range (in excess of 512 times `u16::MAX` seconds, or about
/// 388 days) for a time-based relative locktime.
#[inline]
pub const fn to_relative_mtp_interval_floor(
self,
) -> Result<relative::MtpInterval, relative::TimeOverflowError> {
relative::MtpInterval::from_seconds_floor(self.to_u32())
}
/// Converts a [`BlockMtpInterval`] to a [`locktime::relative::MtpInterval`], rounding up.
///
/// Relative timelock MTP intervals have a resolution of 512 seconds, while
/// [`BlockMtpInterval`], like all block timestamp types, has a one-second resolution.
///
/// # Errors
///
/// Errors if the MTP is out-of-range (in excess of 512 times `u16::MAX` seconds, or about
/// 388 days) for a time-based relative locktime.
#[inline]
pub const fn to_relative_mtp_interval_ceil(
self,
) -> Result<relative::MtpInterval, relative::TimeOverflowError> {
relative::MtpInterval::from_seconds_ceil(self.to_u32())
}
/// Attempt to subtract two [`BlockMtpInterval`]s, returning `None` in case of overflow.
pub fn checked_sub(self, other: Self) -> Option<Self> { self.0.checked_sub(other.0).map(Self) }
/// Attempt to add two [`BlockMtpInterval`]s, returning `None` in case of overflow.
pub fn checked_add(self, other: Self) -> Option<Self> { self.0.checked_add(other.0).map(Self) }
}
impl From<relative::MtpInterval> for BlockMtpInterval {
/// Converts a [`locktime::relative::MtpInterval`] to a [`BlockMtpInterval `].
///
/// A relative locktime MTP interval has a resolution of 512 seconds, and a maximum value
/// of `u16::MAX` 512-second intervals. [`BlockMtpInterval`] may take the full range of
/// `u32`.
fn from(h: relative::MtpInterval) -> Self { Self::from_u32(h.to_seconds()) }
}
/// Error returned when the block interval is too big to be used as a relative lock time. /// Error returned when the block interval is too big to be used as a relative lock time.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TooBigForRelativeBlockHeightIntervalError(u32); pub struct TooBigForRelativeBlockHeightIntervalError(u32);
@ -175,96 +303,141 @@ impl std::error::Error for TooBigForRelativeBlockHeightIntervalError {}
crate::internal_macros::impl_op_for_references! { crate::internal_macros::impl_op_for_references! {
// height - height = interval // height - height = interval
impl ops::Sub<BlockHeight> for BlockHeight { impl ops::Sub<BlockHeight> for BlockHeight {
type Output = BlockInterval; type Output = BlockHeightInterval;
fn sub(self, rhs: BlockHeight) -> Self::Output { fn sub(self, rhs: BlockHeight) -> Self::Output {
let interval = self.to_u32() - rhs.to_u32(); let interval = self.to_u32() - rhs.to_u32();
BlockInterval::from_u32(interval) BlockHeightInterval::from_u32(interval)
} }
} }
// height + interval = height // height + interval = height
impl ops::Add<BlockInterval> for BlockHeight { impl ops::Add<BlockHeightInterval> for BlockHeight {
type Output = BlockHeight; type Output = BlockHeight;
fn add(self, rhs: BlockInterval) -> Self::Output { fn add(self, rhs: BlockHeightInterval) -> Self::Output {
let height = self.to_u32() + rhs.to_u32(); let height = self.to_u32() + rhs.to_u32();
BlockHeight::from_u32(height) BlockHeight::from_u32(height)
} }
} }
// height - interval = height // height - interval = height
impl ops::Sub<BlockInterval> for BlockHeight { impl ops::Sub<BlockHeightInterval> for BlockHeight {
type Output = BlockHeight; type Output = BlockHeight;
fn sub(self, rhs: BlockInterval) -> Self::Output { fn sub(self, rhs: BlockHeightInterval) -> Self::Output {
let height = self.to_u32() - rhs.to_u32(); let height = self.to_u32() - rhs.to_u32();
BlockHeight::from_u32(height) BlockHeight::from_u32(height)
} }
} }
// interval + interval = interval // interval + interval = interval
impl ops::Add<BlockInterval> for BlockInterval { impl ops::Add<BlockHeightInterval> for BlockHeightInterval {
type Output = BlockInterval; type Output = BlockHeightInterval;
fn add(self, rhs: BlockInterval) -> Self::Output { fn add(self, rhs: BlockHeightInterval) -> Self::Output {
let height = self.to_u32() + rhs.to_u32(); let height = self.to_u32() + rhs.to_u32();
BlockInterval::from_u32(height) BlockHeightInterval::from_u32(height)
} }
} }
// interval - interval = interval // interval - interval = interval
impl ops::Sub<BlockInterval> for BlockInterval { impl ops::Sub<BlockHeightInterval> for BlockHeightInterval {
type Output = BlockInterval; type Output = BlockHeightInterval;
fn sub(self, rhs: BlockInterval) -> Self::Output { fn sub(self, rhs: BlockHeightInterval) -> Self::Output {
let height = self.to_u32() - rhs.to_u32(); let height = self.to_u32() - rhs.to_u32();
BlockInterval::from_u32(height) BlockHeightInterval::from_u32(height)
}
}
// height - height = interval
impl ops::Sub<BlockMtp> for BlockMtp {
type Output = BlockMtpInterval;
fn sub(self, rhs: BlockMtp) -> Self::Output {
let interval = self.to_u32() - rhs.to_u32();
BlockMtpInterval::from_u32(interval)
}
}
// height + interval = height
impl ops::Add<BlockMtpInterval> for BlockMtp {
type Output = BlockMtp;
fn add(self, rhs: BlockMtpInterval) -> Self::Output {
let height = self.to_u32() + rhs.to_u32();
BlockMtp::from_u32(height)
}
}
// height - interval = height
impl ops::Sub<BlockMtpInterval> for BlockMtp {
type Output = BlockMtp;
fn sub(self, rhs: BlockMtpInterval) -> Self::Output {
let height = self.to_u32() - rhs.to_u32();
BlockMtp::from_u32(height)
}
}
// interval + interval = interval
impl ops::Add<BlockMtpInterval> for BlockMtpInterval {
type Output = BlockMtpInterval;
fn add(self, rhs: BlockMtpInterval) -> Self::Output {
let height = self.to_u32() + rhs.to_u32();
BlockMtpInterval::from_u32(height)
}
}
// interval - interval = interval
impl ops::Sub<BlockMtpInterval> for BlockMtpInterval {
type Output = BlockMtpInterval;
fn sub(self, rhs: BlockMtpInterval) -> Self::Output {
let height = self.to_u32() - rhs.to_u32();
BlockMtpInterval::from_u32(height)
} }
} }
} }
crate::internal_macros::impl_add_assign!(BlockInterval); crate::internal_macros::impl_add_assign!(BlockHeightInterval);
crate::internal_macros::impl_sub_assign!(BlockInterval); crate::internal_macros::impl_sub_assign!(BlockHeightInterval);
crate::internal_macros::impl_add_assign!(BlockMtpInterval);
crate::internal_macros::impl_sub_assign!(BlockMtpInterval);
impl core::iter::Sum for BlockInterval { impl core::iter::Sum for BlockHeightInterval {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
let sum = iter.map(|interval| interval.0).sum(); let sum = iter.map(|interval| interval.0).sum();
BlockInterval::from_u32(sum) BlockHeightInterval::from_u32(sum)
} }
} }
impl<'a> core::iter::Sum<&'a BlockInterval> for BlockInterval { impl<'a> core::iter::Sum<&'a BlockHeightInterval> for BlockHeightInterval {
fn sum<I>(iter: I) -> Self fn sum<I>(iter: I) -> Self
where where
I: Iterator<Item = &'a BlockInterval>, I: Iterator<Item = &'a BlockHeightInterval>,
{ {
let sum = iter.map(|interval| interval.0).sum(); let sum = iter.map(|interval| interval.0).sum();
BlockInterval::from_u32(sum) BlockHeightInterval::from_u32(sum)
} }
} }
#[cfg(feature = "arbitrary")] impl core::iter::Sum for BlockMtpInterval {
impl<'a> Arbitrary<'a> for BlockHeight { fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { let sum = iter.map(|interval| interval.0).sum();
let choice = u.int_in_range(0..=2)?; BlockMtpInterval::from_u32(sum)
match choice {
0 => Ok(BlockHeight::MIN),
1 => Ok(BlockHeight::MAX),
_ => Ok(BlockHeight::from_u32(u32::arbitrary(u)?)),
}
} }
} }
#[cfg(feature = "arbitrary")] impl<'a> core::iter::Sum<&'a BlockMtpInterval> for BlockMtpInterval {
impl<'a> Arbitrary<'a> for BlockInterval { fn sum<I>(iter: I) -> Self
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> { where
let choice = u.int_in_range(0..=2)?; I: Iterator<Item = &'a BlockMtpInterval>,
match choice { {
0 => Ok(BlockInterval::MIN), let sum = iter.map(|interval| interval.0).sum();
1 => Ok(BlockInterval::MAX), BlockMtpInterval::from_u32(sum)
_ => Ok(BlockInterval::from_u32(u32::arbitrary(u)?)),
}
} }
} }
@ -277,17 +450,19 @@ mod tests {
let height: u32 = BlockHeight(100).into(); let height: u32 = BlockHeight(100).into();
assert_eq!(height, 100); assert_eq!(height, 100);
let interval: u32 = BlockInterval(100).into(); let interval: u32 = BlockHeightInterval(100).into();
assert_eq!(interval, 100); assert_eq!(interval, 100);
let interval_from_height: BlockInterval = relative::HeightInterval::from(10u16).into(); let interval_from_height: BlockHeightInterval =
relative::HeightInterval::from(10u16).into();
assert_eq!(interval_from_height.to_u32(), 10u32); assert_eq!(interval_from_height.to_u32(), 10u32);
let invalid_height_greater = let invalid_height_greater =
relative::HeightInterval::try_from(BlockInterval(u32::from(u16::MAX) + 1)); relative::HeightInterval::try_from(BlockHeightInterval(u32::from(u16::MAX) + 1));
assert!(invalid_height_greater.is_err()); assert!(invalid_height_greater.is_err());
let valid_height = relative::HeightInterval::try_from(BlockInterval(u32::from(u16::MAX))); let valid_height =
relative::HeightInterval::try_from(BlockHeightInterval(u32::from(u16::MAX)));
assert!(valid_height.is_ok()); assert!(valid_height.is_ok());
} }
@ -295,39 +470,41 @@ mod tests {
#[test] #[test]
fn all_available_ops() { fn all_available_ops() {
// height - height = interval // height - height = interval
assert!(BlockHeight(10) - BlockHeight(7) == BlockInterval(3)); assert!(BlockHeight(10) - BlockHeight(7) == BlockHeightInterval(3));
// height + interval = height // height + interval = height
assert!(BlockHeight(100) + BlockInterval(1) == BlockHeight(101)); assert!(BlockHeight(100) + BlockHeightInterval(1) == BlockHeight(101));
// height - interval == height // height - interval == height
assert!(BlockHeight(100) - BlockInterval(1) == BlockHeight(99)); assert!(BlockHeight(100) - BlockHeightInterval(1) == BlockHeight(99));
// interval + interval = interval // interval + interval = interval
assert!(BlockInterval(1) + BlockInterval(2) == BlockInterval(3)); assert!(BlockHeightInterval(1) + BlockHeightInterval(2) == BlockHeightInterval(3));
// interval - interval = interval // interval - interval = interval
assert!(BlockInterval(10) - BlockInterval(7) == BlockInterval(3)); assert!(BlockHeightInterval(10) - BlockHeightInterval(7) == BlockHeightInterval(3));
assert!( assert!(
[BlockInterval(1), BlockInterval(2), BlockInterval(3)].iter().sum::<BlockInterval>() [BlockHeightInterval(1), BlockHeightInterval(2), BlockHeightInterval(3)]
== BlockInterval(6) .iter()
.sum::<BlockHeightInterval>()
== BlockHeightInterval(6)
); );
assert!( assert!(
[BlockInterval(4), BlockInterval(5), BlockInterval(6)] [BlockHeightInterval(4), BlockHeightInterval(5), BlockHeightInterval(6)]
.into_iter() .into_iter()
.sum::<BlockInterval>() .sum::<BlockHeightInterval>()
== BlockInterval(15) == BlockHeightInterval(15)
); );
// interval += interval // interval += interval
let mut int = BlockInterval(1); let mut int = BlockHeightInterval(1);
int += BlockInterval(2); int += BlockHeightInterval(2);
assert_eq!(int, BlockInterval(3)); assert_eq!(int, BlockHeightInterval(3));
// interval -= interval // interval -= interval
let mut int = BlockInterval(10); let mut int = BlockHeightInterval(10);
int -= BlockInterval(7); int -= BlockHeightInterval(7);
assert_eq!(int, BlockInterval(3)); assert_eq!(int, BlockHeightInterval(3));
} }
} }

View File

@ -50,7 +50,6 @@ pub mod amount;
pub mod block; pub mod block;
pub mod fee_rate; pub mod fee_rate;
pub mod locktime; pub mod locktime;
pub mod mtp_height;
pub mod parse; pub mod parse;
pub mod time; pub mod time;
pub mod weight; pub mod weight;
@ -59,10 +58,14 @@ pub mod weight;
#[rustfmt::skip] #[rustfmt::skip]
pub use self::{ pub use self::{
amount::{Amount, SignedAmount}, amount::{Amount, SignedAmount},
block::{BlockHeight, BlockInterval}, block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInterval},
fee_rate::FeeRate, fee_rate::FeeRate,
result::{NumOpError, NumOpResult, MathOp}, result::{NumOpError, NumOpResult, MathOp},
time::BlockTime, time::BlockTime,
weight::Weight weight::Weight
}; };
pub(crate) use self::result::OptionExt; pub(crate) use self::result::OptionExt;
#[deprecated(since = "TBD", note = "use `BlockHeightInterval` instead")]
#[doc(hidden)]
pub type BlockInterval = BlockHeightInterval;

View File

@ -154,6 +154,22 @@ impl Mtp {
/// The maximum MTP allowable in a locktime (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 = Mtp(u32::MAX); pub const MAX: Self = Mtp(u32::MAX);
/// Constructs an [`Mtp`] 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 [`Mtp`] from a big-endian hex-encoded `u32`. /// Constructs a new [`Mtp`] from a big-endian hex-encoded `u32`.
/// ///
/// 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`.
@ -539,4 +555,32 @@ mod tests {
assert_eq!(format!("{}", blocks), "expected lock-by-blockheight (must be < 500000000)"); assert_eq!(format!("{}", blocks), "expected lock-by-blockheight (must be < 500000000)");
assert_eq!(format!("{}", seconds), "expected lock-by-blocktime (must be >= 500000000)"); assert_eq!(format!("{}", seconds), "expected lock-by-blocktime (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!(Mtp::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.reverse();
assert_eq!(Mtp::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.sort();
assert_eq!(Mtp::new(timestamps).unwrap().to_u32(), 500_000_005);
timestamps.reverse();
assert_eq!(Mtp::new(timestamps).unwrap().to_u32(), 500_000_005);
}
} }

View File

@ -9,8 +9,6 @@ use arbitrary::{Arbitrary, Unstructured};
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::mtp_height::MtpAndHeight;
#[deprecated(since = "TBD", note = "use `HeightIterval` instead")] #[deprecated(since = "TBD", note = "use `HeightIterval` instead")]
#[doc(hidden)] #[doc(hidden)]
pub type Height = HeightInterval; pub type Height = HeightInterval;
@ -59,21 +57,26 @@ impl HeightInterval {
/// Determines whether a relativeheight locktime has matured, taking into account /// Determines whether a relativeheight locktime has matured, taking into account
/// both the chain tip and the height at which the UTXO was confirmed. /// 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 /// # Parameters
/// - `self` The relative blockheight delay (`h`) required after confirmation. /// - `self` the relative blockheight delay (`h`) required after confirmation.
/// - `chain_tip` The current chain state (contains the tip height). /// - `chain_tip` the height of the current chain tip
/// - `utxo_mined_at` The chain state at the UTXOs confirmation block (contains that height). /// - `utxo_mined_at` the height of the UTXOs confirmation block
/// ///
/// # Returns /// # Returns
/// - `true` if a UTXO locked by `self` can be spent in a block after `chain_tip`. /// - `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`. /// - `false` if the UTXO is still locked at `chain_tip`.
pub fn is_satisfied_by(self, chain_tip: MtpAndHeight, utxo_mined_at: MtpAndHeight) -> bool { pub fn is_satisfied_by(
// let chain_tip_height = BlockHeight::from(chain_tip); self,
// let utxo_mined_at_height = BlockHeight::from(utxo_mined_at); chain_tip: crate::BlockHeight,
match u32::from(self.to_height()).checked_add(utxo_mined_at.to_height().to_u32()) { utxo_mined_at: crate::BlockHeight,
Some(target_height) => chain_tip.to_height().to_u32() >= target_height, ) -> bool {
None => false, chain_tip
} .checked_sub(utxo_mined_at)
.and_then(|diff: crate::BlockHeightInterval| diff.try_into().ok())
.map_or(false, |diff: Self| diff >= self)
} }
} }
@ -154,6 +157,12 @@ impl MtpInterval {
} }
} }
/// Represents the [`MtpInterval`] 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. /// Returns the inner `u16` value.
#[inline] #[inline]
#[must_use] #[must_use]
@ -174,22 +183,26 @@ impl MtpInterval {
/// Determines whether a relativetime lock has matured, taking into account both /// Determines whether a relativetime lock has matured, taking into account both
/// the UTXOs Median Time Past at confirmation and the required delay. /// 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 /// # Parameters
/// - `self` The relative time delay (`t`) in 512second intervals. /// - `self` the relative time delay (`t`) in 512second intervals.
/// - `chain_tip` The current chain state, providing the tips MTP. /// - `chain_tip` the MTP of the current chain tip
/// - `utxo_mined_at` The chain state at the UTXOs confirmation, providing its MTP. /// - `utxo_mined_at` the MTP of the UTXOs confirmation block
/// ///
/// # Returns /// # Returns
/// - `true` if the relativetime lock has expired by the tips MTP /// - `true` if the relativetime lock has expired by the tips MTP
/// - `false` if the lock has not yet 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: MtpAndHeight, utxo_mined_at: MtpAndHeight) -> bool { pub fn is_satisfied_by(
match u32::from(self.to_512_second_intervals()).checked_mul(512) { self,
Some(seconds) => match seconds.checked_add(utxo_mined_at.to_mtp().to_u32()) { chain_tip: crate::BlockMtp,
Some(required_seconds) => chain_tip.to_mtp().to_u32() >= required_seconds, utxo_mined_at: crate::BlockMtp,
None => false, ) -> bool {
}, chain_tip
None => false, .checked_sub(utxo_mined_at)
} .and_then(|diff: crate::BlockMtpInterval| diff.to_relative_mtp_interval_floor().ok())
.map_or(false, |diff: Self| diff >= self)
} }
} }
@ -264,7 +277,7 @@ mod tests {
use internals::serde_round_trip; use internals::serde_round_trip;
use super::*; use super::*;
use crate::{BlockHeight, BlockTime}; use crate::BlockTime;
const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512; const MAXIMUM_ENCODABLE_SECONDS: u32 = u16::MAX as u32 * 512;
@ -349,6 +362,8 @@ mod tests {
#[test] #[test]
fn test_time_chain_state() { fn test_time_chain_state() {
use crate::BlockMtp;
let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200); let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200);
let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200); let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
@ -361,49 +376,48 @@ mod tests {
// Test case 1: Satisfaction (current_mtp >= utxo_mtp + required_seconds) // Test case 1: Satisfaction (current_mtp >= utxo_mtp + required_seconds)
// 10 intervals × 512 seconds = 5120 seconds // 10 intervals × 512 seconds = 5120 seconds
let time_lock = MtpInterval::from_512_second_intervals(10); let time_lock = MtpInterval::from_512_second_intervals(10);
let chain_state1 = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps); let chain_state1 = BlockMtp::new(timestamps);
let utxo_state1 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let utxo_state1 = BlockMtp::new(utxo_timestamps);
assert!(time_lock.is_satisfied_by(chain_state1, utxo_state1)); assert!(time_lock.is_satisfied_by(chain_state1, utxo_state1));
// Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds) // Test case 2: Not satisfied (current_mtp < utxo_mtp + required_seconds)
let chain_state2 = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps2); let chain_state2 = BlockMtp::new(timestamps2);
let utxo_state2 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps2); let utxo_state2 = BlockMtp::new(utxo_timestamps2);
assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2)); assert!(!time_lock.is_satisfied_by(chain_state2, utxo_state2));
// Test case 3: Test with a larger value (100 intervals = 51200 seconds) // Test case 3: Test with a larger value (100 intervals = 51200 seconds)
let larger_lock = MtpInterval::from_512_second_intervals(100); let larger_lock = MtpInterval::from_512_second_intervals(100);
let chain_state3 = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps3); let chain_state3 = BlockMtp::new(timestamps3);
let utxo_state3 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps3); let utxo_state3 = BlockMtp::new(utxo_timestamps3);
assert!(larger_lock.is_satisfied_by(chain_state3, utxo_state3)); assert!(larger_lock.is_satisfied_by(chain_state3, utxo_state3));
// Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully // Test case 4: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_time_lock = MtpInterval::MAX; let max_time_lock = MtpInterval::MAX;
let chain_state4 = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps); let chain_state4 = BlockMtp::new(timestamps);
let utxo_state4 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let utxo_state4 = BlockMtp::new(utxo_timestamps);
assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4)); assert!(!max_time_lock.is_satisfied_by(chain_state4, utxo_state4));
} }
#[test] #[test]
fn test_height_chain_state() { fn test_height_chain_state() {
let timestamps: [BlockTime; 11] = generate_timestamps(1_600_000_000, 200); use crate::BlockHeight;
let utxo_timestamps: [BlockTime; 11] = generate_timestamps(1_599_000_000, 200);
let height_lock = HeightInterval(10); let height_lock = HeightInterval(10);
// Test case 1: Satisfaction (current_height >= utxo_height + required) // Test case 1: Satisfaction (current_height >= utxo_height + required)
let chain_state1 = MtpAndHeight::new(BlockHeight::from_u32(100), timestamps); let chain_state1 = BlockHeight::from_u32(100);
let utxo_state1 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let utxo_state1 = BlockHeight::from_u32(80);
assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1)); assert!(height_lock.is_satisfied_by(chain_state1, utxo_state1));
// Test case 2: Not satisfied (current_height < utxo_height + required) // Test case 2: Not satisfied (current_height < utxo_height + required)
let chain_state2 = MtpAndHeight::new(BlockHeight::from_u32(89), timestamps); let chain_state2 = BlockHeight::from_u32(89);
let utxo_state2 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let utxo_state2 = BlockHeight::from_u32(80);
assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2)); assert!(!height_lock.is_satisfied_by(chain_state2, utxo_state2));
// Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully // Test case 3: Overflow handling - tests that is_satisfied_by handles overflow gracefully
let max_height_lock = HeightInterval::MAX; let max_height_lock = HeightInterval::MAX;
let chain_state3 = MtpAndHeight::new(BlockHeight::from_u32(1000), timestamps); let chain_state3 = BlockHeight::from_u32(1000);
let utxo_state3 = MtpAndHeight::new(BlockHeight::from_u32(80), utxo_timestamps); let utxo_state3 = BlockHeight::from_u32(80);
assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3)); assert!(!max_height_lock.is_satisfied_by(chain_state3, utxo_state3));
} }
} }

View File

@ -1,68 +0,0 @@
// SPDX-License-Identifier: CC0-1.0
//! Median Time Past (MTP) and height - used for working lock times.
use crate::{BlockHeight, BlockTime};
/// A structure containing both Median Time Past (MTP) and current
/// absolute block height, used for validating relative locktimes.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MtpAndHeight {
/// The Median Time Past (median of the last 11 blocks' timestamps)
mtp: BlockTime,
/// The current block height,
height: BlockHeight,
}
impl MtpAndHeight {
/// Constructs an [`MtpAndHeight`] by computing the mediantimepast from the last 11 block timestamps
///
/// # Parameters
///
/// * `height` - The absolute height of the chain tip
/// * `timestamps` - An array of timestamps from the most recent 11 blocks, where
/// - `timestamps[0]` is the timestamp at height `height - 10`
/// - `timestamps[1]` is the timestamp at height `height - 9`
/// - …
/// - `timestamps[10]` is the timestamp at height `height`
pub fn new(height: BlockHeight, timestamps: [BlockTime; 11]) -> Self {
let mut mtp_timestamps = timestamps;
mtp_timestamps.sort_unstable();
let mtp = mtp_timestamps[5];
MtpAndHeight { mtp, height }
}
/// Returns the median-time-past component.
pub fn to_mtp(self) -> BlockTime { self.mtp }
/// Returns the block-height component.
pub fn to_height(self) -> BlockHeight { self.height }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_chain_computes_mtp() {
let height = BlockHeight::from_u32(100);
let timestamps = [
BlockTime::from_u32(10),
BlockTime::from_u32(3),
BlockTime::from_u32(5),
BlockTime::from_u32(8),
BlockTime::from_u32(1),
BlockTime::from_u32(4),
BlockTime::from_u32(6),
BlockTime::from_u32(9),
BlockTime::from_u32(2),
BlockTime::from_u32(7),
BlockTime::from_u32(0),
];
let result = MtpAndHeight::new(height, timestamps);
assert_eq!(result.height, height);
assert_eq!(result.mtp.to_u32(), 5);
}
}

View File

@ -14,8 +14,8 @@ use arbitrary::{Arbitrary, Unstructured};
// These imports test "typical" usage by user code. // These imports test "typical" usage by user code.
use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`. use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`.
use bitcoin_units::{ use bitcoin_units::{
amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, BlockMtp,
BlockTime, FeeRate, SignedAmount, Weight, BlockMtpInterval, BlockTime, FeeRate, SignedAmount, Weight,
}; };
/// A struct that includes all public non-error enums. /// A struct that includes all public non-error enums.
@ -43,6 +43,8 @@ struct Structs {
j: relative::Time, j: relative::Time,
k: Weight, k: Weight,
l: BlockTime, l: BlockTime,
m: BlockMtp,
n: BlockMtpInterval,
} }
impl Structs { impl Structs {
@ -60,6 +62,8 @@ impl Structs {
j: relative::Time::MAX, j: relative::Time::MAX,
k: Weight::MAX, k: Weight::MAX,
l: BlockTime::from_u32(u32::MAX), l: BlockTime::from_u32(u32::MAX),
m: BlockMtp::MAX,
n: BlockMtpInterval::MAX,
} }
} }
} }
@ -90,6 +94,8 @@ struct CommonTraits {
j: relative::Time, j: relative::Time,
k: Weight, k: Weight,
l: BlockTime, l: BlockTime,
m: BlockMtp,
n: BlockMtpInterval,
} }
/// A struct that includes all types that implement `Default`. /// A struct that includes all types that implement `Default`.
@ -100,6 +106,7 @@ struct Default {
c: BlockInterval, c: BlockInterval,
d: relative::Height, d: relative::Height,
e: relative::Time, e: relative::Time,
f: BlockMtpInterval,
} }
/// A struct that includes all public error types. /// A struct that includes all public error types.
@ -147,7 +154,8 @@ fn api_can_use_modules_from_crate_root() {
#[test] #[test]
fn api_can_use_types_from_crate_root() { fn api_can_use_types_from_crate_root() {
use bitcoin_units::{ use bitcoin_units::{
Amount, BlockHeight, BlockInterval, BlockTime, FeeRate, SignedAmount, Weight, Amount, BlockHeight, BlockInterval, BlockMtp, BlockMtpInterval, BlockTime, FeeRate,
SignedAmount, Weight,
}; };
} }
@ -164,7 +172,7 @@ fn api_can_use_all_types_from_module_amount() {
#[test] #[test]
fn api_can_use_all_types_from_module_block() { fn api_can_use_all_types_from_module_block() {
use bitcoin_units::block::{ use bitcoin_units::block::{
BlockHeight, BlockInterval, TooBigForRelativeBlockHeightIntervalError, BlockHeight, BlockHeightInterval, TooBigForRelativeBlockHeightIntervalError,
}; };
} }
@ -257,6 +265,7 @@ fn regression_default() {
c: BlockInterval::ZERO, c: BlockInterval::ZERO,
d: relative::Height::ZERO, d: relative::Height::ZERO,
e: relative::Time::ZERO, e: relative::Time::ZERO,
f: BlockMtpInterval::ZERO,
}; };
assert_eq!(got, want); assert_eq!(got, want);
} }
@ -298,6 +307,8 @@ impl<'a> Arbitrary<'a> for Structs {
j: relative::Time::arbitrary(u)?, j: relative::Time::arbitrary(u)?,
k: Weight::arbitrary(u)?, k: Weight::arbitrary(u)?,
l: BlockTime::arbitrary(u)?, l: BlockTime::arbitrary(u)?,
m: BlockMtp::arbitrary(u)?,
n: BlockMtpInterval::arbitrary(u)?,
}; };
Ok(a) Ok(a)
} }