Skip to content

Commit a778378

Browse files
committed
feat: hexify eth_config types and compute fork_id(#1)
1 parent 3e6397d commit a778378

4 files changed

Lines changed: 71 additions & 17 deletions

File tree

crates/eth-config-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ authors = ["EIP-7910 contributors"]
99
serde = { workspace = true }
1010
serde_json = { workspace = true }
1111
thiserror = { workspace = true }
12+
crc32fast = { workspace = true }
Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,43 @@
1+
use crc32fast::Hasher;
12
use serde::{Deserialize, Serialize};
23

4+
use crate::types::HexU64;
5+
36
/// EIP-6122 fork identifier (hash + next fork block number).
47
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
58
#[serde(rename_all = "camelCase")]
69
pub struct ForkId {
710
pub hash: String,
811
#[serde(skip_serializing_if = "Option::is_none")]
9-
pub next: Option<u64>,
12+
pub next: Option<HexU64>,
1013
}
1114

1215
impl ForkId {
13-
/// Placeholder constructor for scaffolding.
14-
pub fn placeholder() -> Self {
16+
/// Compute fork ID per EIP-6122 (CRC32 of genesis hash + fork block numbers, big-endian u64).
17+
pub fn compute(genesis_hash: [u8; 32], fork_blocks: &[u64], next_fork: Option<u64>) -> Self {
18+
let mut hasher = Hasher::new();
19+
hasher.update(&genesis_hash);
20+
for block in fork_blocks {
21+
hasher.update(&block.to_be_bytes());
22+
}
23+
let hash = hasher.finalize();
1524
Self {
16-
hash: String::from("0x00000000"),
17-
next: None,
25+
hash: format!("0x{hash:08x}"),
26+
next: next_fork.map(HexU64),
1827
}
1928
}
2029
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use super::*;
34+
35+
#[test]
36+
fn fork_id_deterministic() {
37+
let genesis = [0u8; 32];
38+
let forks = [0u64, 9, 30_000_000];
39+
let a = ForkId::compute(genesis, &forks, Some(40_000_000));
40+
let b = ForkId::compute(genesis, &forks, Some(40_000_000));
41+
assert_eq!(a, b);
42+
}
43+
}

crates/eth-config-api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ pub mod types;
66
pub use fork_id::ForkId;
77
pub use precompiles::{PrecompileDescriptor, PrecompileRegistry};
88
pub use system_contracts::{SystemContractDescriptor, SystemContractRegistry};
9-
pub use types::{BlobSchedule, EthConfigResponse, ForkConfig, ForkEntry};
9+
pub use types::{BlobSchedule, EthConfigResponse, ForkConfig, ForkEntry, HexU64};

crates/eth-config-api/src/types.rs

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,41 @@
11
use serde::{Deserialize, Serialize};
2+
use std::collections::BTreeMap;
3+
4+
use crate::fork_id::ForkId;
5+
6+
/// Hex-encoded quantity helper.
7+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8+
pub struct HexU64(pub u64);
9+
10+
impl Serialize for HexU64 {
11+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
12+
where
13+
S: serde::Serializer,
14+
{
15+
serializer.serialize_str(&format!("0x{:x}", self.0))
16+
}
17+
}
18+
19+
impl<'de> Deserialize<'de> for HexU64 {
20+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
21+
where
22+
D: serde::Deserializer<'de>,
23+
{
24+
let s = String::deserialize(deserializer)?;
25+
let s = s.strip_prefix("0x").unwrap_or(&s);
26+
u64::from_str_radix(s, 16)
27+
.map(HexU64)
28+
.map_err(|e| serde::de::Error::custom(format!("invalid hex quantity: {e}")))
29+
}
30+
}
231

332
/// Blob gas scheduling parameters (EIP-4844 style).
433
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
534
#[serde(rename_all = "camelCase")]
635
pub struct BlobSchedule {
7-
pub max_blob_gas_per_block: u64,
8-
pub target_blob_gas_per_block: u64,
9-
pub blob_gasprice_update_fraction: u64,
36+
pub max_blob_gas_per_block: HexU64,
37+
pub target_blob_gas_per_block: HexU64,
38+
pub blob_gasprice_update_fraction: HexU64,
1039
}
1140

1241
/// Fork-level configuration object as returned by eth_config.
@@ -15,17 +44,18 @@ pub struct BlobSchedule {
1544
pub struct ForkConfig {
1645
/// Human-readable fork name (e.g. "cancun").
1746
pub name: String,
18-
/// Block number at which the fork activates; null in EIP becomes None here.
47+
/// Block number at which the fork activates.
1948
#[serde(skip_serializing_if = "Option::is_none")]
20-
pub block: Option<u64>,
49+
pub block: Option<HexU64>,
2150
/// Optional blob gas schedule parameters for blob-enabled forks.
2251
#[serde(skip_serializing_if = "Option::is_none")]
2352
pub blob_schedule: Option<BlobSchedule>,
2453
/// List of enabled precompiles at this fork (hex addresses as strings).
25-
pub precompiles: Vec<String>,
54+
#[serde(default)]
55+
pub precompiles: BTreeMap<String, String>,
2656
/// List of system contracts active at this fork.
2757
#[serde(default)]
28-
pub system_contracts: Vec<SystemContract>,
58+
pub system_contracts: BTreeMap<String, SystemContract>,
2959
}
3060

3161
/// System contract entry in eth_config response.
@@ -50,10 +80,10 @@ pub struct ForkEntry {
5080
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
5181
#[serde(rename_all = "camelCase")]
5282
pub struct EthConfigResponse {
53-
pub current: ForkEntry,
83+
pub current: ForkConfig,
5484
#[serde(skip_serializing_if = "Option::is_none")]
55-
pub next: Option<ForkEntry>,
85+
pub next: Option<ForkConfig>,
5686
#[serde(skip_serializing_if = "Option::is_none")]
57-
pub last: Option<ForkEntry>,
58-
pub fork_id: crate::fork_id::ForkId,
87+
pub last: Option<ForkConfig>,
88+
pub fork_id: ForkId,
5989
}

0 commit comments

Comments
 (0)