diff --git a/cmd/ethrex/l2/options.rs b/cmd/ethrex/l2/options.rs index 605e113c5ad..02446ca3c1a 100644 --- a/cmd/ethrex/l2/options.rs +++ b/cmd/ethrex/l2/options.rs @@ -189,6 +189,7 @@ impl TryFrom for SequencerConfig { maximum_allowed_max_fee_per_blob_gas: opts .eth_opts .maximum_allowed_max_fee_per_blob_gas, + osaka_activation_time: opts.eth_opts.osaka_activation_time, }, l1_watcher: L1WatcherConfig { bridge_address: opts @@ -339,6 +340,13 @@ pub struct EthOptions { help_heading = "Eth options" )] pub max_retry_delay: u64, + #[clap( + long, + value_name = "UINT64", + env = "ETHREX_OSAKA_ACTIVATION_TIME", + help = "Block timestamp at which the Osaka fork is activated on L1. If not set, it will assume Osaka is already active." + )] + pub osaka_activation_time: Option, } impl Default for EthOptions { @@ -353,6 +361,7 @@ impl Default for EthOptions { backoff_factor: BACKOFF_FACTOR, min_retry_delay: MIN_RETRY_DELAY, max_retry_delay: MAX_RETRY_DELAY, + osaka_activation_time: None, } } } diff --git a/crates/common/crypto/kzg.rs b/crates/common/crypto/kzg.rs index a7d16e11310..08608803a42 100644 --- a/crates/common/crypto/kzg.rs +++ b/crates/common/crypto/kzg.rs @@ -69,7 +69,11 @@ pub fn verify_cell_kzg_proof_batch( let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE); let mut cells = Vec::new(); for blob in blobs { - cells.extend(c_kzg_settings.compute_cells(&(*blob).into())?.into_iter()); + let blob: c_kzg::Blob = (*blob).into(); + let cells_blob = c_kzg_settings + .compute_cells(&blob) + .map_err(KzgError::CKzg)?; + cells.extend(*cells_blob); } c_kzg::KzgSettings::verify_cell_kzg_proof_batch( c_kzg_settings, @@ -180,15 +184,35 @@ pub fn verify_kzg_proof( pub fn blob_to_kzg_commitment_and_proof(blob: &Blob) -> Result<(Commitment, Proof), KzgError> { let blob: c_kzg::Blob = (*blob).into(); - let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment( - c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE), - &blob, - )?; - let commitment_bytes = commitment.to_bytes(); let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE); + + let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment(c_kzg_settings, &blob)?; + + let commitment_bytes = commitment.to_bytes(); let proof = c_kzg_settings.compute_blob_kzg_proof(&blob, &commitment_bytes)?; let proof_bytes = proof.to_bytes(); Ok((commitment_bytes.into_inner(), proof_bytes.into_inner())) } + +#[cfg(feature = "c-kzg")] +pub fn blob_to_commitment_and_cell_proofs( + blob: &Blob, +) -> Result<(Commitment, Vec), KzgError> { + let c_kzg_settings = c_kzg::ethereum_kzg_settings(KZG_PRECOMPUTE); + + let blob: c_kzg::Blob = (*blob).into(); + + let commitment = c_kzg::KzgSettings::blob_to_kzg_commitment(c_kzg_settings, &blob)?; + + let commitment_bytes = commitment.to_bytes(); + + let (_cells, cell_proofs) = c_kzg_settings + .compute_cells_and_kzg_proofs(&blob) + .map_err(KzgError::CKzg)?; + + let cell_proofs = cell_proofs.map(|p| p.to_bytes().into_inner()); + + Ok((commitment_bytes.into_inner(), cell_proofs.to_vec())) +} diff --git a/crates/common/types/blobs_bundle.rs b/crates/common/types/blobs_bundle.rs index e670aa05ca3..8126098a113 100644 --- a/crates/common/types/blobs_bundle.rs +++ b/crates/common/types/blobs_bundle.rs @@ -1,6 +1,8 @@ use std::ops::AddAssign; use crate::serde_utils; +#[cfg(feature = "c-kzg")] +use crate::types::Fork; use crate::types::constants::VERSIONED_HASH_VERSION_KZG; use crate::{Bytes, H256}; @@ -80,23 +82,34 @@ impl BlobsBundle { // In the future we might want to provide a new method that calculates the commitments and proofs using the following. #[cfg(feature = "c-kzg")] - pub fn create_from_blobs(blobs: &Vec) -> Result { - use ethrex_crypto::kzg::blob_to_kzg_commitment_and_proof; + pub fn create_from_blobs( + blobs: &Vec, + wrapper_version: Option, + ) -> Result { + use ethrex_crypto::kzg::{ + blob_to_commitment_and_cell_proofs, blob_to_kzg_commitment_and_proof, + }; let mut commitments = Vec::new(); let mut proofs = Vec::new(); // Populate the commitments and proofs for blob in blobs { - let (commitment, proof) = blob_to_kzg_commitment_and_proof(blob)?; - commitments.push(commitment); - proofs.push(proof); + if wrapper_version.unwrap_or(0) == 0 { + let (commitment, proof) = blob_to_kzg_commitment_and_proof(blob)?; + commitments.push(commitment); + proofs.push(proof); + } else { + let (commitment, cell_proofs) = blob_to_commitment_and_cell_proofs(blob)?; + commitments.push(commitment); + proofs.extend(cell_proofs); + } } Ok(Self { blobs: blobs.clone(), commitments, proofs, - version: 0, + version: wrapper_version.unwrap_or(0), }) } @@ -127,6 +140,10 @@ impl BlobsBundle { return Err(BlobsBundleError::BlobBundleEmptyError); } + if self.version == 0 && fork >= Fork::Osaka || self.version != 0 && fork < Fork::Osaka { + return Err(BlobsBundleError::InvalidBlobVersionForFork); + } + // Check if the blob versioned hashes and blobs bundle content length mismatch if blob_count != self.commitments.len() || (self.version == 0 && blob_count != self.proofs.len()) @@ -231,6 +248,8 @@ pub enum BlobsBundleError { BlobToCommitmentAndProofError, #[error("Max blobs per block exceeded")] MaxBlobsExceeded, + #[error("Invalid blob version for the current fork")] + InvalidBlobVersionForFork, #[cfg(feature = "c-kzg")] #[error("KZG related error: {0}")] Kzg(#[from] ethrex_crypto::kzg::KzgError), @@ -259,7 +278,7 @@ mod tests { }) .collect(); - let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs) + let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, None) .expect("Failed to create blobs bundle"); let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes(); @@ -284,6 +303,78 @@ mod tests { )); } + #[test] + #[cfg(feature = "c-kzg")] + fn transaction_with_valid_blobs_should_pass_on_osaka() { + let blobs = vec!["Hello, world!".as_bytes(), "Goodbye, world!".as_bytes()] + .into_iter() + .map(|data| { + crate::types::blobs_bundle::blob_from_bytes(data.into()) + .expect("Failed to create blob") + }) + .collect(); + + let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, Some(1)) + .expect("Failed to create blobs bundle"); + + let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes(); + + let tx = crate::types::transaction::EIP4844Transaction { + nonce: 3, + max_priority_fee_per_gas: 0, + max_fee_per_gas: 0, + max_fee_per_blob_gas: 0.into(), + gas: 15_000_000, + to: crate::Address::from_low_u64_be(1), // Normal tx + value: crate::U256::zero(), // Value zero + data: crate::Bytes::default(), // No data + access_list: Default::default(), // No access list + blob_versioned_hashes, + ..Default::default() + }; + + assert!(matches!( + blobs_bundle.validate(&tx, crate::types::Fork::Osaka), + Ok(()) + )); + } + + #[test] + #[cfg(feature = "c-kzg")] + fn transaction_with_invalid_fork_should_fail() { + let blobs = vec!["Hello, world!".as_bytes(), "Goodbye, world!".as_bytes()] + .into_iter() + .map(|data| { + crate::types::blobs_bundle::blob_from_bytes(data.into()) + .expect("Failed to create blob") + }) + .collect(); + + let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, Some(1)) + .expect("Failed to create blobs bundle"); + + let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes(); + + let tx = crate::types::transaction::EIP4844Transaction { + nonce: 3, + max_priority_fee_per_gas: 0, + max_fee_per_gas: 0, + max_fee_per_blob_gas: 0.into(), + gas: 15_000_000, + to: crate::Address::from_low_u64_be(1), // Normal tx + value: crate::U256::zero(), // Value zero + data: crate::Bytes::default(), // No data + access_list: Default::default(), // No access list + blob_versioned_hashes, + ..Default::default() + }; + + assert!(!matches!( + blobs_bundle.validate(&tx, crate::types::Fork::Prague), + Ok(()) + )); + } + #[test] #[cfg(feature = "c-kzg")] fn transaction_with_invalid_proofs_should_fail() { @@ -396,7 +487,7 @@ mod tests { let blobs = std::iter::repeat_n(blob, super::MAX_BLOB_COUNT_ELECTRA + 1).collect::>(); - let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs) + let blobs_bundle = crate::types::BlobsBundle::create_from_blobs(&blobs, None) .expect("Failed to create blobs bundle"); let blob_versioned_hashes = blobs_bundle.generate_versioned_hashes(); diff --git a/crates/common/types/transaction.rs b/crates/common/types/transaction.rs index f5457c28a8d..c3bc9243944 100644 --- a/crates/common/types/transaction.rs +++ b/crates/common/types/transaction.rs @@ -2167,6 +2167,7 @@ mod serde_impl { pub authorization_list: Option>, #[serde(default)] pub blob_versioned_hashes: Vec, + pub wrapper_version: Option, #[serde(default, with = "crate::serde_utils::bytes::vec")] pub blobs: Vec, #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")] @@ -2232,6 +2233,7 @@ mod serde_impl { authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], + wrapper_version: None, chain_id: Some(value.chain_id), from: Address::default(), } @@ -2286,6 +2288,7 @@ mod serde_impl { authorization_list: None, blob_versioned_hashes: value.blob_versioned_hashes, blobs: vec![], + wrapper_version: None, chain_id: Some(value.chain_id), from: Address::default(), } @@ -2308,10 +2311,11 @@ mod serde_impl { }) .collect(); + let wrapper_version = value.wrapper_version; Ok(Self { tx: value.try_into()?, - wrapper_version: None, - blobs_bundle: BlobsBundle::create_from_blobs(&blobs)?, + wrapper_version, + blobs_bundle: BlobsBundle::create_from_blobs(&blobs, wrapper_version)?, }) } } @@ -2374,6 +2378,7 @@ mod serde_impl { ), blob_versioned_hashes: vec![], blobs: vec![], + wrapper_version: None, chain_id: Some(value.chain_id), from: Address::default(), } @@ -2401,6 +2406,7 @@ mod serde_impl { authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], + wrapper_version: None, chain_id: Some(value.chain_id), from: value.from, } @@ -2451,6 +2457,7 @@ mod serde_impl { authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], + wrapper_version: None, chain_id: None, input: value.data, } @@ -2481,6 +2488,7 @@ mod serde_impl { authorization_list: None, blob_versioned_hashes: vec![], blobs: vec![], + wrapper_version: None, chain_id: Some(value.chain_id), input: value.data, } @@ -2831,6 +2839,7 @@ mod tests { }], blob_versioned_hashes: Default::default(), blobs: Default::default(), + wrapper_version: None, chain_id: Default::default(), authorization_list: None, }; @@ -2884,6 +2893,7 @@ mod tests { }], blob_versioned_hashes: Default::default(), blobs: Default::default(), + wrapper_version: None, chain_id: Default::default(), authorization_list: None, }; diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 085f9ce26ac..b74e77cf15b 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -45,7 +45,10 @@ L1_AUTH_PORT=8551 L1_RPC_ADDRESS=0.0.0.0 L2_RPC_ADDRESS=0.0.0.0 PROOF_COORDINATOR_ADDRESS?=127.0.0.1 -ETHREX_BLOCK_PRODUCER_OPERATOR_FEE_PER_GAS?=1000000000 \ +ETHREX_BLOCK_PRODUCER_OPERATOR_FEE_PER_GAS?=1000000000 +# Timestamp for Osaka activation in the hoodi. Sufficient to start network with Prague for tests. +# Future: initialize network with Osaka activated once supported. +ETHREX_OSAKA_ACTIVATION_TIME?=1761677592 # Matches the ports used by the blockchain/metrics dir L2_PROMETHEUS_METRICS_PORT = 3702 @@ -140,6 +143,7 @@ init-l2: ## 🚀 Initializes an L2 Lambda ethrex Client --l1.bridge-address ${DEFAULT_BRIDGE_ADDRESS} \ --l1.on-chain-proposer-address ${DEFAULT_ON_CHAIN_PROPOSER_ADDRESS} \ --eth.rpc-url ${L1_RPC_URL} \ + --osaka-activation-time ${ETHREX_OSAKA_ACTIVATION_TIME} \ --block-producer.coinbase-address 0x0007a881CD95B1484fca47615B64803dad620C8d \ --block-producer.base-fee-vault-address 0x000c0d6b7c4516a5b274c51ea331a9410fe69127 \ --block-producer.operator-fee-vault-address 0xd5d2a85751b6F158e5b9B8cD509206A865672362 \ @@ -151,7 +155,7 @@ init-l2: ## 🚀 Initializes an L2 Lambda ethrex Client init-l2-dev: ## 🚀 Initializes an L1 and L2 Lambda ethrex Client COMPILE_CONTRACTS=true \ cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- \ - l2 --dev + l2 --dev --osaka-activation-time ${ETHREX_OSAKA_ACTIVATION_TIME} init-metrics: ## 🚀 Initializes Grafana and Prometheus with containers L1_RPC_URL=${L1_RPC_URL} \ diff --git a/crates/l2/based/block_fetcher.rs b/crates/l2/based/block_fetcher.rs index 39f871a92e3..26d78a5cb39 100644 --- a/crates/l2/based/block_fetcher.rs +++ b/crates/l2/based/block_fetcher.rs @@ -13,7 +13,7 @@ use ethrex_l2_common::{ privileged_transactions::compute_privileged_transactions_hash, state_diff::prepare_state_diff, }; -use ethrex_l2_sdk::{get_last_committed_batch, get_last_fetched_l1_block}; +use ethrex_l2_sdk::{get_l1_active_fork, get_last_committed_batch, get_last_fetched_l1_block}; use ethrex_rlp::decode::RLPDecode; use ethrex_rpc::{EthClient, types::receipt::RpcLog}; use ethrex_storage::Store; @@ -93,6 +93,7 @@ pub struct BlockFetcher { fetch_interval_ms: u64, last_l1_block_fetched: U256, fetch_block_step: U256, + osaka_activation_time: Option, } impl BlockFetcher { @@ -118,6 +119,7 @@ impl BlockFetcher { fetch_interval_ms: cfg.based.block_fetcher.fetch_interval_ms, last_l1_block_fetched, fetch_block_step: cfg.based.block_fetcher.fetch_block_step.into(), + osaka_activation_time: cfg.eth.osaka_activation_time, }) } @@ -388,8 +390,11 @@ impl BlockFetcher { ) .map_err(|_| BlockFetcherError::BlobBundleError)?; - let (blobs_bundle, _) = - generate_blobs_bundle(&state_diff).map_err(|_| BlockFetcherError::BlobBundleError)?; + let l1_fork = get_l1_active_fork(&self.eth_client, self.osaka_activation_time) + .await + .map_err(BlockFetcherError::EthClientError)?; + let (blobs_bundle, _) = generate_blobs_bundle(&state_diff, l1_fork) + .map_err(|_| BlockFetcherError::BlobBundleError)?; Ok(Batch { number: batch_number.as_u64(), diff --git a/crates/l2/docker-compose.yaml b/crates/l2/docker-compose.yaml index 3c6b43dad4a..043877c7f4d 100644 --- a/crates/l2/docker-compose.yaml +++ b/crates/l2/docker-compose.yaml @@ -95,6 +95,7 @@ services: - ETHREX_STATE_UPDATER_SEQUENCER_REGISTRY=${ETHREX_STATE_UPDATER_SEQUENCER_REGISTRY:-0x0000000000000000000000000000000000000000} - ETHREX_COMMITTER_COMMIT_TIME=${ETHREX_COMMITTER_COMMIT_TIME:-60000} - ETHREX_WATCHER_WATCH_INTERVAL=${ETHREX_WATCHER_WATCH_INTERVAL:-12000} + - ETHREX_OSAKA_ACTIVATION_TIME=${ETHREX_OSAKA_ACTIVATION_TIME:-1761677592} volumes: - ../../fixtures/genesis/l2.json:/genesis/l2.json - env:/env/ diff --git a/crates/l2/monitor/app.rs b/crates/l2/monitor/app.rs index 67e1dee20f2..8508149ab6d 100644 --- a/crates/l2/monitor/app.rs +++ b/crates/l2/monitor/app.rs @@ -72,6 +72,8 @@ pub struct EthrexMonitorWidget { pub rollup_store: StoreRollup, pub last_scroll: Instant, pub overview_selected_widget: usize, + + pub osaka_activation_time: Option, } #[derive(Clone, Debug)] @@ -225,6 +227,7 @@ impl EthrexMonitorWidget { rollup_store, last_scroll: Instant::now(), overview_selected_widget: 0, + osaka_activation_time: cfg.eth.osaka_activation_time, }; monitor_widget.selected_table().selected(true); monitor_widget.on_tick().await?; @@ -334,7 +337,11 @@ impl EthrexMonitorWidget { .await?; self.mempool.on_tick(&self.rollup_client).await?; self.batches_table - .on_tick(&self.eth_client, &self.rollup_store) + .on_tick( + &self.eth_client, + &self.rollup_store, + self.osaka_activation_time, + ) .await?; self.blocks_table.on_tick(&self.store).await?; self.l1_to_l2_messages diff --git a/crates/l2/monitor/widget/batches.rs b/crates/l2/monitor/widget/batches.rs index 855298b1610..07be31f03c1 100644 --- a/crates/l2/monitor/widget/batches.rs +++ b/crates/l2/monitor/widget/batches.rs @@ -1,5 +1,8 @@ -use ethrex_common::{Address, H256, types::batch::Batch}; -use ethrex_l2_sdk::get_last_committed_batch; +use ethrex_common::{ + Address, H256, + types::{Fork, batch::Batch}, +}; +use ethrex_l2_sdk::{get_l1_active_fork, get_last_committed_batch}; use ethrex_rpc::EthClient; use ethrex_storage_rollup::StoreRollup; use ratatui::{ @@ -50,27 +53,37 @@ impl BatchesTable { &mut self, eth_client: &EthClient, rollup_store: &StoreRollup, + osaka_activation_time: Option, ) -> Result<(), MonitorError> { let mut new_latest_batches = Self::fetch_new_items( &mut self.last_l1_block_fetched, self.on_chain_proposer_address, eth_client, rollup_store, + osaka_activation_time, ) .await?; new_latest_batches.truncate(BATCH_WINDOW_SIZE); + let l1_fork = get_l1_active_fork(eth_client, osaka_activation_time) + .await + .map_err(MonitorError::EthClientError)?; + let n_new_latest_batches = new_latest_batches.len(); self.items .truncate(BATCH_WINDOW_SIZE - n_new_latest_batches); - self.refresh_items(rollup_store).await?; + self.refresh_items(rollup_store, l1_fork).await?; self.items.extend_from_slice(&new_latest_batches); self.items.rotate_right(n_new_latest_batches); Ok(()) } - async fn refresh_items(&mut self, rollup_store: &StoreRollup) -> Result<(), MonitorError> { + async fn refresh_items( + &mut self, + rollup_store: &StoreRollup, + fork: Fork, + ) -> Result<(), MonitorError> { if self.items.is_empty() { return Ok(()); } @@ -83,7 +96,7 @@ impl BatchesTable { } else { let batch_number = batch.number; let new_batch = rollup_store - .get_batch(batch_number) + .get_batch(batch_number, fork) .await .map_err(|e| MonitorError::GetBatchByNumber(batch_number, e))? .ok_or(MonitorError::BatchNotFound(batch_number))?; @@ -104,6 +117,7 @@ impl BatchesTable { on_chain_proposer_address: Address, eth_client: &EthClient, rollup_store: &StoreRollup, + osaka_activation_time: Option, ) -> Result, MonitorError> { let last_l2_batch_number = get_last_committed_batch(eth_client, on_chain_proposer_address) .await @@ -116,9 +130,17 @@ impl BatchesTable { .map_err(|_| MonitorError::BatchWindow)?, ), ); + let l1_fork = get_l1_active_fork(eth_client, osaka_activation_time) + .await + .map_err(MonitorError::EthClientError)?; - let new_batches = - Self::get_batches(last_l2_batch_fetched, last_l2_batch_number, rollup_store).await?; + let new_batches = Self::get_batches( + last_l2_batch_fetched, + last_l2_batch_number, + rollup_store, + l1_fork, + ) + .await?; Ok(Self::process_batches(new_batches)) } @@ -127,12 +149,13 @@ impl BatchesTable { from: &mut u64, to: u64, rollup_store: &StoreRollup, + fork: Fork, ) -> Result, MonitorError> { let mut new_batches = Vec::new(); for batch_number in *from + 1..=to { let batch = rollup_store - .get_batch(batch_number) + .get_batch(batch_number, fork) .await .map_err(|e| MonitorError::GetBatchByNumber(batch_number, e))? .ok_or(MonitorError::BatchNotFound(batch_number))?; diff --git a/crates/l2/networking/rpc/l2/batch.rs b/crates/l2/networking/rpc/l2/batch.rs index 5a1ae0add99..396ff42fccd 100644 --- a/crates/l2/networking/rpc/l2/batch.rs +++ b/crates/l2/networking/rpc/l2/batch.rs @@ -92,7 +92,12 @@ impl RpcHandler for GetBatchByBatchNumberRequest { async fn handle(&self, context: RpcApiContext) -> Result { debug!("Requested batch with number: {}", self.batch_number); - let Some(batch) = context.rollup_store.get_batch(self.batch_number).await? else { + let l1_fork = context.l1_ctx.blockchain.current_fork().await?; + let Some(batch) = context + .rollup_store + .get_batch(self.batch_number, l1_fork) + .await? + else { return Ok(Value::Null); }; let rpc_batch = RpcBatch::build(batch, self.block_hashes, &context.l1_ctx.storage).await?; diff --git a/crates/l2/prover/src/guest_program/src/execution.rs b/crates/l2/prover/src/guest_program/src/execution.rs index 6d966038e23..9622beff371 100644 --- a/crates/l2/prover/src/guest_program/src/execution.rs +++ b/crates/l2/prover/src/guest_program/src/execution.rs @@ -195,7 +195,7 @@ pub fn stateless_validation_l2( )?; // TODO: this could be replaced with something like a ProverConfig in the future. - let validium = (blob_commitment, blob_proof) == ([0; 48], [0; 48]); + let validium = (blob_commitment, &blob_proof) == ([0; 48], &[0; 48]); // Check state diffs are valid let blob_versioned_hash = if !validium { diff --git a/crates/l2/prover/src/guest_program/src/input.rs b/crates/l2/prover/src/guest_program/src/input.rs index ae00592a4c3..6a352cc68c5 100644 --- a/crates/l2/prover/src/guest_program/src/input.rs +++ b/crates/l2/prover/src/guest_program/src/input.rs @@ -40,7 +40,7 @@ impl Default for ProgramInput { #[cfg(feature = "l2")] blob_commitment: [0; 48], #[cfg(feature = "l2")] - blob_proof: [0; 48], + blob_proof: [0u8; 48], } } } diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs index 605f06a65df..cbcc3e64564 100644 --- a/crates/l2/sdk/src/sdk.rs +++ b/crates/l2/sdk/src/sdk.rs @@ -1,6 +1,7 @@ use bytes::Bytes; use calldata::encode_calldata; use ethereum_types::{H160, H256, U256}; +use ethrex_common::types::Fork; use ethrex_common::utils::keccak; use ethrex_common::{ Address, @@ -846,6 +847,7 @@ pub async fn build_generic_tx( .map(AccessListEntry::from) .collect(), from, + wrapper_version: overrides.wrapper_version, ..Default::default() }; tx.gas_price = tx.max_fee_per_gas.unwrap_or_default(); @@ -992,6 +994,26 @@ pub async fn get_pending_privileged_transactions( from_hex_string_to_h256_array(&response) } +// TODO: This is a work around for now, issue: https://github.com/lambdaclass/ethrex/issues/4828 +pub async fn get_l1_active_fork( + client: &EthClient, + activation_time: Option, +) -> Result { + let Some(osaka_activation_time) = activation_time else { + return Ok(Fork::Osaka); + }; + let current_timestamp = client + .get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false) + .await? + .header + .timestamp; + if current_timestamp < osaka_activation_time { + Ok(Fork::Prague) + } else { + Ok(Fork::Osaka) + } +} + async fn _generic_call( client: &EthClient, selector: &[u8], diff --git a/crates/l2/sequencer/configs.rs b/crates/l2/sequencer/configs.rs index 9dd7d80207d..de669daaec5 100644 --- a/crates/l2/sequencer/configs.rs +++ b/crates/l2/sequencer/configs.rs @@ -49,6 +49,7 @@ pub struct EthConfig { pub backoff_factor: u64, pub min_retry_delay: u64, pub max_retry_delay: u64, + pub osaka_activation_time: Option, } #[derive(Clone, Debug)] diff --git a/crates/l2/sequencer/errors.rs b/crates/l2/sequencer/errors.rs index 002b4837071..b71f79edd91 100644 --- a/crates/l2/sequencer/errors.rs +++ b/crates/l2/sequencer/errors.rs @@ -287,6 +287,8 @@ pub enum CommitterError { Unreachable(String), #[error("Failed to generate batch witness: {0}")] FailedToGenerateBatchWitness(#[source] ChainError), + #[error("Missing blob for batch {0}")] + MissingBlob(u64), #[error("Failed to create checkpoint: {0}")] FailedToCreateCheckpoint(String), #[error("Failed to process blobs: {0}")] diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index b11b2fd85c4..792a020c871 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -16,8 +16,9 @@ use ethrex_blockchain::{ use ethrex_common::{ Address, H256, U256, types::{ - AccountUpdate, BLOB_BASE_FEE_UPDATE_FRACTION, BlobsBundle, Block, BlockNumber, Genesis, - MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, fake_exponential_checked, + AccountUpdate, BLOB_BASE_FEE_UPDATE_FRACTION, BlobsBundle, Block, BlockNumber, Fork, + Genesis, MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, + fake_exponential_checked, }, }; use ethrex_l2_common::{ @@ -33,7 +34,7 @@ use ethrex_l2_common::{ }; use ethrex_l2_rpc::signer::{Signer, SignerHealth}; use ethrex_l2_sdk::{ - build_generic_tx, calldata::encode_calldata, get_last_committed_batch, + build_generic_tx, calldata::encode_calldata, get_l1_active_fork, get_last_committed_batch, send_tx_bump_gas_exponential_backoff, }; #[cfg(feature = "metrics")] @@ -113,6 +114,8 @@ pub struct L1Committer { last_committed_batch: u64, /// Cancellation token for the next inbound InMessage::Commit cancellation_token: Option, + /// Timestamp for Osaka activation on L1. This is used to determine which fork to use when generating blobs proofs. + osaka_activation_time: Option, /// Elasticity multiplier for prover input generation elasticity_multiplier: u64, /// Git commit hash of the build @@ -205,6 +208,7 @@ impl L1Committer { last_committed_batch_timestamp: 0, last_committed_batch, cancellation_token: None, + osaka_activation_time: eth_config.osaka_activation_time, elasticity_multiplier: proposer_config.elasticity_multiplier, git_commit_hash: get_git_commit_hash(), current_checkpoint_store, @@ -259,7 +263,14 @@ impl L1Committer { get_last_committed_batch(&self.eth_client, self.on_chain_proposer_address).await?; let batch_to_commit = last_committed_batch_number + 1; - let batch = match self.rollup_store.get_batch(batch_to_commit).await? { + let l1_fork = get_l1_active_fork(&self.eth_client, self.osaka_activation_time) + .await + .map_err(CommitterError::EthClientError)?; + let batch = match self + .rollup_store + .get_batch(batch_to_commit, l1_fork) + .await? + { Some(batch) => batch, None => { let Some(batch) = self.produce_batch(batch_to_commit).await? else { @@ -614,7 +625,10 @@ impl L1Committer { &acc_privileged_txs, acc_account_updates.clone().into_values().collect(), )?; - generate_blobs_bundle(&state_diff) + let l1_fork = get_l1_active_fork(&self.eth_client, self.osaka_activation_time) + .await + .map_err(CommitterError::EthClientError)?; + generate_blobs_bundle(&state_diff, l1_fork) } else { Ok((BlobsBundle::default(), 0_usize)) }; @@ -781,20 +795,39 @@ impl L1Committer { let BlobsBundle { commitments, proofs, + blobs, .. } = &batch.blobs_bundle; - ( - commitments + let l1_fork = get_l1_active_fork(&self.eth_client, self.osaka_activation_time) + .await + .map_err(CommitterError::EthClientError)?; + + let commitment = commitments + .last() + .cloned() + .ok_or_else(|| CommitterError::MissingBlob(batch.number))?; + + // The prover takes a single proof even for Osaka type proofs, so if + // the committer generated Osaka type proofs (cell proofs), we need + // to create a BlobsBundle from the blobs specifying a pre-Osaka + // fork to get a single proof for the entire blob. + // If we are pre-Osaka, we already have a single proof in the + // previously generated bundle + let proof = if l1_fork < Fork::Osaka { + proofs .first() .cloned() - .ok_or(CommitterError::Unreachable( - "Blob commitment missing in batch blobs bundle".to_string(), - ))?, - proofs.first().cloned().ok_or(CommitterError::Unreachable( - "Blob proof missing in batch blobs bundle".to_string(), - ))?, - ) + .ok_or_else(|| CommitterError::MissingBlob(batch.number))? + } else { + BlobsBundle::create_from_blobs(blobs, Some(0))? + .proofs + .first() + .cloned() + .ok_or_else(|| CommitterError::MissingBlob(batch.number))? + }; + + (commitment, proof) }; let prover_input = ProverInputData { @@ -952,6 +985,7 @@ impl L1Committer { max_fee_per_gas: Some(gas_price), max_priority_fee_per_gas: Some(gas_price), blobs_bundle: Some(batch.blobs_bundle.clone()), + wrapper_version: Some(batch.blobs_bundle.version), ..Default::default() }, ) @@ -1129,15 +1163,18 @@ impl GenServer for L1Committer { /// Generate the blob bundle necessary for the EIP-4844 transaction. pub fn generate_blobs_bundle( state_diff: &StateDiff, + fork: Fork, ) -> Result<(BlobsBundle, usize), CommitterError> { let blob_data = state_diff.encode().map_err(CommitterError::from)?; let blob_size = blob_data.len(); let blob = blobs_bundle::blob_from_bytes(blob_data).map_err(CommitterError::from)?; + let wrapper_version = if fork <= Fork::Prague { None } else { Some(1) }; Ok(( - BlobsBundle::create_from_blobs(&vec![blob]).map_err(CommitterError::from)?, + BlobsBundle::create_from_blobs(&vec![blob], wrapper_version) + .map_err(CommitterError::from)?, blob_size, )) } diff --git a/crates/l2/storage/src/store.rs b/crates/l2/storage/src/store.rs index dfde4519924..99ba432091e 100644 --- a/crates/l2/storage/src/store.rs +++ b/crates/l2/storage/src/store.rs @@ -7,7 +7,9 @@ use crate::store_db::in_memory::Store as InMemoryStore; use crate::store_db::sql::SQLStore; use ethrex_common::{ H256, - types::{AccountUpdate, Blob, BlobsBundle, BlockNumber, batch::Batch, fee_config::FeeConfig}, + types::{ + AccountUpdate, Blob, BlobsBundle, BlockNumber, Fork, batch::Batch, fee_config::FeeConfig, + }, }; use ethrex_l2_common::prover::{BatchProof, ProverInputData, ProverType}; use tracing::info; @@ -157,7 +159,11 @@ impl Store { self.engine.get_last_batch_number().await } - pub async fn get_batch(&self, batch_number: u64) -> Result, RollupStoreError> { + pub async fn get_batch( + &self, + batch_number: u64, + fork: Fork, + ) -> Result, RollupStoreError> { let Some(blocks) = self.get_block_numbers_by_batch(batch_number).await? else { return Ok(None); }; @@ -185,7 +191,8 @@ impl Store { &self .get_blobs_by_batch(batch_number) .await? - .unwrap_or_default() + .unwrap_or_default(), + if fork <= Fork::Prague { None } else { Some(1) }, ).map_err(|e| { RollupStoreError::Custom(format!("Failed to create blobs bundle from blob while getting batch from database: {e}. This is a bug")) })?; diff --git a/crates/l2/tee/quote-gen/Cargo.lock b/crates/l2/tee/quote-gen/Cargo.lock index bafcfe4c168..77b00c85eb5 100644 --- a/crates/l2/tee/quote-gen/Cargo.lock +++ b/crates/l2/tee/quote-gen/Cargo.lock @@ -2075,6 +2075,7 @@ name = "ethrex-blockchain" version = "5.0.0" dependencies = [ "bytes", + "cfg-if 1.0.3", "ethrex-common", "ethrex-metrics", "ethrex-rlp", @@ -2093,15 +2094,14 @@ name = "ethrex-common" version = "5.0.0" dependencies = [ "bytes", + "c-kzg", "crc32fast", "ethereum-types 0.15.1", - "ethrex-crypto", "ethrex-rlp", "ethrex-trie", "hex", "kzg-rs", "lazy_static", - "libc", "once_cell", "rayon", "rkyv", @@ -2163,6 +2163,7 @@ dependencies = [ "axum", "bincode", "bytes", + "cfg-if 1.0.3", "chrono", "clap", "color-eyre", @@ -2365,6 +2366,7 @@ dependencies = [ "axum", "axum-extra", "bytes", + "cfg-if 1.0.3", "envy", "ethereum-types 0.15.1", "ethrex-blockchain", @@ -2501,6 +2503,7 @@ version = "5.0.0" dependencies = [ "bincode", "bytes", + "cfg-if 1.0.3", "derive_more 1.0.0", "dyn-clone", "ethereum-types 0.15.1", @@ -2879,7 +2882,6 @@ dependencies = [ "bytes", "ethrex-blockchain", "ethrex-common", - "ethrex-crypto", "ethrex-l2-common", "ethrex-rlp", "ethrex-storage", diff --git a/crates/networking/p2p/rlpx/l2/l2_connection.rs b/crates/networking/p2p/rlpx/l2/l2_connection.rs index aef99ec939f..23743abfe9e 100644 --- a/crates/networking/p2p/rlpx/l2/l2_connection.rs +++ b/crates/networking/p2p/rlpx/l2/l2_connection.rs @@ -441,7 +441,12 @@ pub(crate) async fn send_sealed_batch( { return Ok(()); } - let Some(batch) = l2_state.store_rollup.get_batch(next_batch_to_send).await? else { + let l1_fork = established.blockchain.current_fork().await?; + let Some(batch) = l2_state + .store_rollup + .get_batch(next_batch_to_send, l1_fork) + .await? + else { return Ok(()); }; match l2_state diff --git a/crates/networking/rpc/clients/eth/errors.rs b/crates/networking/rpc/clients/eth/errors.rs index 998642d0ba9..f7348176718 100644 --- a/crates/networking/rpc/clients/eth/errors.rs +++ b/crates/networking/rpc/clients/eth/errors.rs @@ -45,8 +45,10 @@ pub enum EthClientError { GetWitnessError(#[from] GetWitnessError), #[error("eth_maxPriorityFeePerGas request error: {0}")] GetMaxPriorityFeeError(#[from] GetMaxPriorityFeeError), + #[error("eth_config request error: {0}")] + GetEthConfigError(#[from] GetEthConfigError), #[error("Unreachable nonce")] - UnrecheableNonce, + UnreachableNonce, #[error("Error: {0}")] Custom(String), #[error("Failed to encode calldata: {0}")] @@ -323,6 +325,14 @@ pub enum GetBatchByNumberError { RPCError(String), } +#[derive(Debug, thiserror::Error)] +pub enum GetEthConfigError { + #[error("{0}")] + SerdeJSONError(#[from] serde_json::Error), + #[error("{0}")] + RPCError(String), +} + #[derive(Debug, thiserror::Error)] pub enum GetBaseFeeVaultAddressError { #[error("{0}")] diff --git a/crates/networking/rpc/clients/eth/mod.rs b/crates/networking/rpc/clients/eth/mod.rs index 27275f9c50e..1267a9ee631 100644 --- a/crates/networking/rpc/clients/eth/mod.rs +++ b/crates/networking/rpc/clients/eth/mod.rs @@ -2,10 +2,11 @@ use std::collections::BTreeMap; use crate::{ clients::eth::errors::{ - CallError, GetBlobBaseFeeRequestError, GetPeerCountError, GetWitnessError, - TxPoolContentError, + CallError, GetBlobBaseFeeRequestError, GetEthConfigError, GetPeerCountError, + GetWitnessError, TxPoolContentError, }, debug::execution_witness::RpcExecutionWitness, + eth::client::EthConfigResponse, mempool::MempoolContent, types::{ block::RpcBlock, @@ -68,6 +69,7 @@ pub struct Overrides { pub gas_price_per_blob: Option, pub block: Option, pub blobs_bundle: Option, + pub wrapper_version: Option, } pub const MAX_NUMBER_OF_RETRIES: u64 = 10; @@ -575,6 +577,19 @@ impl EthClient { } } + pub async fn get_eth_config(&self) -> Result { + let request = RpcRequest::new("eth_config", None); + + match self.send_request(request).await? { + RpcResponse::Success(result) => serde_json::from_value(result.result) + .map_err(GetEthConfigError::SerdeJSONError) + .map_err(EthClientError::from), + RpcResponse::Error(error_response) => { + Err(GetEthConfigError::RPCError(error_response.error.message).into()) + } + } + } + pub async fn get_code( &self, address: Address, diff --git a/crates/networking/rpc/eth/client.rs b/crates/networking/rpc/eth/client.rs index bc6eaced353..04a6181c963 100644 --- a/crates/networking/rpc/eth/client.rs +++ b/crates/networking/rpc/eth/client.rs @@ -84,7 +84,7 @@ struct EthConfigObject { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -struct EthConfigResponse { +pub struct EthConfigResponse { current: EthConfigObject, next: Option, last: Option,