---
eip: 8146
title: Block Access List Sidecars
description: Propagate block access lists as independent sidecars, off the critical path and ahead of the execution payload
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) introduced in [EIP-7928](./eip-7928.md) 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](./eip-7805.md) inclusion list builders advance their state view past the current block.

## Motivation

[EIP-7928](./eip-7928.md) adds the BAL to the `ExecutionPayload`, which under [EIP-7732](./eip-7732.md) 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](../assets/eip-7928/bal_size_analysis_60m.md). 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](./eip-7805.md) 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](./eip-7928.md). 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](./eip-7928.md) |
| `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](./eip-7928.md) is removed:

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

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

`block_access_list_hash` is `keccak256(rlp(BlockAccessList))`, the value the execution block header carries under [EIP-7928](./eip-7928.md).

##### `PayloadAttestationData`

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

##### `BlockAccessListSidecar`

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

#### Fork Choice

##### `Store`

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

```python
# [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:

```python
# [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:

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

```python
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](./eip-7732.md), 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](./eip-7928.md) 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](./eip-7928.md).

## Rationale

### Header Commitment Reuse

[EIP-7928](./eip-7928.md) 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:

```text
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](./eip-7928.md) 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](./eip-7732.md) and the engine API methods from [EIP-7928](./eip-7928.md). 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](./eip-7732.md) 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

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