---
eip: 8146
title: Block Access List Sidecars
description: Decouple block access list propagation from execution payload envelopes
author: Toni Wahrstätter (@nerolation), Raúl Kripalani (@raulk)
discussions-to: https://ethereum-magicians.org/t/eip-8146-block-access-list-sidecars/27757
status: Draft
type: Standards Track
category: Core
created: 2026-02-03
requires: 7732, 7928
---

## Abstract

This EIP removes the block access list (BAL) from the `ExecutionPayloadEnvelope` and propagates it as an independent sidecar on a dedicated gossip topic. Builders commit to the BAL exactly once, by including `keccak256(rlp(BAL))` (the same 32-byte value already defined as `block_access_list_hash` in the EL block header by [EIP-7928](./eip-7928.md)) in their `ExecutionPayloadBid`. Sidecar verification uses this commitment; the consensus layer treats the BAL as opaque bytes and never needs to RLP-decode it. No separate sidecar signature is required. The Payload Timeliness Committee (PTC) enforces BAL availability at the attestation deadline.

## Motivation

[EIP-7928](./eip-7928.md) adds block access lists to the `ExecutionPayload`. Under [EIP-7732](./eip-7732.md), the execution payload travels inside a `SignedExecutionPayloadEnvelope` that the builder broadcasts after the beacon block. Including the BAL (~70 KiB average, up to 1 MiB) in the envelope increases its size and propagation latency on the critical path.

Separating the BAL into a sidecar reduces envelope size, improving propagation. The BAL remains required for execution validation; the PTC enforces availability so that the BAL is present before the payload processing deadline.

## Specification

### Consensus Layer

#### Presets

| Name | Value |
| - | - |
| `MAX_BLOCK_ACCESS_LIST_SIZE` | `uint64(2**23)` (= 8 MiB) |


#### Types

The `BlockAccessList` type from [EIP-7928](./eip-7928.md):

| Name | SSZ equivalent | Description |
| - | - | - |
| `BlockAccessList` | `ByteList[MAX_BLOCK_ACCESS_LIST_SIZE]` | RLP-encoded block access list |

#### Modified Containers

##### `ExecutionPayload`

The `block_access_list` field introduced by [EIP-7928](./eip-7928.md) is removed:

```python
class ExecutionPayload(Container):
    parent_hash: Hash32
    fee_recipient: ExecutionAddress
    state_root: Bytes32
    receipts_root: Bytes32
    logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM]
    prev_randao: Bytes32
    block_number: uint64
    gas_limit: uint64
    gas_used: uint64
    timestamp: uint64
    extra_data: ByteList[MAX_EXTRA_DATA_BYTES]
    base_fee_per_gas: uint256
    block_hash: Hash32
    transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD]
    withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD]
    blob_gas_used: uint64
    excess_blob_gas: uint64
    # [Removed in EIP-8146]
    # block_access_list: BlockAccessList
```

##### `ExecutionPayloadBid`

A `block_access_list_hash` field is added. Its value is identical to the EL header field of the same name defined by [EIP-7928](./eip-7928.md), i.e. `keccak256(rlp(BlockAccessList))`. Using the EL's canonical commitment avoids introducing a second hashing scheme on the CL side.

```python
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
```

##### `PayloadAttestationData`

A `block_access_list_present` field is added:

```python
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
```

#### New Containers

##### `BlockAccessListSidecar`

```python
class BlockAccessListSidecar(Container):
    beacon_block_root: Root
    slot: Slot
    block_access_list: BlockAccessList
```

### P2P Networking

#### Gossip

##### Global Topic: `block_access_list_sidecar`

This topic propagates `BlockAccessListSidecar` objects.

The following validations MUST pass before forwarding a `sidecar` on the network:

- _[IGNORE]_ `sidecar.beacon_block_root` has been seen (via gossip or non-gossip sources). A client MAY queue the sidecar for processing once the block is retrieved.
- _[IGNORE]_ No valid `BlockAccessListSidecar` for this `beacon_block_root` has been seen.
- _[IGNORE]_ `sidecar.slot >= compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)`.

Let `block` be the beacon block with root `sidecar.beacon_block_root`. Let `bid` alias `block.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`. The CL treats `sidecar.block_access_list` as opaque bytes; no RLP decoding is performed.

#### Req/Resp

##### `BlockAccessListSidecarsByRange` v1

**Protocol ID:** `/eth2/beacon_chain/req/block_access_list_sidecars_by_range/1/`

Request Content:

```python
(
    start_slot: Slot
    count: uint64
)
```

Response Content:

```python
List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]
```

Returns sidecars in slot range `[start_slot, start_slot + count)`, ordered by slot. The response MUST contain no more than `MAX_REQUEST_PAYLOADS` sidecars. Clients SHOULD respond with at least one sidecar if available.

##### `BlockAccessListSidecarsByRoot` v1

**Protocol ID:** `/eth2/beacon_chain/req/block_access_list_sidecars_by_root/1/`

Request Content:

```python
List[Root, MAX_REQUEST_PAYLOADS]
```

Response Content:

```python
List[BlockAccessListSidecar, MAX_REQUEST_PAYLOADS]
```

Returns sidecars matching the requested beacon block roots.

### Fork Choice

#### Modified `Store`

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

#### Modified `on_block`

When a new block is added to the store, initialize the BAL PTC vote tracker (alongside the existing `ptc_vote` initialization):

```python
# [New in EIP-8146] Add a new PTC BAL voting for this block to the store
store.block_access_list_availability_vote[block_root] = [None] * PTC_SIZE
```

#### Modified `on_payload_attestation_message`

The handler records the BAL availability vote alongside the existing payload presence vote:

```python
def on_payload_attestation_message(
    store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False
) -> None:
    # ... existing validation and payload_present recording ...

    # [New in EIP-8146] Update the BAL vote for the block
    store.block_access_list_availability_vote[data.beacon_block_root][ptc_index] = (
        data.block_access_list_present
    )
```

#### Modified `notify_ptc_messages`

When extracting `PayloadAttestationMessage` objects from `PayloadAttestation` aggregates in a beacon block, the `block_access_list_present` field is propagated from the attestation data.

#### New `on_block_access_list_sidecar`

```python
def on_block_access_list_sidecar(store: Store, sidecar: BlockAccessListSidecar) -> None:
    # The beacon block must be known
    assert sidecar.beacon_block_root in store.blocks
    block = store.blocks[sidecar.beacon_block_root]

    # Verify slot consistency
    assert sidecar.slot == block.slot

    # Verify BAL matches commitment in bid (CL operates on opaque bytes; no RLP)
    bid = block.body.signed_execution_payload_bid.message
    assert keccak256(sidecar.block_access_list) == bid.block_access_list_hash

    # Store BAL
    store.block_access_lists[sidecar.beacon_block_root] = sidecar.block_access_list

    # Notify the EL for early prefetching or post-state root calculation
    EXECUTION_ENGINE.notify_block_access_list(sidecar.block_access_list, bid.block_hash)
```

#### Modified `on_execution_payload_envelope`

BAL availability is required before processing the execution payload. The BAL has already been delivered to the EL via `engine_notifyBlockAccessListV1` when the sidecar was received; `verify_execution_payload_envelope` itself is unchanged.

```python
def on_execution_payload_envelope(
    store: Store, signed_envelope: SignedExecutionPayloadEnvelope
) -> None:
    envelope = signed_envelope.message
    # The corresponding beacon block root needs to be known
    assert envelope.beacon_block_root in store.block_states

    # Check if blob data is available
    assert is_data_available(envelope.beacon_block_root)

    # [New in EIP-8146] Check if the BAL has been received locally
    assert envelope.beacon_block_root in store.block_access_lists

    state = store.block_states[envelope.beacon_block_root]

    # Verify the execution payload envelope (unchanged)
    verify_execution_payload_envelope(state, signed_envelope, EXECUTION_ENGINE)

    # Add execution payload envelope to the store
    store.payloads[envelope.beacon_block_root] = envelope
```

### Engine API

EIP-8146 splits BAL delivery and payload delivery into two engine calls so the EL can start work on the BAL while the payload envelope is still in flight. The CL forwards the BAL the instant the sidecar passes gossip validation, without waiting for the envelope. This is the operational core of the EIP.

#### `engine_getPayloadV6`

Returns the BAL as a separate `blockAccessList` field (RLP-encoded bytes) alongside the `ExecutionPayload`. The builder computes `block_access_list_hash = keccak256(blockAccessList)` for inclusion in the bid; this is the same value the EL writes into the EL header per [EIP-7928](./eip-7928.md), so no additional hashing is required.

#### New `engine_notifyBlockAccessListV1`

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

Parameters:

- `blockAccessList`: RLP-encoded BAL bytes.
- `blockHash`: ties the BAL to a specific payload.

On receipt the EL:

1. Stores the BAL keyed by `blockHash`.
2. Begins **prefetching** the accounts and storage slots the BAL declares will be touched, warming the state cache.
3. MAY begin **parallel post-state root computation** using the BAL's post-values.

The CL MUST call this method from `on_block_access_list_sidecar` immediately after the keccak check against `bid.block_access_list_hash` passes. The CL MUST NOT wait for the payload envelope.

#### `engine_newPayloadV5`

Unchanged from [EIP-7732](./eip-7732.md). It does not carry the BAL. The EL pairs the incoming payload with the previously delivered BAL by `blockHash` and runs execution against the warmed state. If the BAL has not yet arrived (out-of-order receipt), the EL MAY queue the payload until `engine_notifyBlockAccessListV1` is called for the same `blockHash`.

#### Best-case sequencing

```
t0:  CL --engine_notifyBlockAccessListV1--> EL    (BAL arrives; EL prefetches state, MAY precompute post-state root)
t1:  CL --engine_newPayloadV5-------------> EL    (envelope arrives; EL executes against warm cache)
t2:  CL <----------- {status: VALID} ------ EL
```

The `t1 - t0` gap is the head start the EIP creates. With the BAL inside the envelope (pre-EIP-8146), `t0 = t1` and the head start is zero.

### Execution Layer

The execution block header field `block_access_list_hash` (keccak256 of RLP-encoded BAL) is unchanged from [EIP-7928](./eip-7928.md). BAL construction and validation rules are as specified in [EIP-7928](./eip-7928.md).

### Validator Guide

#### Builders

When constructing a bid, the builder:

1. Obtains the BAL from `engine_getPayloadV6`.
2. Computes `block_access_list_hash = keccak256(blockAccessList)` and includes it in the `ExecutionPayloadBid`.
3. After the beacon block is published, broadcasts the `BlockAccessListSidecar` on the `block_access_list_sidecar` gossip topic.
4. Broadcasts the `SignedExecutionPayloadEnvelope` (which no longer contains the BAL).

Builders SHOULD broadcast the BAL sidecar as early as possible, well before the envelope, to give the network's ELs a prefetch window. See [BAL publication timing](#bal-publication-timing) for the safety argument and worst-case discussion.

#### PTC Members

PTC members set `block_access_list_present = True` in their `PayloadAttestationData` only if a valid `BlockAccessListSidecar` for the block was received locally at least 1 second before the payload attestation deadline. The sidecar must pass gossip validation including the `keccak256` commitment check.

PTC members set `payload_present` and `blob_data_available` per [EIP-7732](./eip-7732.md) rules, independently of `block_access_list_present`.

## Rationale

### Single Commitment, Shared Across Layers

[EIP-7928](./eip-7928.md) already commits the BAL into the EL header as `block_access_list_hash = keccak256(rlp(BAL))`. Carrying the same 32 bytes in the bid gives the CL everything it needs to authenticate a sidecar without introducing a second hashing scheme. The CL holds the BAL only as opaque bytes; RLP decoding remains an EL concern. A builder cannot equivocate: `bid.block_hash` transitively commits to the EL header, so if `bid.block_access_list_hash` disagrees with the revealed payload, either `payload.block_hash != bid.block_hash` (envelope rejected) or the EL recomputes a BAL that does not match its own header (payload rejected per EIP-7928).

### No Separate Signature

The BAL sidecar requires no BLS signature. The builder commits to `block_access_list_hash` in the signed `ExecutionPayloadBid`; sidecar verification is `keccak256(sidecar.block_access_list) == bid.block_access_list_hash`. This mirrors `DataColumnSidecar` verification via KZG commitments in the bid.

### Separate PTC Field

BAL availability is tracked as a dedicated `block_access_list_present` boolean in `PayloadAttestationData`, following the same pattern as `blob_data_available`. This keeps concerns separated: `payload_present` signals envelope timeliness, `blob_data_available` signals blob availability, and `block_access_list_present` signals BAL availability. The fork choice `on_execution_payload_envelope` handler independently gates on local BAL availability (`assert root in store.block_access_lists`), ensuring execution validation cannot proceed without the BAL regardless of PTC votes.

### PTC Deadline Independence

[EIP-7732](./eip-7732.md) applies a variable PTC deadline to the execution payload based on payload size. Since the BAL travels as an independent sidecar, and its size scales inversely with the payload size, this variable deadline does not apply to it.

### BAL publication timing

In ePBS, builders generally delay publishing the execution payload envelope until close to the attestation deadline. Releasing the payload too early exposes its transactions to same-slot unbundling: a malicious proposer could see the payload contents and replace them with a competing block that extracts the same MEV.

The BAL is not exposed to this risk. It carries post-state values and access lists but contains no independently-signed items: the transactions are not in the BAL, the sidecar itself has no signature, and the keccak commitment inside the signed bid binds the BAL to the builder so it cannot equivocate between BALs. A proposer who sees the BAL early learns coarse activity (which pools were touched, which balances moved) but cannot lift transactions into a competing block.

To guarantee a minimum prefetch window for the worst case (BAL arriving with or after the envelope), PTC members vote `block_access_list_present = True` only if the sidecar was received at least 1 second before the payload attestation deadline. This 1-second gap is the protocol-enforced lower bound on the prefetch window the EL gets before execution begins. Builders SHOULD publish well before this deadline; they have no economic reason to delay.

### Dedicated Engine API Method

The BAL is delivered to the EL exclusively via `engine_notifyBlockAccessListV1`, separate from `engine_newPayloadV5`. This enables early delivery: the BAL sidecar typically arrives before the execution payload envelope, so the EL can begin prefetching storage slots immediately, or start calculating the post-state root. When the payload arrives later, `engine_newPayloadV5` proceeds without the BAL -- the EL already has it, matched by `blockHash`.

### Data Retention

Clients MUST retain BAL sidecars for at least `MIN_EPOCHS_FOR_BAL_SIDECARS_REQUESTS` epochs to support syncing nodes. After this period, clients MAY prune sidecars.

## Backwards Compatibility

This EIP requires a hard fork. It modifies the `ExecutionPayload` and `ExecutionPayloadBid` containers from [EIP-7732](./eip-7732.md) and changes how [EIP-7928](./eip-7928.md) BALs are propagated.

## Security Considerations

**Withholding**: A builder can withhold the BAL sidecar. PTC members will vote `block_access_list_present = False`, providing network-wide visibility. The fork choice `on_execution_payload_envelope` handler gates on local BAL availability, so the payload cannot be validated without the BAL. The builder withhold safety properties from [EIP-7732](./eip-7732.md) apply: if the beacon block was not timely, the builder is not charged.

**Network overhead**: The BAL sidecar adds ~70 KiB average per slot to gossip traffic, equal to the current overhead when BAL is inside the envelope. Total network load is unchanged; it is redistributed across topics.

**Verification cost**: Sidecar verification requires one `keccak256` computation on up to `MAX_BLOCK_ACCESS_LIST_SIZE` bytes. This is negligible compared to execution validation. CL implementations gain a `keccak256` dependency, satisfied by well-tested off-the-shelf libraries.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
