124 views
# Forks / Upgrades in Phase 0 2020-09-23 Preston Van Loon [@preston_vanloon](https://twitter.com/preston_vanloon) preston@prysmaticlabs.com Canonical link: https://hackmd.prylabs.network/s/BJ6-wdFHw --- [TOC] ## Background <blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">5/ Because the Medalla response to do fuck all is an unacceptable response. None of the teams have demonstrated their ability to &quot;fix&quot; a situation like this. To this date there hasn&#39;t been a single successful fork of an ETH2 beacon chain.</p>&mdash; Chase Wright (@mysticryuujin) <a href="https://twitter.com/mysticryuujin/status/1308573307902492672?ref_src=twsrc%5Etfw">September 23, 2020</a></blockquote> <blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">6/ This is something that the ETH2 teams seem to be proud of as well. Their response to questions like this is &quot;Let&#39;s get it out the door and worry about forks later.&quot; Excuse me?</p>&mdash; Chase Wright (@mysticryuujin) <a href="https://twitter.com/mysticryuujin/status/1308573308791685121?ref_src=twsrc%5Etfw">September 23, 2020</a></blockquote> <blockquote class="twitter-tweet" data-conversation="none"><p lang="en" dir="ltr">7/ No. You want to come into a production network with potentially hundreds of millions of dollars day one with pristine, audited code, only to figure out how to deal with forks later? No thanks. The DAO Part 2 will happen, it&#39;s not a matter of if but when.</p>&mdash; Chase Wright (@mysticryuujin) <a href="https://twitter.com/mysticryuujin/status/1308573309668384768?ref_src=twsrc%5Etfw">September 23, 2020</a></blockquote> In short, Phase 0 should have support for eth2 upgrades (forks) prior to launch or soon thereafter. ## Spec changes The key change to phase 0 is to add logic to process_slot such that a new constant, FORK_SCHEDULE, is checked and if there is a scheduled fork at a given slot, then the fork in the state must be updated with the given fork. It is assumed that a fork in the FORK_SCHEDULE fulfills the following constraints: - fork.previous_version matches the previous fork in the schedule (except genesis fork). - fork.current_version is greater than fork.previous_version. - fork.epoch is at least MINIMUM_FORK_DELAY_EPOCHS greater than the scheduled fork insertion slot. The constraints may be validated by a client implementation at runtime and/or unit tests. MINIMUM_FORK_DELAY_EPOCHS should be set to a reasonable distance such that a reasonable range of skip slots between the fork insertion slot would not be a factor in client implementations. For example, a MINIMUM_FORK_DELAY_EPOCHS of 1024 would give adaquite time for beacon nodes to observe a fork in advance of it taking affect such that the fork would be a smooth transition with respect to computing the DomainData for BLS signatures. Minimal configurations could tolerate shorter minimums, perhaps even a minimum of 1 epoch delay for a fork to become effective. The type for FORK_SCHEDULE is as follows: ```go var ForkSchedule map[Slot]Fork // TODO: Someone please convert this to python. ``` The fork schedule is a map/dict where the key is the slot upon which to insert the given fork into the state and the value is the fork object itself. _specs/phase0/beacon-chain.md_ ```python def process_slot(state: BeaconState) -> None: # Cache state root previous_state_root = hash_tree_root(state) state.state_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_state_root # Cache latest block header state root if state.latest_block_header.state_root == Bytes32(): state.latest_block_header.state_root = previous_state_root # Cache block root previous_block_root = hash_tree_root(state.latest_block_header) state.block_roots[state.slot % SLOTS_PER_HISTORICAL_ROOT] = previous_block_root + # Apply fork schedule + if FORK_SCHEDULE[state.slot] != None: + state.fork = FORK_SCHEDULE[state.slot] ``` _configs/mainnet/phase0.yaml_ ```yaml DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 + +# Fork schedule +# --------------------------------------------------------------- + +MINIMUM_FORK_DELAY_EPOCHS: 1024 + +FORK_SCHEDULE: + 0: # Genesis fork + previous_version: 0x00000000 + current_version: 0x00000000 + epoch: 0 + # 0: # Phase 1 (TBD) + # previous_version: 0x00000000 + # current_version: 0x01000000 + # epoch: 0 # (TBD) ``` _configs/minimal/phase0.yaml_ ```yaml DOMAIN_VOLUNTARY_EXIT: 0x04000000 DOMAIN_SELECTION_PROOF: 0x05000000 DOMAIN_AGGREGATE_AND_PROOF: 0x06000000 + +# Fork schedule +# --------------------------------------------------------------- + +MINIMUM_FORK_DELAY_EPOCHS: 1 + +FORK_SCHEDULE: + 0: # Genesis fork + previous_version: 0x00000001 + current_version: 0x00000001 + epoch: 0 + # 0: # Phase 1 (TBD) + # previous_version: 0x00000001 + # current_version: 0x01000001 + # epoch: 0 # (TBD) ``` I [tried (and failed) to add these changes](https://github.com/ethereum/eth2.0-specs/compare/dev...prestonvanloon:state-fork-update?short_path=f89c846#diff-f89c846634435a87a1ee5a832c90bfa7) to the eth2 specs. I am miserably pathetic at python. ## Examples: Using forks in client implementations Besides phase 1 (which already has fork details [here](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/phase1/phase1-fork.md#upgrading-the-state)), what are some other cases of upgrades that could be necessary in phase 0 before phase 1? To facilitate examples, we add a helper function to determine the fork version from the state. ```python def get_fork_version(state: BeaconState) -> Version: """ Return the current fork version from the beacon state. """ if state.fork.epoch >= compute_epoch_at_slot(state.slot): return state.fork.current_version else: return state.fork.previous_version ``` ### Example 1: Altering rewards / penalty parameters In the event that ETH2 should alter the rewards and penalties calculation, this can be done by using the updated parameters when the state's current fork is greater than or equal to the rewards update fork version. ```python def get_attestation_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ Return attestation reward/penalty deltas for each validator. """ - source_rewards, source_penalties = get_source_deltas(state) - target_rewards, target_penalties = get_target_deltas(state) - head_rewards, head_penalties = get_head_deltas(state) - inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) - _, inactivity_penalties = get_inactivity_penalty_deltas(state) + + if get_fork_version(state) >= REWARDS_UPDATE_FORK_1: + # New adjusted rewards + source_rewards, source_penalties = get_source_deltas_v2(state) + target_rewards, target_penalties = get_target_deltas_v2(state) + head_rewards, head_penalties = get_head_deltas_v2(state) + inclusion_delay_rewards, _ = get_inclusion_delay_deltas_v2(state) + _, inactivity_penalties = get_inactivity_penalty_deltas_v2(state) + else: + # Rewards calculation using original parameters + source_rewards, source_penalties = get_source_deltas(state) + target_rewards, target_penalties = get_target_deltas(state) + head_rewards, head_penalties = get_head_deltas(state) + inclusion_delay_rewards, _ = get_inclusion_delay_deltas(state) + _, inactivity_penalties = get_inactivity_penalty_deltas(state) rewards = [ source_rewards[i] + target_rewards[i] + head_rewards[i] + inclusion_delay_rewards[i] for i in range(len(state.validators)) ] penalties = [ source_penalties[i] + target_penalties[i] + head_penalties[i] + inactivity_penalties[i] for i in range(len(state.validators)) ] return rewards, penalties ``` ### Example 2: Restoring slashed validators _**This example is for demonstration purposes only. These notes do not advocate for or against an irregular state transition to restore any slashed validator.**_ This examples a "one off" irregular state transition which occurs in the state epoch processing method when the epoch matches the fork epoch exactly. ```python def process_epoch(state: BeaconState) -> None: process_justification_and_finalization(state) process_rewards_and_penalties(state) process_registry_updates(state) process_slashings(state) process_final_updates(state) + process_one_off_irregular_transitions(state) + +def process_one_off_irregular_transitions(state: BeaconState) -> None: + if state.fork.epoch != compute_epoch_at_slot(state.slot): + return + + if state.fork.current_version == RESTORE_SLASHED_VALIDATORS_FORK: + process_one_off_restore_slashed_validators(state) + +def process_one_off_restore_slashed_validators(state: BeaconState) -> None: + for index in [5, 100, 234]: + state.validators[index].slashed = false + state.validators[index].effective_balance = EXAMPLE_BALANCE_AT_TIME_OF_SLASHING[index] + state.validators[index].exit_epoch = FAR_FUTURE_EPOCH ``` ## References and links - Prysm reference implementation: [Pull request 7325](https://github.com/prysmaticlabs/prysm/pull/7325). - [Danny Ryan's feedback](https://notes.ethereum.org/@djrtwo/rkKoPYjHP)