Remove the FromHexStr trait

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.
This commit is contained in:
Tobin C. Harding 2024-02-21 15:11:48 +11:00
parent 026537807f
commit 4d762cb08c
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
6 changed files with 65 additions and 146 deletions

View File

@ -18,9 +18,8 @@ use mutagen::mutate;
use crate::absolute; use crate::absolute;
use crate::consensus::encode::{self, Decodable, Encodable}; use crate::consensus::encode::{self, Decodable, Encodable};
use crate::error::ParseIntError; use crate::error::ParseIntError;
use crate::parse::{impl_parse_str, impl_parse_str_from_int_infallible}; use crate::parse::{self, impl_parse_str, impl_parse_str_from_int_infallible};
use crate::prelude::*; 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]). /// The Threshold for deciding whether a lock time value is a height or a time (see [Bitcoin Core]).
/// ///
@ -102,6 +101,14 @@ impl LockTime {
/// The number of bytes that the locktime contributes to the size of a transaction. /// The number of bytes that the locktime contributes to the size of a transaction.
pub const SIZE: usize = 4; // Serialized length of a u32. pub const SIZE: usize = 4; // Serialized length of a u32.
/// Creates a `LockTime` 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, ParseIntError> {
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. /// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY.
/// ///
/// # Examples /// # Examples
@ -325,16 +332,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 lock_time = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
}
impl Encodable for LockTime { impl Encodable for LockTime {
#[inline] #[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> { fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
@ -419,6 +416,11 @@ impl Height {
/// The maximum absolute block height. /// The maximum absolute block height.
pub const MAX: Self = Height(LOCK_TIME_THRESHOLD - 1); 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. /// Constructs a new block height.
/// ///
/// # Errors /// # Errors
@ -460,15 +462,6 @@ impl fmt::Display for Height {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } 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)); impl_parse_str!(Height, ParseHeightError, parser(Height::from_consensus));
/// Error returned when parsing block height fails. /// Error returned when parsing block height fails.
@ -508,6 +501,11 @@ impl Time {
/// The maximum absolute block time (Sun Feb 07 2106 06:28:15 GMT+0000). /// The maximum absolute block time (Sun Feb 07 2106 06:28:15 GMT+0000).
pub const MAX: Self = Time(u32::max_value()); 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. /// Constructs a new block time.
/// ///
/// # Errors /// # Errors
@ -549,15 +547,6 @@ impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } 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)); impl_parse_str!(Time, ParseTimeError, parser(Time::from_consensus));
/// Error returned when parsing block time fails. /// Error returned when parsing block time fails.
@ -599,7 +588,7 @@ where
S: AsRef<str> + Into<String>, S: AsRef<str> + Into<String>,
F: FnOnce(u32) -> Result<T, ConversionError>, 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))?; let n = u32::try_from(n).map_err(|_| ParseError::Conversion(n))?;
f(n).map_err(ParseError::from).map_err(Into::into) f(n).map_err(ParseError::from).map_err(Into::into)
} }
@ -821,61 +810,61 @@ mod tests {
#[test] #[test]
fn time_from_str_hex_happy_path() { 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(); let expected = Time::from_consensus(0x6289C350).unwrap();
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn time_from_str_hex_no_prefix_happy_path() { 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)); assert_eq!(time, Time(0x6289C350));
} }
#[test] #[test]
fn time_from_str_hex_invalid_hex_should_err() { fn time_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93"; let hex = "0xzb93";
let result = Time::from_hex_str(hex); let result = Time::from_hex(hex);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn lock_time_from_str_hex_happy_path() { fn lock_time_from_str_hex_happy_path() {
let actual = LockTime::from_hex_str("0xBA70D").unwrap(); let actual = LockTime::from_hex("0xBA70D").unwrap();
let expected = LockTime::from_consensus(0xBA70D); let expected = LockTime::from_consensus(0xBA70D);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn lock_time_from_str_hex_no_prefix_happy_path() { fn lock_time_from_str_hex_no_prefix_happy_path() {
let lock_time = LockTime::from_hex_str_no_prefix("BA70D").unwrap(); let lock_time = LockTime::from_hex("BA70D").unwrap();
assert_eq!(lock_time, LockTime::from_consensus(0xBA70D)); assert_eq!(lock_time, LockTime::from_consensus(0xBA70D));
} }
#[test] #[test]
fn lock_time_from_str_hex_invalid_hex_should_ergr() { fn lock_time_from_str_hex_invalid_hex_should_ergr() {
let hex = "0xzb93"; let hex = "0xzb93";
let result = LockTime::from_hex_str(hex); let result = LockTime::from_hex(hex);
assert!(result.is_err()); assert!(result.is_err());
} }
#[test] #[test]
fn height_from_str_hex_happy_path() { 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); let expected = Height(0xBA70D);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn height_from_str_hex_no_prefix_happy_path() { 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)); assert_eq!(height, Height(0xBA70D));
} }
#[test] #[test]
fn height_from_str_hex_invalid_hex_should_err() { fn height_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93"; let hex = "0xzb93";
let result = Height::from_hex_str(hex); let result = Height::from_hex(hex);
assert!(result.is_err()); assert!(result.is_err());
} }

View File

@ -25,10 +25,9 @@ use crate::blockdata::witness::Witness;
use crate::blockdata::FeeRate; use crate::blockdata::FeeRate;
use crate::consensus::{encode, Decodable, Encodable}; use crate::consensus::{encode, Decodable, Encodable};
use crate::internal_macros::{impl_consensus_encoding, impl_hashencode}; 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, ParseIntError};
#[cfg(doc)] #[cfg(doc)]
use crate::sighash::{EcdsaSighashType, TapSighashType}; use crate::sighash::{EcdsaSighashType, TapSighashType};
use crate::string::FromHexStr;
use crate::prelude::*; use crate::prelude::*;
use crate::{Amount, SignedAmount, VarInt}; use crate::{Amount, SignedAmount, VarInt};
@ -402,6 +401,14 @@ impl Sequence {
self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0) self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0)
} }
/// Creates a `Sequence` number from a hex string.
///
/// The input string may or may not contain a typical hex prefix e.g., `0x`.
pub fn from_hex(s: &str) -> Result<Self, ParseIntError> {
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
/// Creates a relative lock-time using block height. /// Creates a relative lock-time using block height.
#[inline] #[inline]
pub fn from_height(height: u16) -> Self { Sequence(u32::from(height)) } pub fn from_height(height: u16) -> Self { Sequence(u32::from(height)) }
@ -473,15 +480,6 @@ impl Sequence {
fn low_u16(&self) -> u16 { self.0 as u16 } 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 { impl Default for Sequence {
/// The default value of sequence is 0xffffffff. /// The default value of sequence is 0xffffffff.
fn default() -> Self { Sequence::MAX } fn default() -> Self { Sequence::MAX }
@ -2120,20 +2118,20 @@ mod tests {
#[test] #[test]
fn sequence_from_str_hex_happy_path() { fn sequence_from_str_hex_happy_path() {
let sequence = Sequence::from_hex_str("0xFFFFFFFF").unwrap(); let sequence = Sequence::from_hex("0xFFFFFFFF").unwrap();
assert_eq!(sequence, Sequence::MAX); assert_eq!(sequence, Sequence::MAX);
} }
#[test] #[test]
fn sequence_from_str_hex_no_prefix_happy_path() { fn sequence_from_str_hex_no_prefix_happy_path() {
let sequence = Sequence::from_hex_str_no_prefix("FFFFFFFF").unwrap(); let sequence = Sequence::from_hex("FFFFFFFF").unwrap();
assert_eq!(sequence, Sequence::MAX); assert_eq!(sequence, Sequence::MAX);
} }
#[test] #[test]
fn sequence_from_str_hex_invalid_hex_should_err() { fn sequence_from_str_hex_invalid_hex_should_err() {
let hex = "0xzb93"; let hex = "0xzb93";
let result = Sequence::from_hex_str(hex); let result = Sequence::from_hex(hex);
assert!(result.is_err()); assert!(result.is_err());
} }

View File

@ -108,7 +108,6 @@ pub mod policy;
pub mod pow; pub mod pow;
pub mod psbt; pub mod psbt;
pub mod sign_message; pub mod sign_message;
pub mod string;
pub mod taproot; pub mod taproot;
#[rustfmt::skip] // Keep public re-exports separate. #[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> { 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(), input: s.into(),
bits: u8::try_from(core::mem::size_of::<u32>() * 8).expect("max is 32 bits for u32"), 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(), is_signed: u32::try_from(-1i8).is_ok(),

View File

@ -17,8 +17,7 @@ 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::string::FromHexStr; use crate::parse::{self, ParseIntError};
use crate::prelude::*;
use crate::Network; use crate::Network;
/// Implement traits and methods shared by `Target` and `Work`. /// Implement traits and methods shared by `Target` and `Work`.
@ -272,6 +271,14 @@ do_impl!(Target);
pub struct CompactTarget(u32); pub struct CompactTarget(u32);
impl CompactTarget { impl CompactTarget {
/// Creates a [`CompactTarget`] 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, ParseIntError> {
let lock_time = parse::hex_u32(s)?;
Ok(Self::from_consensus(lock_time))
}
/// Creates a [`CompactTarget`] from a consensus encoded `u32`. /// Creates a [`CompactTarget`] from a consensus encoded `u32`.
pub fn from_consensus(bits: u32) -> Self { Self(bits) } pub fn from_consensus(bits: u32) -> Self { Self(bits) }
@ -283,15 +290,6 @@ impl From<CompactTarget> for Target {
fn from(c: CompactTarget) -> Self { Target::from_compact(c) } 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 { impl Encodable for CompactTarget {
#[inline] #[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> { fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
@ -1525,14 +1523,14 @@ mod tests {
#[test] #[test]
fn compact_target_from_hex_str_happy_path() { fn compact_target_from_hex_str_happy_path() {
let actual = CompactTarget::from_hex_str("0x01003456").unwrap(); let actual = CompactTarget::from_hex("0x01003456").unwrap();
let expected = CompactTarget(0x01003456); let expected = CompactTarget(0x01003456);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn compact_target_from_hex_str_no_prefix_happy_path() { fn compact_target_from_hex_str_no_prefix_happy_path() {
let actual = CompactTarget::from_hex_str_no_prefix("01003456").unwrap(); let actual = CompactTarget::from_hex("01003456").unwrap();
let expected = CompactTarget(0x01003456); let expected = CompactTarget(0x01003456);
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
@ -1540,7 +1538,7 @@ mod tests {
#[test] #[test]
fn compact_target_from_hex_invalid_hex_should_err() { fn compact_target_from_hex_invalid_hex_should_err() {
let hex = "0xzbf9"; let hex = "0xzbf9";
let result = CompactTarget::from_hex_str(hex); let result = CompactTarget::from_hex(hex);
assert!(result.is_err()); 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,
}
}
}