EIP-7862 - Delayed State Root

Created 2024-12-23
Status Draft
Category Core
Type Standards Track
Authors

Abstract

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.

Motivation

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:

  1. Builders compute one state root per slot (for the previous block) instead of thousands during the MEV auction window
  2. State root computation can use BAL data from the previous block to parallelize proof generation
  3. The critical path shifts: state root computation is frontloaded to the beginning of the slot rather than blocking attestations

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.

Specification

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.

Header

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

BlockChain

The BlockChain object tracks the last computed state root:

class BlockChain:
    blocks: List[Block]
    state: State
    chain_id: U64
    last_computed_state_root: Root

Header Validation

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

State Transition

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:]

Fork Activation

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.

Rationale

ePBS Compatibility

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.

BAL Synergy

EIP-7928 provides the block access list, which identifies all storage slots touched during execution. With delayed state roots, clients can:

  1. Receive block n with BAL
  2. Use the BAL to parallelize state root computation for block n
  3. Include that root in block n+1

Without 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.

Light Client Impact

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.

Backwards Compatibility

Requires a hard fork. Clients that do not implement this change will reject blocks with delayed state roots.

Security Considerations

Reorganization Handling

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.

Pre-state Availability

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

Copyright and related rights waived via CC0.