Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-8146: Block Access List Sidecars

Propagate block access lists as independent sidecars, off the critical path and ahead of the execution payload

Authors Toni Wahrstätter (@nerolation), Raúl Kripalani (@raulk)
Created 2026-02-03
Discussion Link https://ethereum-magicians.org/t/eip-8146-block-access-list-sidecars/27757
Requires EIP-7732, EIP-7928

Abstract

This EIP removes the block access list (BAL) introduced in EIP-7928 from the execution payload and propagates it as an independent sidecar on a dedicated gossip topic. Builders commit to the BAL by including keccak256(rlp(BAL)), the same value written to the execution block header, in the ExecutionPayloadBid.

Since a BAL is roughly the size of the payload, this halves the block on the critical propagation path and separates state diffs from the transactions. Delivering the BAL ahead of the payload gives execution clients a guaranteed window to prefetch state and begin post-state root computation before execution starts, and lets EIP-7805 inclusion list builders advance their state view past the current block.

Motivation

EIP-7928 adds the BAL to the ExecutionPayload, which under EIP-7732 travels in a SignedExecutionPayloadEnvelope that the builder reveals during the slot. The BAL therefore arrives with the transactions, on the same topic and under the same deadline.

Propagating it as an independent sidecar has four benefits:

  • Separation of concerns: The BAL is a state diff, the payload is the set of transactions that produced it. Nodes that only advance state, such as clients syncing without re-execution, need the diff and not the transactions.

  • Block size on the critical path: A full BAL is roughly the size of the rest of the compressed block, ~72.4 KiB against ~71.7 KiB in the size analysis at a 60M gas limit. Removing it from the envelope halves object sizes, allowing lower networking caps as DoS protection.

  • Earlier BAL delivery: Inside the envelope, the BAL cannot be used before the payload arrives. With an earlier observation deadline, it precedes the payload by at least one second, enforced by the Payload Timeliness Committee (PTC), so execution clients enter execution with the declared state prefetched and the post-state root under way.

  • Better inclusion lists: The EIP-7805 inclusion list committee of slot N freezes its lists before slot N’s payload is revealed, and may list transactions that the block already includes or invalidates. An early BAL provides the post-values of every account block N touches, so inclusion list builders can advance their state view without executing the payload.

Specification

Execution Layer

BAL construction, validation and the block_access_list_hash header field remain as specified in EIP-7928. Only delivery changes: the BAL reaches the execution layer through a dedicated engine method instead of inside the payload.

Consensus Layer

Constants

Name Value Description
MAX_BLOCK_ACCESS_LIST_SIZE uint64(2**23) (= 8 MiB) SSZ bound on the encoded BAL
MIN_EPOCHS_FOR_BLOCK_ACCESS_LIST_SIDECARS_REQUESTS uint64(3533) Epochs for which sidecars must be served, matching the BAL retention window of EIP-7928
BLOCK_ACCESS_LIST_LEAD_TIME uint64(1) Seconds a sidecar must precede the payload attestation deadline to count as available

Types

Name SSZ equivalent Description
BlockAccessList ByteList[MAX_BLOCK_ACCESS_LIST_SIZE] RLP-encoded block access list, opaque to the consensus layer

Containers

ExecutionPayload

The block_access_list field added by EIP-7928 is removed:

class ExecutionPayload(Container):
    # ... fields unchanged from EIP-7732 ...
    blob_gas_used: uint64
    excess_blob_gas: uint64
    # [Removed in EIP-8146]
    # block_access_list: BlockAccessList
ExecutionPayloadBid
class ExecutionPayloadBid(Container):
    parent_block_hash: Hash32
    parent_block_root: Root
    block_hash: Hash32
    prev_randao: Bytes32
    fee_recipient: ExecutionAddress
    gas_limit: uint64
    builder_index: BuilderIndex
    slot: Slot
    value: Gwei
    execution_payment: Gwei
    blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]
    # [New in EIP-8146]
    block_access_list_hash: Bytes32

block_access_list_hash is keccak256(rlp(BlockAccessList)), the value the execution block header carries under EIP-7928.

PayloadAttestationData
class PayloadAttestationData(Container):
    beacon_block_root: Root
    slot: Slot
    payload_present: boolean
    blob_data_available: boolean
    # [New in EIP-8146]
    block_access_list_present: boolean
BlockAccessListSidecar
class BlockAccessListSidecar(Container):
    beacon_block_root: Root
    slot: Slot
    block_access_list: BlockAccessList

Fork Choice

Store
@dataclass
class Store(object):
    # ... existing fields ...
    # [New in EIP-8146]
    block_access_lists: Dict[Root, BlockAccessList] = field(default_factory=dict)
    block_access_list_availability_vote: Dict[Root, List[Optional[boolean]]] = field(
        default_factory=dict
    )
on_block

Alongside the existing ptc_vote initialization:

# [New in EIP-8146]
store.block_access_list_availability_vote[block_root] = [None] * PTC_SIZE
on_payload_attestation_message

Alongside the existing payload_present recording:

# [New in EIP-8146]
store.block_access_list_availability_vote[data.beacon_block_root][ptc_index] = (
    data.block_access_list_present
)

notify_ptc_messages propagates block_access_list_present when extracting PayloadAttestationMessage objects from the aggregates in a beacon block.

on_block_access_list_sidecar

Called for each sidecar that passes gossip validation:

def on_block_access_list_sidecar(store: Store, sidecar: BlockAccessListSidecar) -> None:
    assert sidecar.beacon_block_root in store.blocks
    block = store.blocks[sidecar.beacon_block_root]
    assert sidecar.slot == block.slot

    # The consensus layer treats the BAL as opaque bytes; no RLP decoding
    bid = block.body.signed_execution_payload_bid.message
    assert keccak256(sidecar.block_access_list) == bid.block_access_list_hash

    store.block_access_lists[sidecar.beacon_block_root] = sidecar.block_access_list

    # Deliver to the execution layer without waiting for the payload
    EXECUTION_ENGINE.notify_block_access_list(sidecar.block_access_list, bid.block_hash)
on_execution_payload_envelope

The payload MUST NOT be passed to the execution layer before the BAL of the same block:

def on_execution_payload_envelope(
    store: Store, signed_envelope: SignedExecutionPayloadEnvelope
) -> None:
    envelope = signed_envelope.message
    assert envelope.beacon_block_root in store.block_states
    assert is_data_available(envelope.beacon_block_root)

    # [New in EIP-8146] The BAL must be available locally
    assert envelope.beacon_block_root in store.block_access_lists

    state = store.block_states[envelope.beacon_block_root]
    verify_execution_payload_envelope(state, signed_envelope, EXECUTION_ENGINE)
    store.payloads[envelope.beacon_block_root] = envelope

verify_execution_payload_envelope is unchanged. An envelope that arrives before its sidecar MUST NOT be dropped: the client caches it and runs this handler again once on_block_access_list_sidecar has stored the matching BAL.

Networking

Gossip: block_access_list_sidecar

A new global topic carrying BlockAccessListSidecar objects. The following validations MUST pass before forwarding a sidecar:

  • [IGNORE] sidecar.slot <= current_slot, allowing for MAXIMUM_GOSSIP_CLOCK_DISPARITY.
  • [IGNORE] sidecar.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch).
  • [IGNORE] No valid sidecar for sidecar.beacon_block_root has been seen.
  • [IGNORE] The beacon block with root sidecar.beacon_block_root has been seen. Clients MAY queue the sidecar until the block arrives.

Let block be that beacon block and bid its body.signed_execution_payload_bid.message:

  • [REJECT] block passes validation.
  • [REJECT] sidecar.slot == block.slot.
  • [REJECT] keccak256(sidecar.block_access_list) == bid.block_access_list_hash.
Req/Resp

BlockAccessListSidecarsByRoot v1, protocol ID /eth2/beacon_chain/req/block_access_list_sidecars_by_root/1/:

Request Response
List[Root, MAX_REQUEST_PAYLOADS] List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]

Returns the sidecars matching the requested beacon block roots.

BlockAccessListSidecarsByRange v1, protocol ID /eth2/beacon_chain/req/block_access_list_sidecars_by_range/1/:

Request Response
(start_slot: Slot, count: uint64) List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]

Returns the sidecars in slot range [start_slot, start_slot + count), ordered by slot, at most MAX_REQUEST_PAYLOADS entries.

Clients MUST serve sidecars over both methods for the most recent MIN_EPOCHS_FOR_BLOCK_ACCESS_LIST_SIDECARS_REQUESTS epochs and MAY prune older ones.

Validator Duties

Builders
  1. Obtain the payload and the BAL from engine_getPayloadV6.
  2. Set block_access_list_hash = keccak256(blockAccessList) in the ExecutionPayloadBid.
  3. Once the beacon block carrying the bid is published, broadcast the BlockAccessListSidecar on the block_access_list_sidecar topic.
  4. Broadcast the SignedExecutionPayloadEnvelope, which no longer carries the BAL.

Builders SHOULD broadcast the sidecar as early as possible and MUST NOT delay step 3 until the payload envelope is revealed in step 4.

PTC Members

A PTC member sets block_access_list_present = True only if a sidecar for the block passed gossip validation locally at least BLOCK_ACCESS_LIST_LEAD_TIME seconds before the payload attestation deadline. payload_present and blob_data_available are set as specified in EIP-7732, independently of block_access_list_present.

Engine API

The BAL is delivered to the execution layer by a dedicated method, separately from the payload. For a given blockHash, the consensus layer MUST call engine_notifyBlockAccessListV1 before engine_newPayloadV5.

engine_getPayloadV6

Returns blockAccessList, the RLP-encoded BAL, as a top-level field of the response instead of a field of the payload structure.

engine_notifyBlockAccessListV1

Delivers the BAL to the execution layer independently of the payload.

Parameters:

  • blockAccessList: RLP-encoded BAL bytes.
  • blockHash: 32 bytes, ties the BAL to a payload.

On receipt, the execution layer:

  1. Stores the BAL under blockHash.
  2. Begins prefetching the accounts and storage slots the BAL declares.
  3. MAY begin computing the post-state root from the BAL’s post-values.

The call is not a validity check; the BAL is validated against the header when the payload is executed. The method acknowledges receipt.

Consensus clients MUST call this method as soon as a sidecar is verified in on_block_access_list_sidecar, without waiting for the payload envelope.

engine_newPayloadV5

The blockAccessList field that EIP-7928 adds to the engine API payload structure is removed; the method is otherwise unchanged. The execution layer pairs the payload with the BAL previously delivered for the same blockHash and validates it as specified in EIP-7928.

Rationale

Header Commitment Reuse

EIP-7928 already commits to the BAL in the execution block header as keccak256(rlp(BAL)). Carrying the same 32 bytes in the bid gives the consensus layer everything it needs to authenticate a sidecar, without a second hashing scheme and without RLP decoding outside the execution layer. A builder cannot equivocate: bid.block_hash transitively commits to the header, so if bid.block_access_list_hash disagrees with the revealed payload, either payload.block_hash != bid.block_hash and the envelope is rejected, or the execution layer recomputes a BAL that does not match its own header and the payload is rejected per EIP-7928.

Removing the BAL from the ExecutionPayload container also removes the need for the hash tree root substitution that EIP-7928 describes for pruned BALs, since sidecar retention no longer affects the payload’s Merkle commitment.

No Sidecar Signature

The builder already signs the bid, and verification is keccak256(sidecar.block_access_list) == bid.block_access_list_hash. A BLS signature on the sidecar would add verification cost without adding binding. This mirrors how blob data is verified against the commitments carried in the bid.

Separate PTC Field

block_access_list_present follows the pattern of blob_data_available. payload_present signals envelope timeliness, blob_data_available signals blob availability, and block_access_list_present signals BAL availability, so a missing object can be attributed to its cause. The vote is a signal; this EIP attaches no penalty to it. Availability is enforced locally, since on_execution_payload_envelope gates on the BAL being present regardless of the PTC outcome.

Publication Timing

In ePBS, builders delay publishing the execution payload envelope until close to the attestation deadline. Releasing it early exposes the transactions to same-slot unbundling, where a malicious proposer inserts adversarial bundles to exploit the builder.

The BAL is not exposed to this risk. It carries access lists and post-state values, not signed transactions. A proposer that receives the BAL early learns which contracts were touched and which balances moved, but to steal the MEV it would risk being slashed.

BLOCK_ACCESS_LIST_LEAD_TIME covers builders that publish late. A BAL that arrives with the payload, or after it, loses the availability vote, which leaves one second as the guaranteed window for prefetching and post-state root computation. Without it, the lead time would depend entirely on builder behaviour.

Dedicated Engine API Method

Delivering the BAL and the payload in separate calls is what creates the lead time:

t0   sidecar received    --engine_notifyBlockAccessListV1-->   EL prefetches state, MAY start post-state root
t1   envelope received   --engine_newPayloadV5------------->   EL executes against warm state
t2                       <--------- {status: VALID}--------

t1 - t0 is the head start this EIP creates; with the BAL inside the envelope it is zero. Ordering is the consensus layer’s responsibility: an envelope that arrives first is cached until the sidecar arrives, so the execution layer never receives a payload without its BAL and needs no fallback path.

Sidecar Retention

EIP-7928 requires the execution layer to retain BALs for the weak subjectivity period, so that a node offline for less than that period can sync by re-execution. MIN_EPOCHS_FOR_BLOCK_ACCESS_LIST_SIDECARS_REQUESTS uses the same window, so a node syncing within the weak subjectivity period can obtain BALs from either layer. Beyond it, BALs can be regenerated by executing the block.

Backwards Compatibility

This proposal changes the ExecutionPayload, ExecutionPayloadBid and PayloadAttestationData containers from EIP-7732 and the engine API methods from EIP-7928. These changes are not backwards compatible and require a hard fork. BALs of pre-fork blocks remain inside their execution payloads and stay retrievable as specified in EIP-7928; the sidecar topic and req/resp methods apply from the fork onwards.

Security Considerations

Withholding

A builder can withhold the BAL sidecar. PTC members then vote block_access_list_present = False, making the failure visible network-wide, and no node can validate the payload since on_execution_payload_envelope gates on local BAL availability. A payload that cannot be validated is not built on, so withholding costs the builder its own block. The payment properties from EIP-7732 are unchanged.

Early BAL Exposure

An early sidecar reveals state changes before the transactions that caused them. No independently signed object is exposed, so nothing can be unbundled. What leaks is coarse in-slot activity, and it leaks to parties that observe the same changes in the payload moments later.

Network Overhead

Bytes per slot are unchanged, as the BAL that travelled inside the envelope now travels on its own topic. The load is redistributed across topics and spread over a wider window instead of concentrating between payload reveal and the attestation deadline.

Verification Cost

Sidecar verification requires one keccak256 over at most MAX_BLOCK_ACCESS_LIST_SIZE bytes, which is negligible compared to payload validation. Consensus clients gain a keccak dependency, satisfied by well-tested off-the-shelf libraries.

Unmatched BALs

The execution layer may hold BALs for payloads that never arrive. Implementations SHOULD bound this cache, for example by slot distance or at finalization. Inflow is bounded by the gossip rules to one sidecar per valid beacon block root.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Toni Wahrstätter (@nerolation), Raúl Kripalani (@raulk), "EIP-8146: Block Access List Sidecars [DRAFT]," Ethereum Improvement Proposals, no. 8146, February 2026. Available: https://eips.ethereum.org/EIPS/eip-8146.