# 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/)