# Gloas ePBS breaks dependant root? Before Gloas there's a unique post-state per tuple `(block_root, slot)`. After Gloas there are two possible post-states, one if the slot is `Full` and one if it's `Empty`. Now the outcome of `process_epoch` depends on wether the payload of the last block is Full or not. After Gloas, the first block in epoch N may choose to: - build on top of the last block of the previous epoch + empty - build on top of the last block of the previous epoch + full Is `dependant_root` capable of specifying those two cases? dependant root usage in the Beacon API is specified [here](https://github.com/ethereum/beacon-APIs/blob/339eea96b41c787dad47765fc781303fb40aa886/apis/eventstream/index.yaml#L51-L55) ```json data: { "slot": "10", "block": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", "state": "0x600e852a08c1200654ddf11025f1ceacb3c2e74bdd5c630cde0838b2591b69f9", "epoch_transition": false, "previous_duty_dependent_root": "0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91", "current_duty_dependent_root": "0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91", "execution_optimistic": false } ``` ```py previous_duty_dependent_root = get_block_root_at_slot( state, compute_start_slot_at_epoch(epoch - 1) - 1 ) ``` ```py current_duty_dependent_root = get_block_root_at_slot( state, compute_start_slot_at_epoch(epoch) - 1 ) ``` Just setting `*_dependent_root` is now insufficient to specify a unique `process_epoch` outcome. Let's define the post state of a block advanced to a certain slot considering if its payload is present or not as `post_state(block_root, slot, payload_present)`. We can assert that in all cases: ``` post_state(block_root, slot, true) != post_state(block_root, slot, false) ``` Now let's define `compute_current_shuffling(state)` and `compute_next_shuffling` as functions that output the attester shufflings for the state's current and next epoch. Do the shufflings change based on the payload status of the block? ``` compute_current_shuffling(post_state(block_root, slot, true)) ?= compute_current_shuffling(post_state(block_root, slot, false)) ``` ``` compute_next_shuffling(post_state(block_root, slot, true)) ?= compute_next_shuffling(post_state(block_root, slot, false)) ``` ## Does payload status actually change the shufflings? Processing the execution payload changes the state slightly: specifically ```py state.latest_block_header.state_root = previous_state_root .. for_ops(requests.deposits, process_deposit_request) for_ops(requests.withdrawals, process_withdrawal_request) for_ops(requests.consolidations, process_consolidation_request) .. state.builder_pending_withdrawals.append(payment.withdrawal) state.builder_pending_payments[SLOTS_PER_EPOCH + state.slot % SLOTS_PER_EPOCH] = BuilderPendingPayment() .. state.execution_payload_availability[state.slot % SLOTS_PER_HISTORICAL_ROOT] = 0b1 state.latest_block_hash = payload.block_hash ``` ```py def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: state.pending_deposits.append(PendingDeposit(..)) ``` ```py def process_withdrawal_request(state: BeaconState, withdrawal_request: WithdrawalRequest) -> None: if is_full_exit_request and pending_balance_to_withdraw == 0: # exit_epoch is at least 1 + MAX_SEED_LOOKAHEAD into the future exit_queue_epoch = compute_exit_epoch_and_update_churn(state, validator.effective_balance) initiate_validator_exit(state, index) else: withdrawable_epoch = Epoch(exit_queue_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) state.pending_partial_withdrawals.append( PendingPartialWithdrawal(.., withdrawable_epoch=withdrawable_epoch) ) ``` ```py def process_consolidation_request(state: BeaconState, consolidation_request: ConsolidationRequest): if is_valid_switch_to_compounding_request(state, consolidation_request): switch_to_compounding_validator(state, source_index) return else: source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(..) source_validator.withdrawable_epoch = Epoch(source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) state.pending_consolidations.append(PendingConsolidation(..)) ``` Does that affect `process_epoch`? ```py # Deposits need to be finalized, so they can't be executed in the very next epoch process_pending_deposits(state) # No, the consolidation does not get executed until `source.withdrawable_epoch > next_epoch` # which can't happen in the very next epoch process_pending_consolidations(state) # No, the payment gets processed in the next epoch, does not affect now process_builder_pending_payments(state) ``` After Fulu the proposers get computed in process_epoch and written in the state ```py def process_proposer_lookahead(state: BeaconState) -> None: last_epoch_start = len(state.proposer_lookahead) - SLOTS_PER_EPOCH # Shift out proposers in the first epoch state.proposer_lookahead[:last_epoch_start] = state.proposer_lookahead[SLOTS_PER_EPOCH:] # Fill in the last epoch with new proposer indices last_epoch_proposers = get_beacon_proposer_indices( state, Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1) ) state.proposer_lookahead[last_epoch_start:] = last_epoch_proposers ``` At the end of epoch N, we compute proposers for epoch N + 2. The list of active incides depends on the activation status of validators at epoch N + 2, and the effective balance at the end of `process_epoch` of the end of epoch N. ```py def get_beacon_proposer_indices( state: BeaconState, epoch: Epoch ) -> Vector[ValidatorIndex, SLOTS_PER_EPOCH]: """ Return the proposer indices for the given ``epoch``. """ indices = get_active_validator_indices(state, epoch) seed = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) return compute_proposer_indices(state, epoch, seed, indices) ``` Processing the execution payload can't change the balance of any validator in that epoch. Nor it can change the active status before `1 + MIN_SEED_LOOKAHEAD`, so the shuffling remains stable. On a preliminary look it seems that the shufflings are stable, so we can say that: ``` compute_current_shuffling(post_state(block_root, slot, true)) == compute_current_shuffling(post_state(block_root, slot, false)) ``` ``` compute_next_shuffling(post_state(block_root, slot, true)) == compute_next_shuffling(post_state(block_root, slot, false)) ``` but I would appreciate more eyes on the topic.