chore: reuse alloy rpc serde helpers (#6105)

This commit is contained in:
Matthias Seitz
2024-01-17 15:12:11 +01:00
committed by GitHub
parent 5637eed19e
commit f23250d88c
5 changed files with 3 additions and 588 deletions

View File

@@ -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::*;

View File

@@ -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<JsonU256> for U256 {
fn from(value: JsonU256) -> Self {
value.0
}
}
impl From<U256> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'a> Deserialize<'a> for JsonU256 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(JsonU256(U256::from(value)))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
let value = u256_from_str(value)?;
Ok(JsonU256(value))
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
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<U256, D::Error>
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<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let num = Option::<JsonU256>::deserialize(deserializer)?;
Ok(num.map(Into::into))
}
/// Supports deserializing a [U256] from a [String].
pub fn u256_from_str<E>(raw: &str) -> Result<U256, E>
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<u64>`, or `Option<f64>` specifically for the mainnet TTD
/// (5.875e22).
pub fn deserialize_json_ttd_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<Value>::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<U256, D::Error>
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:
// <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L1411-L1415>
// <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L479-L484>
// <https://github.com/serde-rs/json/blob/4bc1eaa03a6160593575bc9bc60c94dba4cab1e3/src/de.rs#L102-L108>
//
// serde_json does have an arbitrary precision feature, but this breaks untagged
// enums in serde:
// <https://github.com/serde-rs/serde/issues/2230>
// <https://github.com/serde-rs/serde/issues/1183>
//
// 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<JsonU256> =
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<U256>);
let deserialized: Vec<Ttd> = 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))),
]
);
}
}

View File

@@ -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<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
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<S>(x: &B256, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&format!("{x:x}"))
}

View File

@@ -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<u64> for U64HexOrNumber {
fn from(value: u64) -> Self {
Self(U64::from(value))
}
}
impl From<U64> for U64HexOrNumber {
fn from(value: U64) -> Self {
Self(value)
}
}
impl From<U64HexOrNumber> for u64 {
fn from(value: U64HexOrNumber) -> Self {
value.to()
}
}
impl From<U64HexOrNumber> for U64 {
fn from(value: U64HexOrNumber) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for U64HexOrNumber {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<u64, D::Error>
where
D: Deserializer<'de>,
{
U64::deserialize(deserializer).map(|val| val.to())
}
/// Serializes u64 as hex string
pub fn serialize<S: Serializer>(value: &u64, s: S) -> Result<S::Ok, S::Error> {
U64::from(*value).serialize(s)
}
}
/// serde functions for handling `Option<u64>` 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<S: Serializer>(value: &Option<u64>, s: S) -> Result<S::Ok, S::Error> {
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<Option<u64>, 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<u64, D::Error>
where
D: Deserializer<'de>,
{
U64HexOrNumber::deserialize(deserializer).map(Into::into)
}
/// Serializes u64 as hex string
pub fn serialize<S: Serializer>(value: &u64, s: S) -> Result<S::Ok, S::Error> {
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<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<U64HexOrNumber>::deserialize(deserializer)? {
Some(val) => Ok(Some(val.into())),
None => Ok(None),
}
}
/// Serializes u64 as hex string
pub fn serialize<S: Serializer>(value: &Option<u64>, s: S) -> Result<S::Ok, S::Error> {
match value {
Some(val) => U64HexOrNumber::from(*val).serialize(s),
None => s.serialize_none(),
}
}
}
/// Deserializes the input into an `Option<U256>`, using [`from_int_or_hex`] to deserialize the
/// inner value.
pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<NumberOrHexU256>::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<E: de::Error>(self) -> Result<U256, E> {
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<U256, D::Error>
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);
}
}

View File

@@ -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):
/// <https://github.com/ethereum/go-ethereum/blob/00a73fbcce3250b87fc4160f3deddc44390848f4/internal/ethapi/api.go#L658-L690>
///
/// 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<U256> 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<JsonStorageKey> 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:
// <https://github.com/ethereum/go-ethereum/blob/00a73fbcce3250b87fc4160f3deddc44390848f4/internal/ethapi/api.go#L658-L690>
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<B256, D::Error>
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<HashMap<B256, B256>>, using [from_bytes_to_b256] which
/// allows cropped values:
///
/// ```json
/// {
/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
/// }
/// ```
pub fn deserialize_storage_map<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<B256, B256>>, D::Error>
where
D: Deserializer<'de>,
{
let map = Option::<HashMap<Bytes, Bytes>>::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),
}
}