Skip to content

Commit

Permalink
refactor Hash256 serde to account for Sia Go SiaFoundation/core/pull/224
Browse files Browse the repository at this point in the history


Prior to this change Hash256 was represented as "h:<32 byte hex string>"

It is now represented as a plain hex string with no prefix
  • Loading branch information
Alrighttt committed Nov 15, 2024
1 parent 4cee4a1 commit 1224470
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 157 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ nom = "6.1.2"
blake2b_simd = "0.5"
chrono = { version = "0.4.23", "features" = ["serde"] }
log = { version = "0.4.19", "features" = ["std"] }
hex = "0.4.2"
hex = { version = "0.4.2", "features" = ["serde"] }
reqwest = { version = "0.11.9", features = ["json", "rustls-tls"], default-features = false }
base64 = "0.21.2"
url = { version = "2.2.2", features = ["serde"] }
Expand Down
2 changes: 1 addition & 1 deletion src/transport/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl SiaApiRequest for ConsensusUpdatesRequest {
// Create the path_params HashMap to substitute {height} and {hash} in the path schema
let mut path_params = HashMap::new();
path_params.insert("height".to_owned(), self.height.to_string());
path_params.insert("hash".to_owned(), format!("{:02x}", self.block_hash.0));
path_params.insert("hash".to_owned(), format!("{}", self.block_hash.0));

let mut query_params = HashMap::new();
if let Some(limit) = self.limit {
Expand Down
6 changes: 3 additions & 3 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::fmt;
use std::str::FromStr;

mod hash;
pub use hash::{Hash256, ParseHashError};
pub use hash::{Hash256, Hash256Error};

mod signature;
pub use signature::{Signature, SignatureError};
Expand Down Expand Up @@ -184,7 +184,7 @@ impl<'de> Deserialize<'de> for BlockID {
E: serde::de::Error,
{
if let Some(hex_str) = value.strip_prefix("bid:") {
Hash256::from_str_no_prefix(hex_str)
Hash256::from_str(hex_str)
.map(BlockID)
.map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
} else {
Expand All @@ -207,7 +207,7 @@ impl Serialize for BlockID {
}

impl fmt::Display for BlockID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "bid:{:02x}", self.0) }
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
Expand Down
193 changes: 41 additions & 152 deletions src/types/hash.rs
Original file line number Diff line number Diff line change
@@ -1,110 +1,45 @@
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use hex;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
use std::fmt;
use std::fmt::{self, Display};
use std::str::FromStr;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ParseHashError {
#[error("Hash256 invalid prefix: expected 32 byte hex string prefixed with 'h:', found {0}")]
InvalidPrefix(String),
#[error("Hash256 invalid hex: expected 32 byte hex string prefixed with 'h:', found {0}")]
pub enum Hash256Error {
#[error("Hash256::from_str invalid hex: expected 32 byte hex string, found {0}")]
InvalidHex(String),
#[error("Hash256 invalid length: expected 32 byte hex string prefixed with 'h:', found {0}")]
#[error("Hash256::from_str invalid length: expected 32 byte hex string, found {0}")]
InvalidLength(String),
#[error("Hash256 invalid slice length: expected 32 byte slice, found {0} byte slice")]
InvalidSliceLength(usize),
#[error("Hash256::TryFrom<&[u8]> invalid slice length: expected 32 byte slice, found {0:?}")]
InvalidSliceLength(Vec<u8>),
}
#[derive(Clone, Eq, PartialEq)]
pub struct Hash256(pub [u8; 32]);
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
#[serde(transparent)]
pub struct Hash256(#[serde(with = "hex::serde")] pub [u8; 32]);

impl Hash256 {
const fn const_default() -> Hash256 { Hash256([0; 32]) }
impl FromStr for Hash256 {
type Err = Hash256Error;

// Method for parsing a hex string without the "h:" prefix
pub fn from_str_no_prefix(hex_str: &str) -> Result<Self, ParseHashError> {
fn from_str(hex_str: &str) -> Result<Self, Hash256Error> {
if hex_str.len() != 64 {
return Err(ParseHashError::InvalidLength(hex_str.to_string()));
return Err(Hash256Error::InvalidLength(hex_str.to_string()));
}

let mut bytes = [0u8; 32];
match hex::decode_to_slice(hex_str, &mut bytes) {
Ok(_) => Ok(Hash256(bytes)),
Err(_) => Err(ParseHashError::InvalidHex(hex_str.to_string())),
}
}
}

impl Default for Hash256 {
fn default() -> Self { Hash256::const_default() }
}

impl fmt::Display for Hash256 {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "h:{:02x}", self) }
}

impl fmt::Debug for Hash256 {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) }
}

impl fmt::LowerHex for Hash256 {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
for byte in &self.0 {
write!(f, "{:02x}", byte)?;
Err(_) => Err(Hash256Error::InvalidHex(hex_str.to_string())),
}
Ok(())
}
}

impl Serialize for Hash256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

impl<'de> Deserialize<'de> for Hash256 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct H256Visitor;

impl<'de> serde::de::Visitor<'de> for H256Visitor {
type Value = Hash256;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string prefixed with 'h:' and followed by a 32 byte hex string")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Hash256::from_str(value).map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))
}
}

deserializer.deserialize_str(H256Visitor)
}
}

impl FromStr for Hash256 {
type Err = ParseHashError;

fn from_str(value: &str) -> Result<Self, Self::Err> {
if let Some(hex_str) = value.strip_prefix("h:") {
Hash256::from_str_no_prefix(hex_str)
} else {
Err(ParseHashError::InvalidPrefix(value.to_string()))
}
}
impl Display for Hash256 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", hex::encode(&self.0)) }
}

impl TryFrom<&[u8]> for Hash256 {
type Error = ParseHashError;
type Error = Hash256Error;

fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
let slice_len = slice.len();
Expand All @@ -113,7 +48,7 @@ impl TryFrom<&[u8]> for Hash256 {
array.copy_from_slice(slice);
Ok(Hash256(array))
} else {
Err(ParseHashError::InvalidSliceLength(slice_len))
Err(Hash256Error::InvalidSliceLength(slice.to_owned()))
}
}
}
Expand All @@ -125,115 +60,69 @@ mod tests {

cross_target_tests! {
fn test_default() {
let hash = Hash256::from_str("h:0000000000000000000000000000000000000000000000000000000000000000").unwrap();
let hash = Hash256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
assert_eq!(hash, Hash256::default());
}

fn test_valid() {
let hash = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
assert_eq!(hash.to_string(), "h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
assert_eq!(hash.to_string(), "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
}

fn test_display() {
let hash = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
assert_eq!(hash.to_string(), "h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
}

fn test_debug() {
let hash = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
assert_eq!(format!("{:?}", hash), "h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
assert_eq!(hash.to_string(), "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
}

fn test_serialize() {
let hash = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
let serialized = serde_json::to_string(&hash).unwrap();
assert_eq!(&serialized, r#""h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#);
assert_eq!(&serialized, r#""c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#);
}

fn test_deserialize() {
let hash = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
let deserialized: Hash256 = serde_json::from_str(r#""h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#).unwrap();
let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap();
let deserialized: Hash256 = serde_json::from_str(r#""c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#).unwrap();
assert_eq!(deserialized, hash);
}

fn test_deserialize_missing_prefix() {
let err = serde_json::from_str::<Hash256>(r#""c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#).expect_err("no prefix");
assert!(format!("{:?}", err).contains("expected a string prefixed with 'h:' and followed by a 32 byte hex string"));
}

fn test_missing_prefix() {
let test_case = "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
let err = Hash256::from_str(test_case).expect_err("no prefix");
match err {
ParseHashError::InvalidPrefix(ref e) if test_case == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_corrupt_prefix() {
let test_case = ":c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
let err = Hash256::from_str(test_case).expect_err("no prefix");
match err {
ParseHashError::InvalidPrefix(ref e) if test_case == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_wrong_prefix() {
let test_case = "i:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
let err = Hash256::from_str(test_case).expect_err("wrong prefix");
match err {
ParseHashError::InvalidPrefix(ref e) if test_case == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_invalid_hex() {
let err = Hash256::from_str("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeg").expect_err("no prefix");
let err = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeg").expect_err("no prefix");
let expected = "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeg";
match err {
ParseHashError::InvalidHex(ref e) if expected == e => (),
Hash256Error::InvalidHex(ref e) if expected == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_invalid_length() {
let err = Hash256::from_str("h:badc0de").expect_err("invalid length");
let err = Hash256::from_str("badc0de").expect_err("invalid length");
let expected = "badc0de";
match err {
ParseHashError::InvalidLength(ref e) if expected == e => (),
Hash256Error::InvalidLength(ref e) if expected == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_from_str_no_prefix_valid() {
let hash = Hash256::from_str_no_prefix("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
fn test_from_str_valid() {
let hash = Hash256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap();
assert_eq!(hash, Hash256::default())
}

fn test_from_str_no_prefix_invalid_length() {
let err = Hash256::from_str_no_prefix("badc0de").expect_err("invalid length");
fn test_from_str_invalid_length() {
let err = Hash256::from_str("badc0de").expect_err("invalid length");
let expected = "badc0de";
match err {
ParseHashError::InvalidLength(ref e) if expected == e => (),
Hash256Error::InvalidLength(ref e) if expected == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_from_str_no_prefix_invalid_hex() {
let err = Hash256::from_str_no_prefix("g00000000000000000000000000000000000000000000000000000000000000e").expect_err("invalid hex");
fn test_from_str_invalid_hex() {
let err = Hash256::from_str("g00000000000000000000000000000000000000000000000000000000000000e").expect_err("invalid hex");
let expected = "g00000000000000000000000000000000000000000000000000000000000000e";
match err {
ParseHashError::InvalidHex(ref e) if expected == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}

fn test_from_str_no_prefix_invalid_has_prefix() {
let err = Hash256::from_str_no_prefix("h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").expect_err("invalid hex");
let expected = "h:c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
match err {
ParseHashError::InvalidLength(ref e) if expected == e => (),
Hash256Error::InvalidHex(ref e) if expected == e => (),
_ => panic!("unexpected error: {:?}", err),
}
}
Expand Down

0 comments on commit 1224470

Please sign in to comment.