## EPF Week 11 Update Hi everyone, I started off this week by reviewing a [PR](https://github.com/sigp/lighthouse/pull/7728) that adds the gloas boilerplate to lighthouse's `unstable` branch for us to build on top. After this was merged, the next priority was to rebase our `gloas-containers` branch from `unstable` and then fix all failing tests in the repo, so I made a [PR](https://github.com/sigp/lighthouse/pull/7932) to handle this. ### EPBS Block Proposal for Local Building The next priority we want to tackle is the local block building flow with the main differences now being the introduction of payload bids and envelopes. For local block building, both the payload bids and envelopes can be derived from the `get_payload` call to the EL, and we will now need to cache these values and have them signed by the Validator Client at the appropriate step of the flow. At a high level, the bid is signed and placed in the `BeaconBlockBody`. Then, the `BeaconBlock` is gossiped over the network. Finally, the payload is signed and gossiped as well. Since [Prysm](https://github.com/OffchainLabs/prysm/tree/epbs) already has a block production implementation, let's check it out. The pipeline is all controlled by the validator client's `ProposeBlock` flow: ``` [VC] ProposeBlock ├─ [VC] SubmitHeader │ ├─ [VC -> BN] GetLocalHeader │ │ ├─ [EL] ForkchoiceUpdated │ │ ├─ [EL] GetPayload → ExecutionPayload │ │ └─ [VC] format → ExecutionPayloadHeaderEPBS │ ├─ [VC] signExecutionPayloadHeader(builder domain) │ │ └─ → SignedExecutionPayloadHeader │ └─ [VC→BN] SubmitSignedExecutionPayloadHeader │ ├─ [BN] cache(SignedExecutionPayloadHeader) │ └─ [BN] gossip(SignedExecutionPayloadHeader) ⟂ only if proposer VC lacks it ├─ [VC→BN] GetBeaconBlock │ └─ [BN] BuildBlockParallel │ ├─ assemble consensus fields │ ├─ attach cached SignedExecutionPayloadHeader if present │ └─ return BeaconBlock ├─ [VC] sign BeaconBlock (block domain) │ └─ → SignedBeaconBlock ├─ [VC→BN] ProposeBeaconBlock(SignedBeaconBlock) │ └─ [BN] gossip SignedBeaconBlock └─ [VC] SubmitExecutionPayloadEnvelope ├─ [VC→BN] GetExecutionPayloadEnvelope → Envelope ├─ [VC] signExecutionPayloadEnvelope │ └─ → SignedEnvelope └─ [VC→BN] SubmitSignedExecutionPayloadEnvelope └─ [BN] gossip SignedEnvelope ``` (I had ChatGPT format the diagram above from my notes for readability. Not bad!) Now, in order to see if we can draw some inspiration from the above for lighthouse, we need to first understand how lighthouse currently handles block production. ### Block Production In lighthouse, we start off in the `block_service.rs` where the `do_update` will try to produce a block for any block producers in the `ValidatorStore` and call `publish_block` to both produce and gossip a block. Here are the steps: ``` [VC] do_update() ├─ [VC] publish_block │ ├─ [VC -> BN] get_validator_block // produce block │ ├─ [VC -> BN] sign_and_publish_block // sign and publish block ``` Now, let's look at the two flows within `do_update`: ### `get_validator_block` General Flow: ``` Validator API request → produce_block_v3() → BeaconChain::produce_block_with_verification() ↓ Local execution layer → Build block with local payload ↓ Return BeaconBlockResponseWrapper → Validator client ``` Function Call Paths: ``` HTTP API produce_block.rs:64 ↓ BeaconChain::produce_block_with_verification() ↓ BeaconChain::produce_block_on_state() ↓ BeaconChain::produce_partial_beacon_block() [blocking task] ↓ BeaconChain::complete_partial_beacon_block() [blocking task] ↓ per_block_processing() [full consensus validation] ``` So currently, for the block production steps above, you can see how the validator client requests a block from the beacon node, whom gets the block's payload from the EL and returns the `SignedBeaconBlock` back to the validator. As you might recall from the Prysm flow, the first step is the VC calling a `GetLocalHeader` to get back a bid to sign, and the header itself is derived from the local EL's `ExecutionPayload`. In lighthouse, we'll need to grab a bid as well, so the task is to think of some clean ways to handle this. If we want to have `publish_block` in the VC's `block_service.rs` still drive block production, which I assume we do, we'll need to add a first step to derive an unsigned bid from the beacon node. Then, the validator client should sign the bid using the proposer's pubkey and the `0x03` builder domain. We'll then need to route the bid to the beacon node to include in the `BeaconBlockBody`. At a high level, this would make sense to happen as part of the `produce_block` flow, but ofcourse, we wouldn't need to grab the `ExecutionPayload` from the EL during `produce_block` anymore. The `BeaconBlock` will then be returned to the VC who will sign it like today as part of next step of `publish_block`, which is the `sign_and_publish_block` flow. ### `sign_and_publish_block` General Flow ``` Sign Block → Validator → POST /eth/v1/beacon/blocks → publish_blocks.rs → ProvenancedBlock::Local → GossipVerifiedBlock ↓ block_verification.rs → verification pipeline ``` Function Call Paths: ``` HTTP API publish_blocks.rs ↓ unverified_block.into_gossip_verified_block(&chain) ↓ GossipVerifiedBlock::new() [from block_verification.rs] ↓ chain.process_block(gossip_verified_block, ...) ↓ gossip_verified_block.into_execution_pending_block() ↓ SignatureVerifiedBlock::from_gossip_verified_block_check_slashable() ↓ ExecutionPendingBlock::from_signature_verified_components() ↓ per_block_processing() ← SECOND TIME THROUGH CONSENSUS VALIDATION ``` I feel like this flow will remain largely the same, but the `block_verification.rs` will at least need some tweaks to support validating the `SignedExecutionPayloadBid` included in blocks received over the network from peers for validation. ### ExecutionPayloadEnvelope Finally, we'll need to add another step to the VC's `publish_block` flow, which is responsible for retrieving the `ExecutionPayloadEnvelope` from the `BN`, which would have been cached during our earlier `Get_Header` to the BN to get the `ExecutionPayload` from our local EL. Then, the VC will sign the envelope and return it to the `BN` for gossip. ### Design Decisions: a. In the `BN`, how will we structure the response to `GetLocalHeader` logic to produce a bid from the EL's `get_payload` response? Where will this logic live? We should keep in mind that the `BN` will need to be able to have a pool of `ExecutionPayloadBid` that are received from the network. It will also need to be able to validate and gossip `SignedExecutionPayloadBid` to the network for bids received by the builder api b. Similarly, how will the `BN` handle the `SubmitExecutionPayloadEnvelope` call? For local building, it will need validation logic and the ability to gossip the signed envelope to peers for block validation. We'll need the same validation and gossip logic for peers receiving the blocks. One proposal to handle points `a` and `b` is to have a pipeline similar to `block_verification.rs` but for bids and envelopes. Mark's idea of having an `envelope_verification.rs` in the `BN` handles point `b`, and we could also have a `bid_verification.rs` to handle point `a`. ## TODO Next week: - build out the local block building pipeline