From 64c31cfb97abf0376e17d094cdc2dc67b2d79a66 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 9 Jul 2024 16:24:25 +1000 Subject: [PATCH] Move locktimes and Sequence to primitives The `absolute` and `relative` locktimes as well as the `Sequence` are all primitive bitcoin types. Move the `Sequence`, and `locktime` stuff over to `primitives`. There is nothing surprising here, the consensus encoding stuff stays in `bitcoin` and we re-export everything from `blockdata`. --- Cargo-minimal.lock | 4 + Cargo-recent.lock | 4 + bitcoin/src/blockdata/constants.rs | 4 +- bitcoin/src/blockdata/mod.rs | 44 +++- bitcoin/src/blockdata/script/builder.rs | 2 +- bitcoin/src/blockdata/transaction.rs | 219 +--------------- bitcoin/src/lib.rs | 4 +- bitcoin/src/psbt/mod.rs | 3 +- primitives/Cargo.toml | 18 +- primitives/src/lib.rs | 15 +- .../src}/locktime/absolute.rs | 43 +--- .../src}/locktime/mod.rs | 0 .../src}/locktime/relative.rs | 20 +- primitives/src/sequence.rs | 237 ++++++++++++++++++ 14 files changed, 348 insertions(+), 269 deletions(-) rename {bitcoin/src/blockdata => primitives/src}/locktime/absolute.rs (93%) rename {bitcoin/src/blockdata => primitives/src}/locktime/mod.rs (100%) rename {bitcoin/src/blockdata => primitives/src}/locktime/relative.rs (96%) create mode 100644 primitives/src/sequence.rs diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index aa8080c5e..2aa15fc41 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -101,6 +101,10 @@ name = "bitcoin-primitives" version = "0.100.0" dependencies = [ "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "mutagen", + "ordered", "serde", ] diff --git a/Cargo-recent.lock b/Cargo-recent.lock index be368bc55..085deb0f3 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -100,6 +100,10 @@ name = "bitcoin-primitives" version = "0.100.0" dependencies = [ "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "mutagen", + "ordered", "serde", ] diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index e440c00ba..0f9e4511b 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -16,9 +16,9 @@ use crate::locktime::absolute; use crate::network::Network; use crate::opcodes::all::*; use crate::pow::CompactTarget; -use crate::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut}; +use crate::transaction::{self, OutPoint, Transaction, TxIn, TxOut}; use crate::witness::Witness; -use crate::{script, Amount, BlockHash}; +use crate::{script, Amount, BlockHash, Sequence}; /// How many seconds between blocks we expect on average. pub const TARGET_BLOCK_SPACING: u32 = 600; diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index 10ae8cccf..bd8595807 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -7,7 +7,6 @@ pub mod block; pub mod constants; -pub mod locktime; pub mod script; pub mod transaction; pub mod witness; @@ -47,6 +46,49 @@ pub mod fee_rate { } } +/// Provides absolute and relative locktimes. +pub mod locktime { + pub mod absolute { + //! Provides type [`LockTime`] that implements the logic around nLockTime/OP_CHECKLOCKTIMEVERIFY. + //! + //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by + //! whether `LockTime < LOCKTIME_THRESHOLD`. + + use io::{BufRead, Write}; + + pub use crate::consensus::encode::{self, Decodable, Encodable}; + + /// Re-export everything from the `primitives::locktime::absolute` module. + #[rustfmt::skip] // Keep public re-exports separate. + pub use primitives::locktime::absolute::*; + + impl Encodable for LockTime { + #[inline] + fn consensus_encode(&self, w: &mut W) -> Result { + let v = self.to_consensus_u32(); + v.consensus_encode(w) + } + } + + impl Decodable for LockTime { + #[inline] + fn consensus_decode(r: &mut R) -> Result { + u32::consensus_decode(r).map(LockTime::from_consensus) + } + } + } + + pub mod relative { + //! Provides type [`LockTime`] that implements the logic around nSequence/OP_CHECKSEQUENCEVERIFY. + //! + //! There are two types of lock time: lock-by-blockheight and lock-by-blocktime, distinguished by + //! whether bit 22 of the `u32` consensus value is set. + + /// Re-export everything from the `primitives::locktime::relative` module. + pub use primitives::locktime::relative::*; + } +} + /// Bitcoin script opcodes. pub mod opcodes { /// Re-export everything from the [`primitives::opcodes`] module. diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index de98676a2..6d4e72806 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -7,7 +7,7 @@ use crate::locktime::absolute; use crate::opcodes::all::*; use crate::opcodes::{self, Opcode}; use crate::prelude::Vec; -use crate::transaction::Sequence; +use crate::Sequence; /// An Object which can be used to construct a script piece by piece. #[derive(PartialEq, Eq, Clone)] diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index f0da5df31..ceee9ae69 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -15,13 +15,13 @@ use core::{cmp, fmt, str}; use hashes::sha256d; use internals::write_err; use io::{BufRead, Write}; -use units::parse::{self, PrefixedHexError, UnprefixedHexError}; +use primitives::Sequence; +use units::parse; use super::Weight; use crate::consensus::{encode, Decodable, Encodable}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; use crate::locktime::absolute::{self, Height, Time}; -use crate::locktime::relative::{self, TimeOverflowError}; use crate::prelude::{Borrow, Vec}; use crate::script::{Script, ScriptBuf}; #[cfg(doc)] @@ -330,221 +330,6 @@ impl Default for TxIn { } } -/// Bitcoin transaction input sequence number. -/// -/// The sequence field is used for: -/// - Indicating whether absolute lock-time (specified in `lock_time` field of [`Transaction`]) -/// is enabled. -/// - Indicating and encoding [BIP-68] relative lock-times. -/// - Indicating whether a transaction opts-in to [BIP-125] replace-by-fee. -/// -/// Note that transactions spending an output with `OP_CHECKLOCKTIMEVERIFY`MUST NOT use -/// `Sequence::MAX` for the corresponding input. [BIP-65] -/// -/// [BIP-65]: -/// [BIP-68]: -/// [BIP-125]: -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] -pub struct Sequence(pub u32); - -impl Sequence { - /// The maximum allowable sequence number. - /// - /// This sequence number disables absolute lock time and replace-by-fee. - pub const MAX: Self = Sequence(0xFFFFFFFF); - /// Zero value sequence. - /// - /// This sequence number enables replace-by-fee and absolute lock time. - pub const ZERO: Self = Sequence(0); - /// The sequence number that enables absolute lock time but disables replace-by-fee - /// and relative lock time. - pub const ENABLE_LOCKTIME_NO_RBF: Self = Sequence::MIN_NO_RBF; - /// The sequence number that enables replace-by-fee and absolute lock time but - /// disables relative lock time. - pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD); - - /// The number of bytes that a sequence number contributes to the size of a transaction. - const SIZE: usize = 4; // Serialized length of a u32. - - /// The lowest sequence number that does not opt-in for replace-by-fee. - /// - /// A transaction is considered to have opted in to replacement of itself - /// if any of it's inputs have a `Sequence` number less than this value - /// (Explicit Signalling [BIP-125]). - /// - /// [BIP-125]: - const MIN_NO_RBF: Self = Sequence(0xFFFFFFFE); - /// BIP-68 relative lock time disable flag mask. - const LOCK_TIME_DISABLE_FLAG_MASK: u32 = 0x80000000; - /// BIP-68 relative lock time type flag mask. - const LOCK_TYPE_MASK: u32 = 0x00400000; - - /// Returns `true` if the sequence number enables absolute lock-time ([`Transaction::lock_time`]). - #[inline] - pub fn enables_absolute_lock_time(&self) -> bool { *self != Sequence::MAX } - - /// Returns `true` if the sequence number indicates that the transaction is finalized. - /// - /// Instead of this method please consider using `!enables_absolute_lock_time` because it - /// is equivalent and improves readability for those not steeped in Bitcoin folklore. - /// - /// ## Historical note - /// - /// The term 'final' is an archaic Bitcoin term, it may have come about because the sequence - /// number in the original Bitcoin code was intended to be incremented in order to replace a - /// transaction, so once the sequence number got to `u64::MAX` it could no longer be increased, - /// hence it was 'final'. - /// - /// - /// Some other references to the term: - /// - `CTxIn::SEQUENCE_FINAL` in the Bitcoin Core code. - /// - [BIP-112]: "BIP 68 prevents a non-final transaction from being selected for inclusion in a - /// block until the corresponding input has reached the specified age" - /// - /// [BIP-112]: - #[inline] - pub fn is_final(&self) -> bool { !self.enables_absolute_lock_time() } - - /// Returns true if the transaction opted-in to BIP125 replace-by-fee. - /// - /// Replace by fee is signaled by the sequence being less than 0xfffffffe which is checked by - /// this method. Note, this is the highest "non-final" value (see [`Sequence::is_final`]). - #[inline] - pub fn is_rbf(&self) -> bool { *self < Sequence::MIN_NO_RBF } - - /// Returns `true` if the sequence has a relative lock-time. - #[inline] - pub fn is_relative_lock_time(&self) -> bool { - self.0 & Sequence::LOCK_TIME_DISABLE_FLAG_MASK == 0 - } - - /// Returns `true` if the sequence number encodes a block based relative lock-time. - #[inline] - pub fn is_height_locked(&self) -> bool { - self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK == 0) - } - - /// Returns `true` if the sequence number encodes a time interval based relative lock-time. - #[inline] - pub fn is_time_locked(&self) -> bool { - self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0) - } - - /// Creates a `Sequence` from a prefixed hex string. - pub fn from_hex(s: &str) -> Result { - let lock_time = parse::hex_u32_prefixed(s)?; - Ok(Self::from_consensus(lock_time)) - } - - /// Creates a `Sequence` from an unprefixed hex string. - pub fn from_unprefixed_hex(s: &str) -> Result { - let lock_time = parse::hex_u32_unprefixed(s)?; - Ok(Self::from_consensus(lock_time)) - } - - /// Creates a relative lock-time using block height. - #[inline] - pub fn from_height(height: u16) -> Self { Sequence(u32::from(height)) } - - /// Creates a relative lock-time using time intervals where each interval is equivalent - /// to 512 seconds. - /// - /// Encoding finer granularity of time for relative lock-times is not supported in Bitcoin - #[inline] - pub fn from_512_second_intervals(intervals: u16) -> Self { - Sequence(u32::from(intervals) | Sequence::LOCK_TYPE_MASK) - } - - /// Creates a relative lock-time from seconds, converting the seconds into 512 second - /// interval with floor division. - /// - /// Will return an error if the input cannot be encoded in 16 bits. - #[inline] - pub fn from_seconds_floor(seconds: u32) -> Result { - if let Ok(interval) = u16::try_from(seconds / 512) { - Ok(Sequence::from_512_second_intervals(interval)) - } else { - Err(TimeOverflowError::new(seconds)) - } - } - - /// Creates a relative lock-time from seconds, converting the seconds into 512 second - /// interval with ceiling division. - /// - /// Will return an error if the input cannot be encoded in 16 bits. - #[inline] - pub fn from_seconds_ceil(seconds: u32) -> Result { - if let Ok(interval) = u16::try_from((seconds + 511) / 512) { - Ok(Sequence::from_512_second_intervals(interval)) - } else { - Err(TimeOverflowError::new(seconds)) - } - } - - /// Creates a sequence from a u32 value. - #[inline] - pub fn from_consensus(n: u32) -> Self { Sequence(n) } - - /// Returns the inner 32bit integer value of Sequence. - #[inline] - pub fn to_consensus_u32(self) -> u32 { self.0 } - - /// Creates a [`relative::LockTime`] from this [`Sequence`] number. - #[inline] - pub fn to_relative_lock_time(&self) -> Option { - use crate::locktime::relative::{Height, LockTime, Time}; - - if !self.is_relative_lock_time() { - return None; - } - - let lock_value = self.low_u16(); - - if self.is_time_locked() { - Some(LockTime::from(Time::from_512_second_intervals(lock_value))) - } else { - Some(LockTime::from(Height::from(lock_value))) - } - } - - /// Returns the low 16 bits from sequence number. - /// - /// BIP-68 only uses the low 16 bits for relative lock value. - fn low_u16(&self) -> u16 { self.0 as u16 } -} - -impl Default for Sequence { - /// The default value of sequence is 0xffffffff. - fn default() -> Self { Sequence::MAX } -} - -impl From for u32 { - fn from(sequence: Sequence) -> u32 { sequence.0 } -} - -impl fmt::Display for Sequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - -impl fmt::LowerHex for Sequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) } -} - -impl fmt::UpperHex for Sequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.0, f) } -} - -impl fmt::Debug for Sequence { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // 10 because its 8 digits + 2 for the '0x' - write!(f, "Sequence({:#010x})", self.0) - } -} - -units::impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus); - /// Bitcoin transaction output. /// /// Defines new coins to be created as a result of the transaction, diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 73dd3cc0a..fa45b34b7 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -119,7 +119,7 @@ pub use crate::{ blockdata::script::witness_program::{self, WitnessProgram}, blockdata::script::witness_version::{self, WitnessVersion}, blockdata::script::{self, Script, ScriptBuf, ScriptHash, WScriptHash}, - blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid, Wtxid}, + blockdata::transaction::{self, OutPoint, Transaction, TxIn, TxOut, Txid, Wtxid}, blockdata::weight::Weight, blockdata::witness::{self, Witness}, consensus::encode::VarInt, @@ -134,6 +134,8 @@ pub use crate::{ sighash::{EcdsaSighashType, TapSighashType}, taproot::{TapBranchTag, TapLeafHash, TapLeafTag, TapNodeHash, TapTweakHash, TapTweakTag}, }; +#[doc(inline)] +pub use primitives::Sequence; pub use units::{BlockHeight, BlockInterval}; #[rustfmt::skip] diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 054774815..47d976d85 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -1217,8 +1217,9 @@ mod tests { use crate::network::NetworkKind; use crate::psbt::serialize::{Deserialize, Serialize}; use crate::script::ScriptBuf; - use crate::transaction::{self, OutPoint, Sequence, TxIn}; + use crate::transaction::{self, OutPoint, TxIn}; use crate::witness::Witness; + use crate::Sequence; #[track_caller] pub fn hex_psbt(s: &str) -> Result { diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index db50081fd..b62901478 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -16,18 +16,28 @@ exclude = ["tests", "contrib"] [features] default = ["std"] -std = ["alloc", "internals/std"] -alloc = ["internals/alloc"] -serde = ["actual-serde", "internals/serde"] +std = ["alloc", "internals/std", "io/std", "units/std"] +alloc = ["internals/alloc", "io/alloc", "units/alloc"] +serde = ["actual-serde", "internals/serde", "units/serde"] [dependencies] internals = { package = "bitcoin-internals", version = "0.3.0" } +io = { package = "bitcoin-io", version = "0.1.1", default-features = false } +units = { package = "bitcoin-units", version = "0.1.0", default-features = false } + +ordered = { version = "0.2.0", optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. -actual-serde = { package = "serde", version = "1.0.103", default-features = false, optional = true } +actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = ["derive"], optional = true } [dev-dependencies] +[target.'cfg(mutate)'.dev-dependencies] +mutagen = { git = "https://github.com/llogiq/mutagen" } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints.rust] +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(mutate)'] } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 63758ea1f..5438df424 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -8,8 +8,6 @@ //! //! [`rust-bitcoin`]: -// NB: This crate is empty if `alloc` is not enabled. -#![cfg(feature = "alloc")] #![cfg_attr(all(not(test), not(feature = "std")), no_std)] // Experimental features we need. #![cfg_attr(docsrs, feature(doc_auto_cfg))] @@ -26,9 +24,22 @@ extern crate alloc; extern crate std; #[cfg(feature = "serde")] +#[macro_use] extern crate actual_serde as serde; +#[cfg(feature = "alloc")] +pub mod locktime; pub mod opcodes; +pub mod sequence; + +#[rustfmt::skip] // Keep public re-exports separate. +#[doc(inline)] +#[cfg(feature = "alloc")] +pub use self::{ + locktime::{absolute, relative}, +}; +#[doc(inline)] +pub use self::sequence::Sequence; #[rustfmt::skip] #[allow(unused_imports)] diff --git a/bitcoin/src/blockdata/locktime/absolute.rs b/primitives/src/locktime/absolute.rs similarity index 93% rename from bitcoin/src/blockdata/locktime/absolute.rs rename to primitives/src/locktime/absolute.rs index 1227b7fbd..964ee9016 100644 --- a/bitcoin/src/blockdata/locktime/absolute.rs +++ b/primitives/src/locktime/absolute.rs @@ -8,14 +8,12 @@ use core::cmp::Ordering; use core::fmt; -use io::{BufRead, Write}; #[cfg(all(test, mutate))] use mutagen::mutate; use units::parse::{self, PrefixedHexError, UnprefixedHexError}; #[cfg(doc)] use crate::absolute; -use crate::consensus::encode::{self, Decodable, Encodable}; #[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] @@ -26,14 +24,14 @@ pub use units::locktime::absolute::{ /// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds /// since epoch). /// -/// Used for transaction lock time (`nLockTime` in Bitcoin Core and [`crate::Transaction::lock_time`] +/// Used for transaction lock time (`nLockTime` in Bitcoin Core and `Transaction::lock_time` /// in this library) and also for the argument to opcode 'OP_CHECKLOCKTIMEVERIFY`. /// /// ### Note on ordering /// /// Locktimes may be height- or time-based, and these metrics are incommensurate; there is no total /// ordering on locktimes. We therefore have implemented [`PartialOrd`] but not [`Ord`]. -/// For [`crate::Transaction`], which has a locktime field, we implement a total ordering to make +/// For `Transaction`, which has a locktime field, we implement a total ordering to make /// it easy to store transactions in sorted data structures, and use the locktime's 32-bit integer /// consensus encoding to order it. We also implement [`ordered::ArbitraryOrd`] if the "ordered" /// feature is enabled. @@ -46,7 +44,7 @@ pub use units::locktime::absolute::{ /// # Examples /// /// ``` -/// # use bitcoin::absolute::{LockTime, LockTime::*}; +/// # use bitcoin_primitives::absolute::{LockTime, LockTime::*}; /// # let n = LockTime::from_consensus(741521); // n OP_CHECKLOCKTIMEVERIFY /// # let lock_time = LockTime::from_consensus(741521); // nLockTime /// // To compare absolute lock times there are various `is_satisfied_*` methods, you may also use: @@ -63,7 +61,7 @@ pub enum LockTime { /// # Examples /// /// ```rust - /// use bitcoin::absolute::LockTime; + /// use bitcoin_primitives::absolute::LockTime; /// /// let block: u32 = 741521; /// let n = LockTime::from_height(block).expect("valid height"); @@ -76,7 +74,7 @@ pub enum LockTime { /// # Examples /// /// ```rust - /// use bitcoin::absolute::LockTime; + /// use bitcoin_primitives::absolute::LockTime; /// /// let seconds: u32 = 1653195600; // May 22nd, 5am UTC. /// let n = LockTime::from_time(seconds).expect("valid time"); @@ -87,7 +85,7 @@ pub enum LockTime { } impl LockTime { - /// If [`crate::Transaction::lock_time`] is set to zero it is ignored, in other words a + /// If `Transaction::lock_time` is set to zero it is ignored, in other words a /// transaction with nLocktime==0 is able to be included immediately in any block. pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO); @@ -111,7 +109,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::absolute::LockTime; + /// # use bitcoin_primitives::absolute::LockTime; /// # let n = LockTime::from_consensus(741521); // n OP_CHECKLOCKTIMEVERIFY /// /// // `from_consensus` roundtrips as expected with `to_consensus_u32`. @@ -140,7 +138,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::absolute::LockTime; + /// # use bitcoin_primitives::absolute::LockTime; /// assert!(LockTime::from_height(741521).is_ok()); /// assert!(LockTime::from_height(1653195600).is_err()); /// ``` @@ -166,7 +164,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::absolute::LockTime; + /// # use bitcoin_primitives::absolute::LockTime; /// assert!(LockTime::from_time(1653195600).is_ok()); /// assert!(LockTime::from_time(741521).is_err()); /// ``` @@ -200,13 +198,13 @@ impl LockTime { /// blocktime based lock it is checked against `time`. /// /// A 'timelock constraint' refers to the `n` from `n OP_CHEKCLOCKTIMEVERIFY`, this constraint - /// is satisfied if a transaction with nLockTime ([`crate::Transaction::lock_time`]) set to + /// is satisfied if a transaction with nLockTime (`Transaction::lock_time`) set to /// `height`/`time` is valid. /// /// # Examples /// /// ```no_run - /// # use bitcoin::absolute::{LockTime, Height, Time}; + /// # use bitcoin_primitives::absolute::{LockTime, Height, Time}; /// // Can be implemented if block chain data is available. /// fn get_height() -> Height { todo!("return the current block height") } /// fn get_time() -> Time { todo!("return the current block time") } @@ -241,7 +239,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::absolute::{LockTime, LockTime::*}; + /// # use bitcoin_primitives::absolute::{LockTime, LockTime::*}; /// let lock_time = LockTime::from_consensus(741521); /// let check = LockTime::from_consensus(741521 + 1); /// assert!(lock_time.is_implied_by(check)); @@ -270,7 +268,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::absolute::{LockTime, LockTime::*}; + /// # use bitcoin_primitives::absolute::{LockTime, LockTime::*}; /// # let n = LockTime::from_consensus(741521); // n OP_CHECKLOCKTIMEVERIFY /// # let lock_time = LockTime::from_consensus(741521 + 1); // nLockTime /// @@ -346,21 +344,6 @@ impl fmt::Display for LockTime { } } -impl Encodable for LockTime { - #[inline] - fn consensus_encode(&self, w: &mut W) -> Result { - let v = self.to_consensus_u32(); - v.consensus_encode(w) - } -} - -impl Decodable for LockTime { - #[inline] - fn consensus_decode(r: &mut R) -> Result { - u32::consensus_decode(r).map(LockTime::from_consensus) - } -} - #[cfg(feature = "serde")] impl serde::Serialize for LockTime { fn serialize(&self, serializer: S) -> Result diff --git a/bitcoin/src/blockdata/locktime/mod.rs b/primitives/src/locktime/mod.rs similarity index 100% rename from bitcoin/src/blockdata/locktime/mod.rs rename to primitives/src/locktime/mod.rs diff --git a/bitcoin/src/blockdata/locktime/relative.rs b/primitives/src/locktime/relative.rs similarity index 96% rename from bitcoin/src/blockdata/locktime/relative.rs rename to primitives/src/locktime/relative.rs index d213ef511..4844c1530 100644 --- a/bitcoin/src/blockdata/locktime/relative.rs +++ b/primitives/src/locktime/relative.rs @@ -22,7 +22,7 @@ pub use units::locktime::relative::{Height, Time, TimeOverflowError}; /// A relative lock time value, representing either a block height or time (512 second intervals). /// -/// Used for sequence numbers (`nSequence` in Bitcoin Core and [`crate::TxIn::sequence`] +/// Used for sequence numbers (`nSequence` in Bitcoin Core and `TxIn::sequence` /// in this library) and also for the argument to opcode 'OP_CHECKSEQUENCEVERIFY`. /// /// ### Note on ordering @@ -62,7 +62,7 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::relative::LockTime; + /// # use bitcoin_primitives::relative::LockTime; /// /// // `from_consensus` roundtrips with `to_consensus_u32` for small values. /// let n_lock_time: u32 = 7000; @@ -165,8 +165,8 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::Sequence; - /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// # use bitcoin_primitives::Sequence; + /// # use bitcoin_primitives::locktime::relative::{LockTime, Height, Time}; /// /// # let required_height = 100; // 100 blocks. /// # let intervals = 70; // Approx 10 hours. @@ -205,8 +205,8 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::Sequence; - /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// # use bitcoin_primitives::Sequence; + /// # use bitcoin_primitives::locktime::relative::{LockTime, Height, Time}; /// /// # let required_height = 100; // 100 blocks. /// # let lock = Sequence::from_height(required_height).to_relative_lock_time().expect("valid height"); @@ -253,8 +253,8 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::Sequence; - /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// # use bitcoin_primitives::Sequence; + /// # use bitcoin_primitives::locktime::relative::{LockTime, Height, Time}; /// /// let required_height: u16 = 100; /// let lock = Sequence::from_height(required_height).to_relative_lock_time().expect("valid height"); @@ -280,8 +280,8 @@ impl LockTime { /// # Examples /// /// ```rust - /// # use bitcoin::Sequence; - /// # use bitcoin::locktime::relative::{LockTime, Height, Time}; + /// # use bitcoin_primitives::Sequence; + /// # use bitcoin_primitives::locktime::relative::{LockTime, Height, Time}; /// /// let intervals: u16 = 70; // approx 10 hours; /// let lock = Sequence::from_512_second_intervals(intervals).to_relative_lock_time().expect("valid time"); diff --git a/primitives/src/sequence.rs b/primitives/src/sequence.rs new file mode 100644 index 000000000..63f2a62bb --- /dev/null +++ b/primitives/src/sequence.rs @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Bitcoin transaction input sequence number. +//! +//! The sequence field is used for: +//! - Indicating whether absolute lock-time (specified in `lock_time` field of `Transaction`) +//! is enabled. +//! - Indicating and encoding [BIP-68] relative lock-times. +//! - Indicating whether a transaction opts-in to [BIP-125] replace-by-fee. +//! +//! Note that transactions spending an output with `OP_CHECKLOCKTIMEVERIFY`MUST NOT use +//! `Sequence::MAX` for the corresponding input. [BIP-65] +//! +//! [BIP-65]: +//! [BIP-68]: +//! [BIP-125]: + +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +#[cfg(feature = "alloc")] +use units::locktime::relative::TimeOverflowError; +#[cfg(feature = "alloc")] +use units::parse::{self, PrefixedHexError, UnprefixedHexError}; + +#[cfg(feature = "alloc")] +use crate::locktime::relative; + +/// Bitcoin transaction input sequence number. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Sequence(pub u32); + +impl Sequence { + /// The maximum allowable sequence number. + /// + /// This sequence number disables absolute lock time and replace-by-fee. + pub const MAX: Self = Sequence(0xFFFFFFFF); + /// Zero value sequence. + /// + /// This sequence number enables replace-by-fee and absolute lock time. + pub const ZERO: Self = Sequence(0); + /// The sequence number that enables absolute lock time but disables replace-by-fee + /// and relative lock time. + pub const ENABLE_LOCKTIME_NO_RBF: Self = Sequence::MIN_NO_RBF; + /// The sequence number that enables replace-by-fee and absolute lock time but + /// disables relative lock time. + pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD); + + /// The number of bytes that a sequence number contributes to the size of a transaction. + pub const SIZE: usize = 4; // Serialized length of a u32. + + /// The lowest sequence number that does not opt-in for replace-by-fee. + /// + /// A transaction is considered to have opted in to replacement of itself + /// if any of it's inputs have a `Sequence` number less than this value + /// (Explicit Signalling [BIP-125]). + /// + /// [BIP-125]: + const MIN_NO_RBF: Self = Sequence(0xFFFFFFFE); + /// BIP-68 relative lock time disable flag mask. + const LOCK_TIME_DISABLE_FLAG_MASK: u32 = 0x80000000; + /// BIP-68 relative lock time type flag mask. + const LOCK_TYPE_MASK: u32 = 0x00400000; + + /// Returns `true` if the sequence number enables absolute lock-time (`Transaction::lock_time`). + #[inline] + pub fn enables_absolute_lock_time(&self) -> bool { *self != Sequence::MAX } + + /// Returns `true` if the sequence number indicates that the transaction is finalized. + /// + /// Instead of this method please consider using `!enables_absolute_lock_time` because it + /// is equivalent and improves readability for those not steeped in Bitcoin folklore. + /// + /// ## Historical note + /// + /// The term 'final' is an archaic Bitcoin term, it may have come about because the sequence + /// number in the original Bitcoin code was intended to be incremented in order to replace a + /// transaction, so once the sequence number got to `u64::MAX` it could no longer be increased, + /// hence it was 'final'. + /// + /// + /// Some other references to the term: + /// - `CTxIn::SEQUENCE_FINAL` in the Bitcoin Core code. + /// - [BIP-112]: "BIP 68 prevents a non-final transaction from being selected for inclusion in a + /// block until the corresponding input has reached the specified age" + /// + /// [BIP-112]: + #[inline] + pub fn is_final(&self) -> bool { !self.enables_absolute_lock_time() } + + /// Returns true if the transaction opted-in to BIP125 replace-by-fee. + /// + /// Replace by fee is signaled by the sequence being less than 0xfffffffe which is checked by + /// this method. Note, this is the highest "non-final" value (see [`Sequence::is_final`]). + #[inline] + pub fn is_rbf(&self) -> bool { *self < Sequence::MIN_NO_RBF } + + /// Returns `true` if the sequence has a relative lock-time. + #[inline] + pub fn is_relative_lock_time(&self) -> bool { + self.0 & Sequence::LOCK_TIME_DISABLE_FLAG_MASK == 0 + } + + /// Returns `true` if the sequence number encodes a block based relative lock-time. + #[inline] + pub fn is_height_locked(&self) -> bool { + self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK == 0) + } + + /// Returns `true` if the sequence number encodes a time interval based relative lock-time. + #[inline] + pub fn is_time_locked(&self) -> bool { + self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0) + } + + /// Creates a `Sequence` from a prefixed hex string. + #[cfg(feature = "alloc")] + pub fn from_hex(s: &str) -> Result { + let lock_time = parse::hex_u32_prefixed(s)?; + Ok(Self::from_consensus(lock_time)) + } + + /// Creates a `Sequence` from an unprefixed hex string. + #[cfg(feature = "alloc")] + pub fn from_unprefixed_hex(s: &str) -> Result { + let lock_time = parse::hex_u32_unprefixed(s)?; + Ok(Self::from_consensus(lock_time)) + } + + /// Creates a relative lock-time using block height. + #[inline] + pub fn from_height(height: u16) -> Self { Sequence(u32::from(height)) } + + /// Creates a relative lock-time using time intervals where each interval is equivalent + /// to 512 seconds. + /// + /// Encoding finer granularity of time for relative lock-times is not supported in Bitcoin + #[inline] + pub fn from_512_second_intervals(intervals: u16) -> Self { + Sequence(u32::from(intervals) | Sequence::LOCK_TYPE_MASK) + } + + /// Creates a relative lock-time from seconds, converting the seconds into 512 second + /// interval with floor division. + /// + /// Will return an error if the input cannot be encoded in 16 bits. + #[inline] + #[cfg(feature = "alloc")] + pub fn from_seconds_floor(seconds: u32) -> Result { + if let Ok(interval) = u16::try_from(seconds / 512) { + Ok(Sequence::from_512_second_intervals(interval)) + } else { + Err(TimeOverflowError::new(seconds)) + } + } + + /// Creates a relative lock-time from seconds, converting the seconds into 512 second + /// interval with ceiling division. + /// + /// Will return an error if the input cannot be encoded in 16 bits. + #[inline] + #[cfg(feature = "alloc")] + pub fn from_seconds_ceil(seconds: u32) -> Result { + if let Ok(interval) = u16::try_from((seconds + 511) / 512) { + Ok(Sequence::from_512_second_intervals(interval)) + } else { + Err(TimeOverflowError::new(seconds)) + } + } + + /// Creates a sequence from a u32 value. + #[inline] + pub fn from_consensus(n: u32) -> Self { Sequence(n) } + + /// Returns the inner 32bit integer value of Sequence. + #[inline] + pub fn to_consensus_u32(self) -> u32 { self.0 } + + /// Creates a [`relative::LockTime`] from this [`Sequence`] number. + #[inline] + #[cfg(feature = "alloc")] + pub fn to_relative_lock_time(&self) -> Option { + use crate::locktime::relative::{Height, LockTime, Time}; + + if !self.is_relative_lock_time() { + return None; + } + + let lock_value = self.low_u16(); + + if self.is_time_locked() { + Some(LockTime::from(Time::from_512_second_intervals(lock_value))) + } else { + Some(LockTime::from(Height::from(lock_value))) + } + } + + /// Returns the low 16 bits from sequence number. + /// + /// BIP-68 only uses the low 16 bits for relative lock value. + #[cfg(feature = "alloc")] + fn low_u16(&self) -> u16 { self.0 as u16 } +} + +impl Default for Sequence { + /// The default value of sequence is 0xffffffff. + fn default() -> Self { Sequence::MAX } +} + +impl From for u32 { + fn from(sequence: Sequence) -> u32 { sequence.0 } +} + +impl fmt::Display for Sequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} + +impl fmt::LowerHex for Sequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) } +} + +impl fmt::UpperHex for Sequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.0, f) } +} + +impl fmt::Debug for Sequence { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // 10 because its 8 digits + 2 for the '0x' + write!(f, "Sequence({:#010x})", self.0) + } +} + +#[cfg(feature = "alloc")] +units::impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus);