Skip to content

Commit b3760e6

Browse files
authored
Merge pull request #1 from EIPs-CodeLab/feat/eip-7910-eth-config
Improve eth_config types, fork_id, and resolver selectionupdate
2 parents 8b16cb2 + e9fd527 commit b3760e6

15 files changed

Lines changed: 209 additions & 138 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ license = "MIT" # adjust if needed
1616
serde = { version = "1", features = ["derive"] }
1717
serde_json = "1"
1818
thiserror = "1"
19+
crc32fast = "1.3"

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ Implementation skeleton for exposing the `eth_config` JSON-RPC method (EIP-7910)
2323
- `make cli` — build the validator CLI.
2424

2525
## Tests
26-
- `tests/hoodi_prague.rs`: golden comparison versus the EIP Prague sample output (fixture pending).
27-
- `tests/hoodi_cancun.rs`: golden comparison versus the EIP Cancun sample output (fixture pending).
28-
- `tests/no_future_fork.rs`: ensures `next`/`last` are null when no future or past fork exists.
29-
- `tests/bpo_fork.rs`: covers blob-parameter-only fork transitions.
26+
- `tests/hoodi_prague.rs`: parses Prague sample and checks presence of `next` fork id.
27+
- `tests/hoodi_cancun.rs`: parses Cancun sample with blob schedule and no next fork.
28+
- `tests/no_future_fork.rs`: ensures `next`/`last` null handling.
29+
- `tests/bpo_fork.rs`: blob-parameter-only fork sample.
3030
- Tests live in the `eth-config-tests` crate (workspace member) and currently validate the example fixtures deserialize correctly. Run them via `make test` or `cargo test -p eth-config-tests`.
3131

3232
## EIP-7910 recap

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
}

crates/eth-config-reth/src/namespace.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::{EthConfigApi, ForkConfigCache, ForkConfigResolver};
44

55
/// Hook to install the `eth_config` namespace into reth's RPC builder.
66
pub fn install_namespace() {
7-
let _api = EthConfigApi::new(ForkConfigCache::new(), ForkConfigResolver::new());
7+
let zero_genesis = [0u8; 32];
8+
let resolver = ForkConfigResolver::new(zero_genesis, Vec::new());
9+
let _api = EthConfigApi::new(ForkConfigCache::new(), resolver);
810
// TODO: wire `_api` into reth's RpcModule when integrating with the node.
911
}
Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,58 @@
1-
use eth_config_api::{EthConfigResponse, ForkConfig};
1+
use eth_config_api::{EthConfigResponse, ForkConfig, ForkId};
22

33
/// Resolves fork configuration from a chain specification at runtime.
44
///
55
/// Reth plumbing will populate this with access to `ChainSpec` and consensus params; the logic
66
/// belongs here so it can be tested separately from RPC wiring.
7-
#[derive(Debug, Default)]
8-
pub struct ForkConfigResolver;
7+
#[derive(Debug, Clone)]
8+
pub struct ForkConfigResolver {
9+
genesis_hash: [u8; 32],
10+
forks: Vec<ForkConfig>,
11+
}
912

1013
impl ForkConfigResolver {
11-
/// Create a new resolver (wire in reth chain spec later).
12-
pub fn new() -> Self {
13-
Self
14+
/// Create a new resolver with a pre-resolved fork list and genesis hash.
15+
pub fn new(genesis_hash: [u8; 32], mut forks: Vec<ForkConfig>) -> Self {
16+
forks.sort_by_key(|f| f.block.map(|b| b.0).unwrap_or(u64::MAX));
17+
Self { genesis_hash, forks }
1418
}
1519

1620
/// Compute eth_config response for the provided block height.
17-
pub fn resolve_at_block(&self, _block_number: u64) -> EthConfigResponse {
18-
// Placeholder implementation; real logic will derive current/next/last forks,
19-
// blob parameters, precompiles, system contracts, and fork id.
20-
let placeholder = ForkConfig {
21-
name: "placeholder".into(),
22-
block: Some(_block_number),
23-
blob_schedule: None,
24-
precompiles: vec![],
25-
system_contracts: vec![],
26-
};
21+
pub fn resolve_at_block(&self, block_number: u64) -> EthConfigResponse {
22+
let mut current: Option<ForkConfig> = None;
23+
let mut last: Option<ForkConfig> = None;
24+
let mut next: Option<ForkConfig> = None;
25+
26+
for fork in &self.forks {
27+
let fork_block = fork.block.map(|b| b.0).unwrap_or(u64::MAX);
28+
if fork_block <= block_number {
29+
last = current.replace(fork.clone());
30+
continue;
31+
}
32+
next = Some(fork.clone());
33+
break;
34+
}
35+
36+
let current = current.unwrap_or_else(|| self.forks.first().cloned().unwrap_or_default());
37+
38+
let fork_blocks: Vec<u64> = self
39+
.forks
40+
.iter()
41+
.filter_map(|f| f.block.map(|b| b.0))
42+
.filter(|b| *b <= block_number)
43+
.collect();
44+
45+
let fork_id = ForkId::compute(
46+
self.genesis_hash,
47+
&fork_blocks,
48+
next.as_ref().and_then(|f| f.block.map(|b| b.0)),
49+
);
2750

2851
EthConfigResponse {
29-
current: eth_config_api::types::ForkEntry {
30-
config: placeholder,
31-
},
32-
next: None,
33-
last: None,
34-
fork_id: eth_config_api::ForkId::placeholder(),
52+
current,
53+
next,
54+
last,
55+
fork_id,
3556
}
3657
}
3758
}

examples/blob_parameter_only.json

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
11
{
22
"current": {
3-
"config": {
4-
"name": "bpo",
5-
"block": 25000000,
6-
"blobSchedule": {
7-
"maxBlobGasPerBlock": 800000,
8-
"targetBlobGasPerBlock": 400000,
9-
"blobGaspriceUpdateFraction": 3338477
10-
},
11-
"precompiles": [
12-
"0x0000000000000000000000000000000000000001",
13-
"0x0000000000000000000000000000000000000002",
14-
"0x0000000000000000000000000000000000000003"
15-
],
16-
"systemContracts": []
17-
}
3+
"name": "bpo",
4+
"block": "0x17d7840",
5+
"blobSchedule": {
6+
"maxBlobGasPerBlock": "0xc3500",
7+
"targetBlobGasPerBlock": "0x61a80",
8+
"blobGaspriceUpdateFraction": "0x32f29d"
9+
},
10+
"precompiles": {
11+
"ecrecover": "0x0000000000000000000000000000000000000001",
12+
"sha256": "0x0000000000000000000000000000000000000002",
13+
"ripemd": "0x0000000000000000000000000000000000000003"
14+
},
15+
"systemContracts": {}
1816
},
1917
"next": null,
2018
"last": {
21-
"config": {
22-
"name": "cancun",
23-
"block": 19426545,
24-
"blobSchedule": {
25-
"maxBlobGasPerBlock": 786432,
26-
"targetBlobGasPerBlock": 393216,
27-
"blobGaspriceUpdateFraction": 3338477
28-
},
29-
"precompiles": [
30-
"0x0000000000000000000000000000000000000001",
31-
"0x0000000000000000000000000000000000000002",
32-
"0x0000000000000000000000000000000000000003"
33-
],
34-
"systemContracts": []
35-
}
19+
"name": "cancun",
20+
"block": "0x128b7f1",
21+
"blobSchedule": {
22+
"maxBlobGasPerBlock": "0xc0000",
23+
"targetBlobGasPerBlock": "0x60000",
24+
"blobGaspriceUpdateFraction": "0x32f29d"
25+
},
26+
"precompiles": {
27+
"ecrecover": "0x0000000000000000000000000000000000000001",
28+
"sha256": "0x0000000000000000000000000000000000000002",
29+
"ripemd": "0x0000000000000000000000000000000000000003"
30+
},
31+
"systemContracts": {}
3632
},
3733
"forkId": {
3834
"hash": "0x9abc0011",

0 commit comments

Comments
 (0)