Add merge/fork.md with upgrade_to_merge definition

This commit is contained in:
Mikhail Kalinin 2021-05-31 19:30:54 +06:00
parent d857935d6c
commit fd4369dc7c
10 changed files with 339 additions and 2 deletions

View File

@ -43,6 +43,7 @@ while the details are in review and may change.
* [ethereum.org](https://ethereum.org) high-level description of the merge [here](https://ethereum.org/en/eth2/docking/)
* Specifications:
* [Beacon Chain changes](specs/merge/beacon-chain.md)
* [Merge fork](specs/merge/fork.md)
* [Fork Choice changes](specs/merge/fork-choice.md)
* [Validator additions](specs/merge/validator.md)

View File

@ -865,6 +865,7 @@ class PySpecCommand(Command):
specs/phase0/validator.md
specs/phase0/weak-subjectivity.md
specs/merge/beacon-chain.md
specs/merge/fork.md
specs/merge/fork-choice.md
specs/merge/validator.md
"""

View File

@ -70,8 +70,6 @@ Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `MERGE_FORK_VERSION` | `Version('0x02000000')` |
| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
| `TRANSITION_TOTAL_DIFFICULTY` | **TBD** |
## Containers

89
specs/merge/fork.md Normal file
View File

@ -0,0 +1,89 @@
# Ethereum 2.0 The Merge
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Merge](#fork-to-merge)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction
This document describes the process of the Merge upgrade.
## Configuration
Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `MERGE_FORK_VERSION` | `Version('0x02000000')` |
| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** |
## Fork to Merge
### Fork trigger
TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `MERGE_FORK_EPOCH`.
Note that for the pure Merge networks, we don't apply `upgrade_to_merge` since it starts with Merge version logic.
### Upgrading the state
If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, an irregular state change is made to upgrade to Merge.
The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `MERGE_FORK_EPOCH * SLOTS_PER_EPOCH`.
Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document.
In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead the logic must be within `process_slots`.
```python
def upgrade_to_merge(pre: phase0.BeaconState) -> BeaconState:
epoch = phase0.get_current_epoch(pre)
post = BeaconState(
# Versioning
genesis_time=pre.genesis_time,
genesis_validators_root=pre.genesis_validators_root,
slot=pre.slot,
fork=Fork(
previous_version=pre.fork.current_version,
current_version=MERGE_FORK_VERSION,
epoch=epoch,
),
# History
latest_block_header=pre.latest_block_header,
block_roots=pre.block_roots,
state_roots=pre.state_roots,
historical_roots=pre.historical_roots,
# Eth1
eth1_data=pre.eth1_data,
eth1_data_votes=pre.eth1_data_votes,
eth1_deposit_index=pre.eth1_deposit_index,
# Registry
validators=pre.validators,
balances=pre.balances,
# Randomness
randao_mixes=pre.randao_mixes,
# Slashings
slashings=pre.slashings,
# Attestations
previous_epoch_attestations=pre.previous_epoch_attestations,
current_epoch_attestations=pre.current_epoch_attestations,
# Finality
justification_bits=pre.justification_bits,
previous_justified_checkpoint=pre.previous_justified_checkpoint,
current_justified_checkpoint=pre.current_justified_checkpoint,
finalized_checkpoint=pre.finalized_checkpoint,
# Execution-layer
latest_execution_payload_header=ExecutionPayloadHeader(),
)
return post
```

View File

@ -0,0 +1,45 @@
MERGE_FORK_TEST_META_TAGS = {
'fork': 'merge',
}
def run_fork_test(post_spec, pre_state):
# Clean up state to be more realistic
pre_state.current_epoch_attestations = []
yield 'pre', pre_state
post_state = post_spec.upgrade_to_merge(pre_state)
# Stable fields
stable_fields = [
'genesis_time', 'genesis_validators_root', 'slot',
# History
'latest_block_header', 'block_roots', 'state_roots', 'historical_roots',
# Eth1
'eth1_data', 'eth1_data_votes', 'eth1_deposit_index',
# Registry
'validators', 'balances',
# Randomness
'randao_mixes',
# Slashings
'slashings',
# Attestations
'previous_epoch_attestations', 'current_epoch_attestations',
# Finality
'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint',
]
for field in stable_fields:
assert getattr(pre_state, field) == getattr(post_state, field)
# Modified fields
modified_fields = ['fork']
for field in modified_fields:
assert getattr(pre_state, field) != getattr(post_state, field)
assert pre_state.fork.current_version == post_state.fork.previous_version
assert post_state.fork.current_version == post_spec.config.MERGE_FORK_VERSION
assert post_state.fork.epoch == post_spec.get_current_epoch(post_state)
assert post_state.latest_execution_payload_header == post_spec.ExecutionPayloadHeader()
yield 'post', post_state

View File

@ -0,0 +1,82 @@
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
PHASE0, MERGE,
MINIMAL,
)
from eth2spec.test.helpers.state import (
next_epoch,
next_epoch_via_block,
)
from eth2spec.test.helpers.merge.fork import (
MERGE_FORK_TEST_META_TAGS,
run_fork_test,
)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_base_state(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_next_epoch(spec, phases, state):
next_epoch(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_next_epoch_with_block(spec, phases, state):
next_epoch_via_block(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_many_next_epoch(spec, phases, state):
for _ in range(3):
next_epoch(spec, state)
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_low_balances(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_misc_balances(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@spec_test
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_fork_random_large_validator_set(spec, phases, state):
yield from run_fork_test(phases[MERGE], state)

View File

@ -0,0 +1,120 @@
from random import Random
from eth2spec.test.context import (
with_phases,
with_custom_state,
with_presets,
spec_test, with_state,
low_balances, misc_balances, large_validator_set,
)
from eth2spec.test.utils import with_meta_tags
from eth2spec.test.helpers.constants import (
PHASE0, MERGE,
MINIMAL,
)
from eth2spec.test.helpers.merge.fork import (
MERGE_FORK_TEST_META_TAGS,
run_fork_test,
)
from eth2spec.test.helpers.random import (
randomize_state,
randomize_attestation_participation,
)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_0(spec, phases, state):
randomize_state(spec, state, rng=Random(1010))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_1(spec, phases, state):
randomize_state(spec, state, rng=Random(2020))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_2(spec, phases, state):
randomize_state(spec, state, rng=Random(3030))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_3(spec, phases, state):
randomize_state(spec, state, rng=Random(4040))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_duplicate_attestations(spec, phases, state):
randomize_state(spec, state, rng=Random(1111))
# Note: `run_fork_test` empties `current_epoch_attestations`
state.previous_epoch_attestations = state.previous_epoch_attestations + state.previous_epoch_attestations
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_state
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_mismatched_attestations(spec, phases, state):
# Create a random state
randomize_state(spec, state, rng=Random(2222))
# Now make two copies
state_0 = state.copy()
state_1 = state.copy()
# Randomize attestation participation of both
randomize_attestation_participation(spec, state_0, rng=Random(3333))
randomize_attestation_participation(spec, state_1, rng=Random(4444))
# Note: `run_fork_test` empties `current_epoch_attestations`
# Use pending attestations from both random states in a single state for testing
state_0.previous_epoch_attestations = state_0.previous_epoch_attestations + state_1.previous_epoch_attestations
yield from run_fork_test(phases[MERGE], state_0)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_low_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(5050))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@spec_test
@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_misc_balances(spec, phases, state):
randomize_state(spec, state, rng=Random(6060))
yield from run_fork_test(phases[MERGE], state)
@with_phases(phases=[PHASE0], other_phases=[MERGE])
@with_presets([MINIMAL],
reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated")
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE)
@with_meta_tags(MERGE_FORK_TEST_META_TAGS)
def test_merge_fork_random_large_validator_set(spec, phases, state):
randomize_state(spec, state, rng=Random(7070))
yield from run_fork_test(phases[MERGE], state)

View File

@ -23,6 +23,7 @@ Key of valid `fork` strings that might be found in `meta.yaml`
| String ID | Pre-fork | Post-fork | Function |
| - | - | - | - |
| `altair` | Phase 0 | Altair | `upgrade_to_altair` |
| `merge` | Phase 0 | Merge | `upgrade_to_merge` |
### `pre.ssz_snappy`