## 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.