## EPF Week 7 Updates This week, I finished up resolving comments in my lighthouse [PR](https://github.com/sigp/lighthouse/pull/7807), which focused on adding the core new containers introduced by the [epbs spec](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/). Then, I started modifying the `BeaconBlockBody` and `BeaconState` per the [spec](https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/) as well. Since these are some of the most crucial structs on the CL, it's worth taking a moment to discuss the reasoning for these changes. ## BeaconBlockBody Modifications ```rust struct BeaconBlockBody { // remove `execution_payload // new fields below pub signed_execution_bid: SignedExecutionBid, pub payload_attestations: VariableList<PayloadAttestation<E>, E::MaxPayloadAttestations>, } ``` We have removed the `execution_payload` from the `BeaconBlockBody` since a key component of epbs is decoupling the EL payload from the consensus block. This will result in significantly more lightweight propagation and validation of CL blocks. Now, we will have a `BeaconBlockBody::signed_execution_bid` field, which contains an EL `block_hash` of the EL payload the builder is committing to releasing later in the slot. Additionally, we introduce a list of `BeaconBlockBody::payload_attestations`, which contain the votes of the PTC member's for whether the previous slot's EL payload was released before the PTC deadline. It's worth spending some time checking how fork choice will be impacted by decoupling the `execution_payload` from the `BeaconBlock` and how the PTC votes are incorperated. On every call to `get_head` during an LMD-Ghost run, we'll try to determine the head by traversing down the trie of `ForkChoiceNode` from a justified checkpoint, and run the following logic on each node in the trie: ``` # Sort by latest attesting balance with ties broken lexicographically head = max( children, key=lambda child: ( get_weight(store, child), child.root, get_payload_status_tiebreaker(store, child), ), ``` When trying to select which node at a fork is the heaviest, `get_weight` will provide an `attestation_score`, which can be situationally influenced by an `is_supporting_vote`. `is_supporting_vote` will check if the the unslashed and active validators had included an `attestation.data.index = 1`, indicating the payload was present. Then, if there was a tie between `get_weight` scores, the `get_payload_status_tiebreaker` would take into account the PTC committee votes and return a `ptc_score` that can be used to break the tie. Interestingly, this seems to be the only use case of the PTC committee regarding fork choice. ## BeaconState ```rust struct BeaconState { // latest_execution_payload_header now maps to an `ExecutionBid instead of an `ExecutionPayloadHeader latest_execution_payload_header: ExecutionBid // new fields below pub execution_payload_availability: BitVector<E::SlotsPerHistoricalRoot>, pub builder_pending_payments: Vector<BuilderPendingPayment, E::BuilderPendingPaymentsLimit>, pub builder_pending_withdrawals: List<BuilderPendingWithdrawal, E::BuilderPendingWithdrawalsLimit>, pub latest_block_hash: ExecutionBlockHash, pub latest_full_slot: Slot, pub latest_withdrawals_root: Hash256 } ``` ### BeaconState::latest_execution_payload_header The first change is that the `latest_execution_payload_header` now maps to an `ExecutionBid`, which is a header received from a `BeaconBlock` and can be used later to check against the fields receiving in the actual EL payload once released and gossiped by the builder. `latest_execution_payload_header` is also useful`is_parent_block_full`, which comes into play while processing withdrawals to ensure the parent block wasn't empty: `def is_parent_block_full(state: BeaconState) -> bool: return state.latest_execution_payload_header.block_hash == state.latest_block_hash ` ### BeaconState::execution_payload_availability The `execution_payload_availability` bit ring vector will set a bit for a slot to true once the execution payload is received from the builder. In the scenario where `is_attestation_same_slot` is false while processing attestations from a block being validated, the corresponding bit from`execution_payload_availability` is used to match against the `AttestationData`'s `data.index`. If they match, then this contributes towards the validator's timely head participation. This ensures that the attestation aligns with the availability of the execution payload. ### BeaconState::builder_pending_payments The `builder_pending_payments` is needed to track the pending builder to proposer payments for each slot in an epoch. While a new block's attestations are being processed, the weight of that slot's pending payment is set based off the validator's balance. Then, at the beginning of the next epoch, `process_builder_pending_payments` will run and convert the `builder_pending_payments` from the previous epoch to `builder_pending_withdrawals`. These withdrawals are applicable to be processed by the EL once their `withdrawable_epoch` is reached. ### BeaconState::latest_block_hash The `latest_block_hash` is first used when receiving a new EL payload from the builder over the network to check: ``` assert(payload.parent_hash == state.latest_block_hash) ``` Then, the `latest_block_hash` is updated to be the `block_hash` of the payload. In the next slot during `process_execution_payload_header` it's then used to: ``` # Verify that the bid is for the right parent block assert(header.parent_block_hash == state.latest_block_hash) ``` ### BeaconState::latest_full_slot `latest_full_slot` is also set after receiving a new EL payload from the builder but isn't used in the spec anywhere. Perhaps it's been deprecated. Time will tell. ### BeaconState::latest_withdrawals_root `latest_withdrawals_root` is set while validating a block and running `process_withdrawals`: ``` state.latest_withdrawals_root = hash_tree_root(withdrawals_list) ``` It's also used when receiving a new EL payload from the builder to check: ``` # Verify the withdrawals root assert(hash_tree_root(payload.withdrawals) == state.latest_withdrawals_root)` ``` ## Next week: - Integrate the updated`BeaconBlockBody` and `BeaconState` across lighthouse's codebase, just enough to where it will compile so that the PR won't be too crazy to review - continue implementing the epbs spec - attend Paradigm's Frontiers conference