Skip to content

Commit

Permalink
Add new type for sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
nlanson committed Jul 13, 2022
1 parent 97c680d commit 026bdef
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 19 deletions.
6 changes: 3 additions & 3 deletions src/blockdata/constants.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
});

Expand Down Expand Up @@ -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());
Expand Down
193 changes: 187 additions & 6 deletions src/blockdata/transaction.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -220,12 +221,159 @@ 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);

#[derive(Clone, PartialEq, Eq, Debug)]
/// An error in creating relative lock-times.
pub enum RelativeLockTimeError {
/// The input was too large
InputTooLarge(u32)
}

impl Sequence {
/// The maximum allowable sequence number
pub const MAX: Self = Sequence(MAX_SEQUENCE);
/// The lowest sequence number that does not opt-in for replace-by-fee.
pub const RBF_SIGNAL: Self = Sequence(0xFFFFFFFE);
/// Zero value sequence
pub const ZERO: Self = Sequence(0);
/// 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::RBF_SIGNAL
}

/// 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<Self, RelativeLockTimeError> {
if let Ok(interval) = u16::try_from(seconds >> 9) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(RelativeLockTimeError::InputTooLarge(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<Self, RelativeLockTimeError> {
if let Ok(interval) = u16::try_from((seconds + 511) >> 9) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(RelativeLockTimeError::InputTooLarge(seconds))
}
}

/// Returns `true` if the sequence number enables absolute lock-times.
#[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 {
fn default() -> Self {
Sequence::MAX
}
}

impl From<Sequence> for u32 {
fn from(sequence: Sequence) -> u32 {
sequence.0
}
}

impl fmt::Display for RelativeLockTimeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::InputTooLarge(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)> {
None
}
}

/// Bitcoin transaction output.
///
/// Defines new coins to be created as a result of the transaction,
Expand Down Expand Up @@ -481,7 +629,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(),
});
}
Expand Down Expand Up @@ -723,7 +871,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())
}
}

Expand Down Expand Up @@ -765,6 +913,18 @@ impl Decodable for TxIn {
}
}

impl Encodable for Sequence {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}

impl Decodable for Sequence {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Decodable::consensus_decode(r).map(Sequence)
}
}

impl Encodable for Transaction {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = 0;
Expand Down Expand Up @@ -1088,7 +1248,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);
}
Expand Down Expand Up @@ -1733,6 +1893,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"))]
Expand Down
20 changes: 10 additions & 10 deletions src/util/psbt/mod.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -447,7 +447,7 @@ mod tests {
vout: 0,
},
script_sig: Script::new(),
sequence: 4294967294,
sequence: Sequence::RBF_SIGNAL,
witness: Witness::default(),
}],
output: vec![
Expand Down Expand Up @@ -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![
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -705,7 +705,7 @@ mod tests {
vout: 0,
},
script_sig: Script::new(),
sequence: 4294967294,
sequence: Sequence::RBF_SIGNAL,
witness: Witness::default(),
}],
output: vec![
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down Expand Up @@ -1017,7 +1017,7 @@ mod tests {
vout: 0,
},
script_sig: Script::new(),
sequence: 4294967294,
sequence: Sequence::RBF_SIGNAL,
witness: Witness::default(),
}],
output: vec![
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down

0 comments on commit 026bdef

Please sign in to comment.