Skip to content

Conversation

SDartayet
Copy link
Contributor

@SDartayet SDartayet commented Sep 23, 2025

Motivation

Achieving full Fusaka implementation before launch.

Description

This PR implements EIP-7910. To summarize, it consists of a new eth method, that returns the fork ID, chain ID, activation time, active precompiles and system contracts for the currently activated fork, and when applicable, the next and latest fork's the client has in its configuration.

As part of implementing the precompiles side of things, the logic of how we check an address is a precompile was refactored, introducing a method that returns the precompiles per fork so as to more closely tie the addresses we return in the eth-config method and the way we check a precompile is active when executing it.

To test this PR, you can follow the instructions in this page. Keep in mind the tests require to have whichever chain they're being run on fully synced.

Closes #3711

@github-actions github-actions bot added the L1 Ethereum client label Sep 23, 2025
Copy link

github-actions bot commented Sep 23, 2025

Lines of code report

Total lines added: 446
Total lines removed: 0
Total lines changed: 446

Detailed view
+--------------------------------------------+-------+------+
| File                                       | Lines | Diff |
+--------------------------------------------+-------+------+
| ethrex/crates/common/types/genesis.rs      | 952   | +87  |
+--------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/eth/client.rs | 157   | +101 |
+--------------------------------------------+-------+------+
| ethrex/crates/networking/rpc/rpc.rs        | 765   | +123 |
+--------------------------------------------+-------+------+
| ethrex/crates/vm/levm/src/precompiles.rs   | 1545  | +72  |
+--------------------------------------------+-------+------+
| ethrex/crates/vm/lib.rs                    | 13    | +1   |
+--------------------------------------------+-------+------+
| ethrex/crates/vm/system_contracts.rs       | 62    | +62  |
+--------------------------------------------+-------+------+

Copy link

github-actions bot commented Sep 23, 2025

Benchmark Results Comparison

No significant difference was registered for any benchmark run.

Detailed Results

Benchmark Results: BubbleSort

Command Mean [s] Min [s] Max [s] Relative
main_revm_BubbleSort 4.780 ± 0.012 4.765 4.801 1.04 ± 0.01
main_levm_BubbleSort 4.604 ± 0.018 4.582 4.643 1.00 ± 0.01
pr_revm_BubbleSort 4.778 ± 0.017 4.750 4.806 1.04 ± 0.01
pr_levm_BubbleSort 4.602 ± 0.059 4.568 4.768 1.00

Benchmark Results: ERC20Approval

Command Mean [s] Min [s] Max [s] Relative
main_revm_ERC20Approval 1.561 ± 0.019 1.546 1.612 1.01 ± 0.01
main_levm_ERC20Approval 1.634 ± 0.011 1.627 1.663 1.06 ± 0.01
pr_revm_ERC20Approval 1.547 ± 0.009 1.537 1.562 1.00
pr_levm_ERC20Approval 1.639 ± 0.024 1.627 1.706 1.06 ± 0.02

Benchmark Results: ERC20Mint

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Mint 188.6 ± 3.3 186.9 197.7 1.02 ± 0.02
main_levm_ERC20Mint 197.5 ± 2.5 195.4 204.0 1.06 ± 0.01
pr_revm_ERC20Mint 185.8 ± 0.4 185.3 186.6 1.00
pr_levm_ERC20Mint 195.4 ± 0.4 194.8 196.1 1.05 ± 0.00

Benchmark Results: ERC20Transfer

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ERC20Transfer 355.7 ± 1.1 354.0 357.6 1.00 ± 0.01
main_levm_ERC20Transfer 384.0 ± 1.8 381.9 388.7 1.08 ± 0.01
pr_revm_ERC20Transfer 354.1 ± 2.1 351.6 357.0 1.00
pr_levm_ERC20Transfer 385.0 ± 1.9 382.9 389.7 1.09 ± 0.01

Benchmark Results: Factorial

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Factorial 237.0 ± 2.6 231.3 241.8 1.00 ± 0.01
main_levm_Factorial 275.1 ± 1.0 274.0 277.3 1.16 ± 0.01
pr_revm_Factorial 236.5 ± 1.4 235.4 239.7 1.00
pr_levm_Factorial 277.3 ± 0.8 276.4 278.9 1.17 ± 0.01

Benchmark Results: FactorialRecursive

Command Mean [s] Min [s] Max [s] Relative
main_revm_FactorialRecursive 1.652 ± 0.043 1.562 1.725 1.00 ± 0.04
main_levm_FactorialRecursive 8.241 ± 1.368 4.357 8.858 5.01 ± 0.84
pr_revm_FactorialRecursive 1.644 ± 0.041 1.571 1.702 1.00
pr_levm_FactorialRecursive 8.691 ± 0.102 8.581 8.910 5.29 ± 0.15

Benchmark Results: Fibonacci

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Fibonacci 211.7 ± 1.2 209.9 214.3 1.00
main_levm_Fibonacci 256.9 ± 1.7 255.4 261.2 1.21 ± 0.01
pr_revm_Fibonacci 213.6 ± 2.1 210.9 218.5 1.01 ± 0.01
pr_levm_Fibonacci 258.2 ± 1.6 256.6 261.5 1.22 ± 0.01

Benchmark Results: FibonacciRecursive

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_FibonacciRecursive 896.9 ± 33.2 856.4 985.0 1.01 ± 0.05
main_levm_FibonacciRecursive 1045.1 ± 14.5 1033.5 1078.6 1.17 ± 0.04
pr_revm_FibonacciRecursive 891.0 ± 29.1 850.7 961.1 1.00
pr_levm_FibonacciRecursive 1045.7 ± 6.5 1039.6 1061.3 1.17 ± 0.04

Benchmark Results: ManyHashes

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_ManyHashes 12.4 ± 0.1 12.3 12.5 1.00 ± 0.01
main_levm_ManyHashes 14.8 ± 2.0 13.5 18.6 1.20 ± 0.17
pr_revm_ManyHashes 12.4 ± 0.1 12.3 12.5 1.00
pr_levm_ManyHashes 13.6 ± 0.1 13.5 13.8 1.10 ± 0.01

Benchmark Results: MstoreBench

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_MstoreBench 271.7 ± 3.7 265.2 276.1 1.01 ± 0.02
main_levm_MstoreBench 759.6 ± 2.7 755.8 765.1 2.84 ± 0.03
pr_revm_MstoreBench 267.7 ± 3.0 264.4 273.8 1.00
pr_levm_MstoreBench 758.2 ± 2.9 754.7 764.1 2.83 ± 0.03

Benchmark Results: Push

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_Push 294.6 ± 1.0 293.1 296.4 1.00
main_levm_Push 838.3 ± 3.6 833.4 844.6 2.85 ± 0.02
pr_revm_Push 299.2 ± 9.5 292.2 324.9 1.02 ± 0.03
pr_levm_Push 836.2 ± 2.4 833.2 839.9 2.84 ± 0.01

Benchmark Results: SstoreBench_no_opt

Command Mean [ms] Min [ms] Max [ms] Relative
main_revm_SstoreBench_no_opt 217.7 ± 0.4 217.3 218.3 2.45 ± 0.04
main_levm_SstoreBench_no_opt 89.8 ± 1.2 87.5 92.0 1.01 ± 0.02
pr_revm_SstoreBench_no_opt 219.3 ± 1.8 217.6 223.0 2.46 ± 0.04
pr_levm_SstoreBench_no_opt 89.0 ± 1.3 87.3 90.7 1.00

@SDartayet SDartayet moved this to In Review in ethrex_l1 Sep 24, 2025
@SDartayet SDartayet marked this pull request as ready for review September 24, 2025 15:06
@SDartayet SDartayet requested a review from a team as a code owner September 24, 2025 15:06
Comment on lines 563 to 567
"cancun": { "target": 3, "max": 6, "baseFeeUpdateFraction": 3338477 },
"prague": { "target": 6, "max": 9, "baseFeeUpdateFraction": 5007716 },
"osaka": { "target": 6, "max": 9, "baseFeeUpdateFraction": 5007716 },
"cancun": { "baseFeeUpdateFraction": 3338477, "max": 6, "target": 3 },
"prague": { "baseFeeUpdateFraction": 5007716, "max": 9, "target": 6 },
"osaka": { "baseFeeUpdateFraction": 5007716, "max": 9, "target": 6 },
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Related to the reordering of fields

Comment on lines -116 to +118
pub target: u32,
pub max: u32,
pub base_fee_update_fraction: u64,
pub max: u32,
pub target: u32,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reordered the fields so they'd get serialized in alphabetical order, since the spec for the methods requires them to be so

Fork::Cancun => SpecId::CANCUN,
Fork::Prague => SpecId::PRAGUE,
Fork::Osaka => SpecId::OSAKA,
_ => SpecId::LATEST,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't sure what to do with the BPO forks given this version of REVM doesn't account for them, so I included this as a placeholder. If anyone has a better idea please let me know

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can do the same as in #4643 (comment) and map it to the "real" fork right before BPO forks (in this case Osaka).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Might be worth to just wait for that PR to be merged before this one TBH, since the share a bunch of changes

Comment on lines +145 to +147
let block_number = context.storage.get_latest_block_number().await?;
let fork_id = if let Some(timestamp) = activation_time {
ForkId::new(chain_config, genesis_header, timestamp, block_number).fork_hash
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does passing the latest block number here work? I thought the timestamp and block number need to be for the block which we want the fork ID from.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I only meant for this to work properly with post-merge forks to be fair; since the EIP only requires the method to work form Cancun onwards. Probably should add a proper check and return an error for pre-Merge forks though (the EIP doesn't require the method to work for Paris and Shanghai either, but it's simple enough to return something sensible for them)

Copy link
Collaborator

@MegaRedHand MegaRedHand Sep 26, 2025

Choose a reason for hiding this comment

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

Returning an error for pre-Paris forks is OK. We should also add a comment here, mentioning the block number is not really correct, but being enough for post-merge forks.

.storage
.get_block_by_number(context.storage.get_latest_block_number().await?)
.await?
.unwrap_or_default()
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is the default here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both blockheader and blockbody implement default, which I assume sets the default value for their respective fields (0 for numeric ones, None for options, etc). I'm not sure if that was the best way to handle the unwrap though. Would you say it's better to just use expect? Since getting the latest block number but that block number being missing shouldn't ever really happen

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I don't think .unwrap_or_default() is the correct way of dealing with this. You should return an RpcError if there is no block

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, fixed it here!


async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let chain_config = context.storage.get_chain_config()?;
let latest_block_timestamp = context
Copy link
Collaborator

Choose a reason for hiding this comment

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

are you sure you need the latest block timestamp and not the current time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't the current time for configuration purposes be the timestamp of the latest block?

Copy link
Collaborator

Choose a reason for hiding this comment

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

not necesarily, you might be in a transition between forks. A block with the newest fork hasn't arrived yet, but the time for the old fork has passed.

I would verify how Geth does it

Copy link
Contributor Author

@SDartayet SDartayet Sep 30, 2025

Choose a reason for hiding this comment

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

From what I can see in this file, Geth also gets the latest block header. The relevant lines start at 1207, and what it does is

var (
	c = api.b.ChainConfig()
	t = api.b.CurrentHeader().Time
)
        resp := configResponse{
	Next:    assemble(c, c.Timestamp(c.LatestFork(t)+1)),
	Current: assemble(c, c.Timestamp(c.LatestFork(t))),
	Last:    assemble(c, c.Timestamp(c.LatestFork(^uint64(0)))),
}

CurrentHeader() just fetches the latest blockheader, assemble is the function that builds the eth-config response object

pub fn next_fork(&self, block_timestamp: u64) -> Option<Fork> {
let next = if self.is_bpo5_activated(block_timestamp) {
None
} else if self.is_bpo4_activated(block_timestamp) && self.bpo5_time.is_some() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems we need to implement ordering of forks, then this would be simpler

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think just ordering of forks would suffice; unless there's an approach I'm missing. One, because just ordering wouldn't be enough to ask a fork what the fork after it would be; we'd still need to implement from for Fork or ahave an array with all the enum variants for Fork indexed by its number (or use a crate like strum. Two, because we'd also need to know if the fork is activated, which could only be done if the ChainConfig struct had an array for the fork timestamps we were indexing with the number assigned to the fork.
I still think doing the necessary refactors for that would be worth it, for the sake of future maintainability reasons too; I decided not to do it to not make this PR too big since it seemed a bit out of scope.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok, please create a follow up ticket. Not planning to work on this right now, but it's good to have one

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@SDartayet SDartayet added this pull request to the merge queue Oct 1, 2025
Merged via the queue into main with commit 7b08d9e Oct 1, 2025
55 checks passed
@SDartayet SDartayet deleted the eth-config-method branch October 1, 2025 15:42
@github-project-automation github-project-automation bot moved this from In Review to Done in ethrex_l1 Oct 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
L1 Ethereum client
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Implement eth_config endpoint
3 participants