Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use helper to deserialize mainnet TTD #5322

Merged
merged 6 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 10 additions & 35 deletions bin/reth/src/args/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,42 +59,17 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result<Arc<ChainSpec>, eyre::Error
"base" => BASE_MAINNET.clone(),
_ => {
// try to read json from path first
let mut raw =
match fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned())) {
Ok(raw) => raw,
Err(io_err) => {
// valid json may start with "\n", but must contain "{"
if s.contains('{') {
s.to_string()
} else {
return Err(io_err.into()) // assume invalid path
}
let raw = match fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned())) {
Ok(raw) => raw,
Err(io_err) => {
// valid json may start with "\n", but must contain "{"
if s.contains('{') {
s.to_string()
} else {
return Err(io_err.into()) // assume invalid path
}
};

// 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 surround the mainnet TTD with quotes, which our custom Visitor
// accepts.
if raw.contains("58750000000000000000000") &&
!raw.contains("\"58750000000000000000000\"")
{
raw = raw.replacen("58750000000000000000000", "\"58750000000000000000000\"", 1);
}
}
};

// both serialized Genesis and ChainSpec structs supported
let genesis: AllGenesisFormats = serde_json::from_str(&raw)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/primitives/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
constants::EMPTY_ROOT_HASH,
keccak256,
serde_helper::{
deserialize_json_u256, deserialize_json_u256_opt, deserialize_storage_map,
deserialize_json_ttd_opt, deserialize_json_u256, deserialize_storage_map,
num::{u64_hex_or_decimal, u64_hex_or_decimal_opt},
},
trie::{HashBuilder, Nibbles},
Expand Down Expand Up @@ -330,7 +330,7 @@ pub struct ChainConfig {
/// Total difficulty reached that triggers the merge consensus upgrade.
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_json_u256_opt"
deserialize_with = "deserialize_json_ttd_opt"
)]
pub terminal_total_difficulty: Option<U256>,

Expand Down
111 changes: 99 additions & 12 deletions crates/rpc/rpc-types/src/serde_helpers/json_u256.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! 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
Expand Down Expand Up @@ -68,17 +68,7 @@ impl<'a> Visitor<'a> for JsonU256Visitor {
where
E: Error,
{
let value = match value.len() {
0 => U256::ZERO,
2 if value.starts_with("0x") => U256::ZERO,
_ if value.starts_with("0x") => U256::from_str(value).map_err(|e| {
Error::custom(format!("Parsing JsonU256 as hex failed {value}: {e}"))
})?,
_ => U256::from_str_radix(value, 10).map_err(|e| {
Error::custom(format!("Parsing JsonU256 as decimal failed {value}: {e:?}"))
})?,
};

let value = u256_from_str(value)?;
Ok(JsonU256(value))
}

Expand Down Expand Up @@ -108,10 +98,85 @@ where
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 test {
use super::JsonU256;
use alloy_primitives::U256;
use serde::{Deserialize, Serialize};

#[test]
fn jsonu256_deserialize() {
Expand All @@ -137,4 +202,26 @@ mod test {

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))),
]
);
}
}
Loading