# EIP-7917: How often does it help? Beacon proposers are selected with this function, where the `state` must be advanced to `epoch = compute_epoch_at_slot(slot)` ```python def get_beacon_proposer_index(state: BeaconState, slot: Slot) -> ValidatorIndex: epoch = get_current_epoch(state) seed = hash(get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + uint_to_bytes(slot)) indices = get_active_validator_indices(state, epoch) return compute_proposer_index(state, indices, seed) ``` Through the Beacon API clients returns all proposer indices for a given epoch ```python def get_beacon_proposer_indices( state: BeaconState, epoch: Epoch ) -> List[ValidatorIndex]: advance_state(state, epoch) return [ get_beacon_proposer_index(state, slot) for slot in range(start_slot(epoch), start_slot(epoch + 1)) ] ``` The implementation of `compute_proposer_index` for Electra: ```python def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: """ Return from ``indices`` a random index sampled by effective balance. """ assert len(indices) > 0 MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] i = uint64(0) total = uint64(len(indices)) while True: candidate_index = indices[compute_shuffled_index(i % total, total, seed)] # [Modified in Electra] random_bytes = hash(seed + uint_to_bytes(i // 16)) offset = i % 16 * 2 random_value = bytes_to_uint64(random_bytes[offset:offset + 2]) effective_balance = state.validators[candidate_index].effective_balance # [Modified in Electra:EIP7251] if effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: return candidate_index i += 1 ``` The return of `get_beacon_proposer_indices` is dependant on - The value of `seed`: `get_seed` read the randao mix for `epoch - 2`, so it has a lookahead of 2 epochs. - Active validator indices: Validator activation and de-activation is stable for > 2 epochs as the exit epoch is always assigned to at least `epoch + 5`. - Validator effective balances: Can change in any epoch transition. ## Effective balance change and `compute_proposer_index` ### How does it affect proposer selection? Consider that you want to learn the proposers at epoch 1001, but we are at the start of epoch 1000. You can take the state at the start of epoch 1000 and run `get_beacon_proposer_indices(state_1000, 1001)`. The function `advance_state(state, 1001)` will return the list of proposers as if there were no more blocks in epoch 1000. Consider a run of `compute_proposer_index` with the following values: - `random_value` = 1034 - `effective_balance` = 32000000000 - `effective_balance * MAX_RANDOM_VALUE` = 2097152 - `MAX_EFFECTIVE_BALANCE_ELECTRA * random_value` = 2117632 * 10**9 - `effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value` = `false` The condition returns `false` and the loop will select another proposer. Assume that the balance of this validator is at 33,249,995,423 GWei at the start of epoch 1000, and the last block of the epoch includes its attestation. The average reward of ~9,000 GWei is enough to bump its effective balance to 33 ETH. Then the output of the function above will be - `random_value` = 1034 - `effective_balance` = 33000000000 - `effective_balance * MAX_RANDOM_VALUE` = 2162688 - `MAX_EFFECTIVE_BALANCE_ELECTRA * random_value` = 2117632 * 10**9 - `effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value` = `true` Now the condition returns `true` and this proposer is selected, altering the outcome of `get_beacon_proposer_index` ### Probability of different outcome To compute the probability of `get_beacon_proposer_index` returning a different outcome we need to compute: - Probability of a validator having an effective balance change - Probability of choosing a `random_value` that will be affected by an effective balance change - Average count of loops per `compute_proposer_index` call #### Probability of effective balance change For the effective balance of a validator to change, its balance must change and cross a specific value to trigger a hysteresis update. A validator balance can change due to: - Attestation participation: once per epoch - Sync committee participation: rare - Consolidating: once per validator, voluntary and churned so very rare - Partial withdrawals: voluntary and churned so very rare - Proposing a block: very rare (= being selected twice in two epochs) - Being slashed: extremely rare Accounting for attestation participation, today an Ethereum mainnet validator earns ~9000 GWei per epoch. That results in an effective balance increment every ~100,000 epochs, or once every 1.3 years. ``` P = 1/100000 ``` However, if a validator was assigned to attest in the first slot of epoch 1000 (in our example), when we advance the state to epoch 1001 we will correctly account for its attestation rewards. We will only incorrectly predict its effective balance if we miss the block that includes its attestation. If we aim for a lookahead of 1 slot, that will affect only the predictability of - the first slot in the epoch - if the proposer of the first slot was assigned to attest in the second to last slot in the previous epoch, and - if the last slot of the epoch is not missed #### Probability of affected `random_value` Assume the initial case of bumping from 32 ETH to 33 ETH. - `random_value` = `32*10**9 * MAX_RANDOM_VALUE / MAX_EFFECTIVE_BALANCE_ELECTRA` = 1024 - `random_value` = `33*10**9 * MAX_RANDOM_VALUE / MAX_EFFECTIVE_BALANCE_ELECTRA` = 1056 This is the range of `random_value` that would trigger a condition change if the effective balance increases by 1 ETH. The probability of selecting that range is `(1056 - 1024) / MAX_RANDOM_VALUE` or ``` P = EFFECTIVE_BALANCE_INCREMENT / MAX_EFFECTIVE_BALANCE_ELECTRA = 1/2048 ``` #### Loop count in `compute_proposer_index` Assume the initial and worst case of everyone with 32 ETH effective balance. Given a `random_value` in range `[0..MAX_RANDOM_VALUE]` the probability of this statement `32 ETH * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value` to be true is `32 ETH / MAX_EFFECTIVE_BALANCE_ELECTRA` or $P = 32/2048 = 1/64$. So on average loops per function call ``` C = MAX_EFFECTIVE_BALANCE_ELECTRA / MIN_ACTIVATION_BALANCE = 64 ``` ### Lookahead of 1 slot Slots `[1..32]` can be predicted, only slot `[0]` may return the wrong proposer. The probability for slot 0 to return the wrong proposer: - 64 attempts in `compute_proposer_index`, each has a probability 1/100000 (over estimated) of having an effective balance change, and each has a probability 1/2048 of choosing a sensitive random value = $P_{1slot} = 1/3200000$, or once every 39 years ### Lookahead of 16 slots Slots `[16..32]` can be predicted, only slots `[0..16]` may return the wrong proposer. In the previous section we over-estimated the effective balance change probability to the worst case so we can recycle the number. The proposer selection for each slot is independent, so the probability one of them is wrong: - $P_{16slot} = 16 * P_{1slot} = 1/200000$ or once every 2.4 years ### TODO: Widespread consolidation events, and how they affect the calculations above. - Consolidations are churned, such that only a very few events can happen every epoch. The probability of the proposer consolidating on the epoch it proposes is extremely low.