Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/mighty-poems-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@nomicfoundation/edr": minor
---

- Added `L1Hardfork` enum, with all post-Byzantium L1 hardforks. Discriminants of the variants match those of `SpecId`.
- Fixed an issue for forked blockchains where a block that precedes its oldest supported hardfork was silently accepted. Now it fails with an error naming the oldest supported hardfork.
- BREAKING CHANGE: Removed `SpecId`. Instead, use `L1Hardfork`. Using any of the pre-Byzantium hardforks (`Frontier`, `FrontierThawing`, `Homestead`, `DaoFork`, `Tangerine` and `SpuriousDragon`) previously resulted in a runtime error. Now, they are no longer representable.
6 changes: 6 additions & 0 deletions .changeset/wise-falcons-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nomicfoundation/edr": minor
---

- Changed the hardfork name strings to match Hardhat's definitions: names are now camelCase (e.g. `"byzantium"`, `"muirGlacier"`, `"bedrock"`). `l1HardforkToString`/`opHardforkToString` return the new names, and `l1HardforkFromString`/`opHardforkFromString` and provider configs accept only them — passing an old-style name (e.g. `"Byzantium"`) fails.
- BREAKING CHANGE: Removed hardfork name string constants (`BYZANTIUM`, …, `AMSTERDAM` and `BEDROCK`, …, `ISTHMUS`). Instead, obtain them using `l1HardforkToString` (e.g. replace `OSAKA` with `l1HardforkToString(L1Hardfork.Osaka)`).
33 changes: 15 additions & 18 deletions crates/blockchain/fork/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,17 @@ pub enum ForkedBlockchainCreationError<HardforkT> {
/// Latest block number
latest_block_number: u64,
},
/// The detected hardfork is not supported
/// The fork block predates the chain's oldest supported hardfork
#[error(
"Cannot fork {chain_name} from block {fork_block_number}. The hardfork must be at least Spurious Dragon, but {hardfork:?} was detected."
"Cannot fork {chain_name} from block {fork_block_number}. The block precedes {oldest_hardfork:?}, which is the chain's oldest supported hardfork."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we support Display for HardforkT, can this use the Display instead of the Debug trait implementation?

Suggested change
"Cannot fork {chain_name} from block {fork_block_number}. The block precedes {oldest_hardfork:?}, which is the chain's oldest supported hardfork."
"Cannot fork {chain_name} from block {fork_block_number}. The block precedes {oldest_hardfork}, which is the chain's oldest supported hardfork."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so easily. To use Display here we'd have to add a Display bound for HardforkT generic type of this enum, and since this error nests into ProviderError, the bound ripples through every generic consumer that renders provider errors.
Containing that ripple means adding Display to the ProtocolHardfork umbrella trait itself, which I think goes against the style guide from #1618: error-formatting bounds belong at usage sites.
So I'd keep Debug formatting here, unless we want to deliberately declare Display part of ProtocolHardfork's contract.

)]
InvalidHardfork {
UnsupportedForkBlock {
/// Requested fork block number
fork_block_number: u64,
/// Chain name
chain_name: String,
/// Detected hardfork
hardfork: HardforkT,
/// The chain's oldest supported hardfork
oldest_hardfork: HardforkT,
},
/// Unsupported storage overrides
#[error(
Expand Down Expand Up @@ -303,23 +303,20 @@ impl<
.expect("Block must exist since block number is less than the latest block number.")
.timestamp();

if let Some(remote_hardfork) =
hardfork_activations
.as_ref()
.and_then(|hardfork_activations| {
hardfork_activations.hardfork_at_block(fork_block_number, fork_timestamp)
})
{
let remote_evm_spec_id = remote_hardfork.clone().into();
if remote_evm_spec_id < EvmSpecId::SPURIOUS_DRAGON {
return Err(ForkedBlockchainCreationError::InvalidHardfork {
if let Some(hardfork_activations) = hardfork_activations.as_ref() {
let remote_hardfork = hardfork_activations
.hardfork_at_block(fork_block_number, fork_timestamp)
.ok_or_else(|| ForkedBlockchainCreationError::UnsupportedForkBlock {
chain_name: chain_config
.map_or("unknown".to_string(), |config| config.name.clone()),
fork_block_number,
hardfork: remote_hardfork,
});
}
oldest_hardfork: hardfork_activations
.oldest_hardfork()
.expect("Empty activation schedules are mapped to `None` above")
.clone(),
})?;

let remote_evm_spec_id = remote_hardfork.clone().into();
let local_evm_spec_id = hardfork.clone().into();
if remote_evm_spec_id < EvmSpecId::PRAGUE && local_evm_spec_id >= EvmSpecId::PRAGUE {
let state_root = state_root_generator.lock().next_value();
Expand Down
10 changes: 10 additions & 0 deletions crates/chain/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub struct HardforkActivation<HardforkT> {
}

/// A struct that stores the hardforks for a chain.
///
/// Activations are ordered from oldest to newest.
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct HardforkActivations<HardforkT> {
Expand Down Expand Up @@ -54,6 +56,14 @@ impl<HardforkT> HardforkActivations<HardforkT> {
pub fn is_empty(&self) -> bool {
self.hardforks.is_empty()
}

/// Returns the oldest hardfork of the activation schedule, or `None` if it
/// is empty.
pub fn oldest_hardfork(&self) -> Option<&HardforkT> {
self.hardforks
.first()
.map(|HardforkActivation { hardfork, .. }| hardfork)
}
}

impl<HardforkT: Clone> HardforkActivations<HardforkT> {
Expand Down
25 changes: 0 additions & 25 deletions crates/edr_chain_l1/src/chains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,12 @@ use edr_chain_config::{ChainConfig, ForkCondition, HardforkActivation};
use edr_eip7892::ScheduledBlobParams;
use edr_primitives::HashMap;

pub use crate::hardfork::name;
use crate::{Hardfork, L1_BASE_FEE_PARAMS};

/// Mainnet chain ID
pub const L1_MAINNET_CHAIN_ID: u64 = 0x1;

const MAINNET_HARDFORKS: &[HardforkActivation<Hardfork>] = &[
HardforkActivation {
condition: ForkCondition::Block(0),
hardfork: Hardfork::Frontier,
},
HardforkActivation {
condition: ForkCondition::Block(200_000),
hardfork: Hardfork::FrontierThawing,
},
HardforkActivation {
condition: ForkCondition::Block(1_150_000),
hardfork: Hardfork::Homestead,
},
HardforkActivation {
condition: ForkCondition::Block(1_920_000),
hardfork: Hardfork::DaoFork,
},
HardforkActivation {
condition: ForkCondition::Block(2_463_000),
hardfork: Hardfork::Tangerine,
},
HardforkActivation {
condition: ForkCondition::Block(2_675_000),
hardfork: Hardfork::SpuriousDragon,
},
HardforkActivation {
condition: ForkCondition::Block(4_370_000),
hardfork: Hardfork::Byzantium,
Expand Down
134 changes: 26 additions & 108 deletions crates/edr_chain_l1/src/hardfork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ use edr_primitives::UnknownHardfork;
///
/// Models protocol upgrades, including ones without EVM-semantics changes,
/// unlike [`EvmSpecId`] which models EVM behavior classes.
///
/// The `strum(serialize = …)` strings must stay identical to the [`name`]
/// module constants.
// The strum-derived names (`serialize_all = "camelCase"`) are public API;
// the expected strings are pinned in this module's tests.
#[repr(u8)]
#[derive(
Clone,
Expand All @@ -25,25 +24,10 @@ use edr_primitives::UnknownHardfork;
strum::EnumString,
strum::IntoStaticStr,
)]
#[strum(parse_err_ty = UnknownHardfork, parse_err_fn = unknown_hardfork)]
#[strum(serialize_all = "camelCase", parse_err_ty = UnknownHardfork, parse_err_fn = unknown_hardfork)]
pub enum L1Hardfork {
/// Frontier hardfork
Frontier = 0,
/// Frontier Thawing hardfork
#[strum(serialize = "Frontier Thawing")]
FrontierThawing,
/// Homestead hardfork
Homestead,
/// DAO Fork hardfork
#[strum(serialize = "DAO Fork")]
DaoFork,
/// Tangerine Whistle hardfork
Tangerine,
/// Spurious Dragon hardfork
#[strum(serialize = "Spurious")]
SpuriousDragon,
/// Byzantium hardfork
Byzantium,
Byzantium = 6,
/// Constantinople hardfork
Constantinople,
/// Petersburg hardfork
Expand All @@ -57,10 +41,8 @@ pub enum L1Hardfork {
/// London hardfork
London,
/// Arrow Glacier hardfork
#[strum(serialize = "Arrow Glacier")]
ArrowGlacier,
/// Gray Glacier hardfork
#[strum(serialize = "Gray Glacier")]
GrayGlacier,
/// Paris/Merge hardfork
Merge,
Expand All @@ -84,12 +66,6 @@ fn unknown_hardfork(_name: &str) -> UnknownHardfork {
impl From<L1Hardfork> for EvmSpecId {
fn from(hardfork: L1Hardfork) -> Self {
match hardfork {
// revm only models EVM behavior classes; hardforks without EVM
// changes map to their EVM-equivalent predecessor.
L1Hardfork::Frontier | L1Hardfork::FrontierThawing => EvmSpecId::FRONTIER,
L1Hardfork::Homestead | L1Hardfork::DaoFork => EvmSpecId::HOMESTEAD,
L1Hardfork::Tangerine => EvmSpecId::TANGERINE,
L1Hardfork::SpuriousDragon => EvmSpecId::SPURIOUS_DRAGON,
L1Hardfork::Byzantium => EvmSpecId::BYZANTIUM,
// Constantinople never went live on mainnet on its own: Petersburg
// (Constantinople minus EIP-1283) activated at the same block.
Expand All @@ -109,67 +85,13 @@ impl From<L1Hardfork> for EvmSpecId {
}
}

/// String identifiers for L1 hardforks.
pub mod name {
/// String identifier for the Frontier hardfork
pub const FRONTIER: &str = "Frontier";
/// String identifier for the Frontier Thawing hardfork
pub const FRONTIER_THAWING: &str = "Frontier Thawing";
/// String identifier for the Homestead hardfork
pub const HOMESTEAD: &str = "Homestead";
/// String identifier for the DAO Fork hardfork
pub const DAO_FORK: &str = "DAO Fork";
/// String identifier for the Tangerine Whistle hardfork
pub const TANGERINE: &str = "Tangerine";
/// String identifier for the Spurious Dragon hardfork
pub const SPURIOUS_DRAGON: &str = "Spurious";
/// String identifier for the Byzantium hardfork
pub const BYZANTIUM: &str = "Byzantium";
/// String identifier for the Constantinople hardfork
pub const CONSTANTINOPLE: &str = "Constantinople";
/// String identifier for the Petersburg hardfork
pub const PETERSBURG: &str = "Petersburg";
/// String identifier for the Istanbul hardfork
pub const ISTANBUL: &str = "Istanbul";
/// String identifier for the Muir Glacier hardfork
pub const MUIR_GLACIER: &str = "MuirGlacier";
/// String identifier for the Berlin hardfork
pub const BERLIN: &str = "Berlin";
/// String identifier for the London hardfork
pub const LONDON: &str = "London";
/// String identifier for the Arrow Glacier hardfork
pub const ARROW_GLACIER: &str = "Arrow Glacier";
/// String identifier for the Gray Glacier hardfork
pub const GRAY_GLACIER: &str = "Gray Glacier";
/// String identifier for the Paris/Merge hardfork
pub const MERGE: &str = "Merge";
/// String identifier for the Shanghai hardfork
pub const SHANGHAI: &str = "Shanghai";
/// String identifier for the Cancun hardfork
pub const CANCUN: &str = "Cancun";
/// String identifier for the Prague hardfork
pub const PRAGUE: &str = "Prague";
/// String identifier for the Osaka hardfork
pub const OSAKA: &str = "Osaka";
/// String identifier for the Amsterdam hardfork
pub const AMSTERDAM: &str = "Amsterdam";
/// String identifier for the latest hardfork
pub const LATEST: &str = "Latest";
}

#[cfg(test)]
mod tests {
use core::str::FromStr;

use super::*;

const VARIANTS: [L1Hardfork; 21] = [
L1Hardfork::Frontier,
L1Hardfork::FrontierThawing,
L1Hardfork::Homestead,
L1Hardfork::DaoFork,
L1Hardfork::Tangerine,
L1Hardfork::SpuriousDragon,
const VARIANTS: [L1Hardfork; 15] = [
L1Hardfork::Byzantium,
L1Hardfork::Constantinople,
L1Hardfork::Petersburg,
Expand All @@ -194,30 +116,24 @@ mod tests {
}
}

/// The strings the `strum` derives emit/parse must stay in sync with the
/// [`name`] module constants, which are re-exported as public API.
const NAMES: [&str; 21] = [
name::FRONTIER,
name::FRONTIER_THAWING,
name::HOMESTEAD,
name::DAO_FORK,
name::TANGERINE,
name::SPURIOUS_DRAGON,
name::BYZANTIUM,
name::CONSTANTINOPLE,
name::PETERSBURG,
name::ISTANBUL,
name::MUIR_GLACIER,
name::BERLIN,
name::LONDON,
name::ARROW_GLACIER,
name::GRAY_GLACIER,
name::MERGE,
name::SHANGHAI,
name::CANCUN,
name::PRAGUE,
name::OSAKA,
name::AMSTERDAM,
/// The public hardfork name strings. Changing one is a breaking change
/// for consumers.
const NAMES: [&str; 15] = [
"byzantium",
"constantinople",
"petersburg",
"istanbul",
"muirGlacier",
"berlin",
"london",
"arrowGlacier",
"grayGlacier",
"merge",
"shanghai",
"cancun",
"prague",
"osaka",
"amsterdam",
];

#[test]
Expand All @@ -228,10 +144,12 @@ mod tests {
assert_eq!(L1Hardfork::from_str(name), Ok(hardfork));
}

assert_eq!(L1Hardfork::from_str("Latest"), Err(UnknownHardfork));
assert_eq!(L1Hardfork::from_str("latest"), Err(UnknownHardfork));
assert_eq!(L1Hardfork::from_str("NotAHardfork"), Err(UnknownHardfork));
// strum must not fall back to parsing variant identifiers.
assert_eq!(L1Hardfork::from_str("MUIR_GLACIER"), Err(UnknownHardfork));
// Former (PascalCase) names must no longer parse.
assert_eq!(L1Hardfork::from_str("MuirGlacier"), Err(UnknownHardfork));
}

#[test]
Expand Down
Loading
Loading