diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 33e5b543..1f8de58b 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -30,7 +30,7 @@ pub const DIFFCHANGE_INTERVAL: u32 = 2016; pub const DIFFCHANGE_TIMESPAN: u32 = 14 * 24 * 3600; /// The factor that non-witness serialization data is multiplied by during weight calculation. -pub const WITNESS_SCALE_FACTOR: usize = 4; +pub const WITNESS_SCALE_FACTOR: usize = units::weight::WITNESS_SCALE_FACTOR; /// The maximum allowed number of signature check operations in a block. pub const MAX_BLOCK_SIGOPS_COST: i64 = 80_000; /// Mainnet (bitcoin) pubkey address prefix. diff --git a/bitcoin/src/blockdata/locktime/absolute.rs b/bitcoin/src/blockdata/locktime/absolute.rs index 6cb52c85..a8e9a993 100644 --- a/bitcoin/src/blockdata/locktime/absolute.rs +++ b/bitcoin/src/blockdata/locktime/absolute.rs @@ -6,33 +6,24 @@ //! whether `LockTime < LOCKTIME_THRESHOLD`. //! -use core::cmp::{Ordering, PartialOrd}; +use core::cmp::Ordering; use core::{fmt, mem}; -use internals::write_err; use io::{BufRead, Write}; #[cfg(all(test, mutate))] use mutagen::mutate; +use units::parse; +use crate::consensus::encode::{self, Decodable, Encodable}; +use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError}; #[cfg(doc)] use crate::absolute; -use crate::consensus::encode::{self, Decodable, Encodable}; -use crate::error::{ParseIntError, PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError}; -use crate::parse::{self, impl_parse_str, impl_parse_str_from_int_infallible}; -use crate::prelude::*; -/// 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; +#[rustfmt::skip] // Keep public re-exports separate. +#[doc(inline)] +pub use units::locktime::absolute::{ + Height, Time, LOCK_TIME_THRESHOLD, ConversionError, ParseHeightError, ParseTimeError, +}; /// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds /// since epoch). @@ -138,7 +129,7 @@ impl LockTime { /// assert_eq!(lock_time.to_consensus_u32(), n_lock_time); #[inline] pub fn from_consensus(n: u32) -> Self { - if is_block_height(n) { + if units::locktime::absolute::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")) @@ -293,7 +284,7 @@ impl LockTime { } } -impl_parse_str_from_int_infallible!(LockTime, u32, from_consensus); +units::impl_parse_str_from_int_infallible!(LockTime, u32, from_consensus); impl From for LockTime { #[inline] @@ -415,406 +406,6 @@ impl ordered::ArbitraryOrd for LockTime { } } -/// 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 { - /// Absolute block height 0, the genesis block. - pub const ZERO: Self = Height(0); - - /// The minimum absolute block height (0), the genesis block. - pub const MIN: Self = Self::ZERO; - - /// The maximum absolute block height. - pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1); - - /// Creates a `Height` from a hex string. - /// - /// The input string is may or may not contain a typical hex prefix e.g., `0x`. - pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_consensus) } - - /// Constructs a new block height. - /// - /// # Errors - /// - /// If `n` does not represent a block height value (see documentation on [`LockTime`]). - /// - /// # Examples - /// ```rust - /// use bitcoin::locktime::absolute::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 { - if is_block_height(n) { - Ok(Self(n)) - } else { - Err(ConversionError::invalid_height(n)) - } - } - - /// Converts this `Height` to its inner `u32` value. - /// - /// # Examples - /// ```rust - /// use bitcoin::absolute::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_parse_str!(Height, ParseHeightError, parser(Height::from_consensus)); - -/// Error returned when parsing block height fails. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ParseHeightError(ParseError); - -impl fmt::Display for ParseHeightError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseHeightError { - // To be consistent with `write_err` we need to **not** return source in case of overflow - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } -} - -impl From for ParseHeightError { - fn from(value: ParseError) -> Self { Self(value) } -} - -/// 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 { - /// The minimum absolute block time (Tue Nov 05 1985 00:53:20 GMT+0000). - pub const MIN: Self = Time(LOCK_TIME_THRESHOLD); - - /// The maximum absolute block time (Sun Feb 07 2106 06:28:15 GMT+0000). - pub const MAX: Self = Time(u32::max_value()); - - /// Creates a `Time` from a hex string. - /// - /// The input string is may or may not contain a typical hex prefix e.g., `0x`. - pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_consensus) } - - /// Constructs a new block time. - /// - /// # Errors - /// - /// If `n` does not encode a UNIX time stamp (see documentation on [`LockTime`]). - /// - /// # Examples - /// ```rust - /// use bitcoin::locktime::absolute::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 { - if is_block_time(n) { - Ok(Self(n)) - } else { - Err(ConversionError::invalid_time(n)) - } - } - - /// Converts this `Time` to its inner `u32` value. - /// - /// # Examples - /// ```rust - /// use bitcoin::absolute::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_parse_str!(Time, ParseTimeError, parser(Time::from_consensus)); - -/// Error returned when parsing block time fails. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ParseTimeError(ParseError); - -impl fmt::Display for ParseTimeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseTimeError { - // To be consistent with `write_err` we need to **not** return source in case of overflow - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } -} - -impl From for ParseTimeError { - fn from(value: ParseError) -> Self { Self(value) } -} - -fn parser(f: F) -> impl FnOnce(S) -> Result -where - E: From, - S: AsRef + Into, - F: FnOnce(u32) -> Result, -{ - move |s| { - let n = s.as_ref().parse::().map_err(ParseError::invalid_int(s))?; - let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; - f(n).map_err(ParseError::from).map_err(Into::into) - } -} - -fn parse_hex(s: S, f: F) -> Result -where - E: From, - S: AsRef + Into, - F: FnOnce(u32) -> Result, -{ - let n = i64::from_str_radix(parse::strip_hex_prefix(s.as_ref()), 16).map_err(ParseError::invalid_int(s))?; - let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; - f(n).map_err(ParseError::from).map_err(Into::into) -} - -/// 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), -} - -internals::impl_from_infallible!(Error); - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use 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) => write_err!(f, "failed to parse lock time from string"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use Error::*; - - match *self { - Conversion(ref e) => Some(e), - Operation(ref e) => Some(e), - Parse(ref e) => Some(e), - } - } -} - -impl From for Error { - #[inline] - fn from(e: ConversionError) -> Self { Self::Conversion(e) } -} - -impl From for Error { - #[inline] - fn from(e: OperationError) -> Self { Self::Operation(e) } -} - -impl From for Error { - #[inline] - fn from(e: ParseIntError) -> Self { Self::Parse(e) } -} - -/// An error that occurs when converting a `u32` to a lock time variant. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -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")] -impl std::error::Error for ConversionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } -} - -/// 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, PartialEq, Eq)] -#[non_exhaustive] -pub enum OperationError { - /// Cannot compare different lock time units (height vs time). - InvalidComparison, -} - -internals::impl_from_infallible!(OperationError); - -impl fmt::Display for OperationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use OperationError::*; - - match *self { - InvalidComparison => - f.write_str("cannot compare different lock units (height vs time)"), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for OperationError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use OperationError::*; - - match *self { - InvalidComparison => None, - } - } -} - -/// Internal - common representation for height and time. -#[derive(Debug, Clone, Eq, PartialEq)] -enum ParseError { - InvalidInteger { source: core::num::ParseIntError, input: String }, - // unit implied by outer type - // we use i64 to have nicer messages for negative values - Conversion(i64), -} - -internals::impl_from_infallible!(ParseError); - -impl ParseError { - fn invalid_int>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self { - move |source| Self::InvalidInteger { source, input: s.into() } - } - - fn display(&self, f: &mut fmt::Formatter<'_>, subject: &str, lower_bound: u32, upper_bound: u32) -> fmt::Result { - use core::num::IntErrorKind; - - use ParseError::*; - - match self { - InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => { - write!(f, "{} {} is above limit {}", subject, input, upper_bound) - } - InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => { - write!(f, "{} {} is below limit {}", subject, input, lower_bound) - } - InvalidInteger { source, input } => { - write_err!(f, "failed to parse {} as {}", input, subject; source) - } - Conversion(value) if *value < i64::from(lower_bound) => { - write!(f, "{} {} is below limit {}", subject, value, lower_bound) - } - Conversion(value) => { - write!(f, "{} {} is above limit {}", subject, value, upper_bound) - } - } - } - - // To be consistent with `write_err` we need to **not** return source in case of overflow - #[cfg(feature = "std")] - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use core::num::IntErrorKind; - - use ParseError::*; - - match self { - InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None, - InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None, - InvalidInteger { source, .. } => Some(source), - Conversion(_) => None, - } - } -} - -impl From for ParseError { - fn from(value: ParseIntError) -> Self { - Self::InvalidInteger { source: value.source, input: value.input } - } -} - -impl From for ParseError { - fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) } -} - #[cfg(test)] mod tests { use super::*; @@ -860,46 +451,6 @@ mod tests { assert!(result.is_err()); } - #[test] - fn time_from_str_hex_happy_path() { - let actual = Time::from_hex("0x6289C350").unwrap(); - let expected = Time::from_consensus(0x6289C350).unwrap(); - assert_eq!(actual, expected); - } - - #[test] - fn time_from_str_hex_no_prefix_happy_path() { - let time = Time::from_hex("6289C350").unwrap(); - assert_eq!(time, Time(0x6289C350)); - } - - #[test] - fn time_from_str_hex_invalid_hex_should_err() { - let hex = "0xzb93"; - let result = Time::from_hex(hex); - assert!(result.is_err()); - } - - #[test] - fn height_from_str_hex_happy_path() { - let actual = Height::from_hex("0xBA70D").unwrap(); - let expected = Height(0xBA70D); - assert_eq!(actual, expected); - } - - #[test] - fn height_from_str_hex_no_prefix_happy_path() { - let height = Height::from_hex("BA70D").unwrap(); - assert_eq!(height, Height(0xBA70D)); - } - - #[test] - fn height_from_str_hex_invalid_hex_should_err() { - let hex = "0xzb93"; - let result = Height::from_hex(hex); - assert!(result.is_err()); - } - #[test] fn parses_correctly_to_height_or_time() { let lock = LockTime::from_consensus(750_000); diff --git a/bitcoin/src/blockdata/locktime/relative.rs b/bitcoin/src/blockdata/locktime/relative.rs index ad3b140b..92e55f17 100644 --- a/bitcoin/src/blockdata/locktime/relative.rs +++ b/bitcoin/src/blockdata/locktime/relative.rs @@ -11,10 +11,13 @@ use core::fmt; #[cfg(all(test, mutate))] use mutagen::mutate; -use crate::parse::impl_parse_str_from_int_infallible; #[cfg(doc)] use crate::relative; +#[rustfmt::skip] // Keep public re-exports separate. +#[doc(inline)] +pub use units::locktime::relative::{Height, Time, TimeOverflowError}; + /// A relative lock time value, representing either a block height or time (512 second intervals). /// /// The `relative::LockTime` type does not have any constructors, this is by design, please use @@ -188,105 +191,6 @@ impl fmt::Display for LockTime { } } -/// A relative lock time lock-by-blockheight value. -#[derive(Debug, Default, 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(u16); - -impl Height { - /// Relative block height 0, can be included in any block. - pub const ZERO: Self = Height(0); - - /// The minimum relative block height (0), can be included in any block. - pub const MIN: Self = Self::ZERO; - - /// The maximum relative block height. - pub const MAX: Self = Height(u16::max_value()); - - /// Returns the inner `u16` value. - #[inline] - pub fn value(self) -> u16 { self.0 } -} - -impl From for Height { - #[inline] - fn from(value: u16) -> Self { Height(value) } -} - -impl_parse_str_from_int_infallible!(Height, u16, from); - -impl fmt::Display for Height { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - -/// A relative lock time lock-by-blocktime value. -/// -/// For BIP 68 relative lock-by-blocktime locks, time is measure in 512 second intervals. -#[derive(Debug, Default, 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(u16); - -impl Time { - /// Relative block time 0, can be included in any block. - pub const ZERO: Self = Time(0); - - /// The minimum relative block time (0), can be included in any block. - pub const MIN: Self = Time::ZERO; - - /// The maximum relative block time (33,554,432 seconds or approx 388 days). - pub const MAX: Self = Time(u16::max_value()); - - /// Create a [`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 { Time(intervals) } - - /// Create a [`Time`] from seconds, converting the seconds into 512 second interval with ceiling - /// division. - /// - /// # Errors - /// - /// 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(Time::from_512_second_intervals(interval)) - } else { - Err(TimeOverflowError { seconds }) - } - } - - /// Returns the inner `u16` value. - #[inline] - pub fn value(self) -> u16 { self.0 } -} - -impl_parse_str_from_int_infallible!(Time, u16, from_512_second_intervals); - -impl fmt::Display for Time { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } -} - -/// Input time in seconds was too large to be encoded to a 16 bit 512 second interval. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TimeOverflowError { - /// Time value in seconds that overflowed. - // Private because we maintain an invariant that the `seconds` value does actually overflow. - pub(crate) seconds: u32 -} - -impl fmt::Display for TimeOverflowError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} seconds is too large to be encoded to a 16 bit 512 second interval", self.seconds) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for TimeOverflowError {} - /// Tried to satisfy a lock-by-blocktime lock using a height value. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] diff --git a/bitcoin/src/blockdata/mod.rs b/bitcoin/src/blockdata/mod.rs index 943de54a..1d7ee2bb 100644 --- a/bitcoin/src/blockdata/mod.rs +++ b/bitcoin/src/blockdata/mod.rs @@ -8,12 +8,10 @@ pub mod block; pub mod constants; -pub mod fee_rate; pub mod locktime; pub mod opcodes; pub mod script; pub mod transaction; -pub mod weight; pub mod witness; #[rustfmt::skip] // Keep public re-exports separate. @@ -22,3 +20,37 @@ pub use self::{ fee_rate::FeeRate, weight::Weight }; + +/// Implements `FeeRate` and assoctiated features. +pub mod fee_rate { + /// Re-export everything from the [`units::fee_rate`] module. + pub use units::fee_rate::*; + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn fee_convenience_functions_agree() { + use hex::test_hex_unwrap as hex; + + use crate::blockdata::transaction::Transaction; + use crate::consensus::Decodable; + + const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; + + let raw_tx = hex!(SOME_TX); + let tx: Transaction = Decodable::consensus_decode(&mut raw_tx.as_slice()).unwrap(); + + let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/byte is valid"); + + assert_eq!(rate.fee_vb(tx.vsize() as u64), rate.fee_wu(tx.weight())); + } + } +} + +/// Implements `Weight` and associated features. +pub mod weight { + /// Re-export everything from the [`units::weight`] module. + pub use units::weight::*; +} diff --git a/bitcoin/src/blockdata/script/witness_version.rs b/bitcoin/src/blockdata/script/witness_version.rs index 29fa83c4..a51a01d8 100644 --- a/bitcoin/src/blockdata/script/witness_version.rs +++ b/bitcoin/src/blockdata/script/witness_version.rs @@ -12,11 +12,11 @@ use core::str::FromStr; use bech32::Fe32; use internals::write_err; +use units::{parse, ParseIntError}; use crate::blockdata::opcodes::all::*; use crate::blockdata::opcodes::Opcode; use crate::blockdata::script::Instruction; -use crate::error::ParseIntError; /// Version of the segregated witness program. /// @@ -87,7 +87,7 @@ impl FromStr for WitnessVersion { type Err = FromStrError; fn from_str(s: &str) -> Result { - let version: u8 = crate::parse::int(s)?; + let version: u8 = parse::int(s)?; Ok(WitnessVersion::try_from(version)?) } } diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs index 50c83054..58443b44 100644 --- a/bitcoin/src/blockdata/transaction.rs +++ b/bitcoin/src/blockdata/transaction.rs @@ -16,6 +16,7 @@ use core::{cmp, fmt, str}; use hashes::{sha256d, Hash}; use internals::write_err; use io::{BufRead, Write}; +use units::parse; use super::Weight; use crate::blockdata::locktime::absolute::{self, Height, Time}; @@ -26,7 +27,6 @@ use crate::blockdata::FeeRate; use crate::consensus::{encode, Decodable, Encodable}; use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError}; use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; -use crate::parse::{self, impl_parse_str_from_int_infallible}; #[cfg(doc)] use crate::sighash::{EcdsaSighashType, TapSighashType}; use crate::prelude::*; @@ -169,7 +169,7 @@ fn parse_vout(s: &str) -> Result { return Err(ParseOutPointError::VoutNotCanonical); } } - crate::parse::int(s).map_err(ParseOutPointError::Vout) + parse::int(s).map_err(ParseOutPointError::Vout) } impl core::str::FromStr for OutPoint { @@ -449,7 +449,7 @@ impl Sequence { if let Ok(interval) = u16::try_from(seconds / 512) { Ok(Sequence::from_512_second_intervals(interval)) } else { - Err(TimeOverflowError { seconds }) + Err(TimeOverflowError::new(seconds)) } } @@ -462,7 +462,7 @@ impl Sequence { if let Ok(interval) = u16::try_from((seconds + 511) / 512) { Ok(Sequence::from_512_second_intervals(interval)) } else { - Err(TimeOverflowError { seconds }) + Err(TimeOverflowError::new(seconds)) } } @@ -526,7 +526,7 @@ impl fmt::Debug for Sequence { } } -impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus); +units::impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus); /// Bitcoin transaction output. /// @@ -1702,7 +1702,7 @@ mod tests { OutPoint::from_str( "5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:lol" ), - Err(ParseOutPointError::Vout(crate::parse::int::("lol").unwrap_err())) + Err(ParseOutPointError::Vout(parse::int::("lol").unwrap_err())) ); assert_eq!( diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 70fc1048..7ae3eb76 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -89,7 +89,6 @@ extern crate actual_serde as serde; #[macro_use] mod test_macros; mod internal_macros; -mod parse; #[cfg(feature = "serde")] mod serde_utils; @@ -197,3 +196,9 @@ pub mod amount { } } } + +/// Unit parsing utilities. +pub mod parse { + /// Re-export everything from the [`units::parse`] module. + pub use units::parse::ParseIntError; +} diff --git a/bitcoin/src/parse.rs b/bitcoin/src/parse.rs index 7e5b86c6..e69de29b 100644 --- a/bitcoin/src/parse.rs +++ b/bitcoin/src/parse.rs @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -use core::fmt; -use core::str::FromStr; - -use internals::write_err; - -use crate::prelude::*; - -/// Error with rich context returned when a string can't be parsed as an integer. -/// -/// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to -/// parse as well as type information. As a result it provides very informative error messages that -/// make it easier to understand the problem and correct mistakes. -/// -/// Note that this is larger than the type from `core` so if it's passed through a deep call stack -/// in a performance-critical application you may want to box it or throw away the context by -/// converting to `core` type. -#[derive(Debug, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub struct ParseIntError { - pub(crate) input: String, - // for displaying - see Display impl with nice error message below - bits: u8, - // We could represent this as a single bit but it wouldn't actually derease the cost of moving - // the struct because String contains pointers so there will be padding of bits at least - // pointer_size - 1 bytes: min 1B in practice. - is_signed: bool, - pub(crate) source: core::num::ParseIntError, -} - -impl ParseIntError { - /// Returns the input that was attempted to be parsed. - pub fn input(&self) -> &str { &self.input } -} - -impl fmt::Display for ParseIntError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let signed = if self.is_signed { "signed" } else { "unsigned" }; - let n = if self.bits == 8 { "n" } else { "" }; - write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ParseIntError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) } -} - -impl From for core::num::ParseIntError { - fn from(value: ParseIntError) -> Self { value.source } -} - -impl AsRef for ParseIntError { - fn as_ref(&self) -> &core::num::ParseIntError { &self.source } -} - -/// Not strictly necessary but serves as a lint - avoids weird behavior if someone accidentally -/// passes non-integer to the `parse()` function. -pub(crate) trait Integer: - FromStr + TryFrom + Sized -{ -} - -macro_rules! impl_integer { - ($($type:ty),* $(,)?) => { - $( - impl Integer for $type {} - )* - } -} - -impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); - -/// Parses the input string as an integer returning an error carrying rich context. -/// -/// If the caller owns `String` or `Box` which is not used later it's better to pass it as -/// owned since it avoids allocation in error case. -pub(crate) fn int + Into>(s: S) -> Result { - s.as_ref().parse().map_err(|error| { - ParseIntError { - input: s.into(), - bits: u8::try_from(core::mem::size_of::() * 8).expect("max is 128 bits for u128"), - // We detect if the type is signed by checking if -1 can be represented by it - // this way we don't have to implement special traits and optimizer will get rid of the - // computation. - is_signed: T::try_from(-1i8).is_ok(), - source: error, - } - }) -} - -/// Strips the hex prefix off `s` if one is present. -pub(crate) fn strip_hex_prefix(s: &str) -> &str { - if let Some(stripped) = s.strip_prefix("0x") { - stripped - } else if let Some(stripped) = s.strip_prefix("0X") { - stripped - } else { - s - } -} - -pub(crate) fn hex_u32 + Into>(s: S) -> Result { - u32::from_str_radix(strip_hex_prefix(s.as_ref()), 16).map_err(|error| ParseIntError { - input: s.into(), - bits: u8::try_from(core::mem::size_of::() * 8).expect("max is 32 bits for u32"), - is_signed: u32::try_from(-1i8).is_ok(), - source: error, - }) -} - -/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible -/// conversion function `fn`. -macro_rules! impl_tryfrom_str_from_int_infallible { - ($($from:ty, $to:ident, $inner:ident, $fn:ident);*) => { - $( - impl core::convert::TryFrom<$from> for $to { - type Error = $crate::error::ParseIntError; - - fn try_from(s: $from) -> core::result::Result { - $crate::parse::int::<$inner, $from>(s).map($to::$fn) - } - } - )* - } -} -pub(crate) use impl_tryfrom_str_from_int_infallible; - -/// Implements `FromStr` and `TryFrom<{&str, String, Box}> for $to` using `parse::int`, mapping -/// the output using infallible conversion function `fn`. -/// -/// The `Error` type is `ParseIntError` -macro_rules! impl_parse_str_from_int_infallible { - ($to:ident, $inner:ident, $fn:ident) => { - $crate::parse::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; $crate::prelude::String, $to, $inner, $fn; $crate::prelude::Box, $to, $inner, $fn); - - impl core::str::FromStr for $to { - type Err = $crate::error::ParseIntError; - - fn from_str(s: &str) -> core::result::Result { - $crate::parse::int::<$inner, &str>(s).map($to::$fn) - } - } - - } -} -pub(crate) use impl_parse_str_from_int_infallible; - -macro_rules! impl_tryfrom_str { - ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => { - $( - impl core::convert::TryFrom<$from> for $to { - type Error = $err; - - fn try_from(s: $from) -> core::result::Result { - $inner_fn(s) - } - } - )* - } -} -pub(crate) use impl_tryfrom_str; - -/// Implements standard parsing traits for `$type` by calling into `$inner_fn`. -macro_rules! impl_parse_str { - ($to:ty, $err:ty, $inner_fn:expr) => { - $crate::parse::impl_tryfrom_str!(&str, $to, $err, $inner_fn; String, $to, $err, $inner_fn; Box, $to, $err, $inner_fn); - - impl core::str::FromStr for $to { - type Err = $err; - - fn from_str(s: &str) -> core::result::Result { - $inner_fn(s) - } - } - } -} -pub(crate) use impl_parse_str; diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index e07009e8..b65b5976 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -12,13 +12,14 @@ use core::ops::{Add, Div, Mul, Not, Rem, Shl, Shr, Sub}; use io::{BufRead, Write}; #[cfg(all(test, mutate))] use mutagen::mutate; +use units::parse; use crate::blockdata::block::BlockHash; use crate::consensus::encode::{self, Decodable, Encodable}; #[cfg(doc)] use crate::consensus::Params; use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError}; -use crate::{parse, Network}; +use crate::Network; /// Implement traits and methods shared by `Target` and `Work`. macro_rules! do_impl { diff --git a/bitcoin/src/blockdata/fee_rate.rs b/units/src/fee_rate.rs similarity index 82% rename from bitcoin/src/blockdata/fee_rate.rs rename to units/src/fee_rate.rs index 6aaf83f9..9dcb88dd 100644 --- a/bitcoin/src/blockdata/fee_rate.rs +++ b/units/src/fee_rate.rs @@ -5,8 +5,11 @@ use core::fmt; use core::ops::{Div, Mul}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + use crate::amount::Amount; -use crate::blockdata::weight::Weight; +use crate::weight::Weight; /// Represents fee rate. /// @@ -14,7 +17,6 @@ use crate::blockdata::weight::Weight; /// up the types as well as basic formatting features. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] #[cfg_attr(feature = "serde", serde(transparent))] pub struct FeeRate(u64); @@ -93,18 +95,6 @@ impl FeeRate { /// if overflow occurred. /// /// This is equivalent to `Self::checked_mul_by_weight()`. - /// - /// # Examples - /// - /// ``` - /// # use bitcoin::{absolute, transaction, FeeRate, Transaction}; - /// # // Dummy transaction. - /// # let tx = Transaction { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] }; - /// - /// let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/vbyte is valid"); - /// let fee = rate.fee_wu(tx.weight()).unwrap(); - /// assert_eq!(fee.to_sat(), tx.vsize() as u64); - /// ``` pub fn fee_wu(self, weight: Weight) -> Option { self.checked_mul_by_weight(weight) } /// Calculates fee by multiplying this fee rate by weight, in virtual bytes, returning `None` @@ -153,12 +143,10 @@ impl Div for Amount { fn div(self, rhs: Weight) -> Self::Output { FeeRate(self.to_sat() * 1000 / rhs.to_wu()) } } -crate::parse::impl_parse_str_from_int_infallible!(FeeRate, u64, from_sat_per_kwu); +crate::impl_parse_str_from_int_infallible!(FeeRate, u64, from_sat_per_kwu); #[cfg(test)] mod tests { - use std::u64; - use super::*; #[test] @@ -236,21 +224,4 @@ mod tests { let fee_rate = FeeRate(10).checked_div(0); assert!(fee_rate.is_none()); } - - #[test] - fn fee_convenience_functions_agree() { - use hex::test_hex_unwrap as hex; - - use crate::blockdata::transaction::Transaction; - use crate::consensus::Decodable; - - const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; - - let raw_tx = hex!(SOME_TX); - let tx: Transaction = Decodable::consensus_decode(&mut raw_tx.as_slice()).unwrap(); - - let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/byte is valid"); - - assert_eq!(rate.fee_vb(tx.vsize() as u64), rate.fee_wu(tx.weight())); - } } diff --git a/units/src/lib.rs b/units/src/lib.rs index a79f496c..3b42f1c2 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -34,6 +34,28 @@ extern crate std; pub extern crate serde; pub mod amount; +#[cfg(feature = "alloc")] +pub mod locktime; +#[cfg(feature = "alloc")] +pub mod fee_rate; +#[cfg(feature = "alloc")] +pub mod parse; +#[cfg(feature = "alloc")] +pub mod weight; #[doc(inline)] -pub use self::amount::{Amount, ParseAmountError, SignedAmount}; +pub use self::{ + amount::{Amount, ParseAmountError, SignedAmount}, +}; +#[cfg(feature = "alloc")] +pub use self::parse::ParseIntError; + +#[rustfmt::skip] +#[allow(unused_imports)] +mod prelude { + #[cfg(all(feature = "alloc", not(feature = "std")))] + pub use alloc::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, BorrowMut, Cow, ToOwned}, slice, rc}; + + #[cfg(feature = "std")] + pub use std::{string::{String, ToString}, vec::Vec, boxed::Box, borrow::{Borrow, BorrowMut, Cow, ToOwned}, rc}; +} diff --git a/units/src/locktime/absolute.rs b/units/src/locktime/absolute.rs new file mode 100644 index 00000000..0930f3cd --- /dev/null +++ b/units/src/locktime/absolute.rs @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Provides type `Height` and `Time` types used by the `rust-bitcoin` `absolute::LockTime` type. + +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use internals::write_err; + +#[cfg(feature = "alloc")] +use crate::prelude::*; +use crate::parse::{self, ParseIntError}; + +/// 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; + +/// 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))] +pub struct Height(u32); + +impl Height { + /// Absolute block height 0, the genesis block. + pub const ZERO: Self = Height(0); + + /// The minimum absolute block height (0), the genesis block. + pub const MIN: Self = Self::ZERO; + + /// The maximum absolute block height. + pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1); + + /// Creates a `Height` from a hex string. + /// + /// The input string is may or may not contain a typical hex prefix e.g., `0x`. + pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_consensus) } + + /// Constructs a new block height. + /// + /// # Errors + /// + /// If `n` does not represent a valid block height value. + /// + /// # Examples + /// ```rust + /// use bitcoin_units::locktime::absolute::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 { + if is_block_height(n) { + Ok(Self(n)) + } else { + Err(ConversionError::invalid_height(n)) + } + } + + /// Converts this `Height` to its inner `u32` value. + #[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) } +} + +crate::impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus)); + +/// Error returned when parsing block height fails. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ParseHeightError(ParseError); + +impl fmt::Display for ParseHeightError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseHeightError { + // To be consistent with `write_err` we need to **not** return source in case of overflow + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } +} + +impl From for ParseHeightError { + fn from(value: ParseError) -> Self { Self(value) } +} + +/// 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))] +pub struct Time(u32); + +impl Time { + /// The minimum absolute block time (Tue Nov 05 1985 00:53:20 GMT+0000). + pub const MIN: Self = Time(LOCK_TIME_THRESHOLD); + + /// The maximum absolute block time (Sun Feb 07 2106 06:28:15 GMT+0000). + pub const MAX: Self = Time(u32::max_value()); + + /// Creates a `Time` from a hex string. + /// + /// The input string is may or may not contain a typical hex prefix e.g., `0x`. + pub fn from_hex(s: &str) -> Result { parse_hex(s, Self::from_consensus) } + + /// Constructs a new block time. + /// + /// # Errors + /// + /// If `n` does not encode a valid UNIX time stamp. + /// + /// # Examples + /// ```rust + /// use bitcoin_units::locktime::absolute::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 { + if is_block_time(n) { + Ok(Self(n)) + } else { + Err(ConversionError::invalid_time(n)) + } + } + + /// Converts this `Time` to its inner `u32` value. + #[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) } +} + +crate::impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus)); + +/// Error returned when parsing block time fails. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ParseTimeError(ParseError); + +impl fmt::Display for ParseTimeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.display(f, "block height", LOCK_TIME_THRESHOLD, u32::MAX) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseTimeError { + // To be consistent with `write_err` we need to **not** return source in case of overflow + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() } +} + +impl From for ParseTimeError { + fn from(value: ParseError) -> Self { Self(value) } +} + +fn parser(f: F) -> impl FnOnce(S) -> Result +where + E: From, + S: AsRef + Into, + F: FnOnce(u32) -> Result, +{ + move |s| { + let n = s.as_ref().parse::().map_err(ParseError::invalid_int(s))?; + let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; + f(n).map_err(ParseError::from).map_err(Into::into) + } +} + +fn parse_hex(s: S, f: F) -> Result +where + E: From, + S: AsRef + Into, + F: FnOnce(u32) -> Result, +{ + let n = i64::from_str_radix(parse::strip_hex_prefix(s.as_ref()), 16).map_err(ParseError::invalid_int(s))?; + let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?; + f(n).map_err(ParseError::from).map_err(Into::into) +} + +/// Returns true if `n` is a block height i.e., less than 500,000,000. +pub 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. +pub fn is_block_time(n: u32) -> bool { n >= LOCK_TIME_THRESHOLD } + +/// An error that occurs when converting a `u32` to a lock time variant. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +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")] +impl std::error::Error for ConversionError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// 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), + } + } +} + +/// Internal - common representation for height and time. +#[derive(Debug, Clone, Eq, PartialEq)] +enum ParseError { + InvalidInteger { source: core::num::ParseIntError, input: String }, + // unit implied by outer type + // we use i64 to have nicer messages for negative values + Conversion(i64), +} + +internals::impl_from_infallible!(ParseError); + +impl ParseError { + fn invalid_int>(s: S) -> impl FnOnce(core::num::ParseIntError) -> Self { + move |source| Self::InvalidInteger { source, input: s.into() } + } + + fn display(&self, f: &mut fmt::Formatter<'_>, subject: &str, lower_bound: u32, upper_bound: u32) -> fmt::Result { + use core::num::IntErrorKind; + + use ParseError::*; + + match self { + InvalidInteger { source, input } if *source.kind() == IntErrorKind::PosOverflow => { + write!(f, "{} {} is above limit {}", subject, input, upper_bound) + } + InvalidInteger { source, input } if *source.kind() == IntErrorKind::NegOverflow => { + write!(f, "{} {} is below limit {}", subject, input, lower_bound) + } + InvalidInteger { source, input } => { + write_err!(f, "failed to parse {} as {}", input, subject; source) + } + Conversion(value) if *value < i64::from(lower_bound) => { + write!(f, "{} {} is below limit {}", subject, value, lower_bound) + } + Conversion(value) => { + write!(f, "{} {} is above limit {}", subject, value, upper_bound) + } + } + } + + // To be consistent with `write_err` we need to **not** return source in case of overflow + #[cfg(feature = "std")] + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use core::num::IntErrorKind; + + use ParseError::*; + + match self { + InvalidInteger { source, .. } if *source.kind() == IntErrorKind::PosOverflow => None, + InvalidInteger { source, .. } if *source.kind() == IntErrorKind::NegOverflow => None, + InvalidInteger { source, .. } => Some(source), + Conversion(_) => None, + } + } +} + +impl From for ParseError { + fn from(value: ParseIntError) -> Self { + Self::InvalidInteger { source: value.source, input: value.input } + } +} + +impl From for ParseError { + fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn time_from_str_hex_happy_path() { + let actual = Time::from_hex("0x6289C350").unwrap(); + let expected = Time::from_consensus(0x6289C350).unwrap(); + assert_eq!(actual, expected); + } + + #[test] + fn time_from_str_hex_no_prefix_happy_path() { + let time = Time::from_hex("6289C350").unwrap(); + assert_eq!(time, Time(0x6289C350)); + } + + #[test] + fn time_from_str_hex_invalid_hex_should_err() { + let hex = "0xzb93"; + let result = Time::from_hex(hex); + assert!(result.is_err()); + } + + #[test] + fn height_from_str_hex_happy_path() { + let actual = Height::from_hex("0xBA70D").unwrap(); + let expected = Height(0xBA70D); + assert_eq!(actual, expected); + } + + #[test] + fn height_from_str_hex_no_prefix_happy_path() { + let height = Height::from_hex("BA70D").unwrap(); + assert_eq!(height, Height(0xBA70D)); + } + + #[test] + fn height_from_str_hex_invalid_hex_should_err() { + let hex = "0xzb93"; + let result = Height::from_hex(hex); + assert!(result.is_err()); + } + +} diff --git a/units/src/locktime/mod.rs b/units/src/locktime/mod.rs new file mode 100644 index 00000000..4cf62ad1 --- /dev/null +++ b/units/src/locktime/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Provides absolute and relative locktimes. + +pub mod absolute; +pub mod relative; diff --git a/units/src/locktime/relative.rs b/units/src/locktime/relative.rs new file mode 100644 index 00000000..a44a7486 --- /dev/null +++ b/units/src/locktime/relative.rs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Provides type `Height` and `Time` types used by the `rust-bitcoin` `relative::LockTime` type. + +use core::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A relative lock time lock-by-blockheight value. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Height(u16); + +impl Height { + /// Relative block height 0, can be included in any block. + pub const ZERO: Self = Height(0); + + /// The minimum relative block height (0), can be included in any block. + pub const MIN: Self = Self::ZERO; + + /// The maximum relative block height. + pub const MAX: Self = Height(u16::max_value()); + + /// Returns the inner `u16` value. + #[inline] + pub fn value(self) -> u16 { self.0 } +} + +impl From for Height { + #[inline] + fn from(value: u16) -> Self { Height(value) } +} + +crate::impl_parse_str_from_int_infallible!(Height, u16, from); + +impl fmt::Display for Height { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} + +/// A relative lock time lock-by-blocktime value. +/// +/// For BIP 68 relative lock-by-blocktime locks, time is measure in 512 second intervals. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Time(u16); + +impl Time { + /// Relative block time 0, can be included in any block. + pub const ZERO: Self = Time(0); + + /// The minimum relative block time (0), can be included in any block. + pub const MIN: Self = Time::ZERO; + + /// The maximum relative block time (33,554,432 seconds or approx 388 days). + pub const MAX: Self = Time(u16::max_value()); + + /// Create a [`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 { Time(intervals) } + + /// Create a [`Time`] from seconds, converting the seconds into 512 second interval with ceiling + /// division. + /// + /// # Errors + /// + /// 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(Time::from_512_second_intervals(interval)) + } else { + Err(TimeOverflowError { seconds }) + } + } + + /// Returns the inner `u16` value. + #[inline] + pub fn value(self) -> u16 { self.0 } +} + +crate::impl_parse_str_from_int_infallible!(Time, u16, from_512_second_intervals); + +impl fmt::Display for Time { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } +} + +/// Input time in seconds was too large to be encoded to a 16 bit 512 second interval. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TimeOverflowError { + /// Time value in seconds that overflowed. + // Private because we maintain an invariant that the `seconds` value does actually overflow. + pub(crate) seconds: u32 +} + +impl TimeOverflowError { + /// Creates a new `TimeOverflowError` using `seconds`. + /// + /// # Panics + /// + /// If `seconds` would not actually overflow a `u16`. + pub fn new(seconds: u32) -> Self { + assert!(u16::try_from((seconds + 511) / 512).is_err()); + Self { seconds } + } +} + +impl fmt::Display for TimeOverflowError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} seconds is too large to be encoded to a 16 bit 512 second interval", self.seconds) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TimeOverflowError {} diff --git a/units/src/parse.rs b/units/src/parse.rs new file mode 100644 index 00000000..ba0b4b2c --- /dev/null +++ b/units/src/parse.rs @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Parsing utilities. + +use core::fmt; +use core::str::FromStr; +use alloc::string::String; + +use internals::write_err; + +/// Error with rich context returned when a string can't be parsed as an integer. +/// +/// This is an extension of [`core::num::ParseIntError`], which carries the input that failed to +/// parse as well as type information. As a result it provides very informative error messages that +/// make it easier to understand the problem and correct mistakes. +/// +/// Note that this is larger than the type from `core` so if it's passed through a deep call stack +/// in a performance-critical application you may want to box it or throw away the context by +/// converting to `core` type. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct ParseIntError { + pub(crate) input: String, + // for displaying - see Display impl with nice error message below + bits: u8, + // We could represent this as a single bit but it wouldn't actually derease the cost of moving + // the struct because String contains pointers so there will be padding of bits at least + // pointer_size - 1 bytes: min 1B in practice. + is_signed: bool, + pub(crate) source: core::num::ParseIntError, +} + +impl ParseIntError { + /// Returns the input that was attempted to be parsed. + pub fn input(&self) -> &str { &self.input } +} + +impl fmt::Display for ParseIntError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let signed = if self.is_signed { "signed" } else { "unsigned" }; + let n = if self.bits == 8 { "n" } else { "" }; + write_err!(f, "failed to parse '{}' as a{} {}-bit {} integer", self.input, n, self.bits, signed; self.source) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseIntError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) } +} + +impl From for core::num::ParseIntError { + fn from(value: ParseIntError) -> Self { value.source } +} + +impl AsRef for ParseIntError { + fn as_ref(&self) -> &core::num::ParseIntError { &self.source } +} + +/// Not strictly necessary but serves as a lint - avoids weird behavior if someone accidentally +/// passes non-integer to the `parse()` function. +pub trait Integer: + FromStr + TryFrom + Sized +{ +} + +macro_rules! impl_integer { + ($($type:ty),* $(,)?) => { + $( + impl Integer for $type {} + )* + } +} + +impl_integer!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128); + +/// Parses the input string as an integer returning an error carrying rich context. +/// +/// If the caller owns `String` or `Box` which is not used later it's better to pass it as +/// owned since it avoids allocation in error case. +pub fn int + Into>(s: S) -> Result { + s.as_ref().parse().map_err(|error| { + ParseIntError { + input: s.into(), + bits: u8::try_from(core::mem::size_of::() * 8).expect("max is 128 bits for u128"), + // We detect if the type is signed by checking if -1 can be represented by it + // this way we don't have to implement special traits and optimizer will get rid of the + // computation. + is_signed: T::try_from(-1i8).is_ok(), + source: error, + } + }) +} + +/// Strips the hex prefix off `s` if one is present. +pub(crate) fn strip_hex_prefix(s: &str) -> &str { + if let Some(stripped) = s.strip_prefix("0x") { + stripped + } else if let Some(stripped) = s.strip_prefix("0X") { + stripped + } else { + s + } +} + +/// Parses a u32 from a hex string. +/// +/// Input string may or may not contain a `0x` prefix. +pub fn hex_u32 + Into>(s: S) -> Result { + u32::from_str_radix(strip_hex_prefix(s.as_ref()), 16).map_err(|error| ParseIntError { + input: s.into(), + bits: u8::try_from(core::mem::size_of::() * 8).expect("max is 32 bits for u32"), + is_signed: u32::try_from(-1i8).is_ok(), + source: error, + }) +} + +/// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible +/// conversion function `fn`. +#[macro_export] +macro_rules! impl_tryfrom_str_from_int_infallible { + ($($from:ty, $to:ident, $inner:ident, $fn:ident);*) => { + $( + impl core::convert::TryFrom<$from> for $to { + type Error = $crate::parse::ParseIntError; + + fn try_from(s: $from) -> core::result::Result { + $crate::parse::int::<$inner, $from>(s).map($to::$fn) + } + } + )* + } +} + +/// Implements `FromStr` and `TryFrom<{&str, String, Box}> for $to` using `parse::int`, mapping +/// the output using infallible conversion function `fn`. +/// +/// The `Error` type is `ParseIntError` +#[macro_export] +macro_rules! impl_parse_str_from_int_infallible { + ($to:ident, $inner:ident, $fn:ident) => { + #[cfg(all(feature = "alloc", not(feature = "std")))] + $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; alloc::string::String, $to, $inner, $fn; alloc::boxed::Box, $to, $inner, $fn); + #[cfg(feature = "std")] + $crate::impl_tryfrom_str_from_int_infallible!(&str, $to, $inner, $fn; std::string::String, $to, $inner, $fn; std::boxed::Box, $to, $inner, $fn); + + impl core::str::FromStr for $to { + type Err = $crate::parse::ParseIntError; + + fn from_str(s: &str) -> core::result::Result { + $crate::parse::int::<$inner, &str>(s).map($to::$fn) + } + } + + } +} + +/// Implements `TryFrom<$from> for $to`. +#[macro_export] +macro_rules! impl_tryfrom_str { + ($($from:ty, $to:ty, $err:ty, $inner_fn:expr);*) => { + $( + impl core::convert::TryFrom<$from> for $to { + type Error = $err; + + fn try_from(s: $from) -> core::result::Result { + $inner_fn(s) + } + } + )* + } +} + +/// Implements standard parsing traits for `$type` by calling into `$inner_fn`. +#[macro_export] +macro_rules! impl_parse_str { + ($to:ty, $err:ty, $inner_fn:expr) => { + $crate::impl_tryfrom_str!(&str, $to, $err, $inner_fn; String, $to, $err, $inner_fn; Box, $to, $err, $inner_fn); + + impl core::str::FromStr for $to { + type Err = $err; + + fn from_str(s: &str) -> core::result::Result { + $inner_fn(s) + } + } + } +} diff --git a/bitcoin/src/blockdata/weight.rs b/units/src/weight.rs similarity index 96% rename from bitcoin/src/blockdata/weight.rs rename to units/src/weight.rs index ffd4eb65..eef336ef 100644 --- a/bitcoin/src/blockdata/weight.rs +++ b/units/src/weight.rs @@ -5,13 +5,18 @@ use core::fmt; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The factor that non-witness serialization data is multiplied by during weight calculation. +pub const WITNESS_SCALE_FACTOR: usize = 4; + /// Represents block weight - the weight of a transaction or block. /// /// This is an integer newtype representing weigth in `wu`. It provides protection against mixing /// up the types as well as basic formatting features. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))] #[cfg_attr(feature = "serde", serde(transparent))] pub struct Weight(u64); @@ -30,7 +35,7 @@ impl Weight { pub const MAX: Weight = Weight(u64::MAX); /// The factor that non-witness serialization data is multiplied by during weight calculation. - pub const WITNESS_SCALE_FACTOR: u64 = crate::blockdata::constants::WITNESS_SCALE_FACTOR as u64; + pub const WITNESS_SCALE_FACTOR: u64 = WITNESS_SCALE_FACTOR as u64; /// The maximum allowed weight for a block, see BIP 141 (network rule). pub const MAX_BLOCK: Weight = Weight(4_000_000); @@ -332,4 +337,4 @@ impl<'a> core::iter::Sum<&'a Weight> for Weight { } } -crate::parse::impl_parse_str_from_int_infallible!(Weight, u64, from_wu); +crate::impl_parse_str_from_int_infallible!(Weight, u64, from_wu);