Add lock time types

Add a `LockTime` type to hold the nLockTime `u32` value. Use it in
`Transaction` for `lock_time` instead of a `u32`. Make it public so this
new type can be used by rust-miniscript and other downstream projects.

Add a `PackedLockTime` type that wraps a raw `u32` and derives `Ord`,
this type is for wrapping a consensus lock time value for nesting in
types that would like to derive `Ord`.
This commit is contained in:
Tobin C. Harding 2022-05-17 06:27:31 +10:00
parent 1390ee12ec
commit 0ed78e543b
10 changed files with 842 additions and 29 deletions

View File

@ -43,8 +43,8 @@ use bitcoin::util::bip32::{
};
use bitcoin::util::psbt::{self, Input, Psbt, PsbtSighashType};
use bitcoin::{
Address, Amount, Network, OutPoint, PrivateKey, PublicKey, Script, Sequence, Transaction, TxIn,
TxOut, Txid, Witness,
Address, Amount, Network, OutPoint, PackedLockTime, PrivateKey, PublicKey, Script, Sequence,
Transaction, TxIn, TxOut, Txid, Witness,
};
use self::psbt_sign::*;
@ -207,7 +207,7 @@ impl WatchOnly {
let tx = Transaction {
version: 2,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(INPUT_UTXO_TXID)?,

View File

@ -16,6 +16,7 @@ use crate::hashes::hex::{self, HexIterator};
use crate::hashes::{Hash, sha256d};
use crate::blockdata::opcodes;
use crate::blockdata::script;
use crate::blockdata::locktime::PackedLockTime;
use crate::blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn, Sequence};
use crate::blockdata::block::{Block, BlockHeader};
use crate::blockdata::witness::Witness;
@ -73,7 +74,7 @@ fn bitcoin_genesis_tx() -> Transaction {
// Base
let mut ret = Transaction {
version: 1,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![],
output: vec![],
};
@ -201,6 +202,7 @@ mod test {
use crate::hashes::hex::{ToHex, FromHex};
use crate::network::constants::Network;
use crate::consensus::encode::serialize;
use crate::blockdata::locktime::PackedLockTime;
#[test]
fn bitcoin_genesis_first_transaction() {
@ -218,7 +220,7 @@ mod test {
assert_eq!(serialize(&gen.output[0].script_pubkey),
Vec::from_hex("434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac").unwrap());
assert_eq!(gen.output[0].value, 50 * COIN_VALUE);
assert_eq!(gen.lock_time, 0);
assert_eq!(gen.lock_time, PackedLockTime::ZERO);
assert_eq!(gen.wtxid().to_hex(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b");
}

762
src/blockdata/locktime.rs Normal file
View File

@ -0,0 +1,762 @@
// Rust Bitcoin Library
// Written in 2022 by
// Tobin C. Harding <me@tobin.cc>
// To the extent possible under law, the author(s) have dedicated all
// copyright and related and neighboring rights to this software to
// the public domain worldwide. This software is distributed without
// any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication
// along with this software.
// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
//
//! 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 core::{mem, fmt};
use core::cmp::{PartialOrd, Ordering};
use core::convert::TryFrom;
use core::str::FromStr;
use core::num::ParseIntError;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::io::{self, Read, Write};
use crate::prelude::*;
use crate::internal_macros::write_err;
/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
///
/// `LockTime` values _below_ the threshold are interpreted as block heights, values _above_ (or
/// equal to) the threshold are interpreted as block times (UNIX timestamp, seconds since epoch).
///
/// Bitcoin is able to safely use this value because a block height greater than 500,000,000 would
/// never occur because it would represent a height in approximately 9500 years. Conversely, block
/// times under 500,000,000 will never happen because they would represent times before 1986 which
/// are, for obvious reasons, not useful within the Bitcoin network.
///
/// [Bitcoin Core]: https://github.com/bitcoin/bitcoin/blob/9ccaee1d5e2e4b79b0a7c29aadb41b97e4741332/src/script/script.h#L39
pub const LOCK_TIME_THRESHOLD: u32 = 500_000_000;
/// Packed lock time wraps a [`LockTime`] consensus value i.e., the raw `u32` used by the network.
///
/// This struct may be preferred in performance-critical applications because it's slightly smaller
/// than [`LockTime`] and has a bit more performant (de)serialization. In particular, this may be
/// relevant when the value is not processed, just passed around. Note however that the difference
/// is super-small, so unless you do something extreme you shouldn't worry about it.
///
/// This type implements a naive ordering based on the `u32`, this is _not_ a semantically correct
/// ordering for a lock time, hence [`LockTime`] does not implement `Ord`. This type is useful if
/// you want to use a lock time as part of a struct and wish to derive `Ord`. For all other uses,
/// consider using [`LockTime`] directly.
///
/// # Examples
/// ```
/// # use bitcoin::{Amount, PackedLockTime, LockTime};
/// #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
/// struct S {
/// lock_time: PackedLockTime,
/// amount: Amount,
/// }
///
/// let _ = S {
/// lock_time: LockTime::from_consensus(741521).into(),
/// amount: Amount::from_sat(10_000_000),
/// };
/// ```
#[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 PackedLockTime(pub u32);
impl PackedLockTime {
/// If [`crate::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: PackedLockTime = PackedLockTime(0);
/// Returns the inner `u32`.
#[inline]
pub fn to_u32(&self) -> u32 {
self.0
}
}
impl fmt::Display for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl Encodable for PackedLockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for PackedLockTime {
#[inline]
fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
u32::consensus_decode(r).map(PackedLockTime)
}
}
impl From<LockTime> for PackedLockTime {
fn from(n: LockTime) -> Self {
PackedLockTime(n.to_consensus_u32())
}
}
impl From<PackedLockTime> for LockTime {
fn from(n: PackedLockTime) -> Self {
LockTime::from_consensus(n.0)
}
}
impl From<&LockTime> for PackedLockTime {
fn from(n: &LockTime) -> Self {
PackedLockTime(n.to_consensus_u32())
}
}
impl From<&PackedLockTime> for LockTime {
fn from(n: &PackedLockTime) -> Self {
LockTime::from_consensus(n.0)
}
}
impl From<PackedLockTime> for u32 {
fn from(p: PackedLockTime) -> Self {
p.0
}
}
impl FromStr for PackedLockTime {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(PackedLockTime)
}
}
impl TryFrom<&str> for PackedLockTime {
type Error = ParseIntError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
PackedLockTime::from_str(s)
}
}
impl TryFrom<String> for PackedLockTime {
type Error = ParseIntError;
fn try_from(s: String) -> Result<Self, Self::Error> {
PackedLockTime::from_str(&s)
}
}
impl fmt::LowerHex for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:x}", self.0)
}
}
impl fmt::UpperHex for PackedLockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:X}", self.0)
}
}
/// A 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`]
/// in this library) and also for the argument to opcode 'OP_CHECKLOCKTIMEVERIFY`.
///
/// ### Relevant BIPs
///
/// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki)
/// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki)
///
/// # Examples
/// ```
/// # use bitcoin::{LockTime, LockTime::*};
/// # let n = LockTime::from_consensus(100); // n OP_CHECKLOCKTIMEVERIFY
/// # let lock_time = LockTime::from_consensus(100); // nLockTime
/// // To compare lock times there are various `is_satisfied_*` methods, you may also use:
/// let is_satisfied = match (n, lock_time) {
/// (Blocks(n), Blocks(lock_time)) => n <= lock_time,
/// (Seconds(n), Seconds(lock_time)) => n <= lock_time,
/// _ => panic!("handle invalid comparison error"),
/// };
/// ```
#[allow(clippy::derive_ord_xor_partial_ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub enum LockTime {
/// A block height lock time value.
///
/// # Examples
/// ```rust
/// use bitcoin::LockTime;
///
/// let block: u32 = 741521;
/// let n = LockTime::from_height(block).expect("valid height");
/// assert!(n.is_block_height());
/// assert_eq!(n.to_consensus_u32(), block);
/// ```
Blocks(Height),
/// A UNIX timestamp lock time value.
///
/// # Examples
/// ```rust
/// use bitcoin::LockTime;
///
/// let seconds: u32 = 1653195600; // May 22nd, 5am UTC.
/// let n = LockTime::from_time(seconds).expect("valid time");
/// assert!(n.is_block_time());
/// assert_eq!(n.to_consensus_u32(), seconds);
/// ```
Seconds(Time),
}
impl LockTime {
/// If [`crate::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(0));
/// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY.
///
/// # Examples
///
/// ```rust
/// # use bitcoin::LockTime;
/// # let n = LockTime::from_consensus(741521); // n OP_CHECKLOCKTIMEVERIFY
///
/// // `from_consensus` roundtrips as expected with `to_consensus_u32`.
/// let n_lock_time: u32 = 741521;
/// let lock_time = LockTime::from_consensus(n_lock_time);
/// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
#[inline]
pub fn from_consensus(n: u32) -> Self {
if is_block_height(n) {
Self::Blocks(Height::from_consensus(n).expect("n is valid"))
} else {
Self::Seconds(Time::from_consensus(n).expect("n is valid"))
}
}
/// Constructs a `LockTime` from `n`, expecting `n` to be a valid block height.
///
/// See [`LOCK_TIME_THRESHOLD`] for definition of a valid height value.
///
/// # Examples
/// ```rust
/// # use bitcoin::LockTime;
/// assert!(LockTime::from_height(741521).is_ok());
/// assert!(LockTime::from_height(1653195600).is_err());
/// ```
#[inline]
pub fn from_height(n: u32) -> Result<Self, Error> {
let height = Height::from_consensus(n)?;
Ok(LockTime::Blocks(height))
}
/// Constructs a `LockTime` from `n`, expecting `n` to be a valid block time.
///
/// See [`LOCK_TIME_THRESHOLD`] for definition of a valid time value.
///
/// # Examples
/// ```rust
/// # use bitcoin::LockTime;
/// assert!(LockTime::from_time(1653195600).is_ok());
/// assert!(LockTime::from_time(741521).is_err());
/// ```
#[inline]
pub fn from_time(n: u32) -> Result<Self, Error> {
let time = Time::from_consensus(n)?;
Ok(LockTime::Seconds(time))
}
/// Returns true if both lock times use the same unit i.e., both height based or both time based.
#[inline]
pub fn is_same_unit(&self, other: LockTime) -> bool {
mem::discriminant(self) == mem::discriminant(&other)
}
/// Returns true if this lock time value is a block height.
#[inline]
pub fn is_block_height(&self) -> bool {
match *self {
LockTime::Blocks(_) => true,
LockTime::Seconds(_) => false,
}
}
/// Returns true if this lock time value is a block time (UNIX timestamp).
#[inline]
pub fn is_block_time(&self) -> bool {
!self.is_block_height()
}
/// Returns true if this timelock constraint is satisfied by the respective `height`/`time`.
///
/// If `self` is a blockheight based lock then it is checked against `height` and if `self` is a
/// 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
/// `height`/`time` is valid.
///
/// # Examples
/// ```no_run
/// # use bitcoin::blockdata::locktime::{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") }
///
/// let n = LockTime::from_consensus(741521); // `n OP_CHEKCLOCKTIMEVERIFY`.
/// if n.is_satisfied_by(get_height(), get_time()) {
/// // Can create and mine a transaction that satisfies the OP_CLTV timelock constraint.
/// }
/// ````
#[inline]
pub fn is_satisfied_by(&self, height: Height, time: Time) -> bool {
use LockTime::*;
match *self {
Blocks(n) => n <= height,
Seconds(n) => n <= time,
}
}
/// Returns the inner `u32` value. This is the value used when creating this `LockTime`
/// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime.
///
/// # Warning
///
/// Do not compare values return by this method. The whole point of the `LockTime` type is to
/// assist in doing correct comparisons. Either use `is_satisfied_by` or use the pattern below:
///
/// # Examples
///
/// ```rust
/// # use bitcoin::{LockTime, LockTime::*};
/// # let n = LockTime::from_consensus(100); // n OP_CHECKLOCKTIMEVERIFY
/// # let lock_time = LockTime::from_consensus(100); // nLockTime
///
/// let is_satisfied = match (n, lock_time) {
/// (Blocks(n), Blocks(lock_time)) => n <= lock_time,
/// (Seconds(n), Seconds(lock_time)) => n <= lock_time,
/// _ => panic!("invalid comparison"),
/// };
///
/// // Or, if you have Rust 1.53 or greater
/// // let is_satisfied = n.partial_cmp(&lock_time).expect("invalid comparison").is_le();
/// ```
#[inline]
pub fn to_consensus_u32(&self) -> u32 {
match *self {
LockTime::Blocks(ref h) => h.to_consensus_u32(),
LockTime::Seconds(ref t) => t.to_consensus_u32(),
}
}
}
impl FromStr for LockTime {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(LockTime::from_consensus)
}
}
impl TryFrom<&str> for LockTime {
type Error = ParseIntError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
LockTime::from_str(s)
}
}
impl TryFrom<String> for LockTime {
type Error = ParseIntError;
fn try_from(s: String) -> Result<Self, Self::Error> {
LockTime::from_str(&s)
}
}
impl From<Height> for LockTime {
fn from(h: Height) -> Self {
LockTime::Blocks(h)
}
}
impl From<Time> for LockTime {
fn from(t: Time) -> Self {
LockTime::Seconds(t)
}
}
impl PartialOrd for LockTime {
fn partial_cmp(&self, other: &LockTime) -> Option<Ordering> {
use LockTime::*;
match (*self, *other) {
(Blocks(ref a), Blocks(ref b)) => a.partial_cmp(b),
(Seconds(ref a), Seconds(ref b)) => a.partial_cmp(b),
(_, _) => None,
}
}
}
impl fmt::Display for LockTime {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTime::*;
if f.alternate() {
match *self {
Blocks(ref h) => write!(f, "block-height {}", h),
Seconds(ref t) => write!(f, "block-time {} (seconds since epoch)", t),
}
} else {
match *self {
Blocks(ref h) => fmt::Display::fmt(h, f),
Seconds(ref t) => fmt::Display::fmt(t, f),
}
}
}
}
impl Encodable for LockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let v = self.to_consensus_u32();
v.consensus_encode(w)
}
}
impl Decodable for LockTime {
#[inline]
fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
u32::consensus_decode(r).map(LockTime::from_consensus)
}
}
/// An absolute block height, guaranteed to always contain a valid height value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Height(u32);
impl Height {
/// Constructs a new block height.
///
/// # Errors
///
/// If `n` does not represent a block height value (see documentation on [`LockTime`]).
///
/// # Examples
/// ```rust
/// use bitcoin::blockdata::locktime::Height;
///
/// let h: u32 = 741521;
/// let height = Height::from_consensus(h).expect("invalid height value");
/// assert_eq!(height.to_consensus_u32(), h);
/// ```
#[inline]
pub fn from_consensus(n: u32) -> Result<Height, Error> {
if is_block_height(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_height(n).into())
}
}
/// Converts this `Height` to its inner `u32` value.
///
/// # Examples
/// ```rust
/// use bitcoin::LockTime;
///
/// let n_lock_time: u32 = 741521;
/// let lock_time = LockTime::from_consensus(n_lock_time);
/// assert!(lock_time.is_block_height());
/// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
#[inline]
pub fn to_consensus_u32(&self) -> u32 {
self.0
}
}
impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Height {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s.to_owned()))?;
Height::from_consensus(n)
}
}
impl TryFrom<&str> for Height {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Height::from_str(s)
}
}
impl TryFrom<String> for Height {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s))?;
Height::from_consensus(n)
}
}
/// A UNIX timestamp, seconds since epoch, guaranteed to always contain a valid time value.
///
/// Note that there is no manipulation of the inner value during construction or when using
/// `to_consensus_u32()`. Said another way, `Time(x)` means 'x seconds since epoch' _not_ '(x -
/// threshold) seconds since epoch'.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Time(u32);
impl Time {
/// Constructs a new block time.
///
/// # Errors
///
/// If `n` does not encode a UNIX time stamp (see documentation on [`LockTime`]).
///
/// # Examples
/// ```rust
/// use bitcoin::blockdata::locktime::Time;
///
/// let t: u32 = 1653195600; // May 22nd, 5am UTC.
/// let time = Time::from_consensus(t).expect("invalid time value");
/// assert_eq!(time.to_consensus_u32(), t);
/// ```
#[inline]
pub fn from_consensus(n: u32) -> Result<Time, Error> {
if is_block_time(n) {
Ok(Self(n))
} else {
Err(ConversionError::invalid_time(n).into())
}
}
/// Converts this `Time` to its inner `u32` value.
///
/// # Examples
/// ```rust
/// use bitcoin::LockTime;
///
/// let n_lock_time: u32 = 1653195600; // May 22nd, 5am UTC.
/// let lock_time = LockTime::from_consensus(n_lock_time);
/// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
/// ```
#[inline]
pub fn to_consensus_u32(&self) -> u32 {
self.0
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl FromStr for Time {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s.to_owned()))?;
Time::from_consensus(n)
}
}
impl TryFrom<&str> for Time {
type Error = Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
Time::from_str(s)
}
}
impl TryFrom<String> for Time {
type Error = Error;
fn try_from(s: String) -> Result<Self, Self::Error> {
let n = s.parse::<u32>().map_err(|e| Error::Parse(e, s))?;
Time::from_consensus(n)
}
}
/// Returns true if `n` is a block height i.e., less than 500,000,000.
fn is_block_height(n: u32) -> bool {
n < LOCK_TIME_THRESHOLD
}
/// Returns true if `n` is a UNIX timestamp i.e., greater than or equal to 500,000,000.
fn is_block_time(n: u32) -> bool {
n >= LOCK_TIME_THRESHOLD
}
/// Catchall type for errors that relate to time locks.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
/// An error occurred while converting a `u32` to a lock time variant.
Conversion(ConversionError),
/// An error occurred while operating on lock times.
Operation(OperationError),
/// An error occurred while parsing a string into an `u32`.
Parse(ParseIntError, String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
match *self {
Conversion(ref e) => write_err!(f, "error converting lock time value"; e),
Operation(ref e) => write_err!(f, "error during lock time operation"; e),
Parse(ref e, ref s) => write_err!(f, "error parsing string: {}", s; e),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use self::Error::*;
match *self {
Conversion(ref e) => Some(e),
Operation(ref e) => Some(e),
Parse(ref e, _) => Some(e),
}
}
}
impl From<ConversionError> for Error {
fn from(e: ConversionError) -> Self {
Error::Conversion(e)
}
}
impl From<OperationError> for Error {
fn from(e: OperationError) -> Self {
Error::Operation(e)
}
}
/// An error that occurs when converting a `u32` to a lock time variant.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ConversionError {
/// The expected timelock unit, height (blocks) or time (seconds).
unit: LockTimeUnit,
/// The invalid input value.
input: u32,
}
impl ConversionError {
/// Constructs a `ConversionError` from an invalid `n` when expecting a height value.
fn invalid_height(n: u32) -> Self {
Self {
unit: LockTimeUnit::Blocks,
input: n,
}
}
/// Constructs a `ConversionError` from an invalid `n` when expecting a time value.
fn invalid_time(n: u32) -> Self {
Self {
unit: LockTimeUnit::Seconds,
input: n,
}
}
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for ConversionError {}
/// Describes the two types of locking, lock-by-blockheight and lock-by-blocktime.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
/// Lock by blockheight.
Blocks,
/// Lock by blocktime.
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use LockTimeUnit::*;
match *self {
Blocks => write!(f, "expected lock-by-blockheight (must be < {})", LOCK_TIME_THRESHOLD),
Seconds => write!(f, "expected lock-by-blocktime (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
/// Errors than occur when operating on lock times.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum OperationError {
/// Cannot compare different lock time units (height vs time).
InvalidComparison,
}
impl fmt::Display for OperationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::OperationError::*;
match *self {
InvalidComparison => f.write_str("cannot compare different lock units (height vs time)"),
}
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl std::error::Error for OperationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_and_alternate() {
let n = LockTime::from_consensus(100);
let s = format!("{}", n);
assert_eq!(&s, "100");
let got = format!("{:#}", n);
assert_eq!(got, "block-height 100");
}
}

View File

@ -8,6 +8,7 @@
//!
pub mod constants;
pub mod locktime;
pub mod opcodes;
pub mod script;
pub mod transaction;

View File

@ -26,6 +26,7 @@ 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;
use crate::blockdata::locktime::{LockTime, PackedLockTime, Height, Time};
use crate::consensus::{encode, Decodable, Encodable};
use crate::hash_types::{Sighash, Txid, Wtxid};
use crate::VarInt;
@ -206,6 +207,20 @@ pub struct TxIn {
pub witness: Witness
}
impl TxIn {
/// Returns true if this input enables the [`LockTime`] (aka `nLockTime`) of its [`Transaction`].
///
/// `nLockTime` is enabled if *any* input enables it. See [`Transaction::is_lock_time_enabled`]
/// to check the overall state. If none of the inputs enables it, the lock time value is simply
/// ignored. If this returns false and OP_CHECKLOCKTIMEVERIFY is used in the redeem script with
/// this input then the script execution will fail [BIP-0065].
///
/// [BIP-65](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki)
pub fn enables_lock_time(&self) -> bool {
self.sequence != Sequence::MAX
}
}
impl Default for TxIn {
fn default() -> TxIn {
TxIn {
@ -554,8 +569,13 @@ impl<E> EncodeSigningDataResult<E> {
pub struct Transaction {
/// The protocol version, is currently expected to be 1 or 2 (BIP 68).
pub version: i32,
/// Block number before which this transaction is valid, or 0 for valid immediately.
pub lock_time: u32,
/// Block height or timestamp. Transaction cannot be included in a block until this height/time.
///
/// ### Relevant BIPs
///
/// * [BIP-65 OP_CHECKLOCKTIMEVERIFY](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki)
/// * [BIP-113 Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki)
pub lock_time: PackedLockTime,
/// List of transaction inputs.
pub input: Vec<TxIn>,
/// List of transaction outputs.
@ -916,6 +936,27 @@ impl Transaction {
pub fn is_explicitly_rbf(&self) -> bool {
self.input.iter().any(|input| input.sequence.is_rbf())
}
/// Returns true if this [`Transaction`]'s absolute timelock is satisfied at `height`/`time`.
///
/// # Returns
///
/// By definition if the lock time is not enabled the transaction's absolute timelock is
/// considered to be satisfied i.e., there are no timelock constraints restricting this
/// transaction from being mined immediately.
pub fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool {
if !self.is_lock_time_enabled() {
return true;
}
LockTime::from(self.lock_time).is_satisfied_by(height, time)
}
/// Returns `true` if this transactions nLockTime is enabled ([BIP-65]).
///
/// [BIP-65]: https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki
pub fn is_lock_time_enabled(&self) -> bool {
self.input.iter().any(|i| i.enables_lock_time())
}
}
impl_consensus_encoding!(TxOut, value, script_pubkey);
@ -1224,6 +1265,7 @@ mod tests {
use core::str::FromStr;
use crate::blockdata::constants::WITNESS_SCALE_FACTOR;
use crate::blockdata::script::Script;
use crate::blockdata::locktime::PackedLockTime;
use crate::consensus::encode::serialize;
use crate::consensus::encode::deserialize;
@ -1324,7 +1366,7 @@ mod tests {
"ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string());
assert_eq!(realtx.input[0].previous_output.vout, 1);
assert_eq!(realtx.output.len(), 1);
assert_eq!(realtx.lock_time, 0);
assert_eq!(realtx.lock_time, PackedLockTime::ZERO);
assert_eq!(format!("{:x}", realtx.txid()),
"a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string());
@ -1358,7 +1400,7 @@ mod tests {
"7cac3cf9a112cf04901a51d605058615d56ffe6d04b45270e89d1720ea955859".to_string());
assert_eq!(realtx.input[0].previous_output.vout, 1);
assert_eq!(realtx.output.len(), 1);
assert_eq!(realtx.lock_time, 0);
assert_eq!(realtx.lock_time, PackedLockTime::ZERO);
assert_eq!(format!("{:x}", realtx.txid()),
"f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string());
@ -1552,7 +1594,7 @@ mod tests {
// We need a tx with more inputs than outputs.
let tx = Transaction {
version: 1,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn::default(), TxIn::default()],
output: vec![TxOut::default()],
};

View File

@ -94,6 +94,7 @@ use std::io;
use core2::io;
pub use crate::blockdata::block::{Block, BlockHeader};
pub use crate::blockdata::locktime::{self, LockTime, PackedLockTime};
pub use crate::blockdata::script::Script;
#[allow(deprecated)]
pub use crate::blockdata::transaction::SigHashType;

View File

@ -14,6 +14,7 @@ use crate::hash_types::Sighash;
use crate::blockdata::script::Script;
use crate::blockdata::witness::Witness;
use crate::blockdata::transaction::{Transaction, TxIn, EcdsaSighashType};
use crate::blockdata::locktime::LockTime;
use crate::consensus::{encode, Encodable};
use crate::io;
@ -26,7 +27,7 @@ use crate::util::sighash;
#[deprecated(since = "0.24.0", note = "please use [sighash::SighashCache] instead")]
pub struct SighashComponents {
tx_version: i32,
tx_locktime: u32,
tx_locktime: LockTime,
/// Hash of all the previous outputs
pub hash_prevouts: Sighash,
/// Hash of all the input sequence nos
@ -68,7 +69,7 @@ impl SighashComponents {
SighashComponents {
tx_version: tx.version,
tx_locktime: tx.lock_time,
tx_locktime: tx.lock_time.into(),
hash_prevouts,
hash_sequence,
hash_outputs,
@ -156,9 +157,9 @@ impl<R: DerefMut<Target = Transaction>> SigHashCache<R> {
/// ```
/// use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType};
/// use bitcoin::util::bip143::SigHashCache;
/// use bitcoin::Script;
/// use bitcoin::{PackedLockTime, Script};
///
/// let mut tx_to_sign = Transaction { version: 2, lock_time: 0, input: Vec::new(), output: Vec::new() };
/// let mut tx_to_sign = Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
/// let input_count = tx_to_sign.input.len();
///
/// let mut sig_hasher = SigHashCache::new(&mut tx_to_sign);
@ -181,6 +182,7 @@ mod tests {
use crate::hash_types::Sighash;
use crate::blockdata::script::Script;
use crate::blockdata::transaction::Transaction;
use crate::blockdata::locktime::LockTime;
use crate::consensus::encode::deserialize;
use crate::network::constants::Network;
use crate::util::address::Address;
@ -225,7 +227,7 @@ mod tests {
comp,
SighashComponents {
tx_version: 1,
tx_locktime: 17,
tx_locktime: LockTime::from_consensus(17),
hash_prevouts: hex_hash!(
Sighash, "96b827c8483d4e9b96712b6713a7b68d6e8003a781feba36c31143470b4efd37"
),
@ -261,7 +263,7 @@ mod tests {
comp,
SighashComponents {
tx_version: 1,
tx_locktime: 1170,
tx_locktime: LockTime::from_consensus(1170),
hash_prevouts: hex_hash!(
Sighash, "b0287b4a252ac05af83d2dcef00ba313af78a3e9c329afa216eb3aa2a7b4613a"
),
@ -304,7 +306,7 @@ mod tests {
comp,
SighashComponents {
tx_version: 1,
tx_locktime: 0,
tx_locktime: LockTime::ZERO,
hash_prevouts: hex_hash!(
Sighash, "74afdc312af5183c4198a40ca3c1a275b485496dd3929bca388c4b5e31f7aaa0"
),

View File

@ -366,13 +366,13 @@ mod test {
use crate::hashes::hex::FromHex;
use crate::{
Block, BlockHash, BlockHeader, OutPoint, Script, Sequence, Transaction, TxIn, TxMerkleNode,
TxOut, Txid, Witness,
TxOut, Txid, Witness, LockTime,
};
fn dummy_tx(nonce: &[u8]) -> Transaction {
Transaction {
version: 1,
lock_time: 2,
lock_time: LockTime::from_consensus(2).into(),
input: vec![TxIn {
previous_output: OutPoint::new(Txid::hash(nonce), 0),
script_sig: Script::new(),

View File

@ -335,6 +335,7 @@ impl Decodable for PartiallySignedTransaction {
mod tests {
use super::*;
use crate::blockdata::locktime::PackedLockTime;
use crate::hashes::hex::FromHex;
use crate::hashes::{sha256, hash160, Hash, ripemd160};
use crate::hash_types::Txid;
@ -358,7 +359,7 @@ mod tests {
let psbt = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![],
output: vec![],
},
@ -428,7 +429,7 @@ mod tests {
let expected = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
lock_time: PackedLockTime(1257139),
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(
@ -501,7 +502,7 @@ mod tests {
// create some values to use in the PSBT
let tx = Transaction {
version: 1,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex("e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389").unwrap(),
@ -599,6 +600,7 @@ mod tests {
use crate::blockdata::script::Script;
use crate::blockdata::transaction::{EcdsaSighashType, Transaction, TxIn, TxOut, OutPoint, Sequence};
use crate::consensus::encode::serialize_hex;
use crate::blockdata::locktime::PackedLockTime;
use crate::util::psbt::map::{Map, Input, Output};
use crate::util::psbt::raw;
use crate::util::psbt::{PartiallySignedTransaction, Error};
@ -687,7 +689,7 @@ mod tests {
let unserialized = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
lock_time: PackedLockTime(1257139),
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(
@ -718,7 +720,7 @@ mod tests {
inputs: vec![Input {
non_witness_utxo: Some(Transaction {
version: 1,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(
@ -999,7 +1001,7 @@ mod tests {
let mut unserialized = PartiallySignedTransaction {
unsigned_tx: Transaction {
version: 2,
lock_time: 1257139,
lock_time: PackedLockTime(1257139),
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(
@ -1030,7 +1032,7 @@ mod tests {
inputs: vec![Input {
non_witness_utxo: Some(Transaction {
version: 1,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::from_hex(

View File

@ -759,9 +759,9 @@ impl<R: DerefMut<Target=Transaction>> SighashCache<R> {
/// ```
/// use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType};
/// use bitcoin::util::sighash::SighashCache;
/// use bitcoin::Script;
/// use bitcoin::{PackedLockTime, Script};
///
/// let mut tx_to_sign = Transaction { version: 2, lock_time: 0, input: Vec::new(), output: Vec::new() };
/// let mut tx_to_sign = Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };
/// let input_count = tx_to_sign.input.len();
///
/// let mut sig_hasher = SighashCache::new(&mut tx_to_sign);
@ -812,6 +812,7 @@ impl<'a> Encodable for Annex<'a> {
#[cfg(test)]
mod tests {
use super::*;
use crate::blockdata::locktime::PackedLockTime;
use crate::consensus::deserialize;
use crate::hashes::hex::FromHex;
use crate::hashes::{Hash, HashEngine};
@ -958,7 +959,7 @@ mod tests {
fn test_sighash_errors() {
let dumb_tx = Transaction {
version: 0,
lock_time: 0,
lock_time: PackedLockTime::ZERO,
input: vec![TxIn::default()],
output: vec![],
};