This EIP proposes adding Block-Level Access Lists (BALs). By including a complete list of all addresses and storage keys accessed during a block, along with their post-execution values for writes, we enable parallel disk reads and parallel transaction validation. This improves execution efficiency and accelerates block validation, potentially allowing for future gas limit increases.
Motivation
Currently, transactions without an explicit transaction access list cannot be efficiently parallelized, as the execution engine cannot determine in advance which addresses and storage slots will be accessed. While transaction-level access lists exist via EIP-2930, they are not enforced, making it difficult to optimize execution pipelines.
We propose enforcing access lists at the block level and shifting the responsibility of their creation to the block builder. This enables to efficiently parallelize both disk reads and transaction execution, knowing in advance the exact scope of storage interactions for each transaction.
The inclusion of post-execution values for writes in BALs provides an additional benefit for state syncing. Nodes can use these values to reconstruct state without processing all transactions, verifying correctness by comparing the derived state root to the head block’s state root.
BALs map transactions to (address, storage key, value) tuples and include balance diffs. This approach facilitates parallel disk reads and parallel execution, reducing maximum execution time to parallel IO + parallel EVM and improving overall network performance.
Specification
Block Structure Modification
We introduce three new components in the block body:
A Block Access List (BAL) that maps transactions to accessed addresses and storage slots, transaction indices, and post-execution values for writes.
Balance Diffs that track every address touched by value transfers along with the balance deltas.
Nonces that record the pre-block nonces of contracts using CREATE or CREATE2 within the block.
SSZ Data Structures
# Type aliases
Address=ByteVector(20)StorageKey=ByteVector(32)StorageValue=ByteVector(32)TxIndex=uint16Nonce=uint64# Constants; chosen to support a 630m block gas limit
MAX_TXS=30_000MAX_SLOTS=300_000MAX_ACCOUNTS=300_000MAX_CODE_SIZE=24_576# Maximum contract bytecode size in bytes
# SSZ containers
classPerTxAccess(Container):tx_index:TxIndexvalue_after:StorageValue# value in state after the last access
classSlotAccess(Container):slot:StorageKeyaccesses:List[PerTxAccess,MAX_TXS]# empty for reads
classAccountAccess(Container):address:Addressaccesses:List[SlotAccess,MAX_SLOTS]code:Union[ByteVector(MAX_CODE_SIZE),None]# Optional field for contract bytecode
BlockAccessList=List[AccountAccess,MAX_ACCOUNTS]# Balance Diff structures
BalanceDelta=ByteVector(12)# signed, two's complement encoding
classBalanceChange(Container):tx_index:TxIndexdelta:BalanceDelta# signed integer, encoded as 12-byte vector
classAccountBalanceDiff(Container):address:Addresschanges:List[BalanceChange,MAX_TXS]BalanceDiffs=List[AccountBalanceDiff,MAX_ACCOUNTS]# Post-transaction nonce structures
classAccountNonce(Container):address:Address# account address
nonce_before:Nonce# nonce value after block execution
NonceDiffs=List[AccountNonceDiff,MAX_TXS]
The BlockAccessList is a deduplicated list of accessed addresses. For each address, it MUST contain a list of accessed storage keys.
For writes, each SlotAccess MUST contain an ordered list of transaction indices that accessed this key, and the final storage value after the last access. Transactions with writes that do not change the storage value MUST NOT contain a value_after.
For contract deployments, the value_after MUST contain the runtime bytecode of the newly deployed contract.
The code field in AccountAccess is optional and MUST be present only when a contract is deployed. The code field MUST contain the runtime bytecode of the deployed contract.
For reads, each SlotAccess MUST contain an empty accesses list.
The BalanceDiffs structure tracks every address with a balance change, including transaction senders, recipients, and the block’s coinbase address. Touched accounts without balance changes MUST be omitted.
Each entry MUST include the transaction index and the signed balance delta per address for each transaction.
12 bytes are sufficient to represent the total ETH supply.
The NonceDiffs structure MUST record the pre-transaction nonce values for all CREATE and CREATE2 deployer accounts and the deployed contracts in the block. This includes nonce increases that occur at the deployer contract even when deployments using CREATE or CREATE2 revert, as specified in EIP-7610.
State Transition Function
Modify the state transition function to validate the block-level access lists:
defstate_transition(block:Block)->None:computed_access_list={}computed_balance_diffs={}computed_nonce_diffs={}computed_code_changes={}foridx,txinenumerate(block.transactions):# Execute transaction and track state accesses
accessed_items,balance_changes,code_accesses=execute_transaction(tx)# Process storage accesses
for(address,slot),is_write,valueinaccessed_items:if(address,slot)notincomputed_access_list:computed_access_list[(address,slot)]=[]access_entry={"tx_index":idx}ifis_write:access_entry["value_after"]=valuecomputed_access_list[(address,slot)].append(access_entry)# Process balance changes
foraddress,deltainbalance_changes:ifaddressnotincomputed_balance_diffs:computed_balance_diffs[address]=[]computed_balance_diffs[address].append({"tx_index":idx,"delta":delta})# Process code accesses and changes
foraddress,codeincode_accesses:computed_code_changes[address]=code# Validate access list, balance diffs, and code changes
ifnotvalidate_access_list(block.block_access_list,computed_access_list,computed_code_changes):raiseInvalidBlock("Mismatch in block-level access list.")ifnotvalidate_balance_diffs(block.balance_diffs,computed_balance_diffs):raiseInvalidBlock("Mismatch in balance diffs.")ifnotvalidate_nonce_diffs(block.nonce_diffs,computed_nonce_diffs):raiseInvalidBlock("Mismatch in nonce diffs.")
The BAL MUST be complete and accurate. It MUST NOT contain too few entries (missing accesses) or too many entries (spurious accesses). Any missing or extra entries in the access list, balance diffs, or nonce diffs SHALL result in block invalidation.
Client implementations MUST compare the accessed addresses and storage keys gathered during execution (as defined in EIP-2929) with those included in the BAL to determine validity.
Client implementations MAY invalidate the block right away in case any transaction steps outside the declared state.
Rationale
BAL Design Choice
This design variant was chosen for several key reasons:
Balance between size and parallelization benefits: BALs enable both parallel disk reads and parallel EVM execution while maintaining manageable block sizes. Since worst-case block sizes for reads are larger than for writes, omitting read values from the BAL significantly reduces its size. This approach still allows parallelization of both IO and EVM execution. While including read values would further enable parallelization between IO and EVM operations, analysis of historical data suggests that excluding them strikes a better balance between worst-case block sizes and overall efficiency.
Storage value inclusion for writes: Including post-execution values for write operations facilitates state reconstruction during syncing, enabling faster chain catch-up. Unlike snap sync, state updates in BALs are not individually proved against the state root. Similar to snap sync, execution itself is not proven. However, validators can verify correctness by comparing the final state root with the one received from a light node for the head block.
Balance and nonce tracking: Balance diffs and nonce tracking are crucial for correct handling of parallel transaction execution. While most nonce updates can be anticipated statically (based on sender accounts), contract creation operations (CREATE and CREATE2) can increase an account’s nonce without that account appearing as a sender. The nonce diff structure specifically addresses this edge case by tracking nonces for contract deployers and deployed contracts. For changing delegation under EIP-7702, the transaction type indicates that an authority’s nonce must be updated.
Reasonable overhead with significant benefits: Analysis of historical blocks (random sample of 1000 blocks between 22,195,599 and 22,236,441) shows that BALs would have had an average size of around 57 KiB, with balance diffs adding only 9.6 KiB on average. This represents a reasonable overhead given the substantial performance benefits in block validation time.
High degree of transaction independence: Analysis of the same block sample revealed that approximately 60-80% of transactions within a block are fully independent from one another, meaning they access disjoint sets of storage slots. This high level of independence makes parallelization particularly effective, as most transactions can be processed concurrently.
Block Size Considerations
Including access lists increases block size, potentially impacting network propagation times and blockchain liveness. Based on analysis of historical blocks:
Average BAL size over 1,000 blocks was around 57 KiB (SSZ-encoded, snappy compressed)
Average balance diff size was approximately 9.6 KiB
Worst-case BAL size for consuming the entire block gas limit with storage access operations would be approximately 0.93 MiB
Worst-case balance diff size would be around 0.12 MiB
These sizes are manageable and less than the current worst-case block size achievable through calldata.
Asynchronous Validation
Block execution can proceed with parallel IO and parallel EVM operations, with verification of the access list occurring alongside execution, ensuring correctness without delaying block processing.
Backwards Compatibility
This proposal requires changes to the block structure that are not backwards compatible and require a hard fork.
Security Considerations
Validation Overhead
Validating access lists and balance diffs adds validation overhead but is essential to prevent acceptance of invalid blocks.
Block Size
Including comprehensive access lists, balance diffs and nonce values increases block size, potentially impacting network propagation times. However, as noted in the rationale section, the overhead is reasonable given the performance benefits, with typical BALs averaging around 67 KiB in total.