This proposal decouples state root computation from block validation by deferring the execution layer's state root reference by one block. Each block's header contains the post-state root of the previous block rather than its own. Validators can attest to block validity without waiting for state root computation.
State root computation is a significant bottleneck in block production. With EIP-7732 (ePBS), builders have tighter timing constraints. EIP-7928 (Block-Level Access Lists) enables parallel state access but doesn't help with state root computation for builders.
With delayed state roots:
The state root in block n represents the post-state of block n-1 (equivalently: the pre-state of block n). Light clients experience one slot of additional latency for state proofs.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The state_root field semantics change. No new fields are added.
class Header:
parent_hash: Hash32
ommers_hash: Hash32
coinbase: Address
state_root: Root # Post-state of block (n-1), i.e., pre-state of this block
transactions_root: Root
receipt_root: Root
bloom: Bloom
difficulty: Uint
number: Uint
gas_limit: Uint
gas_used: Uint
timestamp: U256
extra_data: Bytes
prev_randao: Bytes32
nonce: Bytes8
base_fee_per_gas: Uint
withdrawals_root: Root
blob_gas_used: U64
excess_blob_gas: U64
parent_beacon_block_root: Root
requests_hash: Hash32
block_access_list_hash: Hash32
The BlockChain object tracks the last computed state root:
class BlockChain:
blocks: List[Block]
state: State
chain_id: U64
last_computed_state_root: Root
def validate_header(chain: BlockChain, header: Header) -> None:
if header.number < 1:
raise InvalidBlock
parent_header = chain.blocks[-1].header
# Verify delayed state root matches the last computed state root
if header.state_root != chain.last_computed_state_root:
raise InvalidBlock
# ... remaining validation unchanged
def state_transition(chain: BlockChain, block: Block) -> None:
validate_header(chain, block.header)
block_env = vm.BlockEnvironment(...)
block_output = apply_body(
block_env=block_env,
transactions=block.transactions,
withdrawals=block.withdrawals,
)
# Validate all roots except state_root (already validated in header)
if block_output.block_gas_used != block.header.gas_used:
raise InvalidBlock
# ... other validations
# Compute and store state root for the NEXT block
chain.last_computed_state_root = state_root(block_env.state)
chain.blocks.append(block)
if len(chain.blocks) > 255:
chain.blocks = chain.blocks[-255:]
At activation block F:
def apply_fork(old: BlockChain) -> BlockChain:
# Initialize last_computed_state_root to current state root
# (post-state of block F-1)
old.last_computed_state_root = state_root(old.state)
return old
Block F MUST contain the post-state root of block F-1. From F+1 onwards, each block contains its parent's post-state root.
With EIP-7732, the ExecutionPayloadEnvelope contains a CL state_root verified at the end of process_execution_payload. This EIP changes only the EL header's state_root semantics. The CL state root verification is unaffected.
EIP-7928 provides the block access list, which identifies all storage slots touched during execution. With delayed state roots, clients can:
n with BALnn+1Without this EIP, BALs don’t help with state root computation: the root is only known after execution finishes, which is too late to use within the same slot. With this proposal, builders can start constructing a block without fully executing the previous one, by applying the BAL state diff to the slot’s pre-state.
State proofs for block n require waiting for block n+1. Most light client protocols already tolerate multi-block delays for proof generation. The security model is unchanged; only timing shifts by one slot.
Requires a hard fork. Clients that do not implement this change will reject blocks with delayed state roots.
During reorgs, clients MUST recompute last_computed_state_root for each block in the new canonical chain. The delayed nature does not affect reorg logic.
Clients MUST retain the pre-state (post-state of parent block) until the current block's state root is included in the next block. This is already standard practice for reorg handling.
Copyright and related rights waived via CC0.