Merge rust-bitcoin/rust-bitcoin#2569: Move types to `units`
cbee9781e8
Move unit types to units (Tobin C. Harding)5bd0d7194b
Remove unused absolute::Error (Tobin C. Harding) Pull request description: Move the following unit types to the new `units` crate: - `locktime::absolute::{Height, Time}` - `locktime::relative::{Height, Time}` - `FeeRate` - `Weight` Also move the `parse` module as well as constants as required. Do minimal changes to get things building: - Feature gate on "alloc" as needed. - Remove rustdocs that use `bitcoin` types. - Re-export units types so this is a non-breaking change. - Fix import paths. Patch 1 was originally #2526, putting it in via this PR to try and speed up the process. Close: #2282 ACKs for top commit: sanket1729: ACKcbee9781e8
apoelstra: ACKcbee9781e8
lgtm. this is a good start. I think the LockTime types should follow Height and Time Tree-SHA512: 6b0d63c7b054008598d7fa81be7d8c112f2778883b5529d79d446617b94b3c196c9ac735f840d1dfb488700894d3161c6976d44ab0e12ac3af4008068eac5f87
This commit is contained in:
commit
750b4dfb8b
|
@ -30,7 +30,7 @@ pub const DIFFCHANGE_INTERVAL: u32 = 2016;
|
||||||
pub const DIFFCHANGE_TIMESPAN: u32 = 14 * 24 * 3600;
|
pub const DIFFCHANGE_TIMESPAN: u32 = 14 * 24 * 3600;
|
||||||
|
|
||||||
/// The factor that non-witness serialization data is multiplied by during weight calculation.
|
/// 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.
|
/// The maximum allowed number of signature check operations in a block.
|
||||||
pub const MAX_BLOCK_SIGOPS_COST: i64 = 80_000;
|
pub const MAX_BLOCK_SIGOPS_COST: i64 = 80_000;
|
||||||
/// Mainnet (bitcoin) pubkey address prefix.
|
/// Mainnet (bitcoin) pubkey address prefix.
|
||||||
|
|
|
@ -6,33 +6,24 @@
|
||||||
//! whether `LockTime < LOCKTIME_THRESHOLD`.
|
//! whether `LockTime < LOCKTIME_THRESHOLD`.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use core::cmp::{Ordering, PartialOrd};
|
use core::cmp::Ordering;
|
||||||
use core::{fmt, mem};
|
use core::{fmt, mem};
|
||||||
|
|
||||||
use internals::write_err;
|
|
||||||
use io::{BufRead, Write};
|
use io::{BufRead, Write};
|
||||||
#[cfg(all(test, mutate))]
|
#[cfg(all(test, mutate))]
|
||||||
use mutagen::mutate;
|
use mutagen::mutate;
|
||||||
|
use units::parse;
|
||||||
|
|
||||||
|
use crate::consensus::encode::{self, Decodable, Encodable};
|
||||||
|
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::absolute;
|
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]).
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
///
|
#[doc(inline)]
|
||||||
/// `LockTime` values _below_ the threshold are interpreted as block heights, values _above_ (or
|
pub use units::locktime::absolute::{
|
||||||
/// equal to) the threshold are interpreted as block times (UNIX timestamp, seconds since epoch).
|
Height, Time, LOCK_TIME_THRESHOLD, ConversionError, ParseHeightError, ParseTimeError,
|
||||||
///
|
};
|
||||||
/// 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 lock time value, representing either a block height or a UNIX timestamp (seconds
|
/// An absolute lock time value, representing either a block height or a UNIX timestamp (seconds
|
||||||
/// since epoch).
|
/// since epoch).
|
||||||
|
@ -138,7 +129,7 @@ impl LockTime {
|
||||||
/// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
|
/// assert_eq!(lock_time.to_consensus_u32(), n_lock_time);
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_consensus(n: u32) -> Self {
|
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"))
|
Self::Blocks(Height::from_consensus(n).expect("n is valid"))
|
||||||
} else {
|
} else {
|
||||||
Self::Seconds(Time::from_consensus(n).expect("n is valid"))
|
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<Height> for LockTime {
|
impl From<Height> for LockTime {
|
||||||
#[inline]
|
#[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<Self, ParseHeightError> { 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<Height, ConversionError> {
|
|
||||||
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<ParseError> 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<Self, ParseTimeError> { 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<Time, ConversionError> {
|
|
||||||
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<ParseError> for ParseTimeError {
|
|
||||||
fn from(value: ParseError) -> Self { Self(value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
|
|
||||||
where
|
|
||||||
E: From<ParseError>,
|
|
||||||
S: AsRef<str> + Into<String>,
|
|
||||||
F: FnOnce(u32) -> Result<T, ConversionError>,
|
|
||||||
{
|
|
||||||
move |s| {
|
|
||||||
let n = s.as_ref().parse::<i64>().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<T, E, S, F>(s: S, f: F) -> Result<T, E>
|
|
||||||
where
|
|
||||||
E: From<ParseError>,
|
|
||||||
S: AsRef<str> + Into<String>,
|
|
||||||
F: FnOnce(u32) -> Result<T, ConversionError>,
|
|
||||||
{
|
|
||||||
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<ConversionError> for Error {
|
|
||||||
#[inline]
|
|
||||||
fn from(e: ConversionError) -> Self { Self::Conversion(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OperationError> for Error {
|
|
||||||
#[inline]
|
|
||||||
fn from(e: OperationError) -> Self { Self::Operation(e) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ParseIntError> 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: Into<String>>(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<ParseIntError> for ParseError {
|
|
||||||
fn from(value: ParseIntError) -> Self {
|
|
||||||
Self::InvalidInteger { source: value.source, input: value.input }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ConversionError> for ParseError {
|
|
||||||
fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -860,46 +451,6 @@ mod tests {
|
||||||
assert!(result.is_err());
|
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]
|
#[test]
|
||||||
fn parses_correctly_to_height_or_time() {
|
fn parses_correctly_to_height_or_time() {
|
||||||
let lock = LockTime::from_consensus(750_000);
|
let lock = LockTime::from_consensus(750_000);
|
||||||
|
|
|
@ -11,10 +11,13 @@ use core::fmt;
|
||||||
#[cfg(all(test, mutate))]
|
#[cfg(all(test, mutate))]
|
||||||
use mutagen::mutate;
|
use mutagen::mutate;
|
||||||
|
|
||||||
use crate::parse::impl_parse_str_from_int_infallible;
|
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::relative;
|
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).
|
/// 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
|
/// 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<u16> 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<Self, TimeOverflowError> {
|
|
||||||
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.
|
/// Tried to satisfy a lock-by-blocktime lock using a height value.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
|
|
|
@ -8,12 +8,10 @@
|
||||||
|
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod fee_rate;
|
|
||||||
pub mod locktime;
|
pub mod locktime;
|
||||||
pub mod opcodes;
|
pub mod opcodes;
|
||||||
pub mod script;
|
pub mod script;
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
pub mod weight;
|
|
||||||
pub mod witness;
|
pub mod witness;
|
||||||
|
|
||||||
#[rustfmt::skip] // Keep public re-exports separate.
|
#[rustfmt::skip] // Keep public re-exports separate.
|
||||||
|
@ -22,3 +20,37 @@ pub use self::{
|
||||||
fee_rate::FeeRate,
|
fee_rate::FeeRate,
|
||||||
weight::Weight
|
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::*;
|
||||||
|
}
|
||||||
|
|
|
@ -12,11 +12,11 @@ use core::str::FromStr;
|
||||||
|
|
||||||
use bech32::Fe32;
|
use bech32::Fe32;
|
||||||
use internals::write_err;
|
use internals::write_err;
|
||||||
|
use units::{parse, ParseIntError};
|
||||||
|
|
||||||
use crate::blockdata::opcodes::all::*;
|
use crate::blockdata::opcodes::all::*;
|
||||||
use crate::blockdata::opcodes::Opcode;
|
use crate::blockdata::opcodes::Opcode;
|
||||||
use crate::blockdata::script::Instruction;
|
use crate::blockdata::script::Instruction;
|
||||||
use crate::error::ParseIntError;
|
|
||||||
|
|
||||||
/// Version of the segregated witness program.
|
/// Version of the segregated witness program.
|
||||||
///
|
///
|
||||||
|
@ -87,7 +87,7 @@ impl FromStr for WitnessVersion {
|
||||||
type Err = FromStrError;
|
type Err = FromStrError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let version: u8 = crate::parse::int(s)?;
|
let version: u8 = parse::int(s)?;
|
||||||
Ok(WitnessVersion::try_from(version)?)
|
Ok(WitnessVersion::try_from(version)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use core::{cmp, fmt, str};
|
||||||
use hashes::{sha256d, Hash};
|
use hashes::{sha256d, Hash};
|
||||||
use internals::write_err;
|
use internals::write_err;
|
||||||
use io::{BufRead, Write};
|
use io::{BufRead, Write};
|
||||||
|
use units::parse;
|
||||||
|
|
||||||
use super::Weight;
|
use super::Weight;
|
||||||
use crate::blockdata::locktime::absolute::{self, Height, Time};
|
use crate::blockdata::locktime::absolute::{self, Height, Time};
|
||||||
|
@ -26,7 +27,6 @@ use crate::blockdata::FeeRate;
|
||||||
use crate::consensus::{encode, Decodable, Encodable};
|
use crate::consensus::{encode, Decodable, Encodable};
|
||||||
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
|
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
|
||||||
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
|
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode};
|
||||||
use crate::parse::{self, impl_parse_str_from_int_infallible};
|
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::sighash::{EcdsaSighashType, TapSighashType};
|
use crate::sighash::{EcdsaSighashType, TapSighashType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -169,7 +169,7 @@ fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
|
||||||
return Err(ParseOutPointError::VoutNotCanonical);
|
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 {
|
impl core::str::FromStr for OutPoint {
|
||||||
|
@ -449,7 +449,7 @@ impl Sequence {
|
||||||
if let Ok(interval) = u16::try_from(seconds / 512) {
|
if let Ok(interval) = u16::try_from(seconds / 512) {
|
||||||
Ok(Sequence::from_512_second_intervals(interval))
|
Ok(Sequence::from_512_second_intervals(interval))
|
||||||
} else {
|
} else {
|
||||||
Err(TimeOverflowError { seconds })
|
Err(TimeOverflowError::new(seconds))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +462,7 @@ impl Sequence {
|
||||||
if let Ok(interval) = u16::try_from((seconds + 511) / 512) {
|
if let Ok(interval) = u16::try_from((seconds + 511) / 512) {
|
||||||
Ok(Sequence::from_512_second_intervals(interval))
|
Ok(Sequence::from_512_second_intervals(interval))
|
||||||
} else {
|
} 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.
|
/// Bitcoin transaction output.
|
||||||
///
|
///
|
||||||
|
@ -1702,7 +1702,7 @@ mod tests {
|
||||||
OutPoint::from_str(
|
OutPoint::from_str(
|
||||||
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:lol"
|
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:lol"
|
||||||
),
|
),
|
||||||
Err(ParseOutPointError::Vout(crate::parse::int::<u32, _>("lol").unwrap_err()))
|
Err(ParseOutPointError::Vout(parse::int::<u32, _>("lol").unwrap_err()))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -89,7 +89,6 @@ extern crate actual_serde as serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod test_macros;
|
mod test_macros;
|
||||||
mod internal_macros;
|
mod internal_macros;
|
||||||
mod parse;
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
mod serde_utils;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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<ParseIntError> for core::num::ParseIntError {
|
|
||||||
fn from(value: ParseIntError) -> Self { value.source }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<core::num::ParseIntError> 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<Err = core::num::ParseIntError> + TryFrom<i8> + 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<str>` which is not used later it's better to pass it as
|
|
||||||
/// owned since it avoids allocation in error case.
|
|
||||||
pub(crate) fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseIntError> {
|
|
||||||
s.as_ref().parse().map_err(|error| {
|
|
||||||
ParseIntError {
|
|
||||||
input: s.into(),
|
|
||||||
bits: u8::try_from(core::mem::size_of::<T>() * 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<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
|
|
||||||
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::<u32>() * 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<Self, Self::Error> {
|
|
||||||
$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<str>}> 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<str>, $to, $inner, $fn);
|
|
||||||
|
|
||||||
impl core::str::FromStr for $to {
|
|
||||||
type Err = $crate::error::ParseIntError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
|
||||||
$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<Self, Self::Error> {
|
|
||||||
$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<str>, $to, $err, $inner_fn);
|
|
||||||
|
|
||||||
impl core::str::FromStr for $to {
|
|
||||||
type Err = $err;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
|
||||||
$inner_fn(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub(crate) use impl_parse_str;
|
|
|
@ -12,13 +12,14 @@ use core::ops::{Add, Div, Mul, Not, Rem, Shl, Shr, Sub};
|
||||||
use io::{BufRead, Write};
|
use io::{BufRead, Write};
|
||||||
#[cfg(all(test, mutate))]
|
#[cfg(all(test, mutate))]
|
||||||
use mutagen::mutate;
|
use mutagen::mutate;
|
||||||
|
use units::parse;
|
||||||
|
|
||||||
use crate::blockdata::block::BlockHash;
|
use crate::blockdata::block::BlockHash;
|
||||||
use crate::consensus::encode::{self, Decodable, Encodable};
|
use crate::consensus::encode::{self, Decodable, Encodable};
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::consensus::Params;
|
use crate::consensus::Params;
|
||||||
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
|
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
|
||||||
use crate::{parse, Network};
|
use crate::Network;
|
||||||
|
|
||||||
/// Implement traits and methods shared by `Target` and `Work`.
|
/// Implement traits and methods shared by `Target` and `Work`.
|
||||||
macro_rules! do_impl {
|
macro_rules! do_impl {
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops::{Div, Mul};
|
use core::ops::{Div, Mul};
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::amount::Amount;
|
use crate::amount::Amount;
|
||||||
use crate::blockdata::weight::Weight;
|
use crate::weight::Weight;
|
||||||
|
|
||||||
/// Represents fee rate.
|
/// Represents fee rate.
|
||||||
///
|
///
|
||||||
|
@ -14,7 +17,6 @@ use crate::blockdata::weight::Weight;
|
||||||
/// up the types as well as basic formatting features.
|
/// up the types as well as basic formatting features.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(transparent))]
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
pub struct FeeRate(u64);
|
pub struct FeeRate(u64);
|
||||||
|
|
||||||
|
@ -93,18 +95,6 @@ impl FeeRate {
|
||||||
/// if overflow occurred.
|
/// if overflow occurred.
|
||||||
///
|
///
|
||||||
/// This is equivalent to `Self::checked_mul_by_weight()`.
|
/// 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<Amount> { self.checked_mul_by_weight(weight) }
|
pub fn fee_wu(self, weight: Weight) -> Option<Amount> { self.checked_mul_by_weight(weight) }
|
||||||
|
|
||||||
/// Calculates fee by multiplying this fee rate by weight, in virtual bytes, returning `None`
|
/// Calculates fee by multiplying this fee rate by weight, in virtual bytes, returning `None`
|
||||||
|
@ -153,12 +143,10 @@ impl Div<Weight> for Amount {
|
||||||
fn div(self, rhs: Weight) -> Self::Output { FeeRate(self.to_sat() * 1000 / rhs.to_wu()) }
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -236,21 +224,4 @@ mod tests {
|
||||||
let fee_rate = FeeRate(10).checked_div(0);
|
let fee_rate = FeeRate(10).checked_div(0);
|
||||||
assert!(fee_rate.is_none());
|
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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -34,6 +34,28 @@ extern crate std;
|
||||||
pub extern crate serde;
|
pub extern crate serde;
|
||||||
|
|
||||||
pub mod amount;
|
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)]
|
#[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};
|
||||||
|
}
|
||||||
|
|
|
@ -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<Self, ParseHeightError> { 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<Height, ConversionError> {
|
||||||
|
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<ParseError> 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<Self, ParseTimeError> { 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<Time, ConversionError> {
|
||||||
|
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<ParseError> for ParseTimeError {
|
||||||
|
fn from(value: ParseError) -> Self { Self(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parser<T, E, S, F>(f: F) -> impl FnOnce(S) -> Result<T, E>
|
||||||
|
where
|
||||||
|
E: From<ParseError>,
|
||||||
|
S: AsRef<str> + Into<String>,
|
||||||
|
F: FnOnce(u32) -> Result<T, ConversionError>,
|
||||||
|
{
|
||||||
|
move |s| {
|
||||||
|
let n = s.as_ref().parse::<i64>().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<T, E, S, F>(s: S, f: F) -> Result<T, E>
|
||||||
|
where
|
||||||
|
E: From<ParseError>,
|
||||||
|
S: AsRef<str> + Into<String>,
|
||||||
|
F: FnOnce(u32) -> Result<T, ConversionError>,
|
||||||
|
{
|
||||||
|
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: Into<String>>(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<ParseIntError> for ParseError {
|
||||||
|
fn from(value: ParseIntError) -> Self {
|
||||||
|
Self::InvalidInteger { source: value.source, input: value.input }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ConversionError> 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
//! Provides absolute and relative locktimes.
|
||||||
|
|
||||||
|
pub mod absolute;
|
||||||
|
pub mod relative;
|
|
@ -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<u16> 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<Self, TimeOverflowError> {
|
||||||
|
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 {}
|
|
@ -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<ParseIntError> for core::num::ParseIntError {
|
||||||
|
fn from(value: ParseIntError) -> Self { value.source }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<core::num::ParseIntError> 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<Err = core::num::ParseIntError> + TryFrom<i8> + 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<str>` which is not used later it's better to pass it as
|
||||||
|
/// owned since it avoids allocation in error case.
|
||||||
|
pub fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, ParseIntError> {
|
||||||
|
s.as_ref().parse().map_err(|error| {
|
||||||
|
ParseIntError {
|
||||||
|
input: s.into(),
|
||||||
|
bits: u8::try_from(core::mem::size_of::<T>() * 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<S: AsRef<str> + Into<String>>(s: S) -> Result<u32, ParseIntError> {
|
||||||
|
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::<u32>() * 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<Self, Self::Error> {
|
||||||
|
$crate::parse::int::<$inner, $from>(s).map($to::$fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implements `FromStr` and `TryFrom<{&str, String, Box<str>}> 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<str>, $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<str>, $to, $inner, $fn);
|
||||||
|
|
||||||
|
impl core::str::FromStr for $to {
|
||||||
|
type Err = $crate::parse::ParseIntError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
||||||
|
$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<Self, Self::Error> {
|
||||||
|
$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<str>, $to, $err, $inner_fn);
|
||||||
|
|
||||||
|
impl core::str::FromStr for $to {
|
||||||
|
type Err = $err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
||||||
|
$inner_fn(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,13 +5,18 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
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.
|
/// Represents block weight - the weight of a transaction or block.
|
||||||
///
|
///
|
||||||
/// This is an integer newtype representing weigth in `wu`. It provides protection against mixing
|
/// This is an integer newtype representing weigth in `wu`. It provides protection against mixing
|
||||||
/// up the types as well as basic formatting features.
|
/// up the types as well as basic formatting features.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(transparent))]
|
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||||
pub struct Weight(u64);
|
pub struct Weight(u64);
|
||||||
|
|
||||||
|
@ -30,7 +35,7 @@ impl Weight {
|
||||||
pub const MAX: Weight = Weight(u64::MAX);
|
pub const MAX: Weight = Weight(u64::MAX);
|
||||||
|
|
||||||
/// The factor that non-witness serialization data is multiplied by during weight calculation.
|
/// 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).
|
/// The maximum allowed weight for a block, see BIP 141 (network rule).
|
||||||
pub const MAX_BLOCK: Weight = Weight(4_000_000);
|
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);
|
Loading…
Reference in New Issue