This proposal introduces a mechanism to make execution blocks statically verifiable through minimal checks that only require the previous state, without requiring execution of the block’s transactions. This enables validators to attest to a block’s validity without completing its execution.
Motivation
The primary advantage of this proposal is asynchronous block validation. In the current Ethereum protocol, blocks must be fully executed before validators can attest to them. This requirement creates a bottleneck in the consensus process, as attestors must wait for execution results before committing their votes, limiting the network’s throughput potential.
By introducing a mechanism where execution payloads can be reverted rather than invalidating the entire block, execution is no longer an immediate requirement for validation. Instead, a block’s validity can be determined based on its structural correctness and the upfront payment of transaction fees by senders. This allows attestation to happen earlier in the slot, independent of execution, potentially enabling higher block gas limits and significant throughput improvements across the network.
Specification
Header Changes
The block header structure is extended to support delayed execution:
@dataclassclassHeader:# Existing fields
parent_hash:Hash32ommers_hash:Hash32coinbase:Address# Pre-execution state root - this is the state root before executing transactions
pre_state_root:Root# Deferred execution outputs from parent block
parent_transactions_root:Root# Transaction root from parent block
parent_receipt_root:Root# Receipt root from parent block
parent_bloom:Bloom# Logs bloom from parent block
parent_requests_hash:Hash32# Hash of requests from the parent block
parent_execution_reverted:bool# Indicates if parent block's execution was reverted
# Other existing fields
difficulty:Uintnumber:Uintgas_limit:Uintgas_used:Uint# Declared gas used by txs, validated post-execution
timestamp:U256extra_data:Bytesprev_randao:Bytes32nonce:Bytes8base_fee_per_gas:Uintwithdrawals_root:Rootblob_gas_used:U64# Total blob gas used by transactions
excess_blob_gas:U64parent_beacon_block_root:Root
The key changes are:
pre_state_root: Represents the state root before execution (checked against the post-execution state of the parent block)
parent_receipt_root: Receipt root from the parent block (deferred execution output)
parent_bloom: Logs bloom from the parent block (deferred execution output)
parent_requests_hash: Hash of requests from the parent block (deferred execution output)
parent_execution_reverted: Indicates whether the parent block’s execution was reverted
A block header MUST include all these fields to be considered valid under this EIP. The pre_state_root MUST match the state root after applying the parent block’s execution. The parent execution outputs MUST accurately reflect the previous block’s execution results to maintain chain integrity.
Chain State Tracking
The blockchain object is extended to track execution outputs for verification in subsequent blocks:
@dataclassclassBlockChain:blocks:List[Block]state:Statechain_id:U64last_transactions_root:Root# Transaction root from the last executed block
last_receipt_root:Root# Receipt root from last executed block
last_block_logs_bloom:Bloom# Logs bloom from last executed block
last_requests_hash:Bytes# Requests hash from last executed block
last_execution_reverted:bool# Indicates if the last block's execution was reverted
These additional fields are used to verify the deferred execution outputs claimed in subsequent blocks. The last_transactions_root, last_receipt_root, last_block_logs_bloom, last_requests_hash, and last_execution_reverted act as critical chain state references that MUST be updated after each block execution to ensure proper state progression. When a block’s execution is reverted due to a gas mismatch, the last_execution_reverted field is set to True, which affects the base fee calculation of subsequent blocks.
Block Validation
Static validation is performed separately from execution. In this phase, all checks that can be done without executing transactions are performed:
defvalidate_block(chain:BlockChain,block:Block)->None:# Validate header against parent
validate_header(chain,block.header)# Validate deferred execution outputs from the parent
ifblock.header.parent_transactions_root!=chain.last_transactions_root:raiseInvalidBlockifblock.header.parent_receipt_root!=chain.last_receipt_root:raiseInvalidBlockifblock.header.parent_bloom!=chain.last_block_logs_bloom:raiseInvalidBlockifblock.header.parent_requests_hash!=chain.last_requests_hash:raiseInvalidBlockifblock.header.pre_state_root!=state_root(chain.state):raiseInvalidBlockifblock.header.parent_execution_reverted!=chain.last_execution_reverted:raiseInvalidBlock...# Process all transactions trie and validate transactions
total_inclusion_gas=Uint(0)total_blob_gas_used=Uint(0)withdrawals_trie=Trie(secured=False,default=None)# Track sender balances and nonces by address
sender_balances={}sender_nonces={}# Calculate blob gas price based on excess blob gas
blob_gas_price=calculate_blob_gas_price(block.header.excess_blob_gas)# Validate each transaction
fori,txinenumerate(map(decode_transaction,block.transactions)):# Validate transaction parameters (signature, fees, etc.)
validate_transaction(tx,block.header.base_fee_per_gas,block.header.excess_blob_gas)# Recover sender
sender_address=recover_sender(chain.chain_id,tx)# Calculate gas costs (both EIP-7623 intrinsic cost and floor cost)
intrinsic_gas,calldata_floor_gas_cost=calculate_intrinsic_cost(tx)blob_gas_used=calculate_total_blob_gas(tx)# Track total gas usage (using the maximum of intrinsic gas and floor cost)
total_inclusion_gas+=max(intrinsic_gas,calldata_floor_gas_cost)total_blob_gas_used+=blob_gas_used# Calculate maximum gas fee (including blob fees)
effective_gas_price=calculate_effective_gas_price(tx,block.header.base_fee_per_gas)max_gas_fee=tx.gas*effective_gas_price+blob_gas_used*blob_gas_price# Verify sender is an EOA or has delegation support
ifsender_addressnotinsender_balances:account=get_account(chain.state,sender_address)is_sender_eoa=(account.code==bytearray()oris_valid_delegation(account.code))ifnotis_sender_eoa:raiseInvalidBlocksender_balances[sender_address]=account.balancesender_nonces[sender_address]=account.nonce# Verify sender has sufficient balance
ifsender_balances[sender_address]<max_gas_fee+Uint(tx.value):raiseInvalidBlock# Verify correct nonce
ifsender_nonces[sender_address]!=tx.nonce:raiseInvalidBlock# Track balance and nonce changes
sender_balances[sender_address]-=max_gas_fee+Uint(tx.value)sender_nonces[sender_address]+=1# Validate gas constraints
iftotal_inclusion_gas>block.header.gas_used:raiseInvalidBlockiftotal_blob_gas_used!=block.header.blob_gas_used:raiseInvalidBlock# Validate withdrawals trie
fori,wdinenumerate(block.withdrawals):trie_set(withdrawals_trie,rlp.encode(Uint(i)),rlp.encode(wd))ifblock.header.withdrawals_root!=root(withdrawals_trie):raiseInvalidBlock
This validation function enforces several requirements:
Clients MUST validate that the block’s parent execution outputs match the chain’s tracked last execution outputs.
The pre_state_root MUST match the current state root to ensure proper state transition.
All transactions MUST be statically validated for signature correctness and transaction type-specific requirements (EIP-1559, EIP-4844, etc.).
Sender accounts MUST be externally owned accounts (EOAs) or have valid delegation support (EIP-7702).
Senders MUST have sufficient balance to cover maximum possible gas fees plus transaction value.
Transaction nonces MUST be correct and sequential per sender.
Total inclusion gas (using the maximum of intrinsic gas and EIP-7623 floor cost) MUST not exceed the block’s declared gas_used.
Total blob gas MUST match the declared blob_gas_used.
Withdrawal trie root MUST match its respective header field.
When calculating inclusion gas, the implementation uses the maximum of the regular intrinsic gas cost and the EIP-7623 calldata floor cost, which ensures proper accounting for calldata gas regardless of execution outcome.
Block Execution with State Snapshots
After a block passes static validation, execution proceeds with the pre-charged transaction senders:
defapply_body(block_env:vm.BlockEnvironment,transactions:Tuple[Union[LegacyTransaction,Bytes],...],withdrawals:Tuple[Withdrawal,...],)->vm.BlockOutput:block_output=vm.BlockOutput()# Process system transactions first (beacon roots, history storage)
process_system_transaction(block_env=block_env,target_address=BEACON_ROOTS_ADDRESS,data=block_env.parent_beacon_block_root,)process_system_transaction(block_env=block_env,target_address=HISTORY_STORAGE_ADDRESS,data=block_env.block_hashes[-1],# The parent hash
)# Process user transactions
process_transactions(block_env,block_output,transactions)process_withdrawals(block_env,block_output,withdrawals)# Process requests (deposits, withdrawals, consolidations)
process_general_purpose_requests(block_env=block_env,block_output=block_output,)returnblock_outputdefprocess_transactions(block_env:vm.BlockEnvironment,block_output:vm.BlockOutput,transactions:Tuple[Union[LegacyTransaction,Bytes],...],)->None:# Take a block-level snapshot of the state before transaction execution
begin_transaction(block_env.state)# Decode transactions
decoded_transactions=[decode_transaction(tx)fortxintransactions]# Pre-charge senders for maximum possible gas fees upfront
fortxindecoded_transactions:deduct_max_tx_fee_from_sender_balance(block_env,tx)# Execute each transaction
fori,txinenumerate(decoded_transactions):process_transaction(block_env,block_output,tx,Uint(i))# Stop processing if execution is already reverted
ifblock_output.execution_reverted:break# Validate declared gas used against actual gas used
block_output.execution_reverted=(block_output.execution_revertedorblock_output.block_gas_used!=block_env.block_gas_used)# If execution is reverted, reset all outputs and rollback the state
ifblock_output.execution_reverted:rollback_transaction(block_env.state)block_output.block_gas_used=Uint(0)block_output.transactions_trie=Trie(secured=False,default=None)block_output.receipts_trie=Trie(secured=False,default=None)block_output.receipt_keys=()block_output.block_logs=()block_output.requests=[]block_output.execution_reverted=Trueelse:# Commit the state if execution is valid
commit_transaction(block_env.state)
During block execution:
Clients MUST create a block-level snapshot before any transaction execution occurs. This happens after handling the system contracts from EIP-4788 and EIP-2935.
Maximum fees MUST be deducted from sender balances upfront by:
Transactions are executed sequentially until executing a transaction would cause the block to exceed the gas limit.
After execution, clients MUST verify that the total gas used matches the declared gas_used in the block header.
If the actual gas used doesn’t match the declared value, clients MUST:
Set execution_reverted to True
Roll back to the snapshot taken before execution
Reset all execution outputs (receipts, logs, etc.)
Return zero gas used
The block itself remains valid, but execution outputs are not applied to the state.
General purpose requests as defined in EIP-7685 are skipped when execution is reverted.
The execution outputs (last_transactions_root, last_receipt_root, last_block_logs_bloom, last_requests_hash, last_execution_reverted) are updated in the chain state based on the execution results, and will be verified in subsequent blocks.
Execution SHALL be stopped and the payload reverted after exceeding the block gas limit:
The core innovation of deferring execution outputs to the next block enables static and stateful validation without requiring immediate execution. The pre_state_root provides a cryptographically verifiable starting point for validation, while parent execution outputs create a chain of deferred execution results that maintains the integrity of the blockchain state.
This approach eliminates the execution bottleneck in the validation pipeline by allowing validators to attest to a block’s validity based on its structure and the pre-charged transaction fees, without waiting for execution results.
Pre-Charging Mechanism
Pre-charging senders with the maximum possible fees before execution provides a crucial guarantee that transactions have sufficient balance to be included in the block. This mechanism is compatible with existing fee models, including EIP-1559 dynamic fee transactions and EIP-4844 blob transactions.
By tracking sender balances and nonces during validation, the protocol can enforce transaction validity without execution, enabling earlier block attestation.
State Snapshot Architecture
The block-level snapshot mechanism provides a way to revert execution when necessary. This approach allows clients to roll back the entire block’s execution if the actual gas used does not match the declared gas in the header, without invalidating the block structure itself.
This provides two key benefits:
It allows validators to attest to blocks before execution is complete
It ensures execution is eventually performed correctly, with economic penalties for incorrect gas declarations
Execution Reversion Handling
When a block’s execution is reverted due to gas mismatch:
The block itself remains valid and part of the canonical chain
The last_execution_reverted flag is set to True in chain state
The next block must include this flag as parent_execution_reverted
Base fee calculations for subsequent blocks use 0 as parent gas used when parent execution was reverted
Backwards Compatibility
This EIP requires a hard fork, as it alters the block validation and execution process.
Security Considerations
Execution Correctness Guarantees
The protocol ensures execution correctness through these primary mechanisms:
Deferred execution outputs MUST match in subsequent blocks, creating a chain of verifiable execution results.
State rollback MUST occur if the actual gas used doesn’t match the declared value, providing an economic incentive for correct gas declaration.
The parent_execution_reverted flag ensures that blocks acknowledge when parent execution has been reverted, maintaining chain integrity.
Static and stateful validation guarantees that all transactions are properly authorized and senders have sufficient funds for maximum fees.
Base Fee Dynamics with Reverted Execution
When a block’s execution is reverted, the next block’s base fee calculation treats the parent’s gas used as zero, regardless of what was declared in the header. This ensures that base fee adjustments remain responsive to actual chain usage, and prevents manipulation of the fee market through incorrect gas declarations.
Data Availability and Economic Incentives
Block proposers MUST declare correct gas usage or lose transaction fees when execution is reverted. This aligns incentives for correct gas declaration and ensures execution integrity.
Even when a block’s execution is reverted due to incorrect gas declaration, the transaction data (calldata, EIP-2930 access lists, and blob data) MUST still be stored by all nodes for syncing and block validation purposes. This requirement creates a potential attack vector where malicious actors could attempt to place large amounts of data on-chain at a reduced cost by intentionally invalidating blocks through incorrect gas declarations.
However, this attack is not economically sustainable for several reasons:
Block proposers who invalidate blocks through incorrect gas declarations lose all execution layer rewards associated with the block.
The attack requires control of block production, which is a scarce resource in the consensus layer.
The economic costs of forgoing block rewards significantly outweigh any potential benefits, making such attacks financially impractical under normal network conditions.