Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Perf

### 2026-02-25

- Disable balance check for prewarming to avoid early reverts [#6259](https://github.com/lambdaclass/ethrex/pull/6259)

### 2026-02-24

- Expand fast-path dispatch in LEVM interpreter loop [#6245](https://github.com/lambdaclass/ethrex/pull/6245)
Expand Down
8 changes: 7 additions & 1 deletion crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ impl LEVM {
db,
vm_type,
&mut shared_stack_pool,
false,
)?;
if queue_length.load(Ordering::Relaxed) == 0 && tx_since_last_flush > 5 {
LEVM::send_state_transitions_tx(&merkleizer, db, queue_length)?;
Expand Down Expand Up @@ -353,6 +354,7 @@ impl LEVM {
&mut group_db,
vm_type,
stack_pool,
true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: bare boolean at the call site (true / false) doesn't convey intent. Reads a bit better with a /* disable_balance_check */ comment or a constant, e.g.

const WARMING_MODE: bool = true;

Same applies to the false at the normal execution call site (line 238).

);
}
},
Expand Down Expand Up @@ -483,6 +485,7 @@ impl LEVM {
difficulty: block_header.difficulty,
is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
fee_token: tx.fee_token(),
disable_balance_check: false,
};

Ok(env)
Expand Down Expand Up @@ -515,8 +518,10 @@ impl LEVM {
db: &mut GeneralizedDatabase,
vm_type: VMType,
stack_pool: &mut Vec<Stack>,
disable_balance_check: bool,
) -> Result<ExecutionReport, EvmError> {
let env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
let mut env = Self::setup_env(tx, tx_sender, block_header, db, vm_type)?;
env.disable_balance_check = disable_balance_check;
let mut vm = VM::new(env, db, tx, LevmCallTracer::disabled(), vm_type)?;

std::mem::swap(&mut vm.stack_pool, stack_pool);
Expand Down Expand Up @@ -1006,6 +1011,7 @@ fn env_from_generic(
difficulty: header.difficulty,
is_privileged: false,
fee_token: tx.fee_token,
disable_balance_check: false,
})
}

Expand Down
3 changes: 3 additions & 0 deletions crates/vm/levm/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub struct Environment {
pub block_gas_limit: u64,
pub is_privileged: bool,
pub fee_token: Option<Address>,
/// When true, skip balance deduction in `deduct_caller`. Used by the prewarmer
/// to avoid early reverts on insufficient balance so that warming touches more storage.
Comment on lines +44 to +45
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states that skipping balance checks is used by the prewarmer to "avoid early reverts on insufficient balance", but the check being skipped in validate_sender_balance is critical for transaction validity. While this is acceptable for the prewarmer (since it's only warming the cache and not actually executing transactions), the comment should clarify that the prewarmer results are discarded and not used for actual transaction execution. Consider adding a warning comment that this flag MUST NEVER be set to true for actual transaction execution, only for cache warming.

Suggested change
/// When true, skip balance deduction in `deduct_caller`. Used by the prewarmer
/// to avoid early reverts on insufficient balance so that warming touches more storage.
/// When true, skip balance deduction in `deduct_caller`.
///
/// This flag is intended **only** for the prewarmer to avoid early reverts on
/// insufficient balance so that cache warming can touch more storage/state.
///
/// Any execution performed with this flag set to `true` must have its results
/// discarded and MUST NOT be used for actual transaction execution, validation,
/// or consensus-critical state transitions. This flag MUST NEVER be set to
/// `true` in normal transaction processing; it is for cache warming only.

Copilot uses AI. Check for mistakes.
pub disable_balance_check: bool,
}

/// This struct holds special configuration variables specific to the
Expand Down
8 changes: 8 additions & 0 deletions crates/vm/levm/src/hooks/default_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,10 @@ pub fn validate_gas_allowance(vm: &mut VM<'_>) -> Result<(), TxValidationError>
}

pub fn validate_sender_balance(vm: &mut VM<'_>, sender_balance: U256) -> Result<(), VMError> {
if vm.env.disable_balance_check {
return Ok(());
}
Comment on lines 490 to +493
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The L2 hook's deduct_caller_fee_token function (called from L2 transaction validation) doesn't check disable_balance_check, which means L2 transactions with fee tokens will still revert early during prewarming. Consider applying the same pattern to l2_hook::deduct_caller_fee_token at crates/vm/levm/src/hooks/l2_hook.rs:557

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/hooks/default_hook.rs
Line: 490-493

Comment:
The L2 hook's `deduct_caller_fee_token` function (called from L2 transaction validation) doesn't check `disable_balance_check`, which means L2 transactions with fee tokens will still revert early during prewarming. Consider applying the same pattern to `l2_hook::deduct_caller_fee_token` at crates/vm/levm/src/hooks/l2_hook.rs:557

How can I resolve this? If you propose a fix, please make it concise.


// Up front cost is the maximum amount of wei that a user is willing to pay for. Gaslimit * gasprice + value + blob_gas_cost
let value = vm.current_call_frame.msg_value;

Expand Down Expand Up @@ -523,6 +527,10 @@ pub fn deduct_caller(
gas_limit_price_product: U256,
sender_address: Address,
) -> Result<(), VMError> {
if vm.env.disable_balance_check {
return Ok(());
}

// Up front cost is the maximum amount of wei that a user is willing to pay for. Gaslimit * gasprice + value + blob_gas_cost
let value = vm.current_call_frame.msg_value;

Expand Down
1 change: 1 addition & 0 deletions test/tests/levm/eip7708_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ impl TestBuilder {
block_gas_limit: GAS_LIMIT * 2,
is_privileged: false,
fee_token: None,
disable_balance_check: false,
};

let tx = Transaction::EIP1559Transaction(EIP1559Transaction {
Expand Down
1 change: 1 addition & 0 deletions tooling/ef_tests/state/runner/levm_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ pub fn prepare_vm_for_tx<'a>(
block_gas_limit: test.env.current_gas_limit,
is_privileged: false,
fee_token: None,
disable_balance_check: false,
},
db,
&tx,
Expand Down
1 change: 1 addition & 0 deletions tooling/ef_tests/state_v2/src/modules/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ pub fn get_vm_env_for_test(
block_gas_limit: test_env.current_gas_limit,
is_privileged: false,
fee_token: None,
disable_balance_check: false,
})
}

Expand Down
Loading