Skip to content

Commit 49d69bd

Browse files
iovoidjrchatruc
andauthored
perf(l1): batch merkelization requests if the queue is not empty (#5200)
**Motivation** Currently for every transaction being executed, we get the transitions and send the updates to the merkelization thread. However this has two drawbacks: - getting the update list isn't cheap, taking up 10% of block execution - unbatched requests cause more work to be done, increasing total merkelization time The merkelization queue often had dozens of requests by the end of block execution. **Description** We delay calculating and sending updates until the merkelizer is ready (queue is empty), which causes the changes to the batched. --------- Co-authored-by: Javier Rodríguez Chatruc <49622509+jrchatruc@users.noreply.github.com> Co-authored-by: Javier Chatruc <jrchatruc@gmail.com>
1 parent 07692de commit 49d69bd

File tree

2 files changed

+18
-50
lines changed

2 files changed

+18
-50
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
### 2025-11-07
99
- Reuse stack pool in LEVM [#5179](https://github.com/lambdaclass/ethrex/pull/5179)
1010

11+
### 2025-11-05
12+
- Merkelization backpressure and batching [#5200](https://github.com/lambdaclass/ethrex/pull/5200)
13+
1114
### 2025-11-03
1215

1316
- Avoid unnecessary hash validations [#5167](https://github.com/lambdaclass/ethrex/pull/5167)

crates/vm/backends/levm/mod.rs

Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ impl LEVM {
105105
let mut receipts = Vec::new();
106106
let mut cumulative_gas_used = 0;
107107

108+
// Starts at 2 to account for the two precompile calls done in `Self::prepare_block`.
109+
// The value itself can be safely changed.
110+
let mut tx_since_last_flush = 2;
111+
108112
for (tx, tx_sender) in block.body.get_transactions_with_sender().map_err(|error| {
109113
EvmError::Transaction(format!("Couldn't recover addresses with error: {error}"))
110114
})? {
@@ -124,7 +128,12 @@ impl LEVM {
124128
vm_type,
125129
&mut shared_stack_pool,
126130
)?;
127-
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
131+
if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
132+
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
133+
tx_since_last_flush = 0;
134+
} else {
135+
tx_since_last_flush += 1;
136+
}
128137

129138
cumulative_gas_used += report.gas_used;
130139
let receipt = Receipt::new(
@@ -136,6 +145,9 @@ impl LEVM {
136145

137146
receipts.push(receipt);
138147
}
148+
if queue_length.load(Ordering::Relaxed) == 0 {
149+
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
150+
}
139151

140152
for (address, increment) in block
141153
.body
@@ -151,22 +163,15 @@ impl LEVM {
151163

152164
account.info.balance += increment.into();
153165
}
154-
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
155166

156167
// TODO: I don't like deciding the behavior based on the VMType here.
157168
// TODO2: Revise this, apparently extract_all_requests_levm is not called
158169
// in L2 execution, but its implementation behaves differently based on this.
159170
let requests = match vm_type {
160-
VMType::L1 => extract_all_requests_levm_pipeline(
161-
&receipts,
162-
db,
163-
&block.header,
164-
vm_type,
165-
&merkleizer,
166-
queue_length,
167-
)?,
171+
VMType::L1 => extract_all_requests_levm(&receipts, db, &block.header, vm_type)?,
168172
VMType::L2(_) => Default::default(),
169173
};
174+
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
170175

171176
Ok(BlockExecutionResult { receipts, requests })
172177
}
@@ -594,46 +599,6 @@ pub fn extract_all_requests_levm(
594599
Ok(vec![deposits, withdrawals, consolidation])
595600
}
596601

597-
#[allow(unreachable_code)]
598-
#[allow(unused_variables)]
599-
pub fn extract_all_requests_levm_pipeline(
600-
receipts: &[Receipt],
601-
db: &mut GeneralizedDatabase,
602-
header: &BlockHeader,
603-
vm_type: VMType,
604-
merkleizer: &Sender<Vec<AccountUpdate>>,
605-
queue_length: &AtomicUsize,
606-
) -> Result<Vec<Requests>, EvmError> {
607-
if let VMType::L2(_) = vm_type {
608-
return Err(EvmError::InvalidEVM(
609-
"extract_all_requests_levm should not be called for L2 VM".to_string(),
610-
));
611-
}
612-
613-
let chain_config = db.store.get_chain_config()?;
614-
let fork = chain_config.fork(header.timestamp);
615-
616-
if fork < Fork::Prague {
617-
return Ok(Default::default());
618-
}
619-
620-
let withdrawals_data: Vec<u8> = LEVM::read_withdrawal_requests(header, db, vm_type)?
621-
.output
622-
.into();
623-
LEVM::send_state_transitions_tx(merkleizer, db, queue_length)?;
624-
let consolidation_data: Vec<u8> = LEVM::dequeue_consolidation_requests(header, db, vm_type)?
625-
.output
626-
.into();
627-
LEVM::send_state_transitions_tx(merkleizer, db, queue_length)?;
628-
629-
let deposits = Requests::from_deposit_receipts(chain_config.deposit_contract_address, receipts)
630-
.ok_or(EvmError::InvalidDepositRequest)?;
631-
let withdrawals = Requests::from_withdrawals_data(withdrawals_data);
632-
let consolidation = Requests::from_consolidation_data(consolidation_data);
633-
634-
Ok(vec![deposits, withdrawals, consolidation])
635-
}
636-
637602
/// Calculating gas_price according to EIP-1559 rules
638603
/// See https://github.com/ethereum/go-ethereum/blob/7ee9a6e89f59cee21b5852f5f6ffa2bcfc05a25f/internal/ethapi/transaction_args.go#L430
639604
pub fn calculate_gas_price_for_generic(tx: &GenericTransaction, basefee: u64) -> U256 {

0 commit comments

Comments
 (0)