From e34bc538c39df1679724d6ee2cf80b5a77ecee28 Mon Sep 17 00:00:00 2001 From: Noah Lanson Date: Sat, 16 Jul 2022 10:49:03 +1000 Subject: [PATCH] Add new type for sequence --- src/blockdata/constants.rs | 6 +- src/blockdata/transaction.rs | 246 ++++++++++++++++++++++++++++++++++- src/lib.rs | 1 + src/util/psbt/mod.rs | 20 +-- 4 files changed, 254 insertions(+), 19 deletions(-) diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs index cbb4eeff4c..066d20c006 100644 --- a/src/blockdata/constants.rs +++ b/src/blockdata/constants.rs @@ -27,7 +27,7 @@ use crate::hashes::hex::{self, HexIterator}; use crate::hashes::sha256d; use crate::blockdata::opcodes; use crate::blockdata::script; -use crate::blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn}; +use crate::blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn, Sequence}; use crate::blockdata::block::{Block, BlockHeader}; use crate::blockdata::witness::Witness; use crate::network::constants::Network; @@ -94,7 +94,7 @@ fn bitcoin_genesis_tx() -> Transaction { ret.input.push(TxIn { previous_output: OutPoint::null(), script_sig: in_script, - sequence: MAX_SEQUENCE, + sequence: Sequence::MAX, witness: Witness::default(), }); @@ -222,7 +222,7 @@ mod test { assert_eq!(serialize(&gen.input[0].script_sig), Vec::from_hex("4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73").unwrap()); - assert_eq!(gen.input[0].sequence, MAX_SEQUENCE); + assert_eq!(gen.input[0].sequence, Sequence::MAX); assert_eq!(gen.output.len(), 1); assert_eq!(serialize(&gen.output[0].script_pubkey), Vec::from_hex("434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac").unwrap()); diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 1d09f35a0a..351ad9beb2 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -27,12 +27,13 @@ use crate::prelude::*; use crate::io; use core::{fmt, str, default::Default}; +use core::convert::TryFrom; use crate::hashes::{self, Hash, sha256d}; use crate::hashes::hex::FromHex; use crate::util::endian; -use crate::blockdata::constants::WITNESS_SCALE_FACTOR; +use crate::blockdata::constants::{WITNESS_SCALE_FACTOR, MAX_SEQUENCE}; #[cfg(feature="bitcoinconsensus")] use crate::blockdata::script; use crate::blockdata::script::Script; use crate::blockdata::witness::Witness; @@ -206,7 +207,7 @@ pub struct TxIn { /// conflicting transactions should be preferred, or 0xFFFFFFFF /// to ignore this feature. This is generally never used since /// the miner behaviour cannot be enforced. - pub sequence: u32, + pub sequence: Sequence, /// Witness data: an array of byte-arrays. /// Note that this field is *not* (de)serialized with the rest of the TxIn in /// Encodable/Decodable, as it is (de)serialized at the end of the full @@ -220,12 +221,212 @@ impl Default for TxIn { TxIn { previous_output: OutPoint::default(), script_sig: Script::new(), - sequence: u32::max_value(), + sequence: Sequence::MAX, witness: Witness::default(), } } } +/// 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 transcation 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, Debug, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] +pub struct Sequence(pub u32); + +#[derive(Clone, PartialEq, Eq, Debug)] +#[non_exhaustive] +/// An error in creating relative lock-times. +pub enum RelativeLockTimeError { + /// The input was too large + IntegerOverflow(u32) +} + +impl Sequence { + /// The maximum allowable sequence number. + /// + /// This sequence number disables lock-time and replace-by-fee. + pub const MAX: Self = Sequence(MAX_SEQUENCE); + /// Zero value sequence. + /// + /// This sequence number enables replace-by-fee and 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 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; + + /// Retuns `true` if the sequence number indicates that the transaction is finalised. + /// + /// The sequence number being equal to 0xffffffff on all txin sequences indicates + /// that the transaction is finalised. + #[inline] + pub fn is_final(&self) -> bool { + *self == Sequence::MAX + } + + /// 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. + #[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 sequene 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) + } + + /// Create a relative lock-time using block height. + #[inline] + pub fn from_height(height: u16) -> Self { + Sequence(u32::from(height)) + } + + /// Create 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) + } + + /// Create 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(RelativeLockTimeError::IntegerOverflow(seconds)) + } + } + + /// Create 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(RelativeLockTimeError::IntegerOverflow(seconds)) + } + } + + /// Returns `true` if the sequence number enables absolute lock-time ([`Transaction::lock_time`]). + #[inline] + pub fn enables_absolute_lock_time(&self) -> bool { + !self.is_final() + } + + /// Create 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 + } +} + +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::Display for RelativeLockTimeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::IntegerOverflow(val) => write!(f, "input of {} was too large", val) + } + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for RelativeLockTimeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::IntegerOverflow(_) => None + } + } +} + /// Bitcoin transaction output. /// /// Defines new coins to be created as a result of the transaction, @@ -481,7 +682,7 @@ impl Transaction { tx.input.push(TxIn { previous_output: input.previous_output, script_sig: if n == input_index { script_pubkey.clone() } else { Script::new() }, - sequence: if n != input_index && (sighash == EcdsaSighashType::Single || sighash == EcdsaSighashType::None) { 0 } else { input.sequence }, + sequence: if n != input_index && (sighash == EcdsaSighashType::Single || sighash == EcdsaSighashType::None) { Sequence::ZERO } else { input.sequence }, witness: Witness::default(), }); } @@ -723,7 +924,7 @@ impl Transaction { /// **does not** cover the case where a transaction becomes replaceable due to ancestors being /// RBF. pub fn is_explicitly_rbf(&self) -> bool { - self.input.iter().any(|input| input.sequence < (0xffffffff - 1)) + self.input.iter().any(|input| input.sequence.is_rbf()) } } @@ -765,6 +966,18 @@ impl Decodable for TxIn { } } +impl Encodable for Sequence { + fn consensus_encode(&self, w: &mut W) -> Result { + self.0.consensus_encode(w) + } +} + +impl Decodable for Sequence { + fn consensus_decode(r: &mut R) -> Result { + Decodable::consensus_decode(r).map(Sequence) + } +} + impl Encodable for Transaction { fn consensus_encode(&self, w: &mut W) -> Result { let mut len = 0; @@ -1088,7 +1301,7 @@ mod tests { let txin = TxIn::default(); assert_eq!(txin.previous_output, OutPoint::default()); assert_eq!(txin.script_sig, Script::new()); - assert_eq!(txin.sequence, 0xFFFFFFFF); + assert_eq!(txin.sequence, Sequence::from_consensus(0xFFFFFFFF)); assert_eq!(txin.previous_output, OutPoint::default()); assert_eq!(txin.witness.len(), 0); } @@ -1733,6 +1946,27 @@ mod tests { _ => panic!("Wrong error type"), } } + + #[test] + fn sequence_number_tests() { + let seq_final = Sequence::from_consensus(0xFFFFFFFF); + let seq_non_rbf = Sequence::from_consensus(0xFFFFFFFE); + let block_time_lock = Sequence::from_consensus(0xFFFF); + let unit_time_lock = Sequence::from_consensus(0x40FFFF); + let lock_time_disabled = Sequence::from_consensus(0x80000000); + + assert!(seq_final.is_final()); + assert!(!seq_final.is_rbf()); + assert!(!seq_final.is_relative_lock_time()); + assert!(!seq_non_rbf.is_rbf()); + assert!(block_time_lock.is_relative_lock_time()); + assert!(block_time_lock.is_height_locked()); + assert!(block_time_lock.is_rbf()); + assert!(unit_time_lock.is_relative_lock_time()); + assert!(unit_time_lock.is_time_locked()); + assert!(unit_time_lock.is_rbf()); + assert!(!lock_time_disabled.is_relative_lock_time()); + } } #[cfg(all(test, feature = "unstable"))] diff --git a/src/lib.rs b/src/lib.rs index 42f5c22e57..686045287f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,7 @@ pub use crate::blockdata::block::BlockHeader; pub use crate::blockdata::script::Script; pub use crate::blockdata::transaction::Transaction; pub use crate::blockdata::transaction::TxIn; +pub use crate::blockdata::transaction::Sequence; pub use crate::blockdata::transaction::TxOut; pub use crate::blockdata::transaction::OutPoint; pub use crate::blockdata::transaction::EcdsaSighashType; diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 94fbfda766..d437679a2e 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -353,7 +353,7 @@ mod tests { use secp256k1::{Secp256k1, self}; use crate::blockdata::script::Script; - use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint}; + use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence}; use crate::network::constants::Network::Bitcoin; use crate::consensus::encode::{deserialize, serialize, serialize_hex}; use crate::util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, KeySource}; @@ -447,7 +447,7 @@ mod tests { vout: 0, }, script_sig: Script::new(), - sequence: 4294967294, + sequence: Sequence::ENABLE_LOCKTIME_NO_RBF, witness: Witness::default(), }], output: vec![ @@ -518,7 +518,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::MAX, witness: Witness::from_vec(vec![Vec::from_hex("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap()]), }], output: vec![ @@ -607,7 +607,7 @@ mod tests { use crate::hash_types::Txid; use crate::blockdata::script::Script; - use crate::blockdata::transaction::{EcdsaSighashType, Transaction, TxIn, TxOut, OutPoint}; + use crate::blockdata::transaction::{EcdsaSighashType, Transaction, TxIn, TxOut, OutPoint, Sequence}; use crate::consensus::encode::serialize_hex; use crate::util::psbt::map::{Map, Input, Output}; use crate::util::psbt::raw; @@ -705,7 +705,7 @@ mod tests { vout: 0, }, script_sig: Script::new(), - sequence: 4294967294, + sequence: Sequence::ENABLE_LOCKTIME_NO_RBF, witness: Witness::default(), }], output: vec![ @@ -736,7 +736,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::MAX, witness: Witness::from_vec(vec![ Vec::from_hex("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").unwrap(), Vec::from_hex("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap(), @@ -750,7 +750,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014fe3e9ef1a745e974d902c4355943abcb34bd5353"), - sequence: 4294967295, + sequence: Sequence::MAX, witness: Witness::from_vec(vec![ Vec::from_hex("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(), Vec::from_hex("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(), @@ -1017,7 +1017,7 @@ mod tests { vout: 0, }, script_sig: Script::new(), - sequence: 4294967294, + sequence: Sequence::ENABLE_LOCKTIME_NO_RBF, witness: Witness::default(), }], output: vec![ @@ -1048,7 +1048,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::MAX, witness: Witness::from_vec(vec![ Vec::from_hex("304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c01").unwrap(), Vec::from_hex("03d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f2105").unwrap(), @@ -1062,7 +1062,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014fe3e9ef1a745e974d902c4355943abcb34bd5353"), - sequence: 4294967295, + sequence: Sequence::MAX, witness: Witness::from_vec(vec![ Vec::from_hex("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(), Vec::from_hex("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(),