Summary
The Amsterdam BAL-backed parallel execution path in LEVM::execute_block_pipeline does not enforce the same remaining block gas check that the sequential path does.
Why this matters
The sequential path rejects a transaction when cumulative_gas_used + tx.gas_limit() > block.header.gas_limit, using post-refund cumulative gas per EIP-7778.
The parallel path currently executes every transaction, sorts the results, and builds receipts without any equivalent check_gas_limit step. That means a block can get through the parallel validation path even when a later transaction's gas_limit is larger than the remaining block gas, as long as that transaction's actual gas_spent stays low enough for the final receipts and header.gas_used to still line up.
Concrete example
With block_gas_limit = 100:
tx0: gas_limit = 60, gas_spent = 60
tx1: gas_limit = 50, gas_spent = 40
Sequential execution rejects tx1 because only 40 gas remains before it starts.
Parallel execution currently accepts the block because it only accumulates gas_spent after execution and never checks tx1.gas_limit() against the remaining block gas.
Relevant code
- Sequential check:
crates/vm/backends/levm/mod.rs in the sequential execute_block / execute_block_pipeline loops
- Missing check:
crates/vm/backends/levm/mod.rs in execute_block_parallel, during the receipt-building phase after sorting results
Suggested fix
Carry each transaction's gas_limit() into the parallel receipt finalization step and run the same check_gas_limit logic there before updating cumulative receipt gas.
I have a local patch and regression test for this and can open a PR if this direction looks good.
Summary
The Amsterdam BAL-backed parallel execution path in
LEVM::execute_block_pipelinedoes not enforce the same remaining block gas check that the sequential path does.Why this matters
The sequential path rejects a transaction when
cumulative_gas_used + tx.gas_limit() > block.header.gas_limit, using post-refund cumulative gas per EIP-7778.The parallel path currently executes every transaction, sorts the results, and builds receipts without any equivalent
check_gas_limitstep. That means a block can get through the parallel validation path even when a later transaction'sgas_limitis larger than the remaining block gas, as long as that transaction's actualgas_spentstays low enough for the final receipts andheader.gas_usedto still line up.Concrete example
With
block_gas_limit = 100:tx0:gas_limit = 60,gas_spent = 60tx1:gas_limit = 50,gas_spent = 40Sequential execution rejects
tx1because only40gas remains before it starts.Parallel execution currently accepts the block because it only accumulates
gas_spentafter execution and never checkstx1.gas_limit()against the remaining block gas.Relevant code
crates/vm/backends/levm/mod.rsin the sequentialexecute_block/execute_block_pipelineloopscrates/vm/backends/levm/mod.rsinexecute_block_parallel, during the receipt-building phase after sorting resultsSuggested fix
Carry each transaction's
gas_limit()into the parallel receipt finalization step and run the samecheck_gas_limitlogic there before updating cumulative receipt gas.I have a local patch and regression test for this and can open a PR if this direction looks good.