From f23250d88c1eb809ca9aed91079cb913d79fb7be Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 17 Jan 2024 15:12:11 +0100 Subject: [PATCH] chore: reuse alloy rpc serde helpers (#6105) --- crates/rpc/rpc-types/src/lib.rs | 4 +- .../rpc-types/src/serde_helpers/json_u256.rs | 228 ------------------ crates/rpc/rpc-types/src/serde_helpers/mod.rs | 34 --- crates/rpc/rpc-types/src/serde_helpers/num.rs | 223 ----------------- .../rpc-types/src/serde_helpers/storage.rs | 102 -------- 5 files changed, 3 insertions(+), 588 deletions(-) delete mode 100644 crates/rpc/rpc-types/src/serde_helpers/json_u256.rs delete mode 100644 crates/rpc/rpc-types/src/serde_helpers/mod.rs delete mode 100644 crates/rpc/rpc-types/src/serde_helpers/num.rs delete mode 100644 crates/rpc/rpc-types/src/serde_helpers/storage.rs diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 77d2367177..5e5b3d76df 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -18,7 +18,9 @@ mod otterscan; mod peer; pub mod relay; mod rpc; -pub mod serde_helpers; + +// re-export for convenience +pub use alloy_rpc_types::serde_helpers; // Ethereum specific rpc types coming from alloy. pub use alloy_rpc_types::*; diff --git a/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs b/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs deleted file mode 100644 index 0eb3687512..0000000000 --- a/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Json U256 serde helpers. - -use alloy_primitives::U256; -use serde::{ - de::{Error, Visitor}, - Deserialize, Deserializer, Serialize, Serializer, -}; -use serde_json::Value; -use std::{fmt, str::FromStr}; - -/// Wrapper around primitive U256 type that also supports deserializing numbers -#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct JsonU256(pub U256); - -impl From for U256 { - fn from(value: JsonU256) -> Self { - value.0 - } -} - -impl From for JsonU256 { - fn from(value: U256) -> Self { - JsonU256(value) - } -} - -impl fmt::Display for JsonU256 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Serialize for JsonU256 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } -} - -impl<'a> Deserialize<'a> for JsonU256 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_any(JsonU256Visitor) - } -} - -/// Visitor pattern for `JsonU256` deserialization. -struct JsonU256Visitor; - -impl<'a> Visitor<'a> for JsonU256Visitor { - type Value = JsonU256; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "a hex encoding or decimal number") - } - - fn visit_u64(self, value: u64) -> Result - where - E: Error, - { - Ok(JsonU256(U256::from(value))) - } - - fn visit_str(self, value: &str) -> Result - where - E: Error, - { - let value = u256_from_str(value)?; - Ok(JsonU256(value)) - } - - fn visit_string(self, value: String) -> Result - where - E: Error, - { - self.visit_str(value.as_ref()) - } -} - -/// Supports parsing `U256` numbers as strings via [JsonU256] -pub fn deserialize_json_u256<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let num = JsonU256::deserialize(deserializer)?; - Ok(num.into()) -} - -/// Supports parsing `U256` numbers as strings via [JsonU256] -pub fn deserialize_json_u256_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let num = Option::::deserialize(deserializer)?; - Ok(num.map(Into::into)) -} - -/// Supports deserializing a [U256] from a [String]. -pub fn u256_from_str(raw: &str) -> Result -where - E: Error, -{ - let value = match raw.len() { - 0 => U256::ZERO, - 2 if raw.starts_with("0x") => U256::ZERO, - _ if raw.starts_with("0x") => U256::from_str(raw) - .map_err(|e| Error::custom(format!("Parsing JsonU256 as hex failed {raw}: {e}")))?, - _ => U256::from_str_radix(raw, 10).map_err(|e| { - Error::custom(format!("Parsing JsonU256 as decimal failed {raw}: {e:?}")) - })?, - }; - - Ok(value) -} - -/// Supports parsing the TTD as an `Option`, or `Option` specifically for the mainnet TTD -/// (5.875e22). -pub fn deserialize_json_ttd_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let value = Option::::deserialize(deserializer)?; - value.map(|value| ttd_from_value::<'de, D>(value)).transpose() -} - -/// Converts the given [serde_json::Value] into a `U256` value for TTD deserialization. -fn ttd_from_value<'de, D>(val: Value) -> Result -where - D: Deserializer<'de>, -{ - let val = match val { - Value::Number(num) => num, - Value::String(raw) => return u256_from_str(&raw), - _ => return Err(Error::custom("TTD must be a number or string")), - }; - - let num = if let Some(val) = val.as_u64() { - U256::from(val) - } else if let Some(value) = val.as_f64() { - // The ethereum mainnet TTD is 58750000000000000000000, and geth serializes this - // without quotes, because that is how golang `big.Int`s marshal in JSON. Numbers - // are arbitrary precision in JSON, so this is valid JSON. This number is also - // greater than a `u64`. - // - // Unfortunately, serde_json only supports parsing up to `u64`, resorting to `f64` - // once `u64` overflows: - // - // - // - // - // serde_json does have an arbitrary precision feature, but this breaks untagged - // enums in serde: - // - // - // - // To solve this, we use the captured float and return the TTD as a U256 if it's equal. - if value == 5.875e22 { - U256::from(58750000000000000000000u128) - } else { - // We could try to convert to a u128 here but there would probably be loss of - // precision, so we just return an error. - return Err(Error::custom("Deserializing a large non-mainnet TTD is not supported")) - } - } else { - // must be i64 - negative numbers are not supported - return Err(Error::custom("Negative TTD values are invalid and will not be deserialized")) - }; - - Ok(num) -} - -#[cfg(test)] -mod tests { - use super::JsonU256; - use alloy_primitives::U256; - use serde::{Deserialize, Serialize}; - - #[test] - fn jsonu256_deserialize() { - let deserialized: Vec = - serde_json::from_str(r#"["","0", "0x","10",10,"0x10"]"#).unwrap(); - assert_eq!( - deserialized, - vec![ - JsonU256(U256::ZERO), - JsonU256(U256::ZERO), - JsonU256(U256::ZERO), - JsonU256(U256::from(10)), - JsonU256(U256::from(10)), - JsonU256(U256::from(16)), - ] - ); - } - - #[test] - fn jsonu256_serialize() { - let data = JsonU256(U256::from(16)); - let serialized = serde_json::to_string(&data).unwrap(); - - assert_eq!(serialized, r#""0x10""#); - } - - #[test] - fn deserialize_ttd() { - #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] - struct Ttd(#[serde(deserialize_with = "super::deserialize_json_ttd_opt")] Option); - - let deserialized: Vec = serde_json::from_str( - r#"["",0,"0","0x0","58750000000000000000000",58750000000000000000000]"#, - ) - .unwrap(); - assert_eq!( - deserialized, - vec![ - Ttd(Some(U256::ZERO)), - Ttd(Some(U256::ZERO)), - Ttd(Some(U256::ZERO)), - Ttd(Some(U256::ZERO)), - Ttd(Some(U256::from(58750000000000000000000u128))), - Ttd(Some(U256::from(58750000000000000000000u128))), - ] - ); - } -} diff --git a/crates/rpc/rpc-types/src/serde_helpers/mod.rs b/crates/rpc/rpc-types/src/serde_helpers/mod.rs deleted file mode 100644 index adeb4a2458..0000000000 --- a/crates/rpc/rpc-types/src/serde_helpers/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Serde helpers for primitive types. - -use alloy_primitives::B256; -use serde::Serializer; - -pub mod json_u256; -pub use json_u256::JsonU256; - -/// Helpers for dealing with numbers. -pub mod num; -pub use num::*; - -/// Storage related helpers. -pub mod storage; -pub use storage::JsonStorageKey; - -/// Serialize a byte vec as a hex string _without_ the "0x" prefix. -/// -/// This behaves the same as [`hex::encode`](alloy_primitives::hex::encode). -pub fn serialize_hex_string_no_prefix(x: T, s: S) -> Result -where - S: Serializer, - T: AsRef<[u8]>, -{ - s.serialize_str(&alloy_primitives::hex::encode(x.as_ref())) -} - -/// Serialize a [B256] as a hex string _without_ the "0x" prefix. -pub fn serialize_b256_hex_string_no_prefix(x: &B256, s: S) -> Result -where - S: Serializer, -{ - s.serialize_str(&format!("{x:x}")) -} diff --git a/crates/rpc/rpc-types/src/serde_helpers/num.rs b/crates/rpc/rpc-types/src/serde_helpers/num.rs deleted file mode 100644 index 4c34471cd7..0000000000 --- a/crates/rpc/rpc-types/src/serde_helpers/num.rs +++ /dev/null @@ -1,223 +0,0 @@ -//! Numeric serde helpers. - -use alloy_primitives::{U256, U64}; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::str::FromStr; - -/// A `u64` wrapper type that deserializes from hex or a u64 and serializes as hex. -/// -/// -/// ```rust -/// use reth_rpc_types::num::U64HexOrNumber; -/// let number_json = "100"; -/// let hex_json = "\"0x64\""; -/// -/// let number: U64HexOrNumber = serde_json::from_str(number_json).unwrap(); -/// let hex: U64HexOrNumber = serde_json::from_str(hex_json).unwrap(); -/// assert_eq!(number, hex); -/// assert_eq!(hex.to(), 100); -/// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] -pub struct U64HexOrNumber(U64); - -impl U64HexOrNumber { - /// Returns the wrapped u64 - pub fn to(self) -> u64 { - self.0.to() - } -} - -impl From for U64HexOrNumber { - fn from(value: u64) -> Self { - Self(U64::from(value)) - } -} - -impl From for U64HexOrNumber { - fn from(value: U64) -> Self { - Self(value) - } -} - -impl From for u64 { - fn from(value: U64HexOrNumber) -> Self { - value.to() - } -} - -impl From for U64 { - fn from(value: U64HexOrNumber) -> Self { - value.0 - } -} - -impl<'de> Deserialize<'de> for U64HexOrNumber { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum NumberOrHexU64 { - Hex(U64), - Int(u64), - } - match NumberOrHexU64::deserialize(deserializer)? { - NumberOrHexU64::Int(val) => Ok(val.into()), - NumberOrHexU64::Hex(val) => Ok(val.into()), - } - } -} - -/// serde functions for handling `u64` as [U64] -pub mod u64_hex { - use alloy_primitives::U64; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Deserializes an `u64` from [U64] accepting a hex quantity string with optional 0x prefix - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - U64::deserialize(deserializer).map(|val| val.to()) - } - - /// Serializes u64 as hex string - pub fn serialize(value: &u64, s: S) -> Result { - U64::from(*value).serialize(s) - } -} - -/// serde functions for handling `Option` as [U64] -pub mod u64_hex_opt { - use alloy_primitives::U64; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Serializes u64 as hex string - pub fn serialize(value: &Option, s: S) -> Result { - match value { - Some(val) => U64::from(*val).serialize(s), - None => s.serialize_none(), - } - } - - /// Deserializes an `Option` from [U64] accepting a hex quantity string with optional 0x prefix - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - Ok(U64::deserialize(deserializer) - .map_or(None, |v| Some(u64::from_be_bytes(v.to_be_bytes())))) - } -} - -/// serde functions for handling primitive `u64` as [U64] -pub mod u64_hex_or_decimal { - use crate::serde_helpers::num::U64HexOrNumber; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or - /// a number - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - U64HexOrNumber::deserialize(deserializer).map(Into::into) - } - - /// Serializes u64 as hex string - pub fn serialize(value: &u64, s: S) -> Result { - U64HexOrNumber::from(*value).serialize(s) - } -} - -/// serde functions for handling primitive optional `u64` as [U64] -pub mod u64_hex_or_decimal_opt { - use crate::serde_helpers::num::U64HexOrNumber; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or - /// a number - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - match Option::::deserialize(deserializer)? { - Some(val) => Ok(Some(val.into())), - None => Ok(None), - } - } - - /// Serializes u64 as hex string - pub fn serialize(value: &Option, s: S) -> Result { - match value { - Some(val) => U64HexOrNumber::from(*val).serialize(s), - None => s.serialize_none(), - } - } -} - -/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the -/// inner value. -pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - match Option::::deserialize(deserializer)? { - Some(val) => val.try_into_u256().map(Some), - None => Ok(None), - } -} - -/// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. -#[derive(Debug, Deserialize)] -#[serde(untagged)] -pub enum NumberOrHexU256 { - /// An integer - Int(serde_json::Number), - /// A hex U256 - Hex(U256), -} - -impl NumberOrHexU256 { - /// Tries to convert this into a [U256]]. - pub fn try_into_u256(self) -> Result { - match self { - NumberOrHexU256::Int(num) => { - U256::from_str(num.to_string().as_str()).map_err(E::custom) - } - NumberOrHexU256::Hex(val) => Ok(val), - } - } -} - -/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with -/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). -pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - NumberOrHexU256::deserialize(deserializer)?.try_into_u256() -} - -#[cfg(test)] -mod tests { - use super::*; - use serde::{Deserialize, Serialize}; - - #[test] - fn test_hex_u64() { - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - struct Value { - #[serde(with = "u64_hex")] - inner: u64, - } - - let val = Value { inner: 1000 }; - let s = serde_json::to_string(&val).unwrap(); - assert_eq!(s, "{\"inner\":\"0x3e8\"}"); - - let deserialized: Value = serde_json::from_str(&s).unwrap(); - assert_eq!(val, deserialized); - } -} diff --git a/crates/rpc/rpc-types/src/serde_helpers/storage.rs b/crates/rpc/rpc-types/src/serde_helpers/storage.rs deleted file mode 100644 index 885274f5f0..0000000000 --- a/crates/rpc/rpc-types/src/serde_helpers/storage.rs +++ /dev/null @@ -1,102 +0,0 @@ -use alloy_primitives::{Bytes, B256, U256}; -use serde::{Deserialize, Deserializer, Serialize}; -use std::{collections::HashMap, fmt::Write}; - -/// A storage key type that can be serialized to and from a hex string up to 32 bytes. Used for -/// `eth_getStorageAt` and `eth_getProof` RPCs. -/// -/// This is a wrapper type meant to mirror geth's serialization and deserialization behavior for -/// storage keys. -/// -/// In `eth_getStorageAt`, this is used for deserialization of the `index` field. Internally, the -/// index is a [B256], but in `eth_getStorageAt` requests, its serialization can be _up to_ 32 -/// bytes. To support this, the storage key is deserialized first as a U256, and converted to a -/// B256 for use internally. -/// -/// `eth_getProof` also takes storage keys up to 32 bytes as input, so the `keys` field is -/// similarly deserialized. However, geth populates the storage proof `key` fields in the response -/// by mirroring the `key` field used in the input. -/// * See how `storageKey`s (the input) are populated in the `StorageResult` (the output): -/// -/// -/// The contained [B256] and From implementation for String are used to preserve the input and -/// implement this behavior from geth. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] -#[serde(from = "U256", into = "String")] -pub struct JsonStorageKey(pub B256); - -impl From for JsonStorageKey { - fn from(value: U256) -> Self { - // SAFETY: Address (B256) and U256 have the same number of bytes - JsonStorageKey(B256::from(value.to_be_bytes())) - } -} - -impl From for String { - fn from(value: JsonStorageKey) -> Self { - // SAFETY: Address (B256) and U256 have the same number of bytes - let uint = U256::from_be_bytes(value.0 .0); - - // serialize byte by byte - // - // this is mainly so we can return an output that hive testing expects, because the - // `eth_getProof` implementation in geth simply mirrors the input - // - // see the use of `hexKey` in the `eth_getProof` response: - // - let bytes = uint.to_be_bytes_trimmed_vec(); - let mut hex = String::with_capacity(2 + bytes.len() * 2); - hex.push_str("0x"); - for byte in bytes { - write!(hex, "{:02x}", byte).unwrap(); - } - hex - } -} - -/// Converts a Bytes value into a B256, accepting inputs that are less than 32 bytes long. These -/// inputs will be left padded with zeros. -pub fn from_bytes_to_b256<'de, D>(bytes: Bytes) -> Result -where - D: Deserializer<'de>, -{ - if bytes.0.len() > 32 { - return Err(serde::de::Error::custom("input too long to be a B256")) - } - - // left pad with zeros to 32 bytes - let mut padded = [0u8; 32]; - padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0); - - // then convert to B256 without a panic - Ok(B256::from_slice(&padded)) -} - -/// Deserializes the input into an Option>, using [from_bytes_to_b256] which -/// allows cropped values: -/// -/// ```json -/// { -/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22" -/// } -/// ``` -pub fn deserialize_storage_map<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let map = Option::>::deserialize(deserializer)?; - match map { - Some(mut map) => { - let mut res_map = HashMap::with_capacity(map.len()); - for (k, v) in map.drain() { - let k_deserialized = from_bytes_to_b256::<'de, D>(k)?; - let v_deserialized = from_bytes_to_b256::<'de, D>(v)?; - res_map.insert(k_deserialized, v_deserialized); - } - Ok(Some(res_map)) - } - None => Ok(None), - } -}