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..00f0bae04d 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -32,7 +32,7 @@ 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 +206,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 +220,80 @@ 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 sequencing number. +/// +#[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); + +impl Sequence { + /// The maximum allowable ssequence number + pub const MAX: Self = Sequence(MAX_SEQUENCE); + + const RBF_SIGNAL: u32 = 0xFFFFFFFE; + const DISABLE_FLAG_MASK: u32 = 0x80000000; + const LOCK_TYPE_MASK: u32 = 0x00400000; + + /// Retuns `true` if the sequence number is 0xffffffff. + pub fn is_final(&self) -> bool { + *self == Sequence::MAX + } + + /// Returns `true` if the sequence number is less than 0xfffffffe which + /// indicates that the transaction is opted-in to BIP125 replace-by-fee. + pub fn is_rbf(&self) -> bool { + self.0 < Sequence::RBF_SIGNAL + } + + /// Returns `true` if the sequence has a relative timelock. + pub fn is_timelock(&self) -> bool { + self.0 & Sequence::DISABLE_FLAG_MASK == 0 + } + + /// Returns true if the sequence number encodes a block-based lock. + pub fn is_height_locked(&self) -> bool { + self.is_timelock() & (self.0 & Sequence::LOCK_TYPE_MASK == 0) + } + + /// Returns true if the sequene number encodes a time based lock. + pub fn is_time_locked(&self) -> bool { + self.is_timelock() & (self.0 & Sequence::LOCK_TYPE_MASK > 0) + } + + /// Create a relative timelock using block height. + pub fn from_height(height: u16) -> Self { + Sequence::from_consensus(height as u32) + } + + /// Create a relative timelock using time units where each unit is equivalent to 512 seconds. + pub fn from_timeunits(units: u16) -> Self { + Sequence::from_consensus(units as u32 | Sequence::LOCK_TYPE_MASK) + } + + /// Create a sequence from a u32 value. + pub fn from_consensus(n: u32) -> Self { + Sequence(n) + } + + /// Returns the inner 32bit integer value of Sequence. + pub fn to_consensus_u32(&self) -> u32 { + self.0 + } +} + +impl Default for Sequence { + fn default() -> Self { + Sequence::MAX + } +} + /// Bitcoin transaction output. /// /// Defines new coins to be created as a result of the transaction, @@ -481,7 +549,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::from_consensus(0) } else { input.sequence }, witness: Witness::default(), }); } @@ -723,7 +791,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 +833,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 { + Ok(Sequence(Decodable::consensus_decode(r)?)) + } +} + impl Encodable for Transaction { fn consensus_encode(&self, w: &mut W) -> Result { let mut len = 0; @@ -1088,7 +1168,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 +1813,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 timelock_disabled = Sequence::from_consensus(0x80000000); + + assert!(seq_final.is_final()); + assert!(!seq_final.is_rbf()); + assert!(!seq_final.is_timelock()); + assert!(!seq_non_rbf.is_rbf()); + assert!(block_time_lock.is_timelock()); + assert!(block_time_lock.is_height_locked()); + assert!(block_time_lock.is_rbf()); + assert!(unit_time_lock.is_timelock()); + assert!(unit_time_lock.is_time_locked()); + assert!(unit_time_lock.is_rbf()); + assert!(!timelock_disabled.is_timelock()); + } } #[cfg(all(test, feature = "unstable"))] diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 94fbfda766..5bed0ba873 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::from_consensus(4294967294), witness: Witness::default(), }], output: vec![ @@ -518,7 +518,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::from_consensus(4294967295), 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::from_consensus(4294967294), witness: Witness::default(), }], output: vec![ @@ -736,7 +736,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::from_consensus(4294967295), 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::from_consensus(4294967295), 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::from_consensus(4294967294), witness: Witness::default(), }], output: vec![ @@ -1048,7 +1048,7 @@ mod tests { vout: 1, }, script_sig: hex_script!("160014be18d152a9b012039daf3da7de4f53349eecb985"), - sequence: 4294967295, + sequence: Sequence::from_consensus(4294967295), 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::from_consensus(4294967295), witness: Witness::from_vec(vec![ Vec::from_hex("3045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01").unwrap(), Vec::from_hex("0223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab3").unwrap(),