Merge rust-bitcoin/rust-bitcoin#2492: Remove the FromHexStr trait

b873a3cd44 Do infallible int from hex conversions (Tobin C. Harding)
4d762cb08c Remove the FromHexStr trait (Tobin C. Harding)
026537807f Remove mention of packed (Tobin C. Harding)

Pull request description:

  The `FromHexStr` trait is used to parse integer-like types, however we can achieve the same using inherent methods.

  Move the hex parsing functionality to inherent methods, keeping the same behaviour in regard to the `0x` prefix.

  Patch 1 is trivial preparatory cleanup.

ACKs for top commit:
  apoelstra:
    ACK b873a3cd44
  sanket1729:
    ACK b873a3cd44

Tree-SHA512: a280169b68304fcc1a531cc9ffb6914b70238efc4c2241a766105053911a373a0334b73e5ea3525c331ccb81ce98c43fea96dae77668804e608376a48d5ed8ac
This commit is contained in:
Andrew Poelstra 2024-03-07 14:31:09 +00:00
commit ea6aa99ae4
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
7 changed files with 296 additions and 173 deletions

View File

@ -17,10 +17,9 @@ use mutagen::mutate;
#[cfg(doc)]
use crate::absolute;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::error::ParseIntError;
use crate::parse::{impl_parse_str, impl_parse_str_from_int_infallible};
use crate::error::{ParseIntError, PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
use crate::parse::{self, impl_parse_str, impl_parse_str_from_int_infallible};
use crate::prelude::*;
use crate::string::FromHexStr;
/// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
///
@ -102,6 +101,29 @@ impl LockTime {
/// The number of bytes that the locktime contributes to the size of a transaction.
pub const SIZE: usize = 4; // Serialized length of a u32.
/// Creates a `LockTime` from an prefixed hex string.
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let stripped = if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else if let Some(stripped) = s.strip_prefix("0X") {
stripped
} else {
return Err(MissingPrefixError::new(s).into());
};
let lock_time = parse::hex_u32(stripped)?;
Ok(Self::from_consensus(lock_time))
}
/// Creates a `LockTime` from an unprefixed hex string.
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
if s.starts_with("0x") || s.starts_with("0X") {
return Err(ContainsPrefixError::new(s).into());
}
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
/// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY.
///
/// # Examples
@ -325,16 +347,6 @@ impl fmt::Display for LockTime {
}
}
impl FromHexStr for LockTime {
type Error = ParseIntError;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let packed_lock_time = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(packed_lock_time))
}
}
impl Encodable for LockTime {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
@ -419,6 +431,11 @@ impl Height {
/// 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
@ -460,15 +477,6 @@ impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
impl FromHexStr for Height {
type Error = ParseHeightError;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
parse_hex(s, Self::from_consensus)
}
}
impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus));
/// Error returned when parsing block height fails.
@ -508,6 +516,11 @@ impl Time {
/// 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
@ -549,15 +562,6 @@ impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
impl FromHexStr for Time {
type Error = ParseTimeError;
#[inline]
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
parse_hex(s, Self::from_consensus)
}
}
impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus));
/// Error returned when parsing block time fails.
@ -599,7 +603,7 @@ where
S: AsRef<str> + Into<String>,
F: FnOnce(u32) -> Result<T, ConversionError>,
{
let n = i64::from_str_radix(s.as_ref(), 16).map_err(ParseError::invalid_int(s))?;
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)
}
@ -819,63 +823,74 @@ mod tests {
assert_eq!(got, "block-height 741521");
}
#[test]
fn lock_time_from_hex_lower() {
let lock = LockTime::from_hex("0x6289c350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_hex_upper() {
let lock = LockTime::from_hex("0X6289C350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_unprefixed_hex_lower() {
let lock = LockTime::from_unprefixed_hex("6289c350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_unprefixed_hex_upper() {
let lock = LockTime::from_unprefixed_hex("6289C350").unwrap();
assert_eq!(lock, LockTime::from_consensus(0x6289C350));
}
#[test]
fn lock_time_from_invalid_hex_should_err() {
let hex = "0xzb93";
let result = LockTime::from_hex(hex);
assert!(result.is_err());
}
#[test]
fn time_from_str_hex_happy_path() {
let actual = Time::from_hex_str("0x6289C350").unwrap();
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_str_no_prefix("6289C350").unwrap();
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_str(hex);
assert!(result.is_err());
}
#[test]
fn packed_lock_time_from_str_hex_happy_path() {
let actual = LockTime::from_hex_str("0xBA70D").unwrap();
let expected = LockTime::from_consensus(0xBA70D);
assert_eq!(actual, expected);
}
#[test]
fn packed_lock_time_from_str_hex_no_prefix_happy_path() {
let lock_time = LockTime::from_hex_str_no_prefix("BA70D").unwrap();
assert_eq!(lock_time, LockTime::from_consensus(0xBA70D));
}
#[test]
fn packed_lock_time_from_str_hex_invalid_hex_should_ergr() {
let hex = "0xzb93";
let result = LockTime::from_hex_str(hex);
let result = Time::from_hex(hex);
assert!(result.is_err());
}
#[test]
fn height_from_str_hex_happy_path() {
let actual = Height::from_hex_str("0xBA70D").unwrap();
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_str_no_prefix("BA70D").unwrap();
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_str(hex);
let result = Height::from_hex(hex);
assert!(result.is_err());
}

View File

@ -24,11 +24,11 @@ use crate::blockdata::script::{Script, ScriptBuf};
use crate::blockdata::witness::Witness;
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::impl_parse_str_from_int_infallible;
use crate::parse::{self, impl_parse_str_from_int_infallible};
#[cfg(doc)]
use crate::sighash::{EcdsaSighashType, TapSighashType};
use crate::string::FromHexStr;
use crate::prelude::*;
use crate::{Amount, SignedAmount, VarInt};
@ -402,6 +402,29 @@ impl Sequence {
self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0)
}
/// Creates a `Sequence` from an prefixed hex string.
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let stripped = if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else if let Some(stripped) = s.strip_prefix("0X") {
stripped
} else {
return Err(MissingPrefixError::new(s).into());
};
let sequence = parse::hex_u32(stripped)?;
Ok(Self::from_consensus(sequence))
}
/// Creates a `Sequence` from an unprefixed hex string.
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
if s.starts_with("0x") || s.starts_with("0X") {
return Err(ContainsPrefixError::new(s).into());
}
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
/// Creates a relative lock-time using block height.
#[inline]
pub fn from_height(height: u16) -> Self { Sequence(u32::from(height)) }
@ -473,15 +496,6 @@ impl Sequence {
fn low_u16(&self) -> u16 { self.0 as u16 }
}
impl FromHexStr for Sequence {
type Error = crate::parse::ParseIntError;
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let sequence = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(sequence))
}
}
impl Default for Sequence {
/// The default value of sequence is 0xffffffff.
fn default() -> Self { Sequence::MAX }
@ -2119,21 +2133,33 @@ mod tests {
}
#[test]
fn sequence_from_str_hex_happy_path() {
let sequence = Sequence::from_hex_str("0xFFFFFFFF").unwrap();
fn sequence_from_hex_lower() {
let sequence = Sequence::from_hex("0xffffffff").unwrap();
assert_eq!(sequence, Sequence::MAX);
}
#[test]
fn sequence_from_str_hex_no_prefix_happy_path() {
let sequence = Sequence::from_hex_str_no_prefix("FFFFFFFF").unwrap();
fn sequence_from_hex_upper() {
let sequence = Sequence::from_hex("0XFFFFFFFF").unwrap();
assert_eq!(sequence, Sequence::MAX);
}
#[test]
fn sequence_from_unprefixed_hex_lower() {
let sequence = Sequence::from_unprefixed_hex("ffffffff").unwrap();
assert_eq!(sequence, Sequence::MAX);
}
#[test]
fn sequence_from_unprefixed_hex_upper() {
let sequence = Sequence::from_unprefixed_hex("FFFFFFFF").unwrap();
assert_eq!(sequence, Sequence::MAX);
}
#[test]
fn sequence_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93";
let result = Sequence::from_hex_str(hex);
let result = Sequence::from_hex(hex);
assert!(result.is_err());
}

View File

@ -2,6 +2,131 @@
//! Contains error types and other error handling tools.
use core::fmt;
use internals::write_err;
use crate::prelude::*;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use crate::parse::ParseIntError;
/// Error returned when parsing integer from an supposedly prefixed hex string for
/// a type that can be created infallibly from an integer.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum PrefixedHexError {
/// Hex string is missing prefix.
MissingPrefix(MissingPrefixError),
/// Error parsing integer from hex string.
ParseInt(ParseIntError),
}
impl fmt::Display for PrefixedHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use PrefixedHexError::*;
match *self {
MissingPrefix(ref e) => write_err!(f, "hex string is missing prefix"; e),
ParseInt(ref e) => write_err!(f, "prefixed hex string invalid int"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PrefixedHexError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use PrefixedHexError::*;
match *self {
MissingPrefix(ref e) => Some(e),
ParseInt(ref e) => Some(e),
}
}
}
impl From<MissingPrefixError> for PrefixedHexError {
fn from(e: MissingPrefixError) -> Self { Self::MissingPrefix(e) }
}
impl From<ParseIntError> for PrefixedHexError {
fn from(e: ParseIntError) -> Self { Self::ParseInt(e) }
}
/// Error returned when parsing integer from an supposedly un-prefixed hex string.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum UnprefixedHexError {
/// Hex string contains prefix.
ContainsPrefix(ContainsPrefixError),
/// Error parsing integer from string.
ParseInt(ParseIntError),
}
impl fmt::Display for UnprefixedHexError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use UnprefixedHexError::*;
match *self {
ContainsPrefix(ref e) => write_err!(f, "hex string is contains prefix"; e),
ParseInt(ref e) => write_err!(f, "hex string parse int"; e),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnprefixedHexError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use UnprefixedHexError::*;
match *self {
ContainsPrefix(ref e) => Some(e),
ParseInt(ref e) => Some(e),
}
}
}
impl From<ContainsPrefixError> for UnprefixedHexError {
fn from(e: ContainsPrefixError) -> Self { Self::ContainsPrefix(e) }
}
impl From<ParseIntError> for UnprefixedHexError {
fn from(e: ParseIntError) -> Self { Self::ParseInt(e) }
}
/// Error when hex string is missing a prefix (e.g. 0x).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MissingPrefixError {
hex: String,
}
impl MissingPrefixError {
pub(crate) fn new(s: &str) -> Self { Self { hex: s.into() } }
}
impl fmt::Display for MissingPrefixError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hex string is missing a prefix (e.g. 0x): {}", self.hex)
}
}
#[cfg(feature = "std")]
impl std::error::Error for MissingPrefixError {}
/// Error when hex string contains a prefix (e.g. 0x).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ContainsPrefixError {
hex: String,
}
impl ContainsPrefixError {
pub(crate) fn new(s: &str) -> Self { Self { hex: s.into() } }
}
impl fmt::Display for ContainsPrefixError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hex string contains a prefix: {}", self.hex)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ContainsPrefixError {}

View File

@ -111,7 +111,6 @@ pub mod policy;
pub mod pow;
pub mod psbt;
pub mod sign_message;
pub mod string;
pub mod taproot;
#[rustfmt::skip] // Keep public re-exports separate.

View File

@ -90,8 +90,19 @@ pub(crate) fn int<T: Integer, S: AsRef<str> + Into<String>>(s: S) -> Result<T, P
})
}
/// 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(s.as_ref(), 16).map_err(|error| 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(),

View File

@ -17,9 +17,8 @@ use crate::blockdata::block::BlockHash;
use crate::consensus::encode::{self, Decodable, Encodable};
#[cfg(doc)]
use crate::consensus::Params;
use crate::string::FromHexStr;
use crate::prelude::*;
use crate::Network;
use crate::error::{PrefixedHexError, UnprefixedHexError, ContainsPrefixError, MissingPrefixError};
use crate::{parse, Network};
/// Implement traits and methods shared by `Target` and `Work`.
macro_rules! do_impl {
@ -272,6 +271,29 @@ do_impl!(Target);
pub struct CompactTarget(u32);
impl CompactTarget {
/// Creates a `CompactTarget` from an prefixed hex string.
pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
let stripped = if let Some(stripped) = s.strip_prefix("0x") {
stripped
} else if let Some(stripped) = s.strip_prefix("0X") {
stripped
} else {
return Err(MissingPrefixError::new(s).into());
};
let target = parse::hex_u32(stripped)?;
Ok(Self::from_consensus(target))
}
/// Creates a `CompactTarget` from an unprefixed hex string.
pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
if s.starts_with("0x") || s.starts_with("0X") {
return Err(ContainsPrefixError::new(s).into());
}
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
/// Creates a [`CompactTarget`] from a consensus encoded `u32`.
pub fn from_consensus(bits: u32) -> Self { Self(bits) }
@ -283,15 +305,6 @@ impl From<CompactTarget> for Target {
fn from(c: CompactTarget) -> Self { Target::from_compact(c) }
}
impl FromHexStr for CompactTarget {
type Error = crate::parse::ParseIntError;
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let compact_target = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(compact_target))
}
}
impl Encodable for CompactTarget {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
@ -1524,23 +1537,33 @@ mod tests {
}
#[test]
fn compact_target_from_hex_str_happy_path() {
let actual = CompactTarget::from_hex_str("0x01003456").unwrap();
let expected = CompactTarget(0x01003456);
assert_eq!(actual, expected);
fn compact_target_from_hex_lower() {
let target = CompactTarget::from_hex("0x010034ab").unwrap();
assert_eq!(target, CompactTarget(0x010034ab));
}
#[test]
fn compact_target_from_hex_str_no_prefix_happy_path() {
let actual = CompactTarget::from_hex_str_no_prefix("01003456").unwrap();
let expected = CompactTarget(0x01003456);
assert_eq!(actual, expected);
fn compact_target_from_hex_upper() {
let target = CompactTarget::from_hex("0X010034AB").unwrap();
assert_eq!(target, CompactTarget(0x010034ab));
}
#[test]
fn compact_target_from_unprefixed_hex_lower() {
let target = CompactTarget::from_unprefixed_hex("010034ab").unwrap();
assert_eq!(target, CompactTarget(0x010034ab));
}
#[test]
fn compact_target_from_unprefixed_hex_upper() {
let target = CompactTarget::from_unprefixed_hex("010034AB").unwrap();
assert_eq!(target, CompactTarget(0x010034ab));
}
#[test]
fn compact_target_from_hex_invalid_hex_should_err() {
let hex = "0xzbf9";
let result = CompactTarget::from_hex_str(hex);
let result = CompactTarget::from_hex(hex);
assert!(result.is_err());
}

View File

@ -1,76 +0,0 @@
// SPDX-License-Identifier: CC0-1.0
//! Bitcoin string parsing utilities.
//!
//! This module provides utility types and traits
//! to support handling and parsing strings within `rust-bitcoin`.
use core::fmt;
use internals::write_err;
use crate::prelude::*;
/// Trait that allows types to be initialized from hex strings
pub trait FromHexStr: Sized {
/// An error occurred while parsing the hex string.
type Error;
/// Parses provided string as hex requiring 0x prefix.
///
/// This is intended for user-supplied inputs or already-existing protocols in which 0x prefix is used.
fn from_hex_str<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, FromHexError<Self::Error>> {
if !s.as_ref().starts_with("0x") {
Err(FromHexError::MissingPrefix(s.into()))
} else {
Ok(Self::from_hex_str_no_prefix(s.as_ref().trim_start_matches("0x"))?)
}
}
/// Parses provided string as hex without requiring 0x prefix.
///
/// This is **not** recommended for user-supplied inputs because of possible confusion with decimals.
/// It should be only used for existing protocols which always encode values as hex without 0x prefix.
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error>;
}
/// Hex parsing error
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum FromHexError<E> {
/// The input was not a valid hex string, contains the error that occurred while parsing.
ParseHex(E),
/// The input is missing `0x` prefix, contains the invalid input.
MissingPrefix(String),
}
impl<E> From<E> for FromHexError<E> {
fn from(e: E) -> Self { FromHexError::ParseHex(e) }
}
impl<E: fmt::Display> fmt::Display for FromHexError<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use FromHexError::*;
match *self {
ParseHex(ref e) => write_err!(f, "failed to parse hex string"; e),
MissingPrefix(ref value) =>
write_err!(f, "the input value `{}` is missing the `0x` prefix", value; self),
}
}
}
#[cfg(feature = "std")]
impl<E> std::error::Error for FromHexError<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use FromHexError::*;
match *self {
ParseHex(ref e) => Some(e),
MissingPrefix(_) => None,
}
}
}