Skip to content

Commit db67137

Browse files
authored
feat: fusaka support (#704)
* feat: fusaka support * fix blob base fee calculation * fix clippy * update checkpoints * bump version
1 parent 148b89a commit db67137

File tree

13 files changed

+467
-268
lines changed

13 files changed

+467
-268
lines changed

Cargo.lock

Lines changed: 334 additions & 190 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ autobenches = false
66
exclude = ["benches"]
77

88
[workspace.package]
9-
version = "0.9.0"
9+
version = "0.9.1"
1010

1111
[workspace]
1212
members = [
@@ -40,7 +40,7 @@ sha2 = "0.9"
4040
bls12_381 = { version = "0.8.0", features = ["experimental"] }
4141

4242
# execution
43-
alloy = { version = "1.0.3", features = [
43+
alloy = { version = "1.0.37", features = [
4444
"rpc-types",
4545
"consensus",
4646
"rlp",
@@ -52,9 +52,9 @@ alloy = { version = "1.0.3", features = [
5252
"json-rpc",
5353
"signers",
5454
] }
55-
alloy-trie = { version = "0.9", features = ["ethereum"] }
56-
op-alloy-rpc-types = "0.18.0"
57-
revm = { version = "22.0.1", default-features = false, features = [
55+
alloy-trie = { version = "0.9.1", features = ["ethereum"] }
56+
op-alloy-rpc-types = "0.20.0"
57+
revm = { version = "29.0.1", default-features = false, features = [
5858
"std",
5959
"serde",
6060
"optional_block_gas_limit",
@@ -109,7 +109,7 @@ dotenv = "0.15.0"
109109
helios-verifiable-api-server = { path = "verifiable-api/server" }
110110

111111
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
112-
alloy = { version = "1.0.3", features = ["full"] }
112+
alloy = { version = "1.0.37", features = ["full"] }
113113
tokio = { version = "1", features = ["full"] }
114114
eyre = "0.6.8"
115115
dirs = "5.0.1"

common/src/fork_schedule.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub struct ForkSchedule {
2121
pub shanghai_timestamp: u64,
2222
pub cancun_timestamp: u64,
2323
pub prague_timestamp: u64,
24-
// Osaka and beyond TBD
24+
pub osaka_timestamp: u64,
2525

2626
// Optimism Forks
2727
pub bedrock_timestamp: u64,
@@ -56,6 +56,7 @@ impl Default for ForkSchedule {
5656
shanghai_timestamp: u64::MAX,
5757
cancun_timestamp: u64::MAX,
5858
prague_timestamp: u64::MAX,
59+
osaka_timestamp: u64::MAX,
5960

6061
bedrock_timestamp: u64::MAX,
6162
regolith_timestamp: u64::MAX,
@@ -68,3 +69,15 @@ impl Default for ForkSchedule {
6869
}
6970
}
7071
}
72+
73+
impl ForkSchedule {
74+
/// Get the blob base fee update fraction for a given timestamp.
75+
/// The fraction changes from Cancun to Prague according to EIP-7892.
76+
pub fn get_blob_base_fee_update_fraction(&self, timestamp: u64) -> u64 {
77+
if timestamp >= self.prague_timestamp {
78+
5007716 // Prague and later (EIP-7892)
79+
} else {
80+
3338477 // Cancun
81+
}
82+
}
83+
}

core/src/client/node.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,13 @@ impl<N: NetworkSpec, C: Consensus<N::BlockResponse>, E: ExecutionProvider<N>> He
420420
.ok_or(eyre!(ClientError::BlockNotFound(block_id)))?;
421421

422422
if let Some(excess_blob_gas) = block.header().excess_blob_gas() {
423-
let is_prague = block.header().timestamp() >= self.fork_schedule.prague_timestamp;
424-
let price = BlobExcessGasAndPrice::new(excess_blob_gas, is_prague).blob_gasprice;
423+
// Get blob base fee update fraction based on fork
424+
let blob_base_fee_update_fraction = self
425+
.fork_schedule
426+
.get_blob_base_fee_update_fraction(block.header().timestamp());
427+
428+
let price = BlobExcessGasAndPrice::new(excess_blob_gas, blob_base_fee_update_fraction)
429+
.blob_gasprice;
425430
Ok(U256::from(price))
426431
} else {
427432
Ok(U256::ZERO)

ethereum/consensus-core/src/consensus_core.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,12 @@ fn apply_update_no_quorum_check<S: ConsensusSpec>(
227227
store.optimistic_header = store.finalized_header.clone();
228228
}
229229

230-
if store.finalized_header.beacon().slot % S::slots_per_epoch() == 0 {
230+
if store
231+
.finalized_header
232+
.beacon()
233+
.slot
234+
.is_multiple_of(S::slots_per_epoch())
235+
{
231236
let checkpoint = store.finalized_header.beacon().tree_hash_root();
232237
return Some(checkpoint);
233238
}

ethereum/consensus-core/src/proof.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ fn is_proof_valid<T: TreeHash>(
9696
let mut hasher = Sha256::new();
9797

9898
for (i, node) in branch.iter().enumerate() {
99-
if (index / 2usize.pow(i as u32)) % 2 != 0 {
99+
if !(index / 2usize.pow(i as u32)).is_multiple_of(2) {
100100
hasher.update(node);
101101
hasher.update(derived_root);
102102
} else {

ethereum/consensus-core/src/types/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,7 @@ pub struct Forks {
586586
pub capella: Fork,
587587
pub deneb: Fork,
588588
pub electra: Fork,
589+
pub fusaka: Fork,
589590
}
590591

591592
#[derive(Serialize, Deserialize, Debug, Default, Clone)]

ethereum/consensus-core/tests/runner/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,9 @@ fn get_forks(with_electra: bool) -> Forks {
183183
epoch: if with_electra { 0 } else { u64::MAX },
184184
fork_version: fixed_bytes!("05000001"),
185185
},
186+
fusaka: Fork {
187+
epoch: u64::MAX,
188+
fork_version: fixed_bytes!("06000001"),
189+
},
186190
}
187191
}

ethereum/src/config/networks.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ impl Network {
7878
pub fn mainnet() -> BaseConfig {
7979
BaseConfig {
8080
default_checkpoint: b256!(
81-
"0xe4163704b79dbb52a91ba6be1ae6f5504b060522f5495c73b6c55865412b428c"
81+
"9b41a80f58c52068a00e8535b8d6704769c7577a5fd506af5e0c018687991d55"
8282
),
8383
rpc_port: 8545,
8484
consensus_rpc: Some(Url::parse("https://ethereum.operationsolarstorm.org").unwrap()),
@@ -112,6 +112,10 @@ pub fn mainnet() -> BaseConfig {
112112
epoch: 364032,
113113
fork_version: fixed_bytes!("05000000"),
114114
},
115+
fusaka: Fork {
116+
epoch: 411392,
117+
fork_version: fixed_bytes!("06000000"),
118+
},
115119
},
116120
execution_forks: EthereumForkSchedule::mainnet(),
117121
max_checkpoint_age: 1_209_600, // 14 days
@@ -124,7 +128,7 @@ pub fn mainnet() -> BaseConfig {
124128
pub fn sepolia() -> BaseConfig {
125129
BaseConfig {
126130
default_checkpoint: b256!(
127-
"234931a3fe5d791f06092477357e2d65dcf6fa6cad048680eb93ad3ea494bbcd"
131+
"4065c2509eaa15dbe60e1f80cff5205a532aa95aaa1d73c1c286f7f8535555d4"
128132
),
129133
rpc_port: 8545,
130134
consensus_rpc: None,
@@ -158,6 +162,10 @@ pub fn sepolia() -> BaseConfig {
158162
epoch: 222464,
159163
fork_version: fixed_bytes!("90000074"),
160164
},
165+
fusaka: Fork {
166+
epoch: 272640,
167+
fork_version: fixed_bytes!("90000075"),
168+
},
161169
},
162170
execution_forks: EthereumForkSchedule::sepolia(),
163171
max_checkpoint_age: 1_209_600, // 14 days
@@ -170,7 +178,7 @@ pub fn sepolia() -> BaseConfig {
170178
pub fn holesky() -> BaseConfig {
171179
BaseConfig {
172180
default_checkpoint: b256!(
173-
"bb1f40340606d3b6d6d610b9933b388ddab585fc8898320c29eb771f75c61b48"
181+
"e1f575f0b691404fe82cce68a09c2c98af197816de14ce53c0fe9f9bd02d2399"
174182
),
175183
rpc_port: 8545,
176184
consensus_rpc: None,
@@ -204,6 +212,10 @@ pub fn holesky() -> BaseConfig {
204212
epoch: 115968,
205213
fork_version: fixed_bytes!("06017000"),
206214
},
215+
fusaka: Fork {
216+
epoch: 165120,
217+
fork_version: fixed_bytes!("07017000"),
218+
},
207219
},
208220
execution_forks: EthereumForkSchedule::holesky(),
209221
max_checkpoint_age: 1_209_600, // 14 days
@@ -216,7 +228,7 @@ pub fn holesky() -> BaseConfig {
216228
pub fn hoodi() -> BaseConfig {
217229
BaseConfig {
218230
default_checkpoint: b256!(
219-
"689dc3d39faf53c360ada45a734139bfb195f96d04416c797bb0c1a46da903ad"
231+
"3335028555f5fff431f82f978d2503ed59bc8da00a86217eea9befa9d486a049"
220232
),
221233
rpc_port: 8545,
222234
consensus_rpc: None,
@@ -250,6 +262,10 @@ pub fn hoodi() -> BaseConfig {
250262
epoch: 2048,
251263
fork_version: fixed_bytes!("60000910"),
252264
},
265+
fusaka: Fork {
266+
epoch: 50688,
267+
fork_version: fixed_bytes!("70000910"),
268+
},
253269
},
254270
execution_forks: EthereumForkSchedule::hoodi(),
255271
max_checkpoint_age: 1_209_600, // 14 days
@@ -290,6 +306,7 @@ impl EthereumForkSchedule {
290306
shanghai_timestamp: 1681338455,
291307
cancun_timestamp: 1710338135,
292308
prague_timestamp: 1746612311,
309+
osaka_timestamp: 1764798551,
293310

294311
..Default::default()
295312
}
@@ -315,6 +332,7 @@ impl EthereumForkSchedule {
315332
shanghai_timestamp: 1677557088,
316333
cancun_timestamp: 1706655072,
317334
prague_timestamp: 1741159776,
335+
osaka_timestamp: 1760427360,
318336

319337
..Default::default()
320338
}
@@ -340,6 +358,7 @@ impl EthereumForkSchedule {
340358
shanghai_timestamp: 1696000704,
341359
cancun_timestamp: 1707305664,
342360
prague_timestamp: 1740434112,
361+
osaka_timestamp: 1759308480,
343362

344363
..Default::default()
345364
}
@@ -365,6 +384,7 @@ impl EthereumForkSchedule {
365384
shanghai_timestamp: 0,
366385
cancun_timestamp: 0,
367386
prague_timestamp: 1742999832,
387+
osaka_timestamp: 1761677592,
368388

369389
..Default::default()
370390
}

ethereum/src/evm.rs

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use eyre::Result;
1010
use revm::{
1111
context::{result::ExecutionResult, BlockEnv, CfgEnv, ContextTr, TxEnv},
1212
context_interface::block::BlobExcessGasAndPrice,
13-
primitives::{hardfork::SpecId, Address},
13+
primitives::{hardfork::SpecId, Address, U256},
1414
Context, ExecuteEvm, MainBuilder, MainContext,
1515
};
1616
use tracing::debug;
@@ -57,33 +57,33 @@ impl<E: ExecutionProvider<Ethereum>> EthereumEvm<E> {
5757
let mut db = ProofDB::new(self.block_id, self.execution.clone());
5858
_ = db.state.prefetch_state(tx, validate_tx).await;
5959

60-
let mut evm = self
61-
.get_context(tx, self.block_id, validate_tx)
62-
.await?
63-
.with_db(db)
64-
.build_mainnet();
65-
66-
let tx_res = loop {
67-
let db = evm.db();
68-
if db.state.needs_update() {
69-
debug!("evm cache miss: {:?}", db.state.access.as_ref().unwrap());
70-
db.state
71-
.update_state()
72-
.await
73-
.map_err(|e| EvmError::Generic(e.to_string()))?;
60+
// Pre-fetch all required state
61+
loop {
62+
if !db.state.needs_update() {
63+
break;
7464
}
65+
debug!("evm cache miss: {:?}", db.state.access.as_ref().unwrap());
66+
db.state
67+
.update_state()
68+
.await
69+
.map_err(|e| EvmError::Generic(e.to_string()))?;
70+
}
7571

76-
let res = evm.replay();
77-
78-
let db = evm.db();
79-
let needs_update = db.state.needs_update();
72+
// Now execute synchronously with all state loaded
73+
let context = self.get_context(tx, self.block_id, validate_tx).await?;
8074

81-
if res.is_ok() || !needs_update {
82-
break res.map(|res| (res.result, mem::take(&mut db.state.accounts)));
83-
}
75+
// Execute in a scope to ensure EVM is dropped before any async operations
76+
let (result, accounts) = {
77+
let mut evm = context.with_db(db).build_mainnet();
78+
let res = evm.replay();
79+
let db = evm.db_mut();
80+
let accounts = mem::take(&mut db.state.accounts);
81+
(res, accounts)
8482
};
8583

86-
tx_res.map_err(|err| EvmError::Generic(format!("generic: {err}")))
84+
result
85+
.map(|r| (r.result, accounts))
86+
.map_err(|err| EvmError::Generic(format!("generic: {err}")))
8787
}
8888

8989
async fn get_context(
@@ -157,17 +157,20 @@ impl<E: ExecutionProvider<Ethereum>> EthereumEvm<E> {
157157
}
158158

159159
fn block_env(block: &Block<Transaction, Header>, fork_schedule: &ForkSchedule) -> BlockEnv {
160-
let is_prague = block.header.timestamp >= fork_schedule.prague_timestamp;
160+
// Get blob base fee update fraction based on fork
161+
let blob_base_fee_update_fraction =
162+
fork_schedule.get_blob_base_fee_update_fraction(block.header.timestamp());
163+
161164
let blob_excess_gas_and_price = block
162165
.header
163166
.excess_blob_gas()
164-
.map(|v| BlobExcessGasAndPrice::new(v, is_prague))
165-
.unwrap_or_else(|| BlobExcessGasAndPrice::new(0, is_prague));
167+
.map(|v| BlobExcessGasAndPrice::new(v, blob_base_fee_update_fraction))
168+
.unwrap_or_else(|| BlobExcessGasAndPrice::new(0, blob_base_fee_update_fraction));
166169

167170
BlockEnv {
168-
number: block.header.number(),
171+
number: U256::from(block.header.number()),
169172
beneficiary: block.header.beneficiary(),
170-
timestamp: block.header.timestamp(),
173+
timestamp: U256::from(block.header.timestamp()),
171174
gas_limit: block.header.gas_limit(),
172175
basefee: block.header.base_fee_per_gas().unwrap_or_default(),
173176
difficulty: block.header.difficulty(),
@@ -178,7 +181,9 @@ impl<E: ExecutionProvider<Ethereum>> EthereumEvm<E> {
178181
}
179182

180183
pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSchedule) -> SpecId {
181-
if timestamp >= fork_schedule.prague_timestamp {
184+
if timestamp >= fork_schedule.osaka_timestamp {
185+
SpecId::OSAKA
186+
} else if timestamp >= fork_schedule.prague_timestamp {
182187
SpecId::PRAGUE
183188
} else if timestamp >= fork_schedule.cancun_timestamp {
184189
SpecId::CANCUN

0 commit comments

Comments
 (0)