Abstract
A Flashblock Access List (FBAL) is an [EIP-7928][eip-7928]-style block access list scoped to a single flashblock. The builder records every account, balance, nonce, code, and storage change produced while executing a flashblock’s transactions - each tagged with a block-local block access index — and publishes the resulting list, together with the Keccak256 hash of its RLP encoding, as an optional field on the flashblock pre-confirmation payload propagated over the Flashblocks P2P network.
The access list is a self-contained description of the flashblock’s state transition. A consumer that receives it can (1) reconstruct the exact pre-state seen by every transaction in the flashblock from the access list itself, (2) re-execute those transactions independently and in parallel rather than sequentially, (3) compute the post-flashblock state root by applying the diff to the parent bundle without any re-execution, and (4) confirm that the builder’s claimed state transition is correct by re-deriving the access list and checking its hash against the published value, then checking the receipts root, state root, and block hash.
Motivation
Flashblocks deliver sub-block pre-confirmations: a builder publishes an ordered stream of incremental payloads (index = 0, 1, 2, …) for a single block, and consumers apply each delta to maintain a pending block well before the full block is sealed. To surface that pending state through the standard RPC surface, every consuming node must execute each flashblock’s transactions as it arrives.
Naively, each consumer re-executes the flashblock’s transactions sequentially against live state, exactly as the builder did. This has two costs on the hot path between receiving a flashblock and exposing it as pending:
- Sequential dependency. Transactions must run one after another because each may read state written by an earlier one, so execution cannot be parallelized without knowing the data dependencies in advance.
- A full state-root computation that depends on execution. The post-state and therefore the state root are only known once execution finishes.
EIP-7928 observes that if a block ships a complete list of the state it touches and the values it writes — keyed by an intra-block ordering index — then a verifier no longer needs to discover dependencies by executing: the access list is the dependency graph and the post-state diff. This WIP applies that idea to flashblocks specifically:
- The per-index pre-state for any transaction can be materialized directly from the access list, so transactions can be executed in parallel, each against an isolated snapshot.
- The post-flashblock state root can be derived by applying the diff to the parent bundle state, with no execution at all.
- Re-execution is still performed, but only to verify the builder’s claim: the independently re-derived access list must hash to the published value, and the resulting receipts root, state root, and block hash must match the flashblock.
The access list is therefore an optimization and an integrity check layered on the existing pre-confirmation flow, not a change to the canonical block. It is opt-in: a builder may or may not attach it, and a consumer selects the parallel path when it is present and a legacy sequential path when it is absent.
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.
Data Structures
A Flashblock Access List reuses the EIP-7928 change-record types verbatim and wraps them in two World Chain types.
FlashblockAccessList
FlashblockAccessList {
changes: List[AccountChanges], // EIP-7928 account change records
min_tx_index: u16, // lowest block access index covered by this flashblock
max_tx_index: u16, // highest block access index covered by this flashblock
}
changesis the set of [EIP-7928AccountChanges][eip-7928] records produced by the transactions in this flashblock.min_tx_indexandmax_tx_indexare the inclusive bounds of the block-access-index range that this flashblock’s transactions occupy within the block. For the first flashblockmin_tx_indexis0. For a later flashblockmin_tx_indexis the count of transactions already committed in prior flashblocks plus one.max_tx_indexcorresponds to the trailing system transaction (balance increments).
Each AccountChanges record (defined by EIP-7928) groups all changes for one account and MUST be encoded with the following field order:
AccountChanges {
address: Address,
storage_changes: List[SlotChanges],
storage_reads: List[U256],
balance_changes: List[BalanceChange],
nonce_changes: List[NonceChange],
code_changes: List[CodeChange],
}
SlotChanges { slot: U256, changes: List[StorageChange] }
StorageChange { block_access_index: u64, new_value: U256 }
BalanceChange { block_access_index: u64, post_balance: U256 }
NonceChange { block_access_index: u64, new_nonce: u64 }
CodeChange { block_access_index: u64, new_code: Bytes }
Each change record carries the block_access_index at which it occurred and the resulting post-value (the value after the change), not a delta.
FlashblockAccessListData
FlashblockAccessListData {
access_list: FlashblockAccessList,
access_list_hash: B256,
}
access_list_hash is defined as:
access_list_hash = keccak256(rlp(access_list))
where rlp(access_list) is the RLP encoding of the FlashblockAccessList (the inner list, not the FlashblockAccessListData wrapper). The hash MUST be computed over the canonical form defined in Canonical Form.
Placement on the flashblock payload
FlashblockAccessListData is carried as the trailing OPTIONAL field of the flashblock diff:
ExecutionPayloadFlashblockDeltaV1 {
state_root, receipts_root, logs_bloom, gas_used,
block_hash, transactions, withdrawals, withdrawals_root,
access_list_data: Option[FlashblockAccessListData], // trailing optional
}
Because access_list_data is encoded as a trailing optional field, a payload that omits it is wire-compatible with a payload that predates this WIP. The diff is in turn embedded in FlashblocksPayloadV1 and propagated over the Flashblocks P2P network inside an authorized, builder-signed message (AuthorizedMsg::FlashblocksPayloadV1). This WIP adds no new P2P message type; it only populates a previously-empty optional field.
Canonical Form
The access_list_hash is only useful if the builder and every consumer arrive at byte-identical RLP. A FlashblockAccessList MUST therefore be in canonical form before it is hashed. Canonical form is defined by the following rules, which both the builder (when producing) and the consumer (when re-deriving) MUST apply:
- Account ordering.
changesMUST be sorted ascending byaddress. - Slot ordering. Within each account,
storage_changesMUST be sorted ascending byslot, andstorage_readsMUST be sorted ascending and deduplicated. - Change ordering. Within each slot, and within each account’s
balance_changes,nonce_changes, andcode_changes, entries MUST be sorted ascending byblock_access_index. - Consecutive de-duplication. Consecutive change entries that write the same value MUST be collapsed to the single occurrence with the highest
block_access_index. (Executing a value, changing it, and changing it back, or writing the same value twice, MUST NOT inflate the list.) - Empty-account pruning. An account whose net recorded changes are all zero/default — every balance change to
0, every nonce change to0, every code change to empty code, and every storage write to0— MUST be removed fromchangesentirely.
Two access lists that describe the same state transition MUST produce the same canonical form and therefore the same access_list_hash.
Block Access Index Semantics
The block_access_index is the mechanism that lets a consumer reconstruct the pre-state of any transaction without executing the ones before it.
A change tagged with block_access_index = j is the state produced at index j. A reader executing the transaction at index i MUST observe every change with block_access_index < i and MUST NOT observe any change with block_access_index >= i. In other words, the transaction at index i reads the state left behind by all strictly-earlier indices, giving each transaction a snapshot-isolated view of the pre-state.
Implementations construct this view by indexing the access list so that a change recorded at block_access_index = j becomes visible to readers at index j + 1 and later, and a read at index i returns the most recent change whose visibility index is <= i. A read that finds no matching entry MUST fall through to the parent (pre-block) state.
Builder Construction (Producer)
While building a flashblock, the builder MUST record, for each committed transaction at its block_access_index, the post-values of all account fields and storage slots that the transaction changed, plus the storage slots it read. The recording rules are:
- For an account that exists in the pre-state, a
balance_changes/nonce_changes/code_changesentry MUST be recorded only if the corresponding field differs from its pre-transaction value. - For an account that does not exist in the pre-state, the balance, nonce, and code MUST all be recorded at the current index.
- For storage, a
StorageChangeMUST be recorded only when the slot’s post-value differs from the value read before any modification in that transaction (the original value, not a cached intermediate value). - Every storage slot read during execution MUST be recorded in
storage_reads, whether or not it was written.
After all of a flashblock’s transactions are recorded, the builder MUST reduce the accumulated changes to Canonical Form, set min_tx_index / max_tx_index to the block-access-index range the flashblock covers, compute access_list_hash, and attach the resulting FlashblockAccessListData to the diff.
Attaching a Flashblock Access List is OPTIONAL and controlled by builder configuration. A builder that does not produce access lists MUST leave access_list_data absent.
Consumer Validation
A consumer that receives a flashblock whose diff contains access_list_data MAY use the access list to validate the flashblock via the parallel path. A consumer that uses the access list MUST perform all of the following, and MUST reject the flashblock if any step fails:
- Reconstruct pre-state. Build a temporal pre-state from
access_listover the parent block’s state, applying the Block Access Index Semantics. For any account, slot, or code not present in the access list, reads MUST fall through to the parent state. - Derive the post-state. Extend the bundle state committed by prior flashblocks with the changes in
access_listto obtain the post-flashblock bundle, and compute its state root. - Re-execute in parallel. Execute the flashblock’s transactions, each at its own
block_access_indexover an isolated snapshot of the reconstructed pre-state, and aggregate the per-transaction receipts, gas, fees, and re-derived account changes. - Verify the access list. Reduce the re-derived changes to canonical form, compute their hash, and require it to equal the published
access_list_hash. A mismatch MUST be rejected. - Verify the roots and hash. The re-computed
receipts_rootMUST equaldiff.receipts_root, the derivedstate_rootMUST equaldiff.state_root, and the assembled block hash MUST equaldiff.block_hash.
A consumer MUST NOT surface a flashblock’s state as pending unless validation succeeds. If access_list_data is absent, the consumer MUST fall back to sequential re-execution of the diff transactions against the committed state (the legacy path), which produces the same pending block without the access-list optimizations.
Block reconstruction
When a consumer reconstructs a full block header from a reduced set of flashblocks, the access list does not appear in the header. When the Amsterdam hardfork is active at the block timestamp, the reconstructed header MUST set block_access_list_hash = EMPTY_BLOCK_ACCESS_LIST_HASH and slot_number = 0; the Flashblock Access List is a pre-confirmation sidecar and is not committed to in the canonical block.
Rationale
Why reuse EIP-7928 change records. The per-account, per-field, index-tagged change records of EIP-7928 already encode exactly the dependency and post-state information a verifier needs, and reusing the alloy eip7928 types keeps the World Chain wire format aligned with the upstream standard. The only World Chain additions are the min_tx_index / max_tx_index bounds (because a flashblock covers a contiguous sub-range of the block’s indices rather than the whole block) and the access_list_hash (because the list is gossiped and must be integrity-checked against the builder’s signature-covered payload).
Why block_access_index visibility is offset by one. Snapshot isolation requires transaction i to see writes from < i but not its own writes at i. Storing a change produced at index j so that it becomes visible at j + 1 makes a point-in-time read at index i return precisely the state of all strictly-earlier transactions, which is what enables independent parallel execution.
Backwards Compatibility
The Flashblock Access List is carried in a trailing optional field of the flashblock diff and is gated behind builder configuration, so it introduces no backwards-incompatible wire change:
- A builder that does not enable access lists produces byte-identical payloads to the pre-WIP format.
- A consumer that does not understand the field ignores it and uses sequential re-execution.
- A consumer that does understand the field selects the parallel path only when the field is present, and the legacy path otherwise.
Security Considerations
The access list is advisory until verified. A consumer MUST NOT trust a builder-supplied access list.
Index integrity. The snapshot-isolation guarantee depends on correct block_access_index assignment and on the < i visibility rule. An off-by-one in visibility, or a misattributed index, can let a transaction observe state from a later transaction, producing a pre-state that diverges from sequential execution; this would be caught by the re-execution checks but would manifest as a (potentially griefing) validation failure rather than silent corruption.
Authenticity is inherited, not added. This WIP relies on the existing Flashblocks P2P authorization: access lists are only meaningful inside builder-signed, authorizer-authorized payloads. The WIP adds no new trust assumption about who may publish, only new data inside an already-authenticated message.
Resource bounds. A flashblock access list can be large (one entry per touched account/slot per index). Consumers SHOULD bound the work spent reconstructing pre-state and executing in parallel, consistent with the EIP-7928 per-block maxima, so that an oversized access list cannot be used to exhaust a consumer on the pre-confirmation hot path.