From dca054c680959e3456895f8cfd047662699d1a1a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 2 Apr 2024 09:40:19 +1100 Subject: [PATCH 1/6] test: Add unit tests for hex_u32 Test the current behaviour of `hex_u32` - verifies that we handle parsing strings with and without a prefix. --- units/src/parse.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/units/src/parse.rs b/units/src/parse.rs index 787bbdeb..83ea5980 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -182,3 +182,22 @@ macro_rules! impl_parse_str { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_u32_from_hex_prefixed() { + let want = 171; + let got = hex_u32("0xab").expect("failed to parse prefixed hex"); + assert_eq!(got, want); + } + + #[test] + fn parse_u32_from_hex_no_prefix() { + let want = 171; + let got = hex_u32("ab").expect("failed to parse non-prefixed hex"); + assert_eq!(got, want); + } +} From 1269722770be48f7f78442e39e58db18d4f69d58 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 2 Apr 2024 09:44:35 +1100 Subject: [PATCH 2/6] Move helper function Move the `strip_hex_prefix` helper function to below where it is called. Note that I was the original author of this helper so there is no excuse for it being above - bad Tobin no biscuit. --- units/src/parse.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/units/src/parse.rs b/units/src/parse.rs index 83ea5980..9ace652f 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -88,17 +88,6 @@ pub fn int + Into>(s: S) -> Result &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. @@ -111,6 +100,17 @@ pub fn hex_u32 + Into>(s: S) -> Result }) } +/// 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 + } +} + /// Implements `TryFrom<$from> for $to` using `parse::int`, mapping the output using infallible /// conversion function `fn`. #[macro_export] From cf65bf035f81e4a37f21752cc0640387b291b908 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 2 Apr 2024 09:46:21 +1100 Subject: [PATCH 3/6] Introduce local variable To make the stripping of the prefix a little clearer introduce a local variable. Refactor only, no logic changes. --- units/src/parse.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/units/src/parse.rs b/units/src/parse.rs index 9ace652f..ff5e64bd 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -92,7 +92,8 @@ pub fn int + Into>(s: S) -> Result + Into>(s: S) -> Result { - u32::from_str_radix(strip_hex_prefix(s.as_ref()), 16).map_err(|error| ParseIntError { + let stripped = strip_hex_prefix(s.as_ref()); + u32::from_str_radix(stripped, 16).map_err(|error| ParseIntError { input: s.into(), bits: 32, is_signed: false, From 9705d51782264e8ff5876a5571df3cb469a84991 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 2 Apr 2024 09:48:18 +1100 Subject: [PATCH 4/6] docs: Use backticks on stdlib type --- units/src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/units/src/parse.rs b/units/src/parse.rs index ff5e64bd..52418f6b 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -88,7 +88,7 @@ pub fn int + Into>(s: S) -> Result + Into>(s: S) -> Result { From d33625f6e2d4986c4e8116016a8b4530d374f2c9 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 2 Apr 2024 09:51:40 +1100 Subject: [PATCH 5/6] units: Introduce public hex_u128 function Introduce a function for parsing a `u128` from hex. Support strings with and without a `0x` prefix as we do for `hex_u32`. --- units/src/parse.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/units/src/parse.rs b/units/src/parse.rs index 52418f6b..967113f9 100644 --- a/units/src/parse.rs +++ b/units/src/parse.rs @@ -101,6 +101,19 @@ pub fn hex_u32 + Into>(s: S) -> Result }) } +/// Parses a `u128` from a hex string. +/// +/// Input string may or may not contain a `0x` prefix. +pub fn hex_u128 + Into>(s: S) -> Result { + let stripped = strip_hex_prefix(s.as_ref()); + u128::from_str_radix(stripped, 16).map_err(|error| ParseIntError { + input: s.into(), + bits: 128, + is_signed: false, + 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") { @@ -201,4 +214,18 @@ mod tests { let got = hex_u32("ab").expect("failed to parse non-prefixed hex"); assert_eq!(got, want); } + + #[test] + fn parse_u128_from_hex_prefixed() { + let want = 3735928559; + let got = hex_u128("0xdeadbeef").expect("failed to parse prefixed hex"); + assert_eq!(got, want); + } + + #[test] + fn parse_u128_from_hex_no_prefix() { + let want = 3735928559; + let got = hex_u128("deadbeef").expect("failed to parse non-prefixed hex"); + assert_eq!(got, want); + } } From f019e24f1f4c58f85177d5d77647dcbf23941b02 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 28 Feb 2024 15:32:48 +1100 Subject: [PATCH 6/6] Add hex parsing to pow types The `pow` types implement `fmt::LowerHex` but do not implement hex parsing. Add inherent methods `from_hex` and `from_prefixed_hex` to the `pow` types. --- bitcoin/src/pow.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index 4fac62f3..cfb3f342 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -18,12 +18,22 @@ use units::parse; use crate::blockdata::block::BlockHash; use crate::consensus::encode::{self, Decodable, Encodable}; use crate::consensus::Params; -use crate::error::{ContainsPrefixError, MissingPrefixError, PrefixedHexError, UnprefixedHexError}; +use crate::error::{ContainsPrefixError, MissingPrefixError, ParseIntError, PrefixedHexError, UnprefixedHexError}; /// Implement traits and methods shared by `Target` and `Work`. macro_rules! do_impl { ($ty:ident) => { impl $ty { + /// Creates `Self` from a prefixed hex string. + pub fn from_hex(s: &str) -> Result { + Ok($ty(U256::from_hex(s)?)) + } + + /// Creates `Self` from an unprefixed hex string. + pub fn from_unprefixed_hex(s: &str) -> Result { + Ok($ty(U256::from_unprefixed_hex(s)?)) + } + /// Creates `Self` from a big-endian byte array. #[inline] pub fn from_be_bytes(bytes: [u8; 32]) -> $ty { $ty(U256::from_be_bytes(bytes)) } @@ -390,6 +400,43 @@ impl U256 { const ONE: U256 = U256(0, 1); + /// Creates a `U256` from an prefixed hex string. + fn from_hex(s: &str) -> Result { + 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()); + }; + Ok(U256::from_hex_internal(stripped)?) + } + + /// Creates a `CompactTarget` from an unprefixed hex string. + fn from_unprefixed_hex(s: &str) -> Result { + if s.starts_with("0x") || s.starts_with("0X") { + return Err(ContainsPrefixError::new(s).into()); + } + Ok(U256::from_hex_internal(s)?) + } + + fn from_hex_internal(s: &str) -> Result { + let (high, low) = if s.len() < 32 { + let low = parse::hex_u128(s)?; + (0, low) + } else { + let high_len = s.len() - 32; + let high_s = &s[..high_len]; + let low_s = &s[high_len..]; + + let high = parse::hex_u128(high_s)?; + let low = parse::hex_u128(low_s)?; + (high, low) + }; + + Ok(U256(high, low)) + } + /// Creates [`U256`] from a big-endian array of `u8`s. #[cfg_attr(all(test, mutate), mutate)] fn from_be_bytes(a: [u8; 32]) -> U256 { @@ -1507,6 +1554,28 @@ mod tests { ); } + #[test] + fn u256_to_from_hex_roundtrips() { + let val = U256( + 0xDEAD_BEEA_A69B_455C_D41B_B662_A69B_4550, + 0xA69B_455C_D41B_B662_A69B_4555_DEAD_BEEF, + ); + let hex = format!("0x{:x}", val); + let got = U256::from_hex(&hex).expect("failed to parse hex"); + assert_eq!(got, val); + } + + #[test] + fn u256_to_from_unprefixed_hex_roundtrips() { + let val = U256( + 0xDEAD_BEEA_A69B_455C_D41B_B662_A69B_4550, + 0xA69B_455C_D41B_B662_A69B_4555_DEAD_BEEF, + ); + let hex = format!("{:x}", val); + let got = U256::from_unprefixed_hex(&hex).expect("failed to parse hex"); + assert_eq!(got, val); + } + #[cfg(feature = "serde")] #[test] fn u256_serde() {