---
# System prepended metadata

title: 'EPBS: EIP-7732 – Implementation Proposal'

---

# EPBS: EIP-7732 – Implementation Proposal

Enshrined Proposer Builder Separation (epbs) decouples execution payloads from consensus blocks.  The consensus block will undergo a lightweight validation before the attestation deadline whereas the execution payload will be processed independently.

## Motivation
 - Execution is removed from the consensus hot path
 - Execution could have up to 9 seconds for block validation, enabling a significantly increased gas limit
 - Notable increase in blob scaling
 - In-protocol block auctions allow proposers to gain exposure to MEV rewards without relying on external relays

## Project Description 
We will implement [EIP-7732](https://eips.ethereum.org/EIPS/eip-7732) in Lighthouse as it only contains CL modifications.

Scope:
- **CL Block/body changes**: Include signed execution bids and payload attestations in addition to remove the execution payload
- **Validator duties**: Implement PTC message production/aggregation and include aggregates in the next block
- **Networking**: Add gossip support for new libp2p topics
- **Fork-choice**: Traversal and tip selection consistent with the [consensus spec](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/fork-choice/)


## Specification
1. `BeaconBlockBody` is gossiped as part of a `BeaconBlock` across the network.  It used to have an `execution_payload`, which contained the full transaction list.  Instead, it will now have a lightweight payload header signed by the builder, plus payload timeliness attestations from the previous slot.
```rust
 struct BeaconBlockBody {
    // remove execution_payload
     
    // new
    signed_execution_bid: SignedExecutionBid,   // builder’s bid + EL block hash
    payload_attestations: Vec<PayloadAttestation>,  // PTC aggregations from previous slot
     
   // existing fields below
}
```

2. The `BeaconState` will be extended to track fields signaling whether a payload was received by a builder and also track the status of builder to proposer payments:
```rust
pub struct BeaconState {
    // indicates if payload was locally received per slot
    pub execution_payload_availability: BitVector<SLOTS_PER_HISTORICAL_ROOT>,

    // gather the pending builder to proposer payments for each slot in the current epoch
    pub builder_pending_payments: Vector<BuilderPendingPayment, { 2 * SLOTS_PER_EPOCH }>,
    
    // track the pending builder to proposer payments from previous epochs that are ready to be processed by the EL
    pub builder_pending_withdrawals:
        List<BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT>,
    
    // hash of the last fully executed payload
    pub latest_block_hash: Hash256,
    
    // slot at which the last payload was executed
    pub latest_full_slot: Slot,
    
    // withdrawals root processed in CL for the latest slot
    pub latest_withdrawals_root: Hash256,

   // existing fields below
}
```

3. We will add a `builder` duty in-protocol that is responsible for constructing an execution bid committing to releasing an `ExecutionPayload` later in the block if the proposer includes the bid in the `BeaconBlockBody`:
```rust
pub fn builder_duty(slot: Slot) {
    let bid = build_bid(slot);                    // commit to header + value
    gossip("execution_payload_header", bid);
    if bid_included_in_beacon_block(slot) {
        let payload = build_payload_envelope(bid);    // matches committed header
        gossip("execution_payload", payload);         // before PTC deadline
    }
}
```

3. A Payload Timeliness Committee (PTC) will consist of 512 validators per slot and attest to whether a builder revealed their payload timely within the slot.  The `PayloadAttestationMessage` will be gossiped from committee members and aggregated by the proposer as to include in the `BeaconBlockBody`:
```rust
pub struct PayloadAttestationData {
    // the HTR of the beacon block seen for the assigned slot
    pub beacon_block_root: Hash256,
    // the committee member's assigned slot
    pub slot: Slot,
    // indicates the execution payload was received by this validator
    pub payload_present: bool,
    // indicates the blob data was received by this validator
    pub blob_data_available: bool,
}
```

4. The block proposal flow will be modified such that the proposer will receive bids from builders, select the highest bid, and then broadcast a  block including the bid and aggregated `PayloadAttestation`:
```rust
fn propose_block() {
    // select the best bid for the slot
    let bid = select_best_bid(slot);
    let body = BeaconBlockBody {
        // existing fields
        signed_execution_bid: {block_hash, value, ...},
        payload_attestations: collect_previous_slots_ptc(),
    };
    
    sign_and_gossip_block(slot, body);
}
```

5.  Then, when receiving a new block gossiped by the proposer, the validator will process withdrawals and the `ExecutionPayloadHeader` without having to wait for the EL to process transactions anymore before attesting to block validity:
```rust
fn process_block(state: BeaconState, block: BeaconBlock) {
    process_withdrawals(state);
    process_execution_payload_header(state, block); // see below
}
```
```rust
fn process_execution_payload_header(state: BeaconState, block: BeaconBlock) {
    // Verify the header signature
    let signed_header = block.body.signed_execution_payload_header;
    assert!(
        verify_execution_payload_header_signature(state, signed_header)
    );

    // Check that the builder is active, non-slashed, and has funds to cover the bid
    assert!(
        state.balances[builder_index]
            >= amount + pending_payments + pending_withdrawals + MIN_ACTIVATION_BALANCE
    );

    // Verify that the bid is for the current slot
    assert!(header.slot == block.slot);

    // Verify that the bid is for the right parent block
    assert!(header.parent_block_hash == state.latest_block_hash);
    assert!(header.parent_block_root == block.parent_root);

    // Record the pending payment
    state.builder_pending_payments[SLOTS_PER_EPOCH + header.slot % SLOTS_PER_EPOCH] =
        pending_payment;

    // Cache the signed execution payload header
    state.latest_execution_payload_header = header;
}
```

6. Before the PTC deadline during a slot, the builder will reveal and gossip the`ExecutionPayload` inside a `SignedExecutionPayloadEnvelope` including transactions, which will be passed from the the CL to the EL like today for validation.  The validator will now execute `process_execution_payload` upon receiving the `SignedExecutionPayloadEnvelope`:
```rust
fn process_execution_payload(
    state: BeaconState,
    signed_envelope: SignedExecutionPayloadEnvelope,
    execution_engine: ExecutionEngine,
) {
    // Verify the builder's signature
    // check that the payload corresponds to the BeaconBlock for this slot

    // Pass payload to the EL for validation just like today
    assert!(execution_engine.verify_and_notify_new_payload(
        NewPayloadRequest {
            execution_payload: signed_envelope.message.payload,
            versioned_hashes: versioned_hashes,
            parent_beacon_block_root: state.latest_block_header.parent_root,
            execution_requests: requests,
        }
    ));
}
```

7. Fork-choice will incorperate payload timeliness into `head` selection by tracking whether a block's payload is `PENDING`, `EMPTY`, or `FULL`:
```rust
struct ForkChoiceNode {
    root: Root,
    payload_status: PayloadStatus, // PENDING | EMPTY | FULL
}
```
At each step, fork choice will make a payload decision and move to the selected `FULL` or `EMPTY` child. For the slot before the tip, both `FULL` and `EMPTY` have weight 0, so a payload tiebreaker uses PTC signaling. The `FULL` child is only selectable if the payload body is locally available.

8. New libp2p gossip topics:
    - `execution_payload_header` will be submitted by the builder and contain the bid
    - `execution_payload` will be sent by the builder to reveal the payload before the PTC deadline
    - `payload_attestation_message` will be gossiped by the PTC committee members


With these updates, Lighthouse will properly decouple the `ExecutionPayload` from the `BeaconBlock`, which is a clean separation and enables massive L1 scaling.  

## Roadmap (Weeks 6–21)

### Phase 1: Core Data Structures (Weeks 6–9)
**Deliverables:**
- SSZ serialization for all new containers, i.e. `SignedExecutionPayloadEnvelope`
- Extend `BeaconBlockBody` and `BeaconState` with EPBS fields
- Register libp2p topics with stub handlers

**Success Criteria:** All new containers serialize/deserialize correctly, and topic stubs are invoked in networking tests.

### Phase 2: Builder Flow (Weeks 10–13)
**Deliverables:**
- Add builder duty per [consensus specs](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/builder/)
- Proposer bid selection and inclusion of `SignedExecutionBid` in `BeaconBlockBody`
- Record builder to proposer pending payment in `BeaconState` and roll into `builder_pending_withdrawals` at epoch boundary
- Process `SignedExecutionPayloadEnvelope` or withheld message with slot timing enforcement

**Success Criteria:** Valid bids are accepted, invalid ones rejected, and payloads are marked FULL or EMPTY as expected.

### Phase 3: PTC and Fork-Choice (Weeks 14–19)
**Deliverables:**
- Implement PTC committee, gossip handling, and proposer aggregation into the next block
- Extend fork choice nodes with `payload_status` (`PENDING`, `FULL`, `EMPTY`)
- Implement fork-choice rules per [consensus specs](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/fork-choice/)

**Success Criteria:** Fork choice selects the correct head across FULL and EMPTY cases.

### Phase 4: Testing and Benchmarking (Weeks 20–21)
**Deliverables:**
- Kurtosis devnet scenarios to emulate different builder behaviors, i.e. valid or withheld payload
- Fix any race conditions or consensus mismatches found in testing

**Success Criteria:** Devnet finalizes blocks correctly and benchmarks pass within target thresholds.



## Possible challenges
- Maintaining backward compatibility pre-fork boundary
    - Fork-choice head selection with (root, payload_status) nodes adds complexity
    - Pipelined slot structure requires PTC validation and handling of new libp2p topics
    - CL managing builder payments is non-trivial
- Consensus spec changes (FOCIL compatibility, dual deadline PTC, payment flow changes)


## Goal of the project
The overarching goal is to implement EIP-7732 in Lighthouse so that execution payloads are decoupled from consensus blocks, enabling payload validation after the attestation deadline.  The builder’s reveal and withhold paths must work end-to-end so that nodes converge on the same head, finalize without unnecessary reorgs, and maintain stability under all payload availability scenarios. We want a correct and maintainable implementation that integrates cleanly with existing Lighthouse components and is ready for testing on devnets ahead of the Glamsterdam hardfork.

## Collaborators

### Fellow 
[Shane](https://github.com/shane-moore)

### Mentor & Contributor
Lighthouse - [Mark](https://github.com/ethdreamer)

## Resources
[Lighthouse Github](https://github.com/sigp/lighthouse)
[EIP-7732](https://eips.ethereum.org/EIPS/eip-7732)
[Consensus Specs](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/)
