From c9f49f553e37691eeb1bb43021cdbb51877c2118 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 18 Mar 2025 17:40:52 -0300 Subject: [PATCH 01/34] Add withdrawals and deposits size calculcation --- crates/blockchain/payload.rs | 77 +++++++++++++++++++++++++++++++ crates/l2/sequencer/state_diff.rs | 14 ++++++ 2 files changed, 91 insertions(+) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index b58d03c851..08d4d82da9 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use std::{ cmp::{max, Ordering}, collections::HashMap, @@ -5,6 +6,8 @@ use std::{ time::Instant, }; +use ethrex_common::types::{TxKind, BYTES_PER_BLOB}; +use ethrex_common::H160; use ethrex_common::{ constants::GAS_PER_BLOB, types::{ @@ -346,6 +349,9 @@ impl Blockchain { /// Fills the payload with transactions taken from the mempool /// Returns the block value pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { + let mut withdrawals_size: usize = 16; + let mut deposits_size: usize = 16; + let chain_config = context.chain_config()?; let max_blob_number_per_block = chain_config .get_fork_blob_schedule(context.payload.header.timestamp) @@ -414,6 +420,21 @@ impl Blockchain { // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { + if let Err(_) = Self::check_state_diff_size( + &mut withdrawals_size, + &mut deposits_size, + head_tx.clone().into(), + &receipt, + &context.account_updates, + ) { + debug!( + "Skipping transaction: {}, doesn't feet in blob_size", + head_tx.tx.compute_hash() + ); + // We don't have enough space in the blob for the transaction, so we skip all txs from this account + txs.pop(); + continue; + } txs.shift()?; // Pull transaction from the mempool self.remove_transaction_from_pool(&head_tx.tx.compute_hash())?; @@ -444,6 +465,62 @@ impl Blockchain { Ok(()) } + const L2_WITHDRAWAL_SIZE: usize = 160 + 256 + 256; // address(H160) + amount(U256) + tx_hash(H256). + const L2_DEPOSIT_SIZE: usize = 160 + 256; // address(H160) + amount(U256). + pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xff, + ]); + + fn check_state_diff_size( + withdrawals_size: &mut usize, + deposits_size: &mut usize, + tx: Transaction, + receipt: &Receipt, + account_updates: &[AccountUpdate], + ) -> Result<(), ()> { + if Self::is_withdrawal_l2(&tx, receipt) { + *withdrawals_size += Self::L2_WITHDRAWAL_SIZE; + } + if Self::is_deposit_l2(&tx) { + *deposits_size += Self::L2_DEPOSIT_SIZE; + } + let modified_accounts_size = Self::calc_modified_accounts_size(account_updates); + if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { + return Err(()); + } + Ok(()) + } + + fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { + // WithdrawalInitiated(address,address,uint256) + let withdrawal_event_selector: H256 = + H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2") + .unwrap(); + + match tx.to() { + TxKind::Call(to) if to == Self::COMMON_BRIDGE_L2_ADDRESS => { + return receipt.logs.iter().any(|log| { + log.topics + .iter() + .any(|topic| *topic == withdrawal_event_selector) + }); + } + _ => false, + } + } + + fn is_deposit_l2(tx: &Transaction) -> bool { + match tx { + Transaction::PrivilegedL2Transaction(_tx) => true, + _ => false, + } + } + + fn calc_modified_accounts_size(account_updates: &[AccountUpdate]) -> usize { + 0 + } + /// Executes the transaction, updates gas-related context values & return the receipt /// The payload build context should have enough remaining gas to cover the transaction's gas_limit fn apply_transaction( diff --git a/crates/l2/sequencer/state_diff.rs b/crates/l2/sequencer/state_diff.rs index 6deb90475d..401242d564 100644 --- a/crates/l2/sequencer/state_diff.rs +++ b/crates/l2/sequencer/state_diff.rs @@ -104,12 +104,26 @@ impl StateDiff { encoded.extend(diff_encoded); } + let withdrawal_logs_len: u16 = self + .withdrawal_logs + .len() + .try_into() + .map_err(StateDiffError::from)?; + + encoded.extend(withdrawal_logs_len.to_be_bytes()); for withdrawal in self.withdrawal_logs.iter() { encoded.extend(withdrawal.address.0); encoded.extend_from_slice(&withdrawal.amount.to_big_endian()); encoded.extend(&withdrawal.tx_hash.0); } + let deposit_logs_len: u16 = self + .deposit_logs + .len() + .try_into() + .map_err(StateDiffError::from)?; + + encoded.extend(deposit_logs_len.to_be_bytes()); for deposit in self.deposit_logs.iter() { encoded.extend(deposit.address.0); encoded.extend_from_slice(&deposit.amount.to_big_endian()); From 9d5e584920bc4d0c6a0b8d7015808eab030eedc5 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Wed, 19 Mar 2025 17:38:18 -0300 Subject: [PATCH 02/34] Finish calculation and clone context --- crates/blockchain/payload.rs | 89 ++++++++++++++++++++++++++--------- crates/vm/backends/mod.rs | 1 + crates/vm/backends/revm/db.rs | 20 ++++++++ 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 08d4d82da9..5f7a68880d 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -163,8 +163,9 @@ pub fn calc_gas_limit(parent_gas_limit: u64) -> u64 { limit } -pub struct PayloadBuildContext<'a> { - pub payload: &'a mut Block, +#[derive(Clone)] +pub struct PayloadBuildContext { + pub payload: Block, pub remaining_gas: u64, pub receipts: Vec, pub requests: Vec, @@ -177,12 +178,8 @@ pub struct PayloadBuildContext<'a> { pub account_updates: Vec, } -impl<'a> PayloadBuildContext<'a> { - fn new( - payload: &'a mut Block, - evm_engine: EvmEngine, - storage: &Store, - ) -> Result { +impl PayloadBuildContext { + fn new(payload: Block, evm_engine: EvmEngine, storage: &Store) -> Result { let config = storage.get_chain_config()?; let base_fee_per_blob_gas = calculate_base_fee_per_blob_gas( payload.header.excess_blob_gas.unwrap_or_default(), @@ -209,7 +206,7 @@ impl<'a> PayloadBuildContext<'a> { } } -impl<'a> PayloadBuildContext<'a> { +impl PayloadBuildContext { fn parent_hash(&self) -> BlockHash { self.payload.header.parent_hash } @@ -235,7 +232,7 @@ pub struct PayloadBuildResult { pub account_updates: Vec, } -impl<'a> From> for PayloadBuildResult { +impl From for PayloadBuildResult { fn from(value: PayloadBuildContext) -> Self { let PayloadBuildContext { blobs_bundle, @@ -258,7 +255,7 @@ impl<'a> From> for PayloadBuildResult { impl Blockchain { /// Completes the payload building process, return the block value - pub fn build_payload(&self, payload: &mut Block) -> Result { + pub fn build_payload(&self, payload: Block) -> Result { let since = Instant::now(); let gas_limit = payload.header.gas_limit; @@ -349,8 +346,8 @@ impl Blockchain { /// Fills the payload with transactions taken from the mempool /// Returns the block value pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { - let mut withdrawals_size: usize = 16; - let mut deposits_size: usize = 16; + // Two bytes for the len + let (mut withdrawals_size, mut deposits_size): (usize, usize) = (16, 16); let chain_config = context.chain_config()?; let max_blob_number_per_block = chain_config @@ -417,16 +414,18 @@ impl Blockchain { // or we want it before the return? metrics!(METRICS_TX.inc_tx()); + let previous_context = context.clone(); + // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { - if let Err(_) = Self::check_state_diff_size( + if Self::check_state_diff_size( &mut withdrawals_size, &mut deposits_size, head_tx.clone().into(), &receipt, - &context.account_updates, - ) { + &context, + )? { debug!( "Skipping transaction: {}, doesn't feet in blob_size", head_tx.tx.compute_hash() @@ -477,19 +476,19 @@ impl Blockchain { deposits_size: &mut usize, tx: Transaction, receipt: &Receipt, - account_updates: &[AccountUpdate], - ) -> Result<(), ()> { + context: &PayloadBuildContext, + ) -> Result { if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size += Self::L2_WITHDRAWAL_SIZE; } if Self::is_deposit_l2(&tx) { *deposits_size += Self::L2_DEPOSIT_SIZE; } - let modified_accounts_size = Self::calc_modified_accounts_size(account_updates); + let modified_accounts_size = Self::calc_modified_accounts_size(context)?; if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { - return Err(()); + return Ok(false); } - Ok(()) + Ok(true) } fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { @@ -517,8 +516,52 @@ impl Blockchain { } } - fn calc_modified_accounts_size(account_updates: &[AccountUpdate]) -> usize { - 0 + fn calc_modified_accounts_size(context: &PayloadBuildContext) -> Result { + // Starts from modified_accounts_len(u16) + let mut modified_accounts_size: usize = 16; + for account_update in &context.account_updates { + // r#type(u8) + address(H160) + modified_accounts_size += 8 + 160; + if account_update.info.is_some() { + modified_accounts_size += 256; // new_balance(U256) + } + if Self::has_new_nonce(account_update, context)? { + modified_accounts_size += 16; // nonce_diff(u16) + } + if !account_update.added_storage.is_empty() { + modified_accounts_size += 16; // stoarge_len(u16) + for _storage in account_update.added_storage.iter() { + modified_accounts_size += 256; // key(H256) + modified_accounts_size += 256; // value(U256) + } + } + if let Some(bytecode) = &account_update.code { + modified_accounts_size += 16; // bytecode_len(u16) + modified_accounts_size += bytecode.len(); // bytecode(Bytes) + } + } + Ok(modified_accounts_size) + } + + fn has_new_nonce( + account_update: &AccountUpdate, + context: &PayloadBuildContext, + ) -> Result { + let prev_nonce = match context + .store + .get_account_info(context.block_number() - 1, account_update.address) + .map_err(StoreError::from)? + { + Some(acc) => acc.nonce, + None => 0, + }; + + let new_nonce = if let Some(info) = &account_update.info { + info.nonce + } else { + prev_nonce + }; + Ok(prev_nonce != new_nonce) } /// Executes the transaction, updates gas-related context values & return the receipt diff --git a/crates/vm/backends/mod.rs b/crates/vm/backends/mod.rs index db36e8c996..2d247928a2 100644 --- a/crates/vm/backends/mod.rs +++ b/crates/vm/backends/mod.rs @@ -37,6 +37,7 @@ impl TryFrom for EvmEngine { } } +#[derive(Clone)] pub enum Evm { REVM { state: EvmState, diff --git a/crates/vm/backends/revm/db.rs b/crates/vm/backends/revm/db.rs index 7204f1e6fb..199807554f 100644 --- a/crates/vm/backends/revm/db.rs +++ b/crates/vm/backends/revm/db.rs @@ -23,6 +23,26 @@ pub enum EvmState { Execution(Box>), } +impl Clone for EvmState { + fn clone(&self) -> Self { + match self { + EvmState::Store(state) => EvmState::Store(revm::db::State:: { + cache: state.cache.clone(), + database: state.database.clone(), + transition_state: state.transition_state.clone(), + bundle_state: state.bundle_state.clone(), + use_preloaded_bundle: state.use_preloaded_bundle.clone(), + block_hashes: state.block_hashes.clone(), + }), + EvmState::Execution(execution) => { + EvmState::Execution(Box::new(Into::>::into( + *execution.clone(), + ))) + } + } + } +} + impl EvmState { /// Get a reference to inner `Store` database pub fn database(&self) -> Option<&Store> { From 13cacee6af8a509271fc9520d053e16c01f4206e Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Thu, 20 Mar 2025 12:09:58 -0300 Subject: [PATCH 03/34] Finish basic logic --- crates/blockchain/payload.rs | 11 ++++++----- crates/blockchain/smoke_test.rs | 6 +++--- crates/l2/sequencer/block_producer.rs | 11 +++++++---- crates/networking/rpc/engine/payload.rs | 11 ++++++----- crates/vm/backends/revm/db.rs | 2 +- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 5f7a68880d..cd98e62d6a 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -230,6 +230,7 @@ pub struct PayloadBuildResult { pub receipts: Vec, pub requests: Vec, pub account_updates: Vec, + pub payload: Block, } impl From for PayloadBuildResult { @@ -240,6 +241,7 @@ impl From for PayloadBuildResult { requests, receipts, account_updates, + payload, .. } = value; @@ -249,6 +251,7 @@ impl From for PayloadBuildResult { requests, receipts, account_updates, + payload, } } } @@ -424,7 +427,7 @@ impl Blockchain { &mut deposits_size, head_tx.clone().into(), &receipt, - &context, + context, )? { debug!( "Skipping transaction: {}, doesn't feet in blob_size", @@ -432,6 +435,7 @@ impl Blockchain { ); // We don't have enough space in the blob for the transaction, so we skip all txs from this account txs.pop(); + *context = previous_context.clone(); continue; } txs.shift()?; @@ -510,10 +514,7 @@ impl Blockchain { } fn is_deposit_l2(tx: &Transaction) -> bool { - match tx { - Transaction::PrivilegedL2Transaction(_tx) => true, - _ => false, - } + matches!(tx, Transaction::PrivilegedL2Transaction(_tx)) } fn calc_modified_accounts_size(context: &PayloadBuildContext) -> Result { diff --git a/crates/blockchain/smoke_test.rs b/crates/blockchain/smoke_test.rs index 0d85ab7ea2..76ec38b29f 100644 --- a/crates/blockchain/smoke_test.rs +++ b/crates/blockchain/smoke_test.rs @@ -277,9 +277,9 @@ mod blockchain_integration_test { // Create blockchain let blockchain = Blockchain::default_with_store(store.clone().clone()); - let mut block = create_payload(&args, store).unwrap(); - blockchain.build_payload(&mut block).unwrap(); - block + let block = create_payload(&args, store).unwrap(); + let result = blockchain.build_payload(block).unwrap(); + result.payload } fn test_store() -> Store { diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index bf1ca83777..c13b192500 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -99,14 +99,17 @@ impl BlockProducer { beacon_root: Some(head_beacon_block_root), version, }; - let mut payload = create_payload(&args, &store)?; + let payload = create_payload(&args, &store)?; // Blockchain builds the payload from mempool txs and executes them - let payload_build_result = blockchain.build_payload(&mut payload)?; - info!("Built payload for new block {}", payload.header.number); + let payload_build_result = blockchain.build_payload(payload)?; + info!( + "Built payload for new block {}", + payload_build_result.payload.header.number + ); // Blockchain stores block - let block = payload; + let block = payload_build_result.payload; let chain_config = store.get_chain_config()?; validate_block(&block, &head_header, &chain_config)?; diff --git a/crates/networking/rpc/engine/payload.rs b/crates/networking/rpc/engine/payload.rs index 256c12ce99..9ffe031077 100644 --- a/crates/networking/rpc/engine/payload.rs +++ b/crates/networking/rpc/engine/payload.rs @@ -787,27 +787,28 @@ fn validate_fork(block: &Block, fork: Fork, context: &RpcApiContext) -> Result<( fn build_payload_if_necessary( payload_id: u64, - mut payload: PayloadBundle, + payload: PayloadBundle, context: RpcApiContext, ) -> Result { if payload.completed { Ok(payload) } else { - let (blobs_bundle, requests, block_value) = { + let (blobs_bundle, requests, block_value, block) = { let PayloadBuildResult { blobs_bundle, block_value, requests, + payload, .. } = context .blockchain - .build_payload(&mut payload.block) + .build_payload(payload.block) .map_err(|err| RpcErr::Internal(err.to_string()))?; - (blobs_bundle, requests, block_value) + (blobs_bundle, requests, block_value, payload) }; let new_payload = PayloadBundle { - block: payload.block, + block, block_value, blobs_bundle, requests, diff --git a/crates/vm/backends/revm/db.rs b/crates/vm/backends/revm/db.rs index 199807554f..29b21bb30a 100644 --- a/crates/vm/backends/revm/db.rs +++ b/crates/vm/backends/revm/db.rs @@ -31,7 +31,7 @@ impl Clone for EvmState { database: state.database.clone(), transition_state: state.transition_state.clone(), bundle_state: state.bundle_state.clone(), - use_preloaded_bundle: state.use_preloaded_bundle.clone(), + use_preloaded_bundle: state.use_preloaded_bundle, block_hashes: state.block_hashes.clone(), }), EvmState::Execution(execution) => { From cb501315e0db8c2ca4d00932a1abcc46bb28a9db Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 15:43:32 -0300 Subject: [PATCH 04/34] Fix check condition and debug --- crates/blockchain/payload.rs | 32 +++++++++++++++++++++-------- crates/l2/sequencer/l1_committer.rs | 7 +++++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index cd98e62d6a..cf4c03e741 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -43,7 +43,7 @@ use crate::{ Blockchain, }; -use tracing::debug; +use tracing::{debug, warn}; pub struct BuildPayloadArgs { pub parent: BlockHash, @@ -422,7 +422,7 @@ impl Blockchain { // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { - if Self::check_state_diff_size( + if !Self::check_state_diff_size( &mut withdrawals_size, &mut deposits_size, head_tx.clone().into(), @@ -480,8 +480,8 @@ impl Blockchain { deposits_size: &mut usize, tx: Transaction, receipt: &Receipt, - context: &PayloadBuildContext, - ) -> Result { + context: &mut PayloadBuildContext, + ) -> Result { if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size += Self::L2_WITHDRAWAL_SIZE; } @@ -489,7 +489,20 @@ impl Blockchain { *deposits_size += Self::L2_DEPOSIT_SIZE; } let modified_accounts_size = Self::calc_modified_accounts_size(context)?; - if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { + let current_state_diff_size = *withdrawals_size + *deposits_size + modified_accounts_size; + dbg!(current_state_diff_size); + dbg!(&withdrawals_size); + dbg!(&deposits_size); + dbg!(modified_accounts_size); + if *withdrawals_size + *deposits_size + modified_accounts_size > 3000 { + // if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { + if Self::is_withdrawal_l2(&tx, receipt) { + *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; + } + if Self::is_deposit_l2(&tx) { + *deposits_size -= Self::L2_DEPOSIT_SIZE; + } + warn!("Exceeded blob size"); return Ok(false); } Ok(true) @@ -517,16 +530,19 @@ impl Blockchain { matches!(tx, Transaction::PrivilegedL2Transaction(_tx)) } - fn calc_modified_accounts_size(context: &PayloadBuildContext) -> Result { + fn calc_modified_accounts_size(context: &mut PayloadBuildContext) -> Result { // Starts from modified_accounts_len(u16) let mut modified_accounts_size: usize = 16; - for account_update in &context.account_updates { + let account_updates = context + .vm + .get_state_transitions(context.payload.header.parent_hash)?; + for account_update in account_updates { // r#type(u8) + address(H160) modified_accounts_size += 8 + 160; if account_update.info.is_some() { modified_accounts_size += 256; // new_balance(U256) } - if Self::has_new_nonce(account_update, context)? { + if Self::has_new_nonce(&account_update, context)? { modified_accounts_size += 16; // nonce_diff(u16) } if !account_update.added_storage.is_empty() { diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index d21a28201b..a24d655640 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -122,6 +122,9 @@ impl Committer { let withdrawals = self.get_block_withdrawals(&txs_and_receipts)?; let deposits = self.get_block_deposits(&block_to_commit); + info!("L1 commiter: {:?}", withdrawals); + info!("L1 commiter: {:?}", deposits); + let mut withdrawal_hashes = vec![]; for (_, tx) in &withdrawals { @@ -150,6 +153,8 @@ impl Committer { } }; + info!("L1 commiter: {:?}", account_updates); + let state_diff = self.prepare_state_diff( &block_to_commit, self.store.clone(), @@ -340,6 +345,8 @@ impl Committer { fn generate_blobs_bundle(&self, state_diff: &StateDiff) -> Result { let blob_data = state_diff.encode().map_err(CommitterError::from)?; + info!("L1 Commiter: Blob data len: {}", blob_data.len()); + let blob = blobs_bundle::blob_from_bytes(blob_data).map_err(CommitterError::from)?; BlobsBundle::create_from_blobs(&vec![blob]).map_err(CommitterError::from) From 63e85aa6264928c6bb737b66ab946b55b760bd66 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 16:36:37 -0300 Subject: [PATCH 05/34] Use levm and improve logging --- crates/blockchain/payload.rs | 7 ++++--- crates/l2/Makefile | 3 ++- crates/l2/sequencer/block_producer.rs | 4 ++++ crates/l2/sequencer/l1_committer.rs | 8 ++++---- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index cf4c03e741..ff078ab041 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -43,7 +43,7 @@ use crate::{ Blockchain, }; -use tracing::{debug, warn}; +use tracing::{debug, info, warn}; pub struct BuildPayloadArgs { pub parent: BlockHash, @@ -489,12 +489,13 @@ impl Blockchain { *deposits_size += Self::L2_DEPOSIT_SIZE; } let modified_accounts_size = Self::calc_modified_accounts_size(context)?; - let current_state_diff_size = *withdrawals_size + *deposits_size + modified_accounts_size; + let current_state_diff_size = + 8 /* version (u8) */ + *withdrawals_size + *deposits_size + modified_accounts_size; dbg!(current_state_diff_size); dbg!(&withdrawals_size); dbg!(&deposits_size); dbg!(modified_accounts_size); - if *withdrawals_size + *deposits_size + modified_accounts_size > 3000 { + if *withdrawals_size + *deposits_size + modified_accounts_size > 5000 { // if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; diff --git a/crates/l2/Makefile b/crates/l2/Makefile index b348a0ec72..debe9c0c6d 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -130,7 +130,8 @@ init-l2: init-metrics ## 🚀 Initializes an L2 Lambda ethrex Client --http.addr 0.0.0.0 \ --authrpc.port ${L2_AUTH_PORT} \ --metrics.port ${L2_PROMETHEUS_METRICS_PORT} \ - --datadir ${ethrex_L2_DEV_LIBMDBX} + --datadir ${ethrex_L2_DEV_LIBMDBX} \ + --evm levm \ init-metrics: ## 🚀 Initializes Grafana and Prometheus with containers docker compose -f ${ethrex_METRICS_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH} up -d diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index c13b192500..a27f6895f4 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -123,6 +123,10 @@ impl BlockProducer { info!("Stored new block {:x}", block.hash()); // WARN: We're not storing the payload into the Store because there's no use to it by the L2 for now. + info!( + "Storing account_updates from block_producer: {:?}", + execution_result.account_updates + ); // Cache execution result execution_cache.push(block.hash(), execution_result.account_updates)?; diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index a24d655640..205f83aaf5 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -122,8 +122,8 @@ impl Committer { let withdrawals = self.get_block_withdrawals(&txs_and_receipts)?; let deposits = self.get_block_deposits(&block_to_commit); - info!("L1 commiter: {:?}", withdrawals); - info!("L1 commiter: {:?}", deposits); + info!("L1 commiter withdrawals: {:?}", withdrawals); + info!("L1 commiter deposits: {:?}", deposits); let mut withdrawal_hashes = vec![]; @@ -153,7 +153,7 @@ impl Committer { } }; - info!("L1 commiter: {:?}", account_updates); + info!("L1 commiter account updates: {:?}", account_updates); let state_diff = self.prepare_state_diff( &block_to_commit, @@ -345,7 +345,7 @@ impl Committer { fn generate_blobs_bundle(&self, state_diff: &StateDiff) -> Result { let blob_data = state_diff.encode().map_err(CommitterError::from)?; - info!("L1 Commiter: Blob data len: {}", blob_data.len()); + info!("L1 Commiter: Blob data len: {}", blob_data.len() * 8); let blob = blobs_bundle::blob_from_bytes(blob_data).map_err(CommitterError::from)?; From 64e24d04e9443adf5a813ddfd065fc0512ea9338 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 16:48:32 -0300 Subject: [PATCH 06/34] Clone context to avoid using levm --- crates/blockchain/payload.rs | 6 +++--- crates/l2/Makefile | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index ff078ab041..64774c2aff 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -534,12 +534,12 @@ impl Blockchain { fn calc_modified_accounts_size(context: &mut PayloadBuildContext) -> Result { // Starts from modified_accounts_len(u16) let mut modified_accounts_size: usize = 16; - let account_updates = context + let mut temporary_context = context.clone(); // get_state_transitions modifies the context + let account_updates = temporary_context .vm .get_state_transitions(context.payload.header.parent_hash)?; for account_update in account_updates { - // r#type(u8) + address(H160) - modified_accounts_size += 8 + 160; + modified_accounts_size += 8 + 160; // r#type(u8) + address(H160) if account_update.info.is_some() { modified_accounts_size += 256; // new_balance(U256) } diff --git a/crates/l2/Makefile b/crates/l2/Makefile index debe9c0c6d..b348a0ec72 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -130,8 +130,7 @@ init-l2: init-metrics ## 🚀 Initializes an L2 Lambda ethrex Client --http.addr 0.0.0.0 \ --authrpc.port ${L2_AUTH_PORT} \ --metrics.port ${L2_PROMETHEUS_METRICS_PORT} \ - --datadir ${ethrex_L2_DEV_LIBMDBX} \ - --evm levm \ + --datadir ${ethrex_L2_DEV_LIBMDBX} init-metrics: ## 🚀 Initializes Grafana and Prometheus with containers docker compose -f ${ethrex_METRICS_DOCKER_COMPOSE_PATH} -f ${ethrex_METRICS_OVERRIDES_L2_DOCKER_COMPOSE_PATH} up -d From 0711be519b084eb216103e865310dad7e7378e4e Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 17:46:19 -0300 Subject: [PATCH 07/34] Count in bytes instead of bits --- crates/blockchain/payload.rs | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 64774c2aff..e845eb419b 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -350,7 +350,7 @@ impl Blockchain { /// Returns the block value pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { // Two bytes for the len - let (mut withdrawals_size, mut deposits_size): (usize, usize) = (16, 16); + let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); let chain_config = context.chain_config()?; let max_blob_number_per_block = chain_config @@ -468,8 +468,8 @@ impl Blockchain { Ok(()) } - const L2_WITHDRAWAL_SIZE: usize = 160 + 256 + 256; // address(H160) + amount(U256) + tx_hash(H256). - const L2_DEPOSIT_SIZE: usize = 160 + 256; // address(H160) + amount(U256). + const L2_WITHDRAWAL_SIZE: usize = 20 + 32 + 32; // address(H160) + amount(U256) + tx_hash(H256). + const L2_DEPOSIT_SIZE: usize = 20 + 32; // address(H160) + amount(U256). pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, @@ -490,13 +490,12 @@ impl Blockchain { } let modified_accounts_size = Self::calc_modified_accounts_size(context)?; let current_state_diff_size = - 8 /* version (u8) */ + *withdrawals_size + *deposits_size + modified_accounts_size; + 1 /* version (u8) */ + *withdrawals_size + *deposits_size + modified_accounts_size; dbg!(current_state_diff_size); dbg!(&withdrawals_size); dbg!(&deposits_size); dbg!(modified_accounts_size); - if *withdrawals_size + *deposits_size + modified_accounts_size > 5000 { - // if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { + if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; } @@ -532,29 +531,26 @@ impl Blockchain { } fn calc_modified_accounts_size(context: &mut PayloadBuildContext) -> Result { - // Starts from modified_accounts_len(u16) - let mut modified_accounts_size: usize = 16; - let mut temporary_context = context.clone(); // get_state_transitions modifies the context + let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) + + // We use a temporary_context because revm mutates it in `get_state_transitions` + let mut temporary_context = context.clone(); let account_updates = temporary_context .vm .get_state_transitions(context.payload.header.parent_hash)?; for account_update in account_updates { - modified_accounts_size += 8 + 160; // r#type(u8) + address(H160) + modified_accounts_size += 1 + 20; // r#type(u8) + address(H160) if account_update.info.is_some() { - modified_accounts_size += 256; // new_balance(U256) + modified_accounts_size += 32; // new_balance(U256) } if Self::has_new_nonce(&account_update, context)? { - modified_accounts_size += 16; // nonce_diff(u16) - } - if !account_update.added_storage.is_empty() { - modified_accounts_size += 16; // stoarge_len(u16) - for _storage in account_update.added_storage.iter() { - modified_accounts_size += 256; // key(H256) - modified_accounts_size += 256; // value(U256) - } + modified_accounts_size += 2; // nonce_diff(u16) } + // for each added_storage: key(H256) + value(U256) + modified_accounts_size += account_update.added_storage.len() * 2 * 32; + if let Some(bytecode) = &account_update.code { - modified_accounts_size += 16; // bytecode_len(u16) + modified_accounts_size += 2; // bytecode_len(u16) modified_accounts_size += bytecode.len(); // bytecode(Bytes) } } From f9785eb76c501a7e38a2d20fccf842fabe8c134e Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 17:52:28 -0300 Subject: [PATCH 08/34] Remove unused import --- crates/blockchain/payload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index e845eb419b..d36bcf6c58 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -43,7 +43,7 @@ use crate::{ Blockchain, }; -use tracing::{debug, info, warn}; +use tracing::{debug, warn}; pub struct BuildPayloadArgs { pub parent: BlockHash, From 756e9e42ec7b0ae55f580ce501ad6d5ed73ccadc Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 18:13:52 -0300 Subject: [PATCH 09/34] refactor: move l2 functionallity to the bottom --- crates/blockchain/payload.rs | 219 +++++++++++++++++++---------------- 1 file changed, 116 insertions(+), 103 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index d36bcf6c58..0d6462570c 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -468,13 +468,117 @@ impl Blockchain { Ok(()) } + /// Executes the transaction, updates gas-related context values & return the receipt + /// The payload build context should have enough remaining gas to cover the transaction's gas_limit + fn apply_transaction( + &self, + head: &HeadTransaction, + context: &mut PayloadBuildContext, + ) -> Result { + match **head { + Transaction::EIP4844Transaction(_) => self.apply_blob_transaction(head, context), + _ => self.apply_plain_transaction(head, context), + } + } + + /// Runs a blob transaction, updates the gas count & blob data and returns the receipt + fn apply_blob_transaction( + &self, + head: &HeadTransaction, + context: &mut PayloadBuildContext, + ) -> Result { + // Fetch blobs bundle + let tx_hash = head.tx.compute_hash(); + let chain_config = context.chain_config()?; + let max_blob_number_per_block = chain_config + .get_fork_blob_schedule(context.payload.header.timestamp) + .map(|schedule| schedule.max) + .unwrap_or_default() as usize; + let Some(blobs_bundle) = self.mempool.get_blobs_bundle(tx_hash)? else { + // No blob tx should enter the mempool without its blobs bundle so this is an internal error + return Err( + StoreError::Custom(format!("No blobs bundle found for blob tx {tx_hash}")).into(), + ); + }; + if context.blobs_bundle.blobs.len() + blobs_bundle.blobs.len() > max_blob_number_per_block { + // This error will only be used for debug tracing + return Err(EvmError::Custom("max data blobs reached".to_string()).into()); + }; + // Apply transaction + let receipt = self.apply_plain_transaction(head, context)?; + // Update context with blob data + let prev_blob_gas = context.payload.header.blob_gas_used.unwrap_or_default(); + context.payload.header.blob_gas_used = + Some(prev_blob_gas + blobs_bundle.blobs.len() as u64 * GAS_PER_BLOB); + context.blobs_bundle += blobs_bundle; + Ok(receipt) + } + + /// Runs a plain (non blob) transaction, updates the gas count and returns the receipt + fn apply_plain_transaction( + &self, + head: &HeadTransaction, + context: &mut PayloadBuildContext, + ) -> Result { + let (report, gas_used) = context.vm.execute_tx( + &head.tx, + &context.payload.header, + &mut context.remaining_gas, + head.tx.sender(), + )?; + context.block_value += U256::from(gas_used) * head.tip; + Ok(report) + } + + pub fn extract_requests(&self, context: &mut PayloadBuildContext) -> Result<(), EvmError> { + if !context + .chain_config()? + .is_prague_activated(context.payload.header.timestamp) + { + return Ok(()); + }; + + let requests = context + .vm + .extract_requests(&context.receipts, &context.payload.header)?; + + context.requests = requests.iter().map(|r| r.encode()).collect(); + context.requests_hash = Some(compute_requests_hash(&context.requests)); + + Ok(()) + } + + fn finalize_payload(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { + let parent_hash = context.payload.header.parent_hash; + let account_updates = context.vm.get_state_transitions(parent_hash)?; + + context.payload.header.state_root = context + .store + .apply_account_updates(context.parent_hash(), &account_updates)? + .unwrap_or_default(); + context.payload.header.transactions_root = + compute_transactions_root(&context.payload.body.transactions); + context.payload.header.receipts_root = compute_receipts_root(&context.receipts); + context.payload.header.requests_hash = context.requests_hash; + context.payload.header.gas_used = context.payload.header.gas_limit - context.remaining_gas; + context.account_updates = account_updates; + Ok(()) + } + + #[cfg(feature = "l2")] const L2_WITHDRAWAL_SIZE: usize = 20 + 32 + 32; // address(H160) + amount(U256) + tx_hash(H256). + #[cfg(feature = "l2")] const L2_DEPOSIT_SIZE: usize = 20 + 32; // address(H160) + amount(U256). + #[cfg(feature = "l2")] pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, ]); + #[cfg(feature = "l2")] + /// Calculates the size of the current `StateDiff` of the block. + /// If the current size exceeds the blob size limit, returns `Ok(false)`. + /// If there is still space in the blob, returns `Ok(true)`. fn check_state_diff_size( withdrawals_size: &mut usize, deposits_size: &mut usize, @@ -489,25 +593,28 @@ impl Blockchain { *deposits_size += Self::L2_DEPOSIT_SIZE; } let modified_accounts_size = Self::calc_modified_accounts_size(context)?; + let current_state_diff_size = 1 /* version (u8) */ + *withdrawals_size + *deposits_size + modified_accounts_size; - dbg!(current_state_diff_size); - dbg!(&withdrawals_size); - dbg!(&deposits_size); - dbg!(modified_accounts_size); - if *withdrawals_size + *deposits_size + modified_accounts_size > BYTES_PER_BLOB * 31 / 32 { + + if current_state_diff_size > BYTES_PER_BLOB * 31 / 32 { + // Restore the withdrawals and deposits counters. if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; } if Self::is_deposit_l2(&tx) { *deposits_size -= Self::L2_DEPOSIT_SIZE; } - warn!("Exceeded blob size"); + warn!( + "Blob size limit exceeded. current_state_diff_size: {}", + current_state_diff_size + ); return Ok(false); } Ok(true) } + #[cfg(feature = "l2")] fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { // WithdrawalInitiated(address,address,uint256) let withdrawal_event_selector: H256 = @@ -526,10 +633,12 @@ impl Blockchain { } } + #[cfg(feature = "l2")] fn is_deposit_l2(tx: &Transaction) -> bool { matches!(tx, Transaction::PrivilegedL2Transaction(_tx)) } + #[cfg(feature = "l2")] fn calc_modified_accounts_size(context: &mut PayloadBuildContext) -> Result { let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) @@ -557,6 +666,7 @@ impl Blockchain { Ok(modified_accounts_size) } + #[cfg(feature = "l2")] fn has_new_nonce( account_update: &AccountUpdate, context: &PayloadBuildContext, @@ -577,103 +687,6 @@ impl Blockchain { }; Ok(prev_nonce != new_nonce) } - - /// Executes the transaction, updates gas-related context values & return the receipt - /// The payload build context should have enough remaining gas to cover the transaction's gas_limit - fn apply_transaction( - &self, - head: &HeadTransaction, - context: &mut PayloadBuildContext, - ) -> Result { - match **head { - Transaction::EIP4844Transaction(_) => self.apply_blob_transaction(head, context), - _ => self.apply_plain_transaction(head, context), - } - } - - /// Runs a blob transaction, updates the gas count & blob data and returns the receipt - fn apply_blob_transaction( - &self, - head: &HeadTransaction, - context: &mut PayloadBuildContext, - ) -> Result { - // Fetch blobs bundle - let tx_hash = head.tx.compute_hash(); - let chain_config = context.chain_config()?; - let max_blob_number_per_block = chain_config - .get_fork_blob_schedule(context.payload.header.timestamp) - .map(|schedule| schedule.max) - .unwrap_or_default() as usize; - let Some(blobs_bundle) = self.mempool.get_blobs_bundle(tx_hash)? else { - // No blob tx should enter the mempool without its blobs bundle so this is an internal error - return Err( - StoreError::Custom(format!("No blobs bundle found for blob tx {tx_hash}")).into(), - ); - }; - if context.blobs_bundle.blobs.len() + blobs_bundle.blobs.len() > max_blob_number_per_block { - // This error will only be used for debug tracing - return Err(EvmError::Custom("max data blobs reached".to_string()).into()); - }; - // Apply transaction - let receipt = self.apply_plain_transaction(head, context)?; - // Update context with blob data - let prev_blob_gas = context.payload.header.blob_gas_used.unwrap_or_default(); - context.payload.header.blob_gas_used = - Some(prev_blob_gas + blobs_bundle.blobs.len() as u64 * GAS_PER_BLOB); - context.blobs_bundle += blobs_bundle; - Ok(receipt) - } - - /// Runs a plain (non blob) transaction, updates the gas count and returns the receipt - fn apply_plain_transaction( - &self, - head: &HeadTransaction, - context: &mut PayloadBuildContext, - ) -> Result { - let (report, gas_used) = context.vm.execute_tx( - &head.tx, - &context.payload.header, - &mut context.remaining_gas, - head.tx.sender(), - )?; - context.block_value += U256::from(gas_used) * head.tip; - Ok(report) - } - - pub fn extract_requests(&self, context: &mut PayloadBuildContext) -> Result<(), EvmError> { - if !context - .chain_config()? - .is_prague_activated(context.payload.header.timestamp) - { - return Ok(()); - }; - - let requests = context - .vm - .extract_requests(&context.receipts, &context.payload.header)?; - - context.requests = requests.iter().map(|r| r.encode()).collect(); - context.requests_hash = Some(compute_requests_hash(&context.requests)); - - Ok(()) - } - - fn finalize_payload(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { - let parent_hash = context.payload.header.parent_hash; - let account_updates = context.vm.get_state_transitions(parent_hash)?; - - context.payload.header.state_root = context - .store - .apply_account_updates(context.parent_hash(), &account_updates)? - .unwrap_or_default(); - context.payload.header.transactions_root = - compute_transactions_root(&context.payload.body.transactions); - context.payload.header.receipts_root = compute_receipts_root(&context.receipts); - context.payload.header.requests_hash = context.requests_hash; - context.payload.header.gas_used = context.payload.header.gas_limit - context.remaining_gas; - context.account_updates = account_updates; - Ok(()) - } } /// A struct representing suitable mempool transactions waiting to be included in a block From 29ab5d1db1e57f20f87de688d973848ec919c9e4 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 18:22:00 -0300 Subject: [PATCH 10/34] Add missing cfg --- crates/blockchain/payload.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 0d6462570c..4c41b4f93c 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -349,6 +349,7 @@ impl Blockchain { /// Fills the payload with transactions taken from the mempool /// Returns the block value pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { + #[cfg(feature = "l2")] // Two bytes for the len let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); @@ -422,6 +423,7 @@ impl Blockchain { // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { + #[cfg(feature = "l2")] if !Self::check_state_diff_size( &mut withdrawals_size, &mut deposits_size, From 473572640c097345b04148548a165fe1b7727dbb Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 18:31:02 -0300 Subject: [PATCH 11/34] Remove debugs --- crates/blockchain/payload.rs | 1 + crates/l2/sequencer/block_producer.rs | 4 ---- crates/l2/sequencer/l1_committer.rs | 5 ----- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 4c41b4f93c..244c965b01 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -418,6 +418,7 @@ impl Blockchain { // or we want it before the return? metrics!(METRICS_TX.inc_tx()); + #[cfg(feature = "l2")] let previous_context = context.clone(); // Execute tx diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index a27f6895f4..c13b192500 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -123,10 +123,6 @@ impl BlockProducer { info!("Stored new block {:x}", block.hash()); // WARN: We're not storing the payload into the Store because there's no use to it by the L2 for now. - info!( - "Storing account_updates from block_producer: {:?}", - execution_result.account_updates - ); // Cache execution result execution_cache.push(block.hash(), execution_result.account_updates)?; diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index 205f83aaf5..13ba6f6529 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -122,9 +122,6 @@ impl Committer { let withdrawals = self.get_block_withdrawals(&txs_and_receipts)?; let deposits = self.get_block_deposits(&block_to_commit); - info!("L1 commiter withdrawals: {:?}", withdrawals); - info!("L1 commiter deposits: {:?}", deposits); - let mut withdrawal_hashes = vec![]; for (_, tx) in &withdrawals { @@ -345,8 +342,6 @@ impl Committer { fn generate_blobs_bundle(&self, state_diff: &StateDiff) -> Result { let blob_data = state_diff.encode().map_err(CommitterError::from)?; - info!("L1 Commiter: Blob data len: {}", blob_data.len() * 8); - let blob = blobs_bundle::blob_from_bytes(blob_data).map_err(CommitterError::from)?; BlobsBundle::create_from_blobs(&vec![blob]).map_err(CommitterError::from) From 4e3c59a00c3123555d2458ea67f5379f37356288 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 18:41:36 -0300 Subject: [PATCH 12/34] Update count to include header fields --- crates/blockchain/payload.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 244c965b01..6f6b94fd89 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -573,6 +573,8 @@ impl Blockchain { #[cfg(feature = "l2")] const L2_DEPOSIT_SIZE: usize = 20 + 32; // address(H160) + amount(U256). #[cfg(feature = "l2")] + const HEADER_FIELDS_SIZE: usize = 32 + 32 + 8 + 8 + 8 + 8; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). + #[cfg(feature = "l2")] pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, @@ -597,8 +599,7 @@ impl Blockchain { } let modified_accounts_size = Self::calc_modified_accounts_size(context)?; - let current_state_diff_size = - 1 /* version (u8) */ + *withdrawals_size + *deposits_size + modified_accounts_size; + let current_state_diff_size = 1 /* version (u8) */ + Self::HEADER_FIELDS_SIZE + *withdrawals_size + *deposits_size + modified_accounts_size; if current_state_diff_size > BYTES_PER_BLOB * 31 / 32 { // Restore the withdrawals and deposits counters. From 68245492faf835a19da3bd2c47526c260081b615 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 21 Mar 2025 19:18:53 -0300 Subject: [PATCH 13/34] improve code readability --- crates/blockchain/payload.rs | 12 +++++++----- crates/l2/sequencer/l1_committer.rs | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 6f6b94fd89..0962fecfb1 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -6,7 +6,7 @@ use std::{ time::Instant, }; -use ethrex_common::types::{TxKind, BYTES_PER_BLOB}; +use ethrex_common::types::TxKind; use ethrex_common::H160; use ethrex_common::{ constants::GAS_PER_BLOB, @@ -569,11 +569,11 @@ impl Blockchain { } #[cfg(feature = "l2")] - const L2_WITHDRAWAL_SIZE: usize = 20 + 32 + 32; // address(H160) + amount(U256) + tx_hash(H256). + const L2_WITHDRAWAL_SIZE: usize = 84; // address(H160) + amount(U256) + tx_hash(H256). #[cfg(feature = "l2")] - const L2_DEPOSIT_SIZE: usize = 20 + 32; // address(H160) + amount(U256). + const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). #[cfg(feature = "l2")] - const HEADER_FIELDS_SIZE: usize = 32 + 32 + 8 + 8 + 8 + 8; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). + const HEADER_FIELDS_SIZE: usize = 96; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). #[cfg(feature = "l2")] pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -591,6 +591,8 @@ impl Blockchain { receipt: &Receipt, context: &mut PayloadBuildContext, ) -> Result { + use ethrex_common::types::SAFE_BYTES_PER_BLOB; + if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size += Self::L2_WITHDRAWAL_SIZE; } @@ -601,7 +603,7 @@ impl Blockchain { let current_state_diff_size = 1 /* version (u8) */ + Self::HEADER_FIELDS_SIZE + *withdrawals_size + *deposits_size + modified_accounts_size; - if current_state_diff_size > BYTES_PER_BLOB * 31 / 32 { + if current_state_diff_size > SAFE_BYTES_PER_BLOB { // Restore the withdrawals and deposits counters. if Self::is_withdrawal_l2(&tx, receipt) { *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index 17ad60a5ae..bd244a1a50 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -152,8 +152,6 @@ impl Committer { } }; - info!("L1 commiter account updates: {:?}", account_updates); - let state_diff = self.prepare_state_diff( &block_to_commit, self.store.clone(), From a3a1a9b84cd29925aafcd07e8d140b0662d3eb53 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 13:31:43 -0300 Subject: [PATCH 14/34] Move fill transactions to l2 --- crates/blockchain/payload.rs | 183 ++----------- crates/l2/sequencer/block_producer.rs | 1 + .../block_producer/payload_builder.rs | 251 ++++++++++++++++++ crates/l2/sequencer/errors.rs | 8 +- crates/l2/sequencer/l1_committer.rs | 57 ++-- crates/l2/sequencer/state_diff.rs | 30 ++- crates/l2/utils/helpers.rs | 27 ++ crates/l2/utils/mod.rs | 1 + 8 files changed, 345 insertions(+), 213 deletions(-) create mode 100644 crates/l2/sequencer/block_producer/payload_builder.rs create mode 100644 crates/l2/utils/helpers.rs diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index ac3bad18de..f12a05df1d 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -1,4 +1,3 @@ -use std::str::FromStr; use std::{ cmp::{max, Ordering}, collections::HashMap, @@ -6,8 +5,6 @@ use std::{ time::Instant, }; -use ethrex_common::types::TxKind; -use ethrex_common::H160; use ethrex_common::{ constants::GAS_PER_BLOB, types::{ @@ -42,7 +39,7 @@ use crate::{ Blockchain, }; -use tracing::{debug, warn}; +use tracing::debug; pub struct BuildPayloadArgs { pub parent: BlockHash, @@ -178,7 +175,7 @@ pub struct PayloadBuildContext { } impl PayloadBuildContext { - fn new(payload: Block, evm_engine: EvmEngine, storage: &Store) -> Result { + pub fn new(payload: Block, evm_engine: EvmEngine, storage: &Store) -> Result { let config = storage.get_chain_config()?; let base_fee_per_blob_gas = calculate_base_fee_per_blob_gas( payload.header.excess_blob_gas.unwrap_or_default(), @@ -210,7 +207,7 @@ impl PayloadBuildContext { self.payload.header.parent_hash } - fn block_number(&self) -> BlockNumber { + pub fn block_number(&self) -> BlockNumber { self.payload.header.number } @@ -287,7 +284,7 @@ impl Blockchain { Ok(context.into()) } - fn apply_withdrawals(&self, context: &mut PayloadBuildContext) -> Result<(), EvmError> { + pub fn apply_withdrawals(&self, context: &mut PayloadBuildContext) -> Result<(), EvmError> { let binding = Vec::new(); let withdrawals = context .payload @@ -313,7 +310,7 @@ impl Blockchain { /// Fetches suitable transactions from the mempool /// Returns two transaction queues, one for plain and one for blob txs - fn fetch_mempool_transactions( + pub fn fetch_mempool_transactions( &self, context: &mut PayloadBuildContext, ) -> Result<(TransactionQueue, TransactionQueue), ChainError> { @@ -348,10 +345,6 @@ impl Blockchain { /// Fills the payload with transactions taken from the mempool /// Returns the block value pub fn fill_transactions(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { - #[cfg(feature = "l2")] - // Two bytes for the len - let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); - let chain_config = context.chain_config()?; let max_blob_number_per_block = chain_config .get_fork_blob_schedule(context.payload.header.timestamp) @@ -417,29 +410,9 @@ impl Blockchain { // or we want it before the return? metrics!(METRICS_TX.inc_tx()); - #[cfg(feature = "l2")] - let previous_context = context.clone(); - // Execute tx let receipt = match self.apply_transaction(&head_tx, context) { Ok(receipt) => { - #[cfg(feature = "l2")] - if !Self::check_state_diff_size( - &mut withdrawals_size, - &mut deposits_size, - head_tx.clone().into(), - &receipt, - context, - )? { - debug!( - "Skipping transaction: {}, doesn't feet in blob_size", - head_tx.tx.compute_hash() - ); - // We don't have enough space in the blob for the transaction, so we skip all txs from this account - txs.pop(); - *context = previous_context.clone(); - continue; - } txs.shift()?; // Pull transaction from the mempool self.remove_transaction_from_pool(&head_tx.tx.compute_hash())?; @@ -472,7 +445,7 @@ impl Blockchain { /// Executes the transaction, updates gas-related context values & return the receipt /// The payload build context should have enough remaining gas to cover the transaction's gas_limit - fn apply_transaction( + pub fn apply_transaction( &self, head: &HeadTransaction, context: &mut PayloadBuildContext, @@ -550,7 +523,7 @@ impl Blockchain { Ok(()) } - fn finalize_payload(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { + pub fn finalize_payload(&self, context: &mut PayloadBuildContext) -> Result<(), ChainError> { let parent_hash = context.payload.header.parent_hash; let account_updates = context.vm.get_state_transitions(parent_hash)?; @@ -566,137 +539,11 @@ impl Blockchain { context.account_updates = account_updates; Ok(()) } - - #[cfg(feature = "l2")] - const L2_WITHDRAWAL_SIZE: usize = 84; // address(H160) + amount(U256) + tx_hash(H256). - #[cfg(feature = "l2")] - const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). - #[cfg(feature = "l2")] - const HEADER_FIELDS_SIZE: usize = 96; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). - #[cfg(feature = "l2")] - pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xff, 0xff, - ]); - - #[cfg(feature = "l2")] - /// Calculates the size of the current `StateDiff` of the block. - /// If the current size exceeds the blob size limit, returns `Ok(false)`. - /// If there is still space in the blob, returns `Ok(true)`. - fn check_state_diff_size( - withdrawals_size: &mut usize, - deposits_size: &mut usize, - tx: Transaction, - receipt: &Receipt, - context: &mut PayloadBuildContext, - ) -> Result { - use ethrex_common::types::SAFE_BYTES_PER_BLOB; - - if Self::is_withdrawal_l2(&tx, receipt) { - *withdrawals_size += Self::L2_WITHDRAWAL_SIZE; - } - if Self::is_deposit_l2(&tx) { - *deposits_size += Self::L2_DEPOSIT_SIZE; - } - let modified_accounts_size = Self::calc_modified_accounts_size(context)?; - - let current_state_diff_size = 1 /* version (u8) */ + Self::HEADER_FIELDS_SIZE + *withdrawals_size + *deposits_size + modified_accounts_size; - - if current_state_diff_size > SAFE_BYTES_PER_BLOB { - // Restore the withdrawals and deposits counters. - if Self::is_withdrawal_l2(&tx, receipt) { - *withdrawals_size -= Self::L2_WITHDRAWAL_SIZE; - } - if Self::is_deposit_l2(&tx) { - *deposits_size -= Self::L2_DEPOSIT_SIZE; - } - warn!( - "Blob size limit exceeded. current_state_diff_size: {}", - current_state_diff_size - ); - return Ok(false); - } - Ok(true) - } - - #[cfg(feature = "l2")] - fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { - // WithdrawalInitiated(address,address,uint256) - let withdrawal_event_selector: H256 = - H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2") - .unwrap(); - - match tx.to() { - TxKind::Call(to) if to == Self::COMMON_BRIDGE_L2_ADDRESS => { - return receipt.logs.iter().any(|log| { - log.topics - .iter() - .any(|topic| *topic == withdrawal_event_selector) - }); - } - _ => false, - } - } - - #[cfg(feature = "l2")] - fn is_deposit_l2(tx: &Transaction) -> bool { - matches!(tx, Transaction::PrivilegedL2Transaction(_tx)) - } - - #[cfg(feature = "l2")] - fn calc_modified_accounts_size(context: &mut PayloadBuildContext) -> Result { - let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) - - // We use a temporary_context because revm mutates it in `get_state_transitions` - let mut temporary_context = context.clone(); - let account_updates = temporary_context - .vm - .get_state_transitions(context.payload.header.parent_hash)?; - for account_update in account_updates { - modified_accounts_size += 1 + 20; // r#type(u8) + address(H160) - if account_update.info.is_some() { - modified_accounts_size += 32; // new_balance(U256) - } - if Self::has_new_nonce(&account_update, context)? { - modified_accounts_size += 2; // nonce_diff(u16) - } - // for each added_storage: key(H256) + value(U256) - modified_accounts_size += account_update.added_storage.len() * 2 * 32; - - if let Some(bytecode) = &account_update.code { - modified_accounts_size += 2; // bytecode_len(u16) - modified_accounts_size += bytecode.len(); // bytecode(Bytes) - } - } - Ok(modified_accounts_size) - } - - #[cfg(feature = "l2")] - fn has_new_nonce( - account_update: &AccountUpdate, - context: &PayloadBuildContext, - ) -> Result { - let prev_nonce = match context - .store - .get_account_info(context.block_number() - 1, account_update.address) - .map_err(StoreError::from)? - { - Some(acc) => acc.nonce, - None => 0, - }; - - let new_nonce = if let Some(info) = &account_update.info { - info.nonce - } else { - prev_nonce - }; - Ok(prev_nonce != new_nonce) - } } /// A struct representing suitable mempool transactions waiting to be included in a block // TODO: Consider using VecDequeue instead of Vec -struct TransactionQueue { +pub struct TransactionQueue { // The first transaction for each account along with its tip, sorted by highest tip heads: Vec, // The remaining txs grouped by account and sorted by nonce @@ -706,8 +553,8 @@ struct TransactionQueue { } #[derive(Clone, Debug, Eq, PartialEq)] -struct HeadTransaction { - tx: MempoolTransaction, +pub struct HeadTransaction { + pub tx: MempoolTransaction, tip: u64, } @@ -756,24 +603,24 @@ impl TransactionQueue { } /// Remove all transactions from the queue - fn clear(&mut self) { + pub fn clear(&mut self) { self.heads.clear(); self.txs.clear(); } /// Returns true if there are no more transactions in the queue - fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.heads.is_empty() } /// Returns the head transaction with the highest tip /// If there is more than one transaction with the highest tip, return the one with the lowest timestamp - fn peek(&self) -> Option { + pub fn peek(&self) -> Option { self.heads.first().cloned() } /// Removes current head transaction and all transactions from the given sender - fn pop(&mut self) { + pub fn pop(&mut self) { if !self.is_empty() { let sender = self.heads.remove(0).tx.sender(); self.txs.remove(&sender); @@ -782,7 +629,7 @@ impl TransactionQueue { /// Remove the top transaction /// Add a tx from the same sender to the head transactions - fn shift(&mut self) -> Result<(), ChainError> { + pub fn shift(&mut self) -> Result<(), ChainError> { let tx = self.heads.remove(0); if let Some(txs) = self.txs.get_mut(&tx.tx.sender()) { // Fetch next head diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index c13b192500..b529c11460 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -1,3 +1,4 @@ +mod payload_builder; use std::{ sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs new file mode 100644 index 0000000000..28e3c83204 --- /dev/null +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -0,0 +1,251 @@ +use std::sync::Arc; + +use ethrex_blockchain::{ + constants::TX_GAS_COST, + payload::{PayloadBuildContext, PayloadBuildResult}, + Blockchain, +}; +use ethrex_common::types::{Block, Receipt, Transaction, SAFE_BYTES_PER_BLOB}; +use ethrex_metrics::metrics; +use ethrex_storage::Store; +use std::ops::Div; +use tokio::time::Instant; +use tracing::debug; + +use crate::{ + sequencer::{errors::BlockProducerError, state_diff::get_nonce_diff}, + utils::helpers::{is_deposit_l2, is_withdrawal_l2}, +}; + +const HEADER_FIELDS_SIZE: usize = 96; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). +const L2_WITHDRAWAL_SIZE: usize = 84; // address(H160) + amount(U256) + tx_hash(H256). +const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). + +/// L2 payload builder +/// Completes the payload building process, return the block value +/// Same as blockchain::build_payload without applying system operations and using a different method to `fill_transactions` +pub fn build_payload( + blockchain: Arc, + payload: Block, + store: Store, +) -> Result { + let since = Instant::now(); + let gas_limit = payload.header.gas_limit; + + debug!("Building payload"); + let mut context = PayloadBuildContext::new(payload, blockchain.evm_engine, &store)?; + + blockchain.apply_withdrawals(&mut context)?; + fill_transactions(blockchain.clone(), &mut context, store)?; + blockchain.extract_requests(&mut context)?; + blockchain.finalize_payload(&mut context)?; + + let interval = Instant::now().duration_since(since).as_millis(); + tracing::info!("[METRIC] BUILDING PAYLOAD TOOK: {interval} ms"); + if let Some(gas_used) = gas_limit.checked_sub(context.remaining_gas) { + let as_gigas = (gas_used as f64).div(10_f64.powf(9_f64)); + + if interval != 0 { + let throughput = (as_gigas) / (interval as f64) * 1000_f64; + tracing::info!( + "[METRIC] BLOCK BUILDING THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs" + ); + } + } + + Ok(context.into()) +} + +/// Same as blockchain_fill transactions but checks the resulting `StateDiff` size to not exceed +pub fn fill_transactions( + blockchain: Arc, + context: &mut PayloadBuildContext, + store: Store, +) -> Result<(), BlockProducerError> { + // Two bytes for the len + let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); + + let chain_config = store.get_chain_config()?; + let max_blob_number_per_block = chain_config + .get_fork_blob_schedule(context.payload.header.timestamp) + .map(|schedule| schedule.max) + .unwrap_or_default() as usize; + + debug!("Fetching transactions from mempool"); + // Fetch mempool transactions + let (mut plain_txs, mut blob_txs) = blockchain.fetch_mempool_transactions(context)?; + // Execute and add transactions to payload (if suitable) + loop { + // Check if we have enough gas to run more transactions + if context.remaining_gas < TX_GAS_COST { + debug!("No more gas to run transactions"); + break; + }; + if !blob_txs.is_empty() && context.blobs_bundle.blobs.len() >= max_blob_number_per_block { + debug!("No more blob gas to run blob transactions"); + blob_txs.clear(); + } + // Fetch the next transactions + let (head_tx, is_blob) = match (plain_txs.peek(), blob_txs.peek()) { + (None, None) => break, + (None, Some(tx)) => (tx, true), + (Some(tx), None) => (tx, false), + (Some(a), Some(b)) if b < a => (b, true), + (Some(tx), _) => (tx, false), + }; + + let txs = if is_blob { + &mut blob_txs + } else { + &mut plain_txs + }; + + // Check if we have enough gas to run the transaction + if context.remaining_gas < head_tx.tx.gas_limit() { + debug!( + "Skipping transaction: {}, no gas left", + head_tx.tx.compute_hash() + ); + // We don't have enough gas left for the transaction, so we skip all txs from this account + txs.pop(); + continue; + } + + // TODO: maybe fetch hash too when filtering mempool so we don't have to compute it here (we can do this in the same refactor as adding timestamp) + let tx_hash = head_tx.tx.compute_hash(); + + // Check wether the tx is replay-protected + if head_tx.tx.protected() && !chain_config.is_eip155_activated(context.block_number()) { + // Ignore replay protected tx & all txs from the sender + // Pull transaction from the mempool + debug!("Ignoring replay-protected transaction: {}", tx_hash); + txs.pop(); + blockchain.remove_transaction_from_pool(&head_tx.tx.compute_hash())?; + continue; + } + + // Increment the total transaction counter + // CHECK: do we want it here to count every processed transaction + // or we want it before the return? + metrics!(METRICS_TX.inc_tx()); + + let previous_context = context.clone(); + + // Execute tx + let receipt = match blockchain.apply_transaction(&head_tx, context) { + Ok(receipt) => { + if !check_state_diff_size( + &mut withdrawals_size, + &mut deposits_size, + head_tx.clone().into(), + &receipt, + context, + )? { + debug!( + "Skipping transaction: {}, doesn't feet in blob_size", + head_tx.tx.compute_hash() + ); + // We don't have enough space in the blob for the transaction, so we skip all txs from this account + txs.pop(); + *context = previous_context.clone(); + continue; + } + txs.shift()?; + // Pull transaction from the mempool + blockchain.remove_transaction_from_pool(&head_tx.tx.compute_hash())?; + + metrics!(METRICS_TX.inc_tx_with_status_and_type( + MetricsTxStatus::Succeeded, + MetricsTxType(head_tx.tx_type()) + )); + receipt + } + // Ignore following txs from sender + Err(e) => { + debug!("Failed to execute transaction: {}, {e}", tx_hash); + metrics!(METRICS_TX.inc_tx_with_status_and_type( + MetricsTxStatus::Failed, + MetricsTxType(head_tx.tx_type()) + )); + txs.pop(); + continue; + } + }; + // Add transaction to block + debug!("Adding transaction: {} to payload", tx_hash); + context.payload.body.transactions.push(head_tx.into()); + // Save receipt for hash calculation + context.receipts.push(receipt); + } + Ok(()) +} + +/// Calculates the size of the current `StateDiff` of the block. +/// If the current size exceeds the blob size limit, returns `Ok(false)`. +/// If there is still space in the blob, returns `Ok(true)`. +fn check_state_diff_size( + withdrawals_size: &mut usize, + deposits_size: &mut usize, + tx: Transaction, + receipt: &Receipt, + context: &mut PayloadBuildContext, +) -> Result { + if is_withdrawal_l2(&tx, receipt) { + *withdrawals_size += L2_WITHDRAWAL_SIZE; + } + if is_deposit_l2(&tx) { + *deposits_size += L2_DEPOSIT_SIZE; + } + let modified_accounts_size = calc_modified_accounts_size(context)?; + + let current_state_diff_size = 1 /* version (u8) */ + HEADER_FIELDS_SIZE + *withdrawals_size + *deposits_size + modified_accounts_size; + + if current_state_diff_size > SAFE_BYTES_PER_BLOB { + // Restore the withdrawals and deposits counters. + if is_withdrawal_l2(&tx, receipt) { + *withdrawals_size -= L2_WITHDRAWAL_SIZE; + } + if is_deposit_l2(&tx) { + *deposits_size -= L2_DEPOSIT_SIZE; + } + debug!( + "Blob size limit exceeded. current_state_diff_size: {}", + current_state_diff_size + ); + return Ok(false); + } + Ok(true) +} + +fn calc_modified_accounts_size( + context: &mut PayloadBuildContext, +) -> Result { + let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) + + // We use a temporary_context because revm mutates it in `get_state_transitions` + let mut temporary_context = context.clone(); + let account_updates = temporary_context + .vm + .get_state_transitions(context.payload.header.parent_hash)?; + for account_update in account_updates { + modified_accounts_size += 1 + 20; // r#type(u8) + address(H160) + if account_update.info.is_some() { + modified_accounts_size += 32; // new_balance(U256) + } + let nonce_diff = get_nonce_diff(&account_update, &context.store, context.block_number()) + .map_err(|e| { + BlockProducerError::Custom(format!("Block Producer failed to get nonce diff: {e}")) + })?; + if nonce_diff != 0 { + modified_accounts_size += 2; // nonce_diff(u16) + } + // for each added_storage: key(H256) + value(U256) + modified_accounts_size += account_update.added_storage.len() * 2 * 32; + + if let Some(bytecode) = &account_update.code { + modified_accounts_size += 2; // bytecode_len(u16) + modified_accounts_size += bytecode.len(); // bytecode(Bytes) + } + } + Ok(modified_accounts_size) +} diff --git a/crates/l2/sequencer/errors.rs b/crates/l2/sequencer/errors.rs index 5b70547e21..fde1b16a4f 100644 --- a/crates/l2/sequencer/errors.rs +++ b/crates/l2/sequencer/errors.rs @@ -95,6 +95,8 @@ pub enum BlockProducerError { FailedToDecodeJWT(#[from] hex::FromHexError), #[error("Block Producer failed because of an execution cache error")] ExecutionCache(#[from] ExecutionCacheError), + #[error("{0}")] + Custom(String), } #[derive(Debug, thiserror::Error)] @@ -109,8 +111,6 @@ pub enum CommitterError { ExecutionCache(#[from] ExecutionCacheError), #[error("Committer failed retrieve data from storage")] FailedToRetrieveDataFromStorage, - #[error("Committer registered a negative nonce in AccountUpdate")] - FailedToCalculateNonce, #[error("Committer failed to generate blobs bundle: {0}")] FailedToGenerateBlobsBundle(#[from] BlobsBundleError), #[error("Committer failed to get information from storage")] @@ -171,6 +171,10 @@ pub enum StateDiffError { LengthTooBig(#[from] core::num::TryFromIntError), #[error("DB Error: {0}")] DbError(#[from] TrieError), + #[error("Store Error: {0}")] + StoreError(#[from] StoreError), + #[error("StateDiff registered a negative nonce in AccountUpdate")] + FailedToCalculateNonce, } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index bd244a1a50..a305e6a4ef 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -1,9 +1,12 @@ use crate::{ sequencer::{ errors::CommitterError, - state_diff::{AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}, + state_diff::{get_nonce_diff, AccountStateDiff, DepositLog, StateDiff, WithdrawalLog}, + }, + utils::{ + config::{committer::CommitterConfig, errors::ConfigError, eth::EthConfig}, + helpers::is_withdrawal_l2, }, - utils::config::{committer::CommitterConfig, errors::ConfigError, eth::EthConfig}, }; use ethrex_common::{ @@ -15,15 +18,15 @@ use ethrex_common::{ Address, H256, U256, }; use ethrex_l2_sdk::calldata::{encode_calldata, Value}; -use ethrex_l2_sdk::{get_withdrawal_hash, merkle_tree::merkelize, COMMON_BRIDGE_L2_ADDRESS}; +use ethrex_l2_sdk::{get_withdrawal_hash, merkle_tree::merkelize}; use ethrex_rpc::clients::eth::{ eth_sender::Overrides, BlockByNumber, EthClient, WrappedTransaction, }; -use ethrex_storage::{error::StoreError, AccountUpdate, Store}; +use ethrex_storage::{AccountUpdate, Store}; use ethrex_vm::backends::Evm; use keccak_hash::keccak; use secp256k1::SecretKey; -use std::{collections::HashMap, str::FromStr, sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use tokio::time::sleep; use tracing::{debug, error, info, warn}; @@ -186,26 +189,13 @@ impl Committer { &self, txs_and_receipts: &[(Transaction, Receipt)], ) -> Result, CommitterError> { - // WithdrawalInitiated(address,address,uint256) - let withdrawal_event_selector: H256 = - H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2").map_err(|_| CommitterError::InternalError("Failed to convert WithdrawalInitiated event selector to H256. This should never happen.".to_owned()))?; let mut ret = vec![]; for (tx, receipt) in txs_and_receipts { - match tx.to() { - TxKind::Call(to) if to == COMMON_BRIDGE_L2_ADDRESS => { - if receipt.logs.iter().any(|log| { - log.topics - .iter() - .any(|topic| *topic == withdrawal_event_selector) - }) { - ret.push((tx.compute_hash(), tx.clone())) - } - } - _ => continue, + if is_withdrawal_l2(tx, receipt) { + ret.push((tx.compute_hash(), tx.clone())) } } - Ok(ret) } @@ -275,32 +265,17 @@ impl Committer { let mut modified_accounts = HashMap::new(); for account_update in account_updates { - let prev_nonce = match store - // If we want the state_diff of a batch, we will have to change the -1 with the `batch_size` - // and we may have to keep track of the latestCommittedBlock (last block of the batch), - // the batch_size and the latestCommittedBatch in the contract. - .get_account_info(block.header.number - 1, account_update.address) - .map_err(StoreError::from)? - { - Some(acc) => acc.nonce, - None => 0, - }; - - let new_nonce = if let Some(info) = &account_update.info { - info.nonce - } else { - prev_nonce - }; + // If we want the state_diff of a batch, we will have to change the -1 with the `batch_size` + // and we may have to keep track of the latestCommittedBlock (last block of the batch), + // the batch_size and the latestCommittedBatch in the contract. + let nonce_diff = get_nonce_diff(account_update, &store, block.header.number) + .map_err(CommitterError::from)?; modified_accounts.insert( account_update.address, AccountStateDiff { new_balance: account_update.info.clone().map(|info| info.balance), - nonce_diff: new_nonce - .checked_sub(prev_nonce) - .ok_or(CommitterError::FailedToCalculateNonce)? - .try_into() - .map_err(CommitterError::from)?, + nonce_diff, storage: account_update.added_storage.clone().into_iter().collect(), bytecode: account_update.code.clone(), bytecode_hash: None, diff --git a/crates/l2/sequencer/state_diff.rs b/crates/l2/sequencer/state_diff.rs index 6443c716d7..718af3dcc5 100644 --- a/crates/l2/sequencer/state_diff.rs +++ b/crates/l2/sequencer/state_diff.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use bytes::Bytes; use ethereum_types::{Address, H256, U256}; -use ethrex_common::types::{AccountInfo, AccountState, BlockHeader}; +use ethrex_common::types::{AccountInfo, AccountState, BlockHeader, BlockNumber}; use ethrex_rlp::decode::RLPDecode; -use ethrex_storage::{hash_address, AccountUpdate}; +use ethrex_storage::{error::StoreError, hash_address, AccountUpdate, Store}; use ethrex_trie::Trie; use super::errors::StateDiffError; @@ -487,3 +487,29 @@ impl Decoder { Ok(Bytes::copy_from_slice(res)) } } + +pub fn get_nonce_diff( + account_update: &AccountUpdate, + store: &Store, + current_block_number: BlockNumber, +) -> Result { + let prev_nonce = match store + .get_account_info(current_block_number - 1, account_update.address) + .map_err(StoreError::from)? + { + Some(acc) => acc.nonce, + None => 0, + }; + + let new_nonce = if let Some(info) = account_update.info.clone() { + info.nonce + } else { + prev_nonce + }; + let nonce_diff = new_nonce + .checked_sub(prev_nonce) + .ok_or(StateDiffError::FailedToCalculateNonce)? + .try_into() + .map_err(StateDiffError::from)?; + Ok(nonce_diff) +} diff --git a/crates/l2/utils/helpers.rs b/crates/l2/utils/helpers.rs new file mode 100644 index 0000000000..8302ce0e67 --- /dev/null +++ b/crates/l2/utils/helpers.rs @@ -0,0 +1,27 @@ +use ethrex_common::{ + types::{Receipt, Transaction, TxKind}, + H256, +}; +use ethrex_l2_sdk::COMMON_BRIDGE_L2_ADDRESS; +use std::str::FromStr; + +pub fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { + // WithdrawalInitiated(address,address,uint256) + let withdrawal_event_selector: H256 = + H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2").unwrap(); + + match tx.to() { + TxKind::Call(to) if to == COMMON_BRIDGE_L2_ADDRESS => { + return receipt.logs.iter().any(|log| { + log.topics + .iter() + .any(|topic| *topic == withdrawal_event_selector) + }); + } + _ => false, + } +} + +pub fn is_deposit_l2(tx: &Transaction) -> bool { + matches!(tx, Transaction::PrivilegedL2Transaction(_tx)) +} diff --git a/crates/l2/utils/mod.rs b/crates/l2/utils/mod.rs index 04d38e0aae..5591808462 100644 --- a/crates/l2/utils/mod.rs +++ b/crates/l2/utils/mod.rs @@ -1,5 +1,6 @@ pub mod config; pub mod error; +pub mod helpers; pub mod parse; pub mod prover; pub mod test_data_io; From 7e505af5acb0d965dcf9861c9a47e2cc9bf15589 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 13:37:19 -0300 Subject: [PATCH 15/34] Use l2 implementation in block producer --- crates/l2/sequencer/block_producer.rs | 3 ++- crates/l2/sequencer/block_producer/payload_builder.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/l2/sequencer/block_producer.rs b/crates/l2/sequencer/block_producer.rs index b529c11460..db7ba921b4 100644 --- a/crates/l2/sequencer/block_producer.rs +++ b/crates/l2/sequencer/block_producer.rs @@ -13,6 +13,7 @@ use ethrex_common::Address; use ethrex_storage::Store; use ethrex_vm::backends::BlockExecutionResult; use keccak_hash::H256; +use payload_builder::build_payload; use tokio::time::sleep; use tracing::{debug, error, info}; @@ -103,7 +104,7 @@ impl BlockProducer { let payload = create_payload(&args, &store)?; // Blockchain builds the payload from mempool txs and executes them - let payload_build_result = blockchain.build_payload(payload)?; + let payload_build_result = build_payload(blockchain.clone(), payload, &store)?; info!( "Built payload for new block {}", payload_build_result.payload.header.number diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 28e3c83204..3ae172289b 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -27,7 +27,7 @@ const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). pub fn build_payload( blockchain: Arc, payload: Block, - store: Store, + store: &Store, ) -> Result { let since = Instant::now(); let gas_limit = payload.header.gas_limit; @@ -60,7 +60,7 @@ pub fn build_payload( pub fn fill_transactions( blockchain: Arc, context: &mut PayloadBuildContext, - store: Store, + store: &Store, ) -> Result<(), BlockProducerError> { // Two bytes for the len let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); From a0c79cbc8f477b25c991fd4a09cfafa9d509378e Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 15:37:33 -0300 Subject: [PATCH 16/34] Fix linter --- crates/blockchain/payload.rs | 1 - .../block_producer/payload_builder.rs | 30 ++++++++++++------- crates/l2/sequencer/errors.rs | 2 ++ crates/l2/utils/helpers.rs | 4 ++- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index f12a05df1d..8bac88af03 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -261,7 +261,6 @@ impl Blockchain { debug!("Building payload"); let mut context = PayloadBuildContext::new(payload, self.evm_engine, &self.storage)?; - #[cfg(not(feature = "l2"))] self.apply_system_operations(&mut context)?; self.apply_withdrawals(&mut context)?; self.fill_transactions(&mut context)?; diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 3ae172289b..b0648e935a 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -6,7 +6,10 @@ use ethrex_blockchain::{ Blockchain, }; use ethrex_common::types::{Block, Receipt, Transaction, SAFE_BYTES_PER_BLOB}; -use ethrex_metrics::metrics; +use ethrex_metrics::{ + metrics, + metrics_transactions::{MetricsTxStatus, MetricsTxType, METRICS_TX}, +}; use ethrex_storage::Store; use std::ops::Div; use tokio::time::Instant; @@ -33,20 +36,25 @@ pub fn build_payload( let gas_limit = payload.header.gas_limit; debug!("Building payload"); - let mut context = PayloadBuildContext::new(payload, blockchain.evm_engine, &store)?; + let mut context = PayloadBuildContext::new(payload, blockchain.evm_engine, store)?; blockchain.apply_withdrawals(&mut context)?; fill_transactions(blockchain.clone(), &mut context, store)?; blockchain.extract_requests(&mut context)?; blockchain.finalize_payload(&mut context)?; - let interval = Instant::now().duration_since(since).as_millis(); + let interval: u64 = Instant::now() + .duration_since(since) + .as_millis() + .try_into()?; tracing::info!("[METRIC] BUILDING PAYLOAD TOOK: {interval} ms"); if let Some(gas_used) = gas_limit.checked_sub(context.remaining_gas) { - let as_gigas = (gas_used as f64).div(10_f64.powf(9_f64)); + let gas_used = f64::from_ne_bytes(gas_used.to_ne_bytes()); + let as_gigas = gas_used.div(10_f64.powf(9_f64)); if interval != 0 { - let throughput = (as_gigas) / (interval as f64) * 1000_f64; + let interval = f64::from_ne_bytes(interval.to_ne_bytes()); + let throughput = (as_gigas) / (interval * 1000_f64); tracing::info!( "[METRIC] BLOCK BUILDING THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs" ); @@ -56,20 +64,21 @@ pub fn build_payload( Ok(context.into()) } -/// Same as blockchain_fill transactions but checks the resulting `StateDiff` size to not exceed +/// Same as blockchain::fill_transactions but checks the resulting `StateDiff` size to not exceed Blob size limit pub fn fill_transactions( blockchain: Arc, context: &mut PayloadBuildContext, store: &Store, ) -> Result<(), BlockProducerError> { // Two bytes for the len - let (mut withdrawals_size, mut deposits_size): (usize, usize) = (2, 2); + let (mut withdrawals_acc_size, mut deposits_acc_size): (usize, usize) = (2, 2); let chain_config = store.get_chain_config()?; let max_blob_number_per_block = chain_config .get_fork_blob_schedule(context.payload.header.timestamp) .map(|schedule| schedule.max) - .unwrap_or_default() as usize; + .unwrap_or_default() + .try_into()?; debug!("Fetching transactions from mempool"); // Fetch mempool transactions @@ -135,8 +144,8 @@ pub fn fill_transactions( let receipt = match blockchain.apply_transaction(&head_tx, context) { Ok(receipt) => { if !check_state_diff_size( - &mut withdrawals_size, - &mut deposits_size, + &mut withdrawals_acc_size, + &mut deposits_acc_size, head_tx.clone().into(), &receipt, context, @@ -223,6 +232,7 @@ fn calc_modified_accounts_size( let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) // We use a temporary_context because revm mutates it in `get_state_transitions` + // TODO: remove when we stop using revm let mut temporary_context = context.clone(); let account_updates = temporary_context .vm diff --git a/crates/l2/sequencer/errors.rs b/crates/l2/sequencer/errors.rs index fde1b16a4f..e816656878 100644 --- a/crates/l2/sequencer/errors.rs +++ b/crates/l2/sequencer/errors.rs @@ -95,6 +95,8 @@ pub enum BlockProducerError { FailedToDecodeJWT(#[from] hex::FromHexError), #[error("Block Producer failed because of an execution cache error")] ExecutionCache(#[from] ExecutionCacheError), + #[error("Interval does not fit in u64")] + TryIntoError(#[from] std::num::TryFromIntError), #[error("{0}")] Custom(String), } diff --git a/crates/l2/utils/helpers.rs b/crates/l2/utils/helpers.rs index 8302ce0e67..7331017bb0 100644 --- a/crates/l2/utils/helpers.rs +++ b/crates/l2/utils/helpers.rs @@ -5,10 +5,12 @@ use ethrex_common::{ use ethrex_l2_sdk::COMMON_BRIDGE_L2_ADDRESS; use std::str::FromStr; +#[allow(clippy::expect_used)] pub fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { // WithdrawalInitiated(address,address,uint256) let withdrawal_event_selector: H256 = - H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2").unwrap(); + H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2") + .expect("Unable to parse withdrawal_event_selector"); match tx.to() { TxKind::Call(to) if to == COMMON_BRIDGE_L2_ADDRESS => { From 0e8c0f039dc758f2dbab86f50cd3dd3827687c49 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 15:57:55 -0300 Subject: [PATCH 17/34] Improve logging --- crates/l2/sequencer/block_producer/payload_builder.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index b0648e935a..2d28da03f8 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -26,7 +26,7 @@ const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). /// L2 payload builder /// Completes the payload building process, return the block value -/// Same as blockchain::build_payload without applying system operations and using a different method to `fill_transactions` +/// Same as `blockchain::build_payload` without applying system operations and using a different `fill_transactions` pub fn build_payload( blockchain: Arc, payload: Block, @@ -64,7 +64,8 @@ pub fn build_payload( Ok(context.into()) } -/// Same as blockchain::fill_transactions but checks the resulting `StateDiff` size to not exceed Blob size limit +/// Same as `blockchain::fill_transactions` but enforces that the `StateDiff` size +/// stays within the blob size limit after processing each transaction. pub fn fill_transactions( blockchain: Arc, context: &mut PayloadBuildContext, @@ -143,6 +144,7 @@ pub fn fill_transactions( // Execute tx let receipt = match blockchain.apply_transaction(&head_tx, context) { Ok(receipt) => { + // This call is the part that differs from the original `fill_transactions`. if !check_state_diff_size( &mut withdrawals_acc_size, &mut deposits_acc_size, From e5d8b34d824f581c4d4b965b1b1cf029a77649b1 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 17:13:30 -0300 Subject: [PATCH 18/34] Rename acc counters --- .../block_producer/payload_builder.rs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 2d28da03f8..4687146991 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -6,10 +6,10 @@ use ethrex_blockchain::{ Blockchain, }; use ethrex_common::types::{Block, Receipt, Transaction, SAFE_BYTES_PER_BLOB}; -use ethrex_metrics::{ - metrics, - metrics_transactions::{MetricsTxStatus, MetricsTxType, METRICS_TX}, -}; +use ethrex_metrics::metrics; + +#[cfg(feature = "metrics")] +use ethrex_metrics::metrics_transactions::{MetricsTxStatus, MetricsTxType, METRICS_TX}; use ethrex_storage::Store; use std::ops::Div; use tokio::time::Instant; @@ -72,7 +72,7 @@ pub fn fill_transactions( store: &Store, ) -> Result<(), BlockProducerError> { // Two bytes for the len - let (mut withdrawals_acc_size, mut deposits_acc_size): (usize, usize) = (2, 2); + let (mut acc_withdrawals_size, mut acc_deposits_size): (usize, usize) = (2, 2); let chain_config = store.get_chain_config()?; let max_blob_number_per_block = chain_config @@ -146,8 +146,8 @@ pub fn fill_transactions( Ok(receipt) => { // This call is the part that differs from the original `fill_transactions`. if !check_state_diff_size( - &mut withdrawals_acc_size, - &mut deposits_acc_size, + &mut acc_withdrawals_size, + &mut acc_deposits_size, head_tx.clone().into(), &receipt, context, @@ -195,29 +195,29 @@ pub fn fill_transactions( /// If the current size exceeds the blob size limit, returns `Ok(false)`. /// If there is still space in the blob, returns `Ok(true)`. fn check_state_diff_size( - withdrawals_size: &mut usize, - deposits_size: &mut usize, + acc_withdrawals_size: &mut usize, + acc_deposits_size: &mut usize, tx: Transaction, receipt: &Receipt, context: &mut PayloadBuildContext, ) -> Result { if is_withdrawal_l2(&tx, receipt) { - *withdrawals_size += L2_WITHDRAWAL_SIZE; + *acc_withdrawals_size += L2_WITHDRAWAL_SIZE; } if is_deposit_l2(&tx) { - *deposits_size += L2_DEPOSIT_SIZE; + *acc_deposits_size += L2_DEPOSIT_SIZE; } let modified_accounts_size = calc_modified_accounts_size(context)?; - let current_state_diff_size = 1 /* version (u8) */ + HEADER_FIELDS_SIZE + *withdrawals_size + *deposits_size + modified_accounts_size; + let current_state_diff_size = 1 /* version (u8) */ + HEADER_FIELDS_SIZE + *acc_withdrawals_size + *acc_deposits_size + modified_accounts_size; if current_state_diff_size > SAFE_BYTES_PER_BLOB { // Restore the withdrawals and deposits counters. if is_withdrawal_l2(&tx, receipt) { - *withdrawals_size -= L2_WITHDRAWAL_SIZE; + *acc_withdrawals_size -= L2_WITHDRAWAL_SIZE; } if is_deposit_l2(&tx) { - *deposits_size -= L2_DEPOSIT_SIZE; + *acc_deposits_size -= L2_DEPOSIT_SIZE; } debug!( "Blob size limit exceeded. current_state_diff_size: {}", From b5e6a6895fceccc7b9cfeb85f1bcf7d913475e1e Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 17:57:55 -0300 Subject: [PATCH 19/34] Restore feature flag --- crates/blockchain/payload.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 8bac88af03..f12a05df1d 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -261,6 +261,7 @@ impl Blockchain { debug!("Building payload"); let mut context = PayloadBuildContext::new(payload, self.evm_engine, &self.storage)?; + #[cfg(not(feature = "l2"))] self.apply_system_operations(&mut context)?; self.apply_withdrawals(&mut context)?; self.fill_transactions(&mut context)?; From 3f7b17f792bfab09e69a47cd548a70be29ee6348 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Tue, 25 Mar 2025 18:59:48 -0300 Subject: [PATCH 20/34] fix convertion from u64 to usize --- crates/l2/sequencer/block_producer/payload_builder.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 4687146991..9e3f61d641 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -13,7 +13,7 @@ use ethrex_metrics::metrics_transactions::{MetricsTxStatus, MetricsTxType, METRI use ethrex_storage::Store; use std::ops::Div; use tokio::time::Instant; -use tracing::debug; +use tracing::{debug, warn}; use crate::{ sequencer::{errors::BlockProducerError, state_diff::get_nonce_diff}, @@ -75,11 +75,12 @@ pub fn fill_transactions( let (mut acc_withdrawals_size, mut acc_deposits_size): (usize, usize) = (2, 2); let chain_config = store.get_chain_config()?; - let max_blob_number_per_block = chain_config + let max_blob_number_per_block: usize = chain_config .get_fork_blob_schedule(context.payload.header.timestamp) .map(|schedule| schedule.max) .unwrap_or_default() - .try_into()?; + .try_into() + .unwrap_or_default(); debug!("Fetching transactions from mempool"); // Fetch mempool transactions @@ -152,7 +153,7 @@ pub fn fill_transactions( &receipt, context, )? { - debug!( + warn!( "Skipping transaction: {}, doesn't feet in blob_size", head_tx.tx.compute_hash() ); From b5e9801eac37077a4217245b40d25a214f1a81db Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Wed, 26 Mar 2025 15:15:44 -0300 Subject: [PATCH 21/34] Fix throughput logs --- .../sequencer/block_producer/payload_builder.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 9e3f61d641..91d718f8bd 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -13,7 +13,7 @@ use ethrex_metrics::metrics_transactions::{MetricsTxStatus, MetricsTxType, METRI use ethrex_storage::Store; use std::ops::Div; use tokio::time::Instant; -use tracing::{debug, warn}; +use tracing::debug; use crate::{ sequencer::{errors::BlockProducerError, state_diff::get_nonce_diff}, @@ -43,18 +43,14 @@ pub fn build_payload( blockchain.extract_requests(&mut context)?; blockchain.finalize_payload(&mut context)?; - let interval: u64 = Instant::now() - .duration_since(since) - .as_millis() - .try_into()?; + let interval = Instant::now().duration_since(since).as_millis(); tracing::info!("[METRIC] BUILDING PAYLOAD TOOK: {interval} ms"); + #[allow(clippy::as_conversions)] if let Some(gas_used) = gas_limit.checked_sub(context.remaining_gas) { - let gas_used = f64::from_ne_bytes(gas_used.to_ne_bytes()); - let as_gigas = gas_used.div(10_f64.powf(9_f64)); + let as_gigas = (gas_used as f64).div(10_f64.powf(9_f64)); if interval != 0 { - let interval = f64::from_ne_bytes(interval.to_ne_bytes()); - let throughput = (as_gigas) / (interval * 1000_f64); + let throughput = (as_gigas) / (interval as f64) * 1000_f64; tracing::info!( "[METRIC] BLOCK BUILDING THROUGHPUT: {throughput} Gigagas/s TIME SPENT: {interval} msecs" ); @@ -153,7 +149,7 @@ pub fn fill_transactions( &receipt, context, )? { - warn!( + debug!( "Skipping transaction: {}, doesn't feet in blob_size", head_tx.tx.compute_hash() ); From 6e69ba544cdf1dbb9533651869b132d4a8fe85b6 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 28 Mar 2025 12:52:10 -0300 Subject: [PATCH 22/34] perf: add accounts_info cache --- .../block_producer/payload_builder.rs | 26 +++++++--- crates/l2/sequencer/l1_committer.rs | 2 +- crates/l2/sequencer/state_diff.rs | 49 +++++++++++++++++-- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 91d718f8bd..ac00512391 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::Arc; use ethrex_blockchain::{ @@ -5,7 +6,10 @@ use ethrex_blockchain::{ payload::{PayloadBuildContext, PayloadBuildResult}, Blockchain, }; -use ethrex_common::types::{Block, Receipt, Transaction, SAFE_BYTES_PER_BLOB}; +use ethrex_common::{ + types::{AccountInfo, Block, Receipt, Transaction, SAFE_BYTES_PER_BLOB}, + Address, +}; use ethrex_metrics::metrics; #[cfg(feature = "metrics")] @@ -69,6 +73,7 @@ pub fn fill_transactions( ) -> Result<(), BlockProducerError> { // Two bytes for the len let (mut acc_withdrawals_size, mut acc_deposits_size): (usize, usize) = (2, 2); + let mut accounts_info_cache = HashMap::new(); let chain_config = store.get_chain_config()?; let max_blob_number_per_block: usize = chain_config @@ -148,6 +153,7 @@ pub fn fill_transactions( head_tx.clone().into(), &receipt, context, + &mut accounts_info_cache, )? { debug!( "Skipping transaction: {}, doesn't feet in blob_size", @@ -197,6 +203,7 @@ fn check_state_diff_size( tx: Transaction, receipt: &Receipt, context: &mut PayloadBuildContext, + accounts_info_cache: &mut HashMap>, ) -> Result { if is_withdrawal_l2(&tx, receipt) { *acc_withdrawals_size += L2_WITHDRAWAL_SIZE; @@ -204,7 +211,7 @@ fn check_state_diff_size( if is_deposit_l2(&tx) { *acc_deposits_size += L2_DEPOSIT_SIZE; } - let modified_accounts_size = calc_modified_accounts_size(context)?; + let modified_accounts_size = calc_modified_accounts_size(context, accounts_info_cache)?; let current_state_diff_size = 1 /* version (u8) */ + HEADER_FIELDS_SIZE + *acc_withdrawals_size + *acc_deposits_size + modified_accounts_size; @@ -227,6 +234,7 @@ fn check_state_diff_size( fn calc_modified_accounts_size( context: &mut PayloadBuildContext, + accounts_info_cache: &mut HashMap>, ) -> Result { let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) @@ -241,10 +249,16 @@ fn calc_modified_accounts_size( if account_update.info.is_some() { modified_accounts_size += 32; // new_balance(U256) } - let nonce_diff = get_nonce_diff(&account_update, &context.store, context.block_number()) - .map_err(|e| { - BlockProducerError::Custom(format!("Block Producer failed to get nonce diff: {e}")) - })?; + + let nonce_diff = get_nonce_diff( + &account_update, + &context.store, + Some(accounts_info_cache), + context.block_number(), + ) + .map_err(|e| { + BlockProducerError::Custom(format!("Block Producer failed to get nonce diff: {e}")) + })?; if nonce_diff != 0 { modified_accounts_size += 2; // nonce_diff(u16) } diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index c4af99b46b..6e1fe8ba10 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -267,7 +267,7 @@ impl Committer { // If we want the state_diff of a batch, we will have to change the -1 with the `batch_size` // and we may have to keep track of the latestCommittedBlock (last block of the batch), // the batch_size and the latestCommittedBatch in the contract. - let nonce_diff = get_nonce_diff(account_update, &store, block.header.number) + let nonce_diff = get_nonce_diff(account_update, &store, None, block.header.number) .map_err(CommitterError::from)?; modified_accounts.insert( diff --git a/crates/l2/sequencer/state_diff.rs b/crates/l2/sequencer/state_diff.rs index 718af3dcc5..1518853dd7 100644 --- a/crates/l2/sequencer/state_diff.rs +++ b/crates/l2/sequencer/state_diff.rs @@ -488,28 +488,67 @@ impl Decoder { } } +/// Calculates nonce_diff between current and previous block. +/// Uses cache if provided to optimize account_info lookups. pub fn get_nonce_diff( account_update: &AccountUpdate, store: &Store, + accounts_info_cache: Option<&mut HashMap>>, current_block_number: BlockNumber, ) -> Result { - let prev_nonce = match store - .get_account_info(current_block_number - 1, account_update.address) - .map_err(StoreError::from)? - { - Some(acc) => acc.nonce, + // Get previous account_info either from store or cache + let account_info = match accounts_info_cache { + None => store + .get_account_info(current_block_number - 1, account_update.address) + .map_err(StoreError::from)?, + Some(cache) => account_info_from_cache( + cache, + store, + account_update.address, + current_block_number - 1, + )?, + }; + + // Get previous nonce + let prev_nonce = match account_info { + Some(info) => info.nonce, None => 0, }; + // Get current nonce let new_nonce = if let Some(info) = account_update.info.clone() { info.nonce } else { prev_nonce }; + + // Calculate nonce diff let nonce_diff = new_nonce .checked_sub(prev_nonce) .ok_or(StateDiffError::FailedToCalculateNonce)? .try_into() .map_err(StateDiffError::from)?; + Ok(nonce_diff) } + +/// Retrieves account info from cache or falls back to store. +/// Updates cache with fresh data if cache miss occurs. +fn account_info_from_cache( + cache: &mut HashMap>, + store: &Store, + address: Address, + block_number: BlockNumber, +) -> Result, StateDiffError> { + let account_info = match cache.get(&address) { + Some(account_info) => account_info.clone(), + None => { + let account_info = store + .get_account_info(block_number, address) + .map_err(StoreError::from)?; + cache.insert(address, account_info.clone()); + account_info + } + }; + Ok(account_info) +} From b64abc661669d2384f9601e18e374bbc7a069df4 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 28 Mar 2025 15:23:13 -0300 Subject: [PATCH 23/34] Remove expect --- .../block_producer/payload_builder.rs | 4 ++-- crates/l2/sequencer/errors.rs | 5 ++++ crates/l2/sequencer/l1_committer.rs | 2 +- crates/l2/utils/error.rs | 6 +++++ crates/l2/utils/helpers.rs | 23 ++++++++++--------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index ac00512391..c596ac9106 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -205,7 +205,7 @@ fn check_state_diff_size( context: &mut PayloadBuildContext, accounts_info_cache: &mut HashMap>, ) -> Result { - if is_withdrawal_l2(&tx, receipt) { + if is_withdrawal_l2(&tx, receipt)? { *acc_withdrawals_size += L2_WITHDRAWAL_SIZE; } if is_deposit_l2(&tx) { @@ -217,7 +217,7 @@ fn check_state_diff_size( if current_state_diff_size > SAFE_BYTES_PER_BLOB { // Restore the withdrawals and deposits counters. - if is_withdrawal_l2(&tx, receipt) { + if is_withdrawal_l2(&tx, receipt)? { *acc_withdrawals_size -= L2_WITHDRAWAL_SIZE; } if is_deposit_l2(&tx) { diff --git a/crates/l2/sequencer/errors.rs b/crates/l2/sequencer/errors.rs index 606b1c54a8..aa0901820d 100644 --- a/crates/l2/sequencer/errors.rs +++ b/crates/l2/sequencer/errors.rs @@ -1,4 +1,5 @@ use crate::utils::config::errors::ConfigError; +use crate::utils::error::UtilsError; use crate::utils::prover::errors::SaveStateError; use ethereum_types::FromStrRadixErr; use ethrex_blockchain::error::{ChainError, InvalidForkChoice}; @@ -87,6 +88,8 @@ pub enum BlockProducerError { TryIntoError(#[from] std::num::TryFromIntError), #[error("{0}")] Custom(String), + #[error("Failed to parse withdrawal: {0}")] + FailedToParseWithdrawal(#[from] UtilsError), } #[derive(Debug, thiserror::Error)] @@ -127,6 +130,8 @@ pub enum CommitterError { CalldataEncodeError(#[from] CalldataEncodeError), #[error("Unexpected Error: {0}")] InternalError(String), + #[error("Failed to get withdrawals: {0}")] + FailedToGetWithdrawals(#[from] UtilsError), } #[derive(Debug, thiserror::Error)] diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs index 6e1fe8ba10..3c4605c4a5 100644 --- a/crates/l2/sequencer/l1_committer.rs +++ b/crates/l2/sequencer/l1_committer.rs @@ -191,7 +191,7 @@ impl Committer { let mut ret = vec![]; for (tx, receipt) in txs_and_receipts { - if is_withdrawal_l2(tx, receipt) { + if is_withdrawal_l2(tx, receipt)? { ret.push((tx.compute_hash(), tx.clone())) } } diff --git a/crates/l2/utils/error.rs b/crates/l2/utils/error.rs index 56a98b63ec..624f2e13c2 100644 --- a/crates/l2/utils/error.rs +++ b/crates/l2/utils/error.rs @@ -16,3 +16,9 @@ pub enum ProverInputError { #[error("ExecutionDB error: {0}")] ExecutionDBError(#[from] ExecutionDBError), } + +#[derive(Debug, thiserror::Error)] +pub enum UtilsError { + #[error("Unable to parse withdrawal_event_selector: {0}")] + WithdrawalSelectorError(String), +} diff --git a/crates/l2/utils/helpers.rs b/crates/l2/utils/helpers.rs index 7331017bb0..3aad063975 100644 --- a/crates/l2/utils/helpers.rs +++ b/crates/l2/utils/helpers.rs @@ -5,23 +5,24 @@ use ethrex_common::{ use ethrex_l2_sdk::COMMON_BRIDGE_L2_ADDRESS; use std::str::FromStr; +use super::error::UtilsError; + #[allow(clippy::expect_used)] -pub fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> bool { +pub fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> Result { // WithdrawalInitiated(address,address,uint256) let withdrawal_event_selector: H256 = H256::from_str("bb2689ff876f7ef453cf8865dde5ab10349d222e2e1383c5152fbdb083f02da2") - .expect("Unable to parse withdrawal_event_selector"); + .map_err(|e| UtilsError::WithdrawalSelectorError(e.to_string()))?; - match tx.to() { - TxKind::Call(to) if to == COMMON_BRIDGE_L2_ADDRESS => { - return receipt.logs.iter().any(|log| { - log.topics - .iter() - .any(|topic| *topic == withdrawal_event_selector) - }); - } + let is_withdrawal = match tx.to() { + TxKind::Call(to) if to == COMMON_BRIDGE_L2_ADDRESS => receipt.logs.iter().any(|log| { + log.topics + .iter() + .any(|topic| *topic == withdrawal_event_selector) + }), _ => false, - } + }; + Ok(is_withdrawal) } pub fn is_deposit_l2(tx: &Transaction) -> bool { From 855de6b487023d80151396eca751ba58b4886493 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 28 Mar 2025 15:24:03 -0300 Subject: [PATCH 24/34] Remove unnecesary annotation --- crates/l2/utils/helpers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/l2/utils/helpers.rs b/crates/l2/utils/helpers.rs index 3aad063975..691003d65e 100644 --- a/crates/l2/utils/helpers.rs +++ b/crates/l2/utils/helpers.rs @@ -7,7 +7,6 @@ use std::str::FromStr; use super::error::UtilsError; -#[allow(clippy::expect_used)] pub fn is_withdrawal_l2(tx: &Transaction, receipt: &Receipt) -> Result { // WithdrawalInitiated(address,address,uint256) let withdrawal_event_selector: H256 = From 651ed903f65b4ed9ea56cacfd09cb5ef58eb30f0 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 31 Mar 2025 10:50:37 -0300 Subject: [PATCH 25/34] Improve function docs --- .../block_producer/payload_builder.rs | 36 +++++++++++++------ crates/l2/sequencer/errors.rs | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index c596ac9106..32ced9b929 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -24,9 +24,17 @@ use crate::{ utils::helpers::{is_deposit_l2, is_withdrawal_l2}, }; -const HEADER_FIELDS_SIZE: usize = 96; // transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). -const L2_WITHDRAWAL_SIZE: usize = 84; // address(H160) + amount(U256) + tx_hash(H256). -const L2_DEPOSIT_SIZE: usize = 52; // address(H160) + amount(U256). +// transactions_root(H256) + receipts_root(H256) + gas_limit(u64) + gas_used(u64) + timestamp(u64) + base_fee_per_gas(u64). +// 32bytes + 32bytes + 8bytes + 8bytes + 8bytes + 8bytes +const HEADER_FIELDS_SIZE: usize = 96; + +// address(H160) + amount(U256) + tx_hash(H256). +// 20bytes + 32bytes + 32bytes. +const L2_WITHDRAWAL_SIZE: usize = 84; + +// address(H160) + amount(U256). +// 20bytes + 32bytes +const L2_DEPOSIT_SIZE: usize = 52; /// L2 payload builder /// Completes the payload building process, return the block value @@ -197,6 +205,14 @@ pub fn fill_transactions( /// Calculates the size of the current `StateDiff` of the block. /// If the current size exceeds the blob size limit, returns `Ok(false)`. /// If there is still space in the blob, returns `Ok(true)`. +/// StateDiff: +/// +-------------------+ +/// | Version | +/// | HeaderFields | +/// | AccountsStateDiff | +/// | Withdrawals | +/// | Deposits | +/// +-------------------+ fn check_state_diff_size( acc_withdrawals_size: &mut usize, acc_deposits_size: &mut usize, @@ -236,7 +252,7 @@ fn calc_modified_accounts_size( context: &mut PayloadBuildContext, accounts_info_cache: &mut HashMap>, ) -> Result { - let mut modified_accounts_size: usize = 2; // modified_accounts_len(u16) + let mut modified_accounts_size: usize = 2; // 2bytes | modified_accounts_len(u16) // We use a temporary_context because revm mutates it in `get_state_transitions` // TODO: remove when we stop using revm @@ -245,9 +261,9 @@ fn calc_modified_accounts_size( .vm .get_state_transitions(context.payload.header.parent_hash)?; for account_update in account_updates { - modified_accounts_size += 1 + 20; // r#type(u8) + address(H160) + modified_accounts_size += 1 + 20; // 1byte + 20bytes | r#type(u8) + address(H160) if account_update.info.is_some() { - modified_accounts_size += 32; // new_balance(U256) + modified_accounts_size += 32; // 32bytes | new_balance(U256) } let nonce_diff = get_nonce_diff( @@ -260,14 +276,14 @@ fn calc_modified_accounts_size( BlockProducerError::Custom(format!("Block Producer failed to get nonce diff: {e}")) })?; if nonce_diff != 0 { - modified_accounts_size += 2; // nonce_diff(u16) + modified_accounts_size += 2; // 2bytes | nonce_diff(u16) } - // for each added_storage: key(H256) + value(U256) + // for each added_storage: 32bytes + 32bytes | key(H256) + value(U256) modified_accounts_size += account_update.added_storage.len() * 2 * 32; if let Some(bytecode) = &account_update.code { - modified_accounts_size += 2; // bytecode_len(u16) - modified_accounts_size += bytecode.len(); // bytecode(Bytes) + modified_accounts_size += 2; // 2bytes | bytecode_len(u16) + modified_accounts_size += bytecode.len(); // (len)bytes | bytecode(Bytes) } } Ok(modified_accounts_size) diff --git a/crates/l2/sequencer/errors.rs b/crates/l2/sequencer/errors.rs index aa0901820d..d6378a9152 100644 --- a/crates/l2/sequencer/errors.rs +++ b/crates/l2/sequencer/errors.rs @@ -168,7 +168,7 @@ pub enum StateDiffError { DbError(#[from] TrieError), #[error("Store Error: {0}")] StoreError(#[from] StoreError), - #[error("StateDiff registered a negative nonce in AccountUpdate")] + #[error("New nonce is lower than the previous one")] FailedToCalculateNonce, } From 621592bf7ee24e639925f8fe9ecc92f1510622a4 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 31 Mar 2025 17:26:30 -0300 Subject: [PATCH 26/34] Fix typo --- crates/l2/sequencer/block_producer/payload_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 32ced9b929..a55bd6bc46 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -164,7 +164,7 @@ pub fn fill_transactions( &mut accounts_info_cache, )? { debug!( - "Skipping transaction: {}, doesn't feet in blob_size", + "Skipping transaction: {}, doesn't fit in blob_size", head_tx.tx.compute_hash() ); // We don't have enough space in the blob for the transaction, so we skip all txs from this account From 3169adb0f4544c8f607872e600deccd745934269 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Mon, 31 Mar 2025 19:09:03 -0300 Subject: [PATCH 27/34] Add state diff size short circuit --- .../l2/sequencer/block_producer/payload_builder.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index a55bd6bc46..56fec0b9d2 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -36,6 +36,10 @@ const L2_WITHDRAWAL_SIZE: usize = 84; // 20bytes + 32bytes const L2_DEPOSIT_SIZE: usize = 52; +// State diff size for a simple transfer. +// Two `AccountUpdates` with new_balance, one of which also has nonce_diff. +const TX_STATE_DIFF_SIZE: usize = 116; + /// L2 payload builder /// Completes the payload building process, return the block value /// Same as `blockchain::build_payload` without applying system operations and using a different `fill_transactions` @@ -81,6 +85,7 @@ pub fn fill_transactions( ) -> Result<(), BlockProducerError> { // Two bytes for the len let (mut acc_withdrawals_size, mut acc_deposits_size): (usize, usize) = (2, 2); + let mut acc_state_diff_size = 0; let mut accounts_info_cache = HashMap::new(); let chain_config = store.get_chain_config()?; @@ -101,6 +106,12 @@ pub fn fill_transactions( debug!("No more gas to run transactions"); break; }; + + // Check if we have enough space for the StateDiff to run more transactions + if acc_state_diff_size + TX_STATE_DIFF_SIZE > SAFE_BYTES_PER_BLOB { + debug!("No more StateDiff space to run transactions"); + break; + }; if !blob_txs.is_empty() && context.blobs_bundle.blobs.len() >= max_blob_number_per_block { debug!("No more blob gas to run blob transactions"); blob_txs.clear(); @@ -158,6 +169,7 @@ pub fn fill_transactions( if !check_state_diff_size( &mut acc_withdrawals_size, &mut acc_deposits_size, + &mut acc_state_diff_size, head_tx.clone().into(), &receipt, context, @@ -216,6 +228,7 @@ pub fn fill_transactions( fn check_state_diff_size( acc_withdrawals_size: &mut usize, acc_deposits_size: &mut usize, + acc_state_diff_size: &mut usize, tx: Transaction, receipt: &Receipt, context: &mut PayloadBuildContext, @@ -245,6 +258,7 @@ fn check_state_diff_size( ); return Ok(false); } + *acc_state_diff_size = current_state_diff_size; Ok(true) } From dc9d5d8878bf52778e1c1f3932de180089ea4771 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Thu, 3 Apr 2025 15:31:14 -0300 Subject: [PATCH 28/34] Add comment to EvmState clone() --- crates/vm/backends/revm/db.rs | 2 ++ crates/vm/levm/src/vm.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/vm/backends/revm/db.rs b/crates/vm/backends/revm/db.rs index 4a043c454f..6ad50b6709 100644 --- a/crates/vm/backends/revm/db.rs +++ b/crates/vm/backends/revm/db.rs @@ -31,6 +31,8 @@ pub enum EvmState { Execution(Box>), } +// Needed because revm::db::State is not cloneable and we need to +// restore the previous EVM state after executing a transaction in L2 mode whose resulting state diff doesn't fit in a blob. impl Clone for EvmState { fn clone(&self) -> Self { match self { diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index d46bc2c7b1..2baf6708d2 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -177,6 +177,7 @@ pub struct VM<'a> { pub cache_backup: CacheDB, // Backup of the cache before executing the transaction } +#[derive(Clone)] pub struct GeneralizedDatabase { pub store: Arc, pub cache: CacheDB, From 13ebbba0f1fe7de3f3d9a27fd8ecf958cf95c036 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Thu, 3 Apr 2025 16:15:36 -0300 Subject: [PATCH 29/34] Fix linter --- crates/l2/sequencer/block_producer/payload_builder.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 56fec0b9d2..aa60d87934 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -271,9 +271,10 @@ fn calc_modified_accounts_size( // We use a temporary_context because revm mutates it in `get_state_transitions` // TODO: remove when we stop using revm let mut temporary_context = context.clone(); - let account_updates = temporary_context - .vm - .get_state_transitions(context.payload.header.parent_hash)?; + + let chain_config = &context.store.get_chain_config()?; + let fork = chain_config.fork(context.payload.header.timestamp); + let account_updates = temporary_context.vm.get_state_transitions(fork)?; for account_update in account_updates { modified_accounts_size += 1 + 20; // 1byte + 20bytes | r#type(u8) + address(H160) if account_update.info.is_some() { From cdb1eecef331cf4b8752b45c02dc7c0494be08bc Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Thu, 3 Apr 2025 16:55:07 -0300 Subject: [PATCH 30/34] Update comment --- crates/l2/sequencer/block_producer/payload_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index aa60d87934..d96e1ed3d7 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -268,7 +268,7 @@ fn calc_modified_accounts_size( ) -> Result { let mut modified_accounts_size: usize = 2; // 2bytes | modified_accounts_len(u16) - // We use a temporary_context because revm mutates it in `get_state_transitions` + // We use a temporary_context because `get_state_transitions` mutates it. // TODO: remove when we stop using revm let mut temporary_context = context.clone(); From 65a00ffee8c0fe801ae751d03be7af88788f3dc2 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Fri, 4 Apr 2025 16:21:34 -0300 Subject: [PATCH 31/34] Make block_producer::build_payload async --- crates/blockchain/payload.rs | 2 +- crates/l2/sequencer/block_producer/payload_builder.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/blockchain/payload.rs b/crates/blockchain/payload.rs index 9f66fda4db..033b54d230 100644 --- a/crates/blockchain/payload.rs +++ b/crates/blockchain/payload.rs @@ -525,7 +525,7 @@ impl Blockchain { pub async fn finalize_payload( &self, - context: &mut PayloadBuildContext<'_>, + context: &mut PayloadBuildContext, ) -> Result<(), ChainError> { let chain_config = &context.store.get_chain_config()?; let fork = chain_config.fork(context.payload.header.timestamp); diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index d96e1ed3d7..2479fd5efe 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -43,7 +43,7 @@ const TX_STATE_DIFF_SIZE: usize = 116; /// L2 payload builder /// Completes the payload building process, return the block value /// Same as `blockchain::build_payload` without applying system operations and using a different `fill_transactions` -pub fn build_payload( +pub async fn build_payload( blockchain: Arc, payload: Block, store: &Store, @@ -57,7 +57,7 @@ pub fn build_payload( blockchain.apply_withdrawals(&mut context)?; fill_transactions(blockchain.clone(), &mut context, store)?; blockchain.extract_requests(&mut context)?; - blockchain.finalize_payload(&mut context)?; + blockchain.finalize_payload(&mut context).await?; let interval = Instant::now().duration_since(since).as_millis(); tracing::info!("[METRIC] BUILDING PAYLOAD TOOK: {interval} ms"); From ff33e1025f0a7ffa071add0826b28404ecffac42 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Wed, 16 Apr 2025 10:07:23 -0300 Subject: [PATCH 32/34] Remove REVM comment --- crates/l2/sequencer/block_producer/payload_builder.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index a0d127848a..ae86008ac8 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -271,7 +271,6 @@ async fn calc_modified_accounts_size( let mut modified_accounts_size: usize = 2; // 2bytes | modified_accounts_len(u16) // We use a temporary_context because `get_state_transitions` mutates it. - // TODO: remove when we stop using revm let mut temporary_context = context.clone(); let chain_config = &context.store.get_chain_config()?; From 0a1050a8950adfe74b9140f8a9d732bb45736030 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Wed, 16 Apr 2025 11:28:13 -0300 Subject: [PATCH 33/34] Add doc comments on mutable variables --- crates/l2/sequencer/block_producer/payload_builder.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index ae86008ac8..460ccf9811 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -219,7 +219,14 @@ pub async fn fill_transactions( /// Calculates the size of the current `StateDiff` of the block. /// If the current size exceeds the blob size limit, returns `Ok(false)`. /// If there is still space in the blob, returns `Ok(true)`. -/// StateDiff: +/// Updates the following mutable variables in the process: +/// - `acc_withdrawals_size`: Accumulated size of withdrawals (incremented by L2_WITHDRAWAL_SIZE if tx is withdrawal) +/// - `acc_deposits_size`: Accumulated size of deposits (incremented by L2_DEPOSIT_SIZE if tx is deposit) +/// - `acc_state_diff_size`: Set to current total state diff size if within limit +/// - `context`: Must be mutable because `get_state_transitions` requires mutable access +/// - `accounts_info_cache`: When calculating account updates, we store account info in the cache if it's not already present +/// +/// StateDiff: /// +-------------------+ /// | Version | /// | HeaderFields | @@ -227,7 +234,7 @@ pub async fn fill_transactions( /// | Withdrawals | /// | Deposits | /// +-------------------+ -async fn check_state_diff_size( +async fn update_state_diff_size( acc_withdrawals_size: &mut usize, acc_deposits_size: &mut usize, acc_state_diff_size: &mut usize, From 29265cf444187f37ce1d3f5733bb603bcad98952 Mon Sep 17 00:00:00 2001 From: avilagaston9 Date: Wed, 16 Apr 2025 11:30:45 -0300 Subject: [PATCH 34/34] Fix linter --- crates/l2/sequencer/block_producer/payload_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/l2/sequencer/block_producer/payload_builder.rs b/crates/l2/sequencer/block_producer/payload_builder.rs index 460ccf9811..7533e10a7f 100644 --- a/crates/l2/sequencer/block_producer/payload_builder.rs +++ b/crates/l2/sequencer/block_producer/payload_builder.rs @@ -166,7 +166,7 @@ pub async fn fill_transactions( let receipt = match blockchain.apply_transaction(&head_tx, context) { Ok(receipt) => { // This call is the part that differs from the original `fill_transactions`. - if !check_state_diff_size( + if !update_state_diff_size( &mut acc_withdrawals_size, &mut acc_deposits_size, &mut acc_state_diff_size,