---
eip: 8411
title: Fast Execution Payload Broadcast
description: Faster execution payload propagation via chunked gossip
author: Kamil Salakhiev (@kamilsa), Csaba Kiraly (@cskiraly), Potuz (@potuz), Raúl Kripalani (@raulk), Satyajit Das (@satushh)
discussions-to: https://ethereum-magicians.org/t/eip-8411-fast-execution-payload-broadcast/29613
status: Draft
type: Standards Track
category: Networking
created: 2026-09-05
requires: 7732
---

## Abstract

[EIP-7732](./eip-7732.md) propagates the execution payload as a single message on the `execution_payload` gossipsub topic. This EIP replaces that topic with `execution_payload_chunks`, which carries the payload envelope split into `TOTAL_EXECUTION_PAYLOAD_CHUNKS` chunks. The builder commits to the chunk set with a Merkle root carried in the execution bid, so each chunk is independently verifiable from a single bid signature check plus an inclusion proof.

## Motivation

The gas-limit growth that [EIP-7732](./eip-7732.md) enables implies larger payloads leading to increased latency. If Payload Timeliness Committee (PTC) members do not receive the payload within the attestation deadline, an otherwise valid payload is considered missed.

The existing `execution_payload` gossipsub topic scales poorly as payloads grow. Two effects dominate:

1. Once a peer has received a large message, it is hard to cancel in-flight duplicates, so the same bytes arrive several times and consume bandwidth that could carry new data.
2. A peer must download the full message before it can forward any of it, so each hop adds a full transfer, leading to increased latency proportional to message size multiplied by hop count.

Both effects are mitigated when the message is chunked. Large duplicates are impossible with chunk granularity, and a peer can forward each chunk as soon as it arrives, so hops overlap instead of serializing.

## 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](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

### Parameters

| Constant | Value |
| --- | --- |
| `TOTAL_EXECUTION_PAYLOAD_CHUNKS` | `64` |
| `CHUNK_INCLUSION_PROOF_DEPTH` | `6` |
| `MAX_PAYLOAD_CHUNK_SIZE` | `MAX_PAYLOAD_SIZE // TOTAL_EXECUTION_PAYLOAD_CHUNKS` (163,840 bytes) |

### Custom types

| Name | SSZ equivalent | Description |
| --- | --- | --- |
| `ChunkIndex` | `uint64` | Index of a chunk, `0` through `TOTAL_EXECUTION_PAYLOAD_CHUNKS - 1` |
| `Chunk` | `ByteList[MAX_PAYLOAD_CHUNK_SIZE]` | Contiguous slice of the serialized `ExecutionPayloadEnvelope` |

### Commitment

The execution bid commits to the chunked envelope:

```python
class ExecutionPayloadBid(ProgressiveContainer):
    ...
    payload_chunks_root: Root  # [New in this EIP]
    ...
```

`payload_chunks_root` is the SSZ `hash_tree_root` of the `Vector[Bytes32, TOTAL_EXECUTION_PAYLOAD_CHUNKS]` of SHA-256 hashes of the chunks of the serialized `ExecutionPayloadEnvelope`, as computed by `compute_payload_chunks_root` in the Chunking section. A receiver therefore verifies one bid signature and, per chunk, one Merkle inclusion proof. Once the envelope is reconstructed, it MUST be checked against the payload hash committed in the beacon block.

### Envelope

Chunking is applied to the envelope:

```python
class ExecutionPayloadEnvelope():
    payload: ExecutionPayload
    execution_requests: ExecutionRequests
    parent_beacon_block_root: Root
    builder_index: BuilderIndex
    # [Modified in this EIP] removed `beacon_block_root`
```

`beacon_block_root` is removed because it is not known at the time of envelope construction.

Under [EIP-7732](./eip-7732.md) the builder constructs the envelope after observing that its bid was included, and can therefore embed the beacon block root. Under this EIP the builder must commit to the chunks when it produces the bid — before the block exists — so the root cannot be part of the committed bytes. It is instead carried per chunk in the `PayloadChunk` message, together with the `payload_chunks_root` committed in the bid:

```python
class PayloadChunk():
    payload_chunks_root: Root
    index: ChunkIndex
    chunk: Chunk
    chunk_inclusion_proof: Vector[Bytes32, CHUNK_INCLUSION_PROOF_DEPTH]
    slot: Slot
    beacon_block_root: Root
```

`payload_chunks_root` matches the commitment in the bid and identifies the chunk set. Because bids propagate on their own gossip topic before the beacon block, a receiver that has observed a valid bid committing to this root can verify the chunk as soon as it arrives, without waiting for the block.

`slot` and `beacon_block_root` sit outside the inclusion proof and are unauthenticated hints: `slot` allows windowing a chunk cheaply and MUST equal the slot of a bid committing to `payload_chunks_root`, while `beacon_block_root` lets a receiver locate or request the corresponding beacon block and MUST equal the root of the block containing that bid. Receivers MUST NOT treat either field as authenticated and MUST use them only to route lookups; a chunk is bound to a bid solely through `payload_chunks_root`.

### Chunking

The builder MUST derive the chunks, and therefore `payload_chunks_root`, as follows. No padding is added: the last non-empty chunk can be shorter, and trailing chunks can be empty.

```python
def compute_payload_chunks(
    envelope: ExecutionPayloadEnvelope,
) -> Vector[Chunk, TOTAL_EXECUTION_PAYLOAD_CHUNKS]:
    data = serialize(envelope)
    chunk_size = (len(data) + TOTAL_EXECUTION_PAYLOAD_CHUNKS - 1) // TOTAL_EXECUTION_PAYLOAD_CHUNKS
    chunks = []
    for i in range(TOTAL_EXECUTION_PAYLOAD_CHUNKS):
        start = i * chunk_size
        chunks.append(Chunk(data[start:start + chunk_size]))
    return chunks


def compute_payload_chunks_root(envelope: ExecutionPayloadEnvelope) -> Root:
    leaves = Vector[Bytes32, TOTAL_EXECUTION_PAYLOAD_CHUNKS](
        [hash(chunk) for chunk in compute_payload_chunks(envelope)]
    )
    return hash_tree_root(leaves)
```

Each leaf is `hash(chunk)`, the SHA-256 hash of the chunk's bytes; `chunk_inclusion_proof` is the corresponding Merkle branch:

```python
def verify_payload_chunk_inclusion_proof(payload_chunk: PayloadChunk) -> bool:
    return is_valid_merkle_branch(
        leaf=hash(payload_chunk.chunk),
        branch=payload_chunk.chunk_inclusion_proof,
        depth=CHUNK_INCLUSION_PROOF_DEPTH,
        index=payload_chunk.index,
        root=payload_chunk.payload_chunks_root,
    )
```

Receivers concatenate chunks `0` through `TOTAL_EXECUTION_PAYLOAD_CHUNKS - 1` and deserialize the result; nothing is stripped, since each leaf hashes its chunk's exact bytes.

### Networking

This EIP introduces the `execution_payload_chunks` gossip topic and deprecates `execution_payload`. The topic carries `PayloadChunk` messages.

Once the builder observes a beacon block containing its bid, it is able to construct the `TOTAL_EXECUTION_PAYLOAD_CHUNKS` `PayloadChunk` messages and publishes them to the topic.

#### Seen cache

The `Seen` cache used by the gossip validation functions is extended to retain the chunk commitments of observed bids and to track received chunks:

```python
@dataclass
class Seen:
    ...
    payload_chunks_roots: Set[Tuple[Slot, Root]]  # [New in this EIP]
    payload_chunk_tuples: Set[Tuple[Root, ChunkIndex]]  # [New in this EIP]
```

The tuple `(bid.slot, bid.payload_chunks_root)` is added to `payload_chunks_roots` for every bid whose `SignedExecutionPayloadBid` is accepted on the bid gossip topic, and for the bid contained in every valid beacon block. Entries MUST be retained at least until the end of their slot (within `MAXIMUM_GOSSIP_CLOCK_DISPARITY`) and MAY be pruned afterwards. The set stays small: the bid topic's own validation rules bound how many bids enter it per slot.

`payload_chunk_tuples` records the `(payload_chunks_root, index)` pair of every accepted chunk, analogous to `data_column_sidecar_tuples` for column sidecars, and backs the first-seen check below.

#### Gossip validation

The following validations MUST pass before a node forwards a `PayloadChunk` `chunk` on the topic.

- _[IGNORE]_ `chunk.slot` equals the current slot, within `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance.
- _[REJECT]_ `chunk.index` is less than `TOTAL_EXECUTION_PAYLOAD_CHUNKS`.
- _[IGNORE]_ `(chunk.slot, chunk.payload_chunks_root)` is present in `seen.payload_chunks_roots`. Clients MAY queue chunks referencing an unknown root for a short period and revalidate once the bid arrives.
- _[REJECT]_ The inclusion proof is valid as verified by `verify_payload_chunk_inclusion_proof(chunk)`.
- _[REJECT]_ If the node has seen a valid beacon block whose bid commits to `chunk.payload_chunks_root`: `chunk.beacon_block_root` equals the hash tree root of that block.
- _[IGNORE]_ `(chunk.payload_chunks_root, chunk.index)` is not present in `seen.payload_chunk_tuples` (first-seen per commitment and index); the tuple is added upon acceptance.

A chunk that passes these validations MUST be forwarded immediately, without waiting for the remaining chunks or for the beacon block, and buffered until the envelope can be reconstructed.

### Open questions and future considerations

- The networking scheme could be extended by erasure coding the chunks, so that a receiver can reconstruct the envelope from a subset of chunks. This introduces additional complexity and may lead to higher bandwidth usage. However, it may mitigate "coupon collector" problems, where a receiver missing a small number of chunks may wait a long time to receive them.
- In future it might be considered to introduce a `bal_chunks` topic following the same broadcasting scheme for Block Access List (BAL, [EIP-7928](./eip-7928.md)) messages, which are currently part of the execution payload. This would allow BAL message propagation to start sooner than the rest of the payload.

## Rationale

**Why a Merkle commitment.** Alternatives such as KZG place proof generation on the builder's critical path. A Merkle tree over chunks is cheap enough to build at bid time, and a single root in the bid makes each chunk self-verifying against one already-verified signature.

**Why `TOTAL_EXECUTION_PAYLOAD_CHUNKS = 64`.** The value is not derived from measurement; smaller chunk counts already show benefit compared to single large messages. 64 is chosen for forward compatibility in case we decide to extend protocol erasure coding via `c-kzg`, which segments data into 64 chunks and extends them to 128.

**Why unpadded `ByteList` chunks.** SSZ has no outer length prefix, so padding bytes would deserialize as envelope data; hashing each chunk's exact bytes commits its true length instead. Pinning the split and merkleization to SSZ lets two clients derive identical roots with existing code.

## Backwards Compatibility

The `execution_payload` topic is removed, so this change is not backwards compatible and requires a fork.

## Reference Implementation

TBD.

## Security Considerations

Chunks are not individually signed; their authenticity derives entirely from the bid signature via `payload_chunks_root`. The root carried in a chunk is unauthenticated on its own, but a chunk is only accepted if a bid that already passed signature validation committed to that root: a chunk referencing an unknown root is not forwarded, so unsigned chunk spam is filtered at the first hop.

`slot` and `beacon_block_root` are covered by neither the bid signature nor the inclusion proof, which is why the specification restricts them to routing lookups. The consistency checks in gossip validation bound their misuse: a chunk replayed with a divergent `slot` fails the `(slot, payload_chunks_root)` membership check, and a divergent `beacon_block_root` is rejected once the block containing the bid is known. Because the chunk bytes are proof-bound, a message that differs only in these hint fields cannot alter the reconstructed envelope.

Per-index equivocation is not possible: two distinct chunks that both verify at the same `(payload_chunks_root, index)` would imply a collision in the Merkle tree, so at most one chunk per index can propagate for a given commitment.

The `chunk.index` bound is load-bearing: `is_valid_merkle_branch` reads only the low `CHUNK_INCLUSION_PROOF_DEPTH` bits, so an unbounded index would let the same chunk propagate again at `index + k * TOTAL_EXECUTION_PAYLOAD_CHUNKS`.

Pre-block buffering is bounded: a node stores at most `TOTAL_EXECUTION_PAYLOAD_CHUNKS` chunks per entry in `payload_chunks_roots`, and the size of that set is itself limited by bid gossip validation (builder signature and slot window).

Gossip validity of a chunk does not imply validity of the reconstructed envelope; full envelope validation still happens after reconstruction, as under [EIP-7732](./eip-7732.md).

## Copyright

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