Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-8268: Storage Roots in Block Access Lists

Extend EIP-7928 with per-account post-block storage trie roots so partially stateful nodes can verify the state root

Authors Toni Wahrstätter (@nerolation), Carlos Perez (@CPerezz)
Created 2026-05-21
Discussion Link https://ethereum-magicians.org/t/eip-8268-storage-roots-in-block-access-lists/28585
Requires EIP-7928

Abstract

This EIP extends EIP-7928 by attaching the post-block storage trie root to every Block Access List (BAL) entry that records state modifications. Partially stateful nodes can then verify the post-block state root without holding the full storage tries of modified accounts.

Motivation

A BAL exposes per-slot post-values but not the post-block storage trie root of each modified account. A node that holds only a partition of the state therefore cannot derive the storage root for a modified account it does not store, and so cannot reconstruct the state-trie leaf needed to verify the post-block state root.

Including the post-block storage root for every modified account closes this gap. Combined with the balance, nonce, and code information already present in the BAL, any node can assemble the full state-trie leaf for every modified account regardless of which portion of state it retains.

Specification

The key words “MUST”, “MUST NOT”, “SHOULD”, and “MAY” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Modified AccountChanges

Each AccountChanges entry whose state-change lists (storage_changes, balance_changes, nonce_changes, or code_changes) are not all empty MUST carry an additional trailing field storage_root:

StorageRoot = bytes32      # 32-byte trie root, or the empty byte string when the trie is empty

AccountChanges = [
    Address,
    List[SlotChanges],     # storage_changes
    List[StorageKey],      # storage_reads
    List[BalanceChange],   # balance_changes
    List[NonceChange],     # nonce_changes
    List[CodeChange],      # code_changes
    StorageRoot,           # post-block storage trie root (new)
]

storage_root is the root of the account’s storage trie evaluated against the post-block state. For accounts whose post-block storage trie is empty, storage_root MUST be the empty byte string (RLP-encoded as 0x80); the 32-byte canonical empty trie root 0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421 MUST NOT be used. A consumer MUST treat the empty byte string as that canonical empty trie root when reconstructing the state-trie leaf. This condition is keyed on the post-block storage trie being empty, not on account type, so externally owned accounts, newly created or cleared contracts, and EIP-7702 delegated accounts are all covered by the same rule without inspecting code.

Entries that describe accessed-but-unchanged accounts (every change list empty) MUST NOT include storage_root and retain the six-field layout from EIP-7928.

Validation

A block is invalid if, for any AccountChanges carrying storage_root:

  • the post-block storage trie is non-empty and storage_root is not its 32-byte storage trie root produced by executing the block; or
  • the post-block storage trie is empty and storage_root is not the empty byte string.

Equivalently, every empty storage trie MUST be encoded as the empty byte string and every non-empty storage trie as its 32-byte root, so each block has exactly one valid encoding.

All other rules from EIP-7928, including ordering, uniqueness, block_access_list_hash computation, and engine API encoding, remain unchanged. storage_root is included in the RLP that the hash commits to.

Rationale

The storage root is the single piece of post-block state that cannot be reconstructed from BAL contents alone, since it depends on the entire storage trie of the account rather than only the modified slots. Balance, nonce, and code are already recoverable from the BAL diffs, so committing to the storage root is sufficient to derive the full state-trie leaf. Restricting the field to accounts with state changes keeps the overhead at 32 bytes per modified account; touched-only entries add nothing.

Most accounts mutated in a block are externally owned accounts that change only balance or nonce and hold no storage, so their post-block storage trie is empty. Encoding that case as the 32-byte canonical empty root would repeat a well-known constant for the majority of entries. Representing the empty trie as the empty byte string (0x80) instead removes 32 bytes per such account while remaining unambiguous, since 0x80 cannot collide with any 32-byte root. Keying the rule on the post-block storage trie being empty, rather than on account type, keeps it correct for newly created or cleared contracts and for EIP-7702 delegated accounts, whose storage may be empty or non-empty, without inspecting account code.

Backwards Compatibility

This proposal alters the BAL encoding introduced by EIP-7928 and requires a hard fork. The two encodings are not interchangeable: clients implementing only EIP-7928 will reject blocks under this EIP and vice versa.

Security Considerations

storage_root is a redundant commitment over data already produced during execution, so any divergence is detected as part of normal BAL validation. The added bandwidth is bounded by 32 * modified_accounts, well below the BAL size envelope described in EIP-7928.

Mandating a single canonical encoding for the empty storage trie — the empty byte string, never the 32-byte constant — ensures every block has exactly one valid BAL serialization. block_access_list_hash therefore stays deterministic, and clients cannot disagree on the commitment for identical post-block state.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Toni Wahrstätter (@nerolation), Carlos Perez (@CPerezz), "EIP-8268: Storage Roots in Block Access Lists [DRAFT]," Ethereum Improvement Proposals, no. 8268, May 2026. Available: https://eips.ethereum.org/EIPS/eip-8268.