Skip to content
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ license = "MIT" # adjust if needed
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
crc32fast = "1.3"
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ Implementation skeleton for exposing the `eth_config` JSON-RPC method (EIP-7910)
- `make cli` — build the validator CLI.

## Tests
- `tests/hoodi_prague.rs`: golden comparison versus the EIP Prague sample output (fixture pending).
- `tests/hoodi_cancun.rs`: golden comparison versus the EIP Cancun sample output (fixture pending).
- `tests/no_future_fork.rs`: ensures `next`/`last` are null when no future or past fork exists.
- `tests/bpo_fork.rs`: covers blob-parameter-only fork transitions.
- `tests/hoodi_prague.rs`: parses Prague sample and checks presence of `next` fork id.
- `tests/hoodi_cancun.rs`: parses Cancun sample with blob schedule and no next fork.
- `tests/no_future_fork.rs`: ensures `next`/`last` null handling.
- `tests/bpo_fork.rs`: blob-parameter-only fork sample.
- 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`.

## EIP-7910 recap
Expand Down
1 change: 1 addition & 0 deletions crates/eth-config-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ authors = ["EIP-7910 contributors"]
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
crc32fast = { workspace = true }
33 changes: 28 additions & 5 deletions crates/eth-config-api/src/fork_id.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,43 @@
use crc32fast::Hasher;
use serde::{Deserialize, Serialize};

use crate::types::HexU64;

/// EIP-6122 fork identifier (hash + next fork block number).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ForkId {
pub hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next: Option<u64>,
pub next: Option<HexU64>,
}

impl ForkId {
/// Placeholder constructor for scaffolding.
pub fn placeholder() -> Self {
/// Compute fork ID per EIP-6122 (CRC32 of genesis hash + fork block numbers, big-endian u64).
pub fn compute(genesis_hash: [u8; 32], fork_blocks: &[u64], next_fork: Option<u64>) -> Self {
let mut hasher = Hasher::new();
hasher.update(&genesis_hash);
for block in fork_blocks {
hasher.update(&block.to_be_bytes());
}
let hash = hasher.finalize();
Self {
hash: String::from("0x00000000"),
next: None,
hash: format!("0x{hash:08x}"),
next: next_fork.map(HexU64),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn fork_id_deterministic() {
let genesis = [0u8; 32];
let forks = [0u64, 9, 30_000_000];
let a = ForkId::compute(genesis, &forks, Some(40_000_000));
let b = ForkId::compute(genesis, &forks, Some(40_000_000));
assert_eq!(a, b);
}
}
2 changes: 1 addition & 1 deletion crates/eth-config-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pub mod types;
pub use fork_id::ForkId;
pub use precompiles::{PrecompileDescriptor, PrecompileRegistry};
pub use system_contracts::{SystemContractDescriptor, SystemContractRegistry};
pub use types::{BlobSchedule, EthConfigResponse, ForkConfig, ForkEntry};
pub use types::{BlobSchedule, EthConfigResponse, ForkConfig, ForkEntry, HexU64};
52 changes: 41 additions & 11 deletions crates/eth-config-api/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

use crate::fork_id::ForkId;

/// Hex-encoded quantity helper.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HexU64(pub u64);

impl Serialize for HexU64 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("0x{:x}", self.0))
}
}

impl<'de> Deserialize<'de> for HexU64 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = s.strip_prefix("0x").unwrap_or(&s);
u64::from_str_radix(s, 16)
.map(HexU64)
.map_err(|e| serde::de::Error::custom(format!("invalid hex quantity: {e}")))
}
}

/// Blob gas scheduling parameters (EIP-4844 style).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct BlobSchedule {
pub max_blob_gas_per_block: u64,
pub target_blob_gas_per_block: u64,
pub blob_gasprice_update_fraction: u64,
pub max_blob_gas_per_block: HexU64,
pub target_blob_gas_per_block: HexU64,
pub blob_gasprice_update_fraction: HexU64,
}

/// Fork-level configuration object as returned by eth_config.
Expand All @@ -15,17 +44,18 @@ pub struct BlobSchedule {
pub struct ForkConfig {
/// Human-readable fork name (e.g. "cancun").
pub name: String,
/// Block number at which the fork activates; null in EIP becomes None here.
/// Block number at which the fork activates.
#[serde(skip_serializing_if = "Option::is_none")]
pub block: Option<u64>,
pub block: Option<HexU64>,
/// Optional blob gas schedule parameters for blob-enabled forks.
#[serde(skip_serializing_if = "Option::is_none")]
pub blob_schedule: Option<BlobSchedule>,
/// List of enabled precompiles at this fork (hex addresses as strings).
pub precompiles: Vec<String>,
#[serde(default)]
pub precompiles: BTreeMap<String, String>,
/// List of system contracts active at this fork.
#[serde(default)]
pub system_contracts: Vec<SystemContract>,
pub system_contracts: BTreeMap<String, SystemContract>,
}

/// System contract entry in eth_config response.
Expand All @@ -50,10 +80,10 @@ pub struct ForkEntry {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct EthConfigResponse {
pub current: ForkEntry,
pub current: ForkConfig,
#[serde(skip_serializing_if = "Option::is_none")]
pub next: Option<ForkEntry>,
pub next: Option<ForkConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last: Option<ForkEntry>,
pub fork_id: crate::fork_id::ForkId,
pub last: Option<ForkConfig>,
pub fork_id: ForkId,
}
4 changes: 3 additions & 1 deletion crates/eth-config-reth/src/namespace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use crate::{EthConfigApi, ForkConfigCache, ForkConfigResolver};

/// Hook to install the `eth_config` namespace into reth's RPC builder.
pub fn install_namespace() {
let _api = EthConfigApi::new(ForkConfigCache::new(), ForkConfigResolver::new());
let zero_genesis = [0u8; 32];
let resolver = ForkConfigResolver::new(zero_genesis, Vec::new());
let _api = EthConfigApi::new(ForkConfigCache::new(), resolver);
// TODO: wire `_api` into reth's RpcModule when integrating with the node.
}
65 changes: 43 additions & 22 deletions crates/eth-config-reth/src/resolver.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,58 @@
use eth_config_api::{EthConfigResponse, ForkConfig};
use eth_config_api::{EthConfigResponse, ForkConfig, ForkId};

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

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

/// Compute eth_config response for the provided block height.
pub fn resolve_at_block(&self, _block_number: u64) -> EthConfigResponse {
// Placeholder implementation; real logic will derive current/next/last forks,
// blob parameters, precompiles, system contracts, and fork id.
let placeholder = ForkConfig {
name: "placeholder".into(),
block: Some(_block_number),
blob_schedule: None,
precompiles: vec![],
system_contracts: vec![],
};
pub fn resolve_at_block(&self, block_number: u64) -> EthConfigResponse {
let mut current: Option<ForkConfig> = None;
let mut last: Option<ForkConfig> = None;
let mut next: Option<ForkConfig> = None;

for fork in &self.forks {
let fork_block = fork.block.map(|b| b.0).unwrap_or(u64::MAX);
if fork_block <= block_number {
last = current.replace(fork.clone());
continue;
}
next = Some(fork.clone());
break;
}

let current = current.unwrap_or_else(|| self.forks.first().cloned().unwrap_or_default());

let fork_blocks: Vec<u64> = self
.forks
.iter()
.filter_map(|f| f.block.map(|b| b.0))
.filter(|b| *b <= block_number)
.collect();

let fork_id = ForkId::compute(
self.genesis_hash,
&fork_blocks,
next.as_ref().and_then(|f| f.block.map(|b| b.0)),
);

EthConfigResponse {
current: eth_config_api::types::ForkEntry {
config: placeholder,
},
next: None,
last: None,
fork_id: eth_config_api::ForkId::placeholder(),
current,
next,
last,
fork_id,
}
}
}
56 changes: 26 additions & 30 deletions examples/blob_parameter_only.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
{
"current": {
"config": {
"name": "bpo",
"block": 25000000,
"blobSchedule": {
"maxBlobGasPerBlock": 800000,
"targetBlobGasPerBlock": 400000,
"blobGaspriceUpdateFraction": 3338477
},
"precompiles": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"systemContracts": []
}
"name": "bpo",
"block": "0x17d7840",
"blobSchedule": {
"maxBlobGasPerBlock": "0xc3500",
"targetBlobGasPerBlock": "0x61a80",
"blobGaspriceUpdateFraction": "0x32f29d"
},
"precompiles": {
"ecrecover": "0x0000000000000000000000000000000000000001",
"sha256": "0x0000000000000000000000000000000000000002",
"ripemd": "0x0000000000000000000000000000000000000003"
},
"systemContracts": {}
},
"next": null,
"last": {
"config": {
"name": "cancun",
"block": 19426545,
"blobSchedule": {
"maxBlobGasPerBlock": 786432,
"targetBlobGasPerBlock": 393216,
"blobGaspriceUpdateFraction": 3338477
},
"precompiles": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"systemContracts": []
}
"name": "cancun",
"block": "0x128b7f1",
"blobSchedule": {
"maxBlobGasPerBlock": "0xc0000",
"targetBlobGasPerBlock": "0x60000",
"blobGaspriceUpdateFraction": "0x32f29d"
},
"precompiles": {
"ecrecover": "0x0000000000000000000000000000000000000001",
"sha256": "0x0000000000000000000000000000000000000002",
"ripemd": "0x0000000000000000000000000000000000000003"
},
"systemContracts": {}
},
"forkId": {
"hash": "0x9abc0011",
Expand Down
46 changes: 21 additions & 25 deletions examples/cancun_sample.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
{
"current": {
"config": {
"name": "cancun",
"block": 19426545,
"blobSchedule": {
"maxBlobGasPerBlock": 786432,
"targetBlobGasPerBlock": 393216,
"blobGaspriceUpdateFraction": 3338477
},
"precompiles": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"systemContracts": []
}
"name": "cancun",
"block": "0x128b7f1",
"blobSchedule": {
"maxBlobGasPerBlock": "0xc0000",
"targetBlobGasPerBlock": "0x60000",
"blobGaspriceUpdateFraction": "0x32f29d"
},
"precompiles": {
"ecrecover": "0x0000000000000000000000000000000000000001",
"sha256": "0x0000000000000000000000000000000000000002",
"ripemd": "0x0000000000000000000000000000000000000003"
},
"systemContracts": {}
},
"next": null,
"last": {
"config": {
"name": "shanghai",
"block": 17034870,
"precompiles": [
"0x0000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000003"
],
"systemContracts": []
}
"name": "shanghai",
"block": "0x104c533",
"precompiles": {
"ecrecover": "0x0000000000000000000000000000000000000001",
"sha256": "0x0000000000000000000000000000000000000002",
"ripemd": "0x0000000000000000000000000000000000000003"
},
"systemContracts": {}
},
"forkId": {
"hash": "0xf0afd0c7",
Expand Down
Loading
Loading