From 51cb4155c5afaa9314ac28bd0c5232855510248a Mon Sep 17 00:00:00 2001 From: Lucas Fiegl Date: Wed, 5 Nov 2025 15:20:16 -0300 Subject: [PATCH 1/5] batch merkelization requests if the queue is not empty --- crates/vm/backends/levm/mod.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 75a08724997..82646a8c3b8 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -102,6 +102,8 @@ impl LEVM { let mut receipts = Vec::new(); let mut cumulative_gas_used = 0; + let mut tx_since_last_flush = 2; + for (tx, tx_sender) in block.body.get_transactions_with_sender().map_err(|error| { EvmError::Transaction(format!("Couldn't recover addresses with error: {error}")) })? { @@ -114,7 +116,12 @@ impl LEVM { } let report = Self::execute_tx(tx, tx_sender, &block.header, db, vm_type)?; - LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; + if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 { + LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; + tx_since_last_flush = 0; + } else { + tx_since_last_flush += 1; + } cumulative_gas_used += report.gas_used; let receipt = Receipt::new( @@ -126,6 +133,9 @@ impl LEVM { receipts.push(receipt); } + if queue_length.load(Ordering::Relaxed) == 0 { + LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; + } for (address, increment) in block .body @@ -140,8 +150,10 @@ impl LEVM { .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?; account.info.balance += increment.into(); + if queue_length.load(Ordering::Relaxed) == 0 { + LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; + } } - LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; // TODO: I don't like deciding the behavior based on the VMType here. // TODO2: Revise this, apparently extract_all_requests_levm is not called @@ -157,6 +169,7 @@ impl LEVM { )?, VMType::L2(_) => Default::default(), }; + LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; Ok(BlockExecutionResult { receipts, requests }) } @@ -589,11 +602,9 @@ pub fn extract_all_requests_levm_pipeline( let withdrawals_data: Vec = LEVM::read_withdrawal_requests(header, db, vm_type)? .output .into(); - LEVM::send_state_transitions_tx(merkleizer, db, queue_length)?; let consolidation_data: Vec = LEVM::dequeue_consolidation_requests(header, db, vm_type)? .output .into(); - LEVM::send_state_transitions_tx(merkleizer, db, queue_length)?; let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts) .ok_or(EvmError::InvalidDepositRequest)?; From 43a58369fb86c0275eb2df692470acc8ca5ea117 Mon Sep 17 00:00:00 2001 From: Lucas Fiegl Date: Wed, 5 Nov 2025 15:25:43 -0300 Subject: [PATCH 2/5] update perf changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d439fbe30bd..abd3a37f1e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Perf +### 2025-11-05 +- Merkelization backpressure and batching [#5200](https://github.com/lambdaclass/ethrex/pull/5200) + ### 2025-11-03 - Avoid unnecessary hash validations [#5167](https://github.com/lambdaclass/ethrex/pull/5167) From 2fd0617485ad3b5904120e16c3a1309992b502cb Mon Sep 17 00:00:00 2001 From: Lucas Fiegl Date: Wed, 5 Nov 2025 16:53:00 -0300 Subject: [PATCH 3/5] improve heuristic --- crates/vm/backends/levm/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 82646a8c3b8..8cd7c259294 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -133,7 +133,7 @@ impl LEVM { receipts.push(receipt); } - if queue_length.load(Ordering::Relaxed) == 0 { + if queue_length.load(Ordering::Relaxed) > 0 { LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; } @@ -150,9 +150,6 @@ impl LEVM { .map_err(|_| EvmError::DB(format!("Withdrawal account {address} not found")))?; account.info.balance += increment.into(); - if queue_length.load(Ordering::Relaxed) == 0 { - LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; - } } // TODO: I don't like deciding the behavior based on the VMType here. From 8861f88ebad804790d3c0990be36add2e206d6f2 Mon Sep 17 00:00:00 2001 From: Lucas Fiegl Date: Tue, 11 Nov 2025 14:28:36 -0300 Subject: [PATCH 4/5] improvements --- crates/vm/backends/levm/mod.rs | 49 ++-------------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 24bde5abbf2..7228aaca329 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -143,7 +143,7 @@ impl LEVM { receipts.push(receipt); } - if queue_length.load(Ordering::Relaxed) > 0 { + if queue_length.load(Ordering::Relaxed) == 0 { LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; } @@ -166,14 +166,7 @@ impl LEVM { // TODO2: Revise this, apparently extract_all_requests_levm is not called // in L2 execution, but its implementation behaves differently based on this. let requests = match vm_type { - VMType::L1 => extract_all_requests_levm_pipeline( - &receipts, - db, - &block.header, - vm_type, - &merkleizer, - queue_length, - )?, + VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type)?, VMType::L2(_) => Default::default(), }; LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?; @@ -604,44 +597,6 @@ pub fn extract_all_requests_levm( Ok(vec![deposits, withdrawals, consolidation]) } -#[allow(unreachable_code)] -#[allow(unused_variables)] -pub fn extract_all_requests_levm_pipeline( - receipts: &[Receipt], - db: &mut GeneralizedDatabase, - header: &BlockHeader, - vm_type: VMType, - merkleizer: &Sender>, - queue_length: &AtomicUsize, -) -> Result, EvmError> { - if let VMType::L2(_) = vm_type { - return Err(EvmError::InvalidEVM( - "extract_all_requests_levm should not be called for L2 VM".to_string(), - )); - } - - let chain_config = db.store.get_chain_config()?; - let fork = chain_config.fork(header.timestamp); - - if fork < Fork::Prague { - return Ok(Default::default()); - } - - let withdrawals_data: Vec = LEVM::read_withdrawal_requests(header, db, vm_type)? - .output - .into(); - let consolidation_data: Vec = LEVM::dequeue_consolidation_requests(header, db, vm_type)? - .output - .into(); - - let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts) - .ok_or(EvmError::InvalidDepositRequest)?; - let withdrawals = Requests::from_withdrawals_data(withdrawals_data); - let consolidation = Requests::from_consolidation_data(consolidation_data); - - Ok(vec![deposits, withdrawals, consolidation]) -} - /// Calculating gas_price according to EIP-1559 rules /// See https://github.com/ethereum/go-ethereum/blob/7ee9a6e89f59cee21b5852f5f6ffa2bcfc05a25f/internal/ethapi/transaction_args.go#L430 pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 { From 2a02cc15a3771e63542265873110e802101dedfa Mon Sep 17 00:00:00 2001 From: Lucas Fiegl Date: Tue, 11 Nov 2025 14:30:53 -0300 Subject: [PATCH 5/5] document constant --- crates/vm/backends/levm/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/vm/backends/levm/mod.rs b/crates/vm/backends/levm/mod.rs index 7228aaca329..6c1669d75df 100644 --- a/crates/vm/backends/levm/mod.rs +++ b/crates/vm/backends/levm/mod.rs @@ -105,6 +105,8 @@ impl LEVM { let mut receipts = Vec::new(); let mut cumulative_gas_used = 0; + // Starts at 2 to account for the two precompile calls done in `Self::prepare_block`. + // The value itself can be safely changed. let mut tx_since_last_flush = 2; for (tx, tx_sender) in block.body.get_transactions_with_sender().map_err(|error| {