## EPF Week 10 Updates Lighthouse epbs related PR's this week: - [PR](https://github.com/shane-moore/lighthouse/pull/9) updating `per_slot_processing` and `per_epoch_processing` per the [spec](https://ethereum.github.io/consensus-specs/specs/gloas/beacon-chain/) - draft [PR](https://github.com/shane-moore/lighthouse/pull/10) for building out a `ptc_cache.rs` so that way we can save on the shuffling computation during various calls to `get_ptc` during block proposal and validation, which we discuss later - reviewed the `gloas` boilerplate [PR](https://github.com/sigp/lighthouse/pull/7728) for lighthouse, which we're trying to get into the `unstable` branch ### `ptc_cache` We want to build out a cache for `get_ptc`: ```rust pub fn get_ptc(&self, slot: Slot, spec: &ChainSpec) -> Result<PTC<E>, Error> { let committee_cache = self.committee_cache_at_slot(slot)?; let committees = committee_cache.get_beacon_committees_at_slot(slot)?; let seed = self.get_ptc_attester_seed(slot, spec)?; let committee_indices: Vec<usize> = committees .iter() .flat_map(|committee| committee.committee.iter().copied()) .collect(); let selected_indices = self.compute_balance_weighted_selection( &committee_indices, &seed, E::ptc_size(), false, spec, )?; Ok(PTC(FixedVector::from(selected_indices))) } ``` `get_ptc` should be deterministic per slot and return a list of validators who are assigned to a PTC committee for a given slot and should be a subset of the total validators who are assigned to an attestation committee for that slot per the [spec](https://github.com/shane-moore/lighthouse/pull/10). We can luckily save the computation of attestation committee members by grabbing them from the `committee_cache`, but without a ptc committee cache itself, we'd still have to calculate if a given validator from the current slot's attestation committee has been selected for the ptc committee by running a balance weighted selection algorythm, and it would be nice to not have to do this heavy computation on each call to `get_ptc`. This is where a `ptc_cache` would come in handy. Let's look at how `get_ptc` is used as to understand the requirements. ### Use Case 1: Proposer - Block Proposal When creating `PayloadAttestation` in the `BeaconBlockBody`, each `PayloadAttestation` should be an aggregate of PTC member's who voted for `payload present` and `blob_data_available`. Therefore, there are four variations of `PayloadAttestation` to store in the `BeaconBlockBody`. Each `PayloadAttestation` contains a bitlist, `payload_attestation::aggregation_bits`, indicating which committee members voted for that variation. Essentially, a `0` bit indicates a ptc member did not contribute a signature to this aggregate. Each block proposer will need to fill the `payload_attestation::aggregation_bits` bitlist in a deterministic order derived from `get_ptc(slot-1)`. ### Use Case 2: Validator - Block Validation Once a block is received over the network, a validator will want to validate that the `BeaconBlockBody::payload_attestations` are legit, so they will need to grab the pubkeys of each validator who actually participated in the PTC last slot for signature verification. To get the pubkeys, the `process_operations` flow will call `process_payload_attestation` and use `get_indexed_payload_attestation(payload_attestation)` to find the validator indices that voted for that `PayloadAttestation` variant by looping over the ptc validator indices derived from `get_ptc(slot)` and checking which members had a bit `1` set in `payload_attestations::aggregation_bits` at the corresponding index. The resulting validator indices list is sorted and returned for aggregated BLS signature verification. ## Use Case 3: Validator PTC Assignment `get_ptc_assignment` will be used at the start of each epoch to get the ptc assignments for the next epoch for a given validator index. While looping over each slot in the next epoch during `get_ptc_assignment`, `get_ptc` will run to check whether the validator's index is included in any of the upcoming ptc committees. If so, mark the slot. ### `ptc_cache` Requirements With these use cases in mind, we can see that the requirements of the `ptc_cache` should be to store a list of ptc assignments per slot per epoch for the previous, current, and next epoch. We can model this similar to the `committee_cache` where we store the shuffling and position lookups for efficient access. ### `PTCCache` Architecture The `PTCCache` struct follows the same pattern as `CommitteeCache` but optimized for PTC operations. Here's how we structured it in [PR](https://github.com/shane-moore/lighthouse/pull/10): ```rust pub struct PTCCache { initialized_epoch: Option<Epoch>, ptc_shuffling: Vec<usize>, // Flat list of all PTC members ptc_positions: Vec<NonZeroUsizeOption>, // Position lookup for each validator ptc_size: usize, // Constant 512 for Gloas+ slots_per_epoch: u64, // Usually 32 } ``` ### `Design Considerations`: - Flat Shuffling Storage: Instead of a nested `Vec<Vec<usize>>`, we use a single flat vector `ptc_shuffling` that stores all PTC members for an entire epoch. The layout is [slot0_members..., slot1_members..., slot2_members...]. This gives us O(1) slot lookups by calculating `start_index = slot_offset * ptc_size` and slicing the appropriate range since we know an epoch is 32 slots. - Position Lookup Optimization: The `ptc_positions` vector maps each validator index to their position in the flat shuffling. This enables O(1) lookup for [get_ptc_assignment](https://ethereum.github.io/consensus-specs/specs/gloas/validator/#validator-assignment) instead of having to linearly search through all slots. - Per-Epoch Initialization: Like `CommitteeCache`, we initialize the entire epoch's worth of PTC assignments at once during epoch transitions instead of doing it on-demand as to offload the expensive balance weighted selection computation.