Skip to content

Commit

Permalink
Merge pull request #1971 from ethereum/hwwhww/phase-1-fork-slot
Browse files Browse the repository at this point in the history
Rework initial shard slot (`PHASE_1_FORK_SLOT`)
  • Loading branch information
hwwhww authored Jul 28, 2020
2 parents cf1a9e8 + b1eb157 commit fc38fc1
Show file tree
Hide file tree
Showing 22 changed files with 144 additions and 105 deletions.
2 changes: 1 addition & 1 deletion configs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Over time, the need to sync an older state may be deprecated.
In this case, the prefix on the new constant may be removed, and the old constant will keep a special name before completely being removed.

A previous iteration of forking made use of "timelines", but this collides with the definitions used in the spec (constants for special forking slots, etc.), and was not integrated sufficiently in any of the spec tools or implementations.
Instead, the config essentially doubles as fork definition now, e.g. changing the value for `PHASE_1_GENESIS_SLOT` changes the fork.
Instead, the config essentially doubles as fork definition now, e.g. changing the value for `PHASE_1_FORK_SLOT` changes the fork.

Another reason to prefer forking through constants is the ability to program a forking moment based on context, instead of being limited to a static slot number.

Expand Down
2 changes: 1 addition & 1 deletion configs/mainnet/phase1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# ---------------------------------------------------------------
PHASE_1_FORK_VERSION: 0x01000000
# [STUB]
PHASE_1_GENESIS_SLOT: 32
PHASE_1_FORK_SLOT: 0
INITIAL_ACTIVE_SHARDS: 64


Expand Down
4 changes: 2 additions & 2 deletions configs/minimal/phase1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# ---------------------------------------------------------------
# [customized] for testnet distinction
PHASE_1_FORK_VERSION: 0x01000001
# [customized] for testing
PHASE_1_GENESIS_SLOT: 8
# [STUB]
PHASE_1_FORK_SLOT: 0
# [customized] reduced for testing
INITIAL_ACTIVE_SHARDS: 2

Expand Down
29 changes: 18 additions & 11 deletions specs/phase1/beacon-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -760,20 +760,24 @@ def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
committee = get_beacon_committee(state, data.slot, data.index)
assert len(attestation.aggregation_bits) == len(committee)

if attestation.data.target.epoch == get_current_epoch(state):
assert attestation.data.source == state.current_justified_checkpoint
if data.target.epoch == get_current_epoch(state):
assert data.source == state.current_justified_checkpoint
else:
assert attestation.data.source == state.previous_justified_checkpoint
assert data.source == state.previous_justified_checkpoint

# Type 1: on-time attestations
if is_on_time_attestation(state, attestation.data):
if is_on_time_attestation(state, data):
# Correct parent block root
assert data.beacon_block_root == get_block_root_at_slot(state, compute_previous_slot(state.slot))
# Correct shard number
shard = compute_shard_from_committee_index(state, attestation.data.index, attestation.data.slot)
assert attestation.data.shard == shard
# On-time attestations should have a non-empty shard transition root
assert attestation.data.shard_transition_root != hash_tree_root(ShardTransition())
shard = compute_shard_from_committee_index(state, data.index, data.slot)
assert data.shard == shard
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if data.slot > GENESIS_SLOT:
# On-time attestations should have a non-empty shard transition root
assert data.shard_transition_root != hash_tree_root(ShardTransition())
else:
assert data.shard_transition_root == hash_tree_root(ShardTransition())
# Type 2: no shard transition
else:
# Ensure delayed attestation
Expand Down Expand Up @@ -811,7 +815,7 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
```python
def apply_shard_transition(state: BeaconState, shard: Shard, transition: ShardTransition) -> None:
# TODO: only need to check it once when phase 1 starts
assert state.slot > PHASE_1_GENESIS_SLOT
assert state.slot > PHASE_1_FORK_SLOT

# Correct data root count
offset_slots = get_offset_slots(state, shard)
Expand Down Expand Up @@ -976,8 +980,11 @@ def verify_empty_shard_transition(state: BeaconState, shard_transitions: Sequenc
def process_shard_transitions(state: BeaconState,
shard_transitions: Sequence[ShardTransition],
attestations: Sequence[Attestation]) -> None:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if compute_previous_slot(state.slot) > GENESIS_SLOT:
# Process crosslinks
process_crosslinks(state, shard_transitions, attestations)

# Verify the empty proposal shard states
assert verify_empty_shard_transition(state, shard_transitions)
```
Expand Down
8 changes: 4 additions & 4 deletions specs/phase1/phase1-fork.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ Warning: this configuration is not definitive.
| Name | Value |
| - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `PHASE_1_GENESIS_SLOT` | `2**5` **TBD** |
| `PHASE_1_FORK_SLOT` | `Slot(0)` **TBD** |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |

## Fork to Phase 1

### 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 slot `PHASE_1_GENESIS_SLOT`, where `PHASE_1_GENESIS_SLOT % SLOTS_PER_EPOCH == 0`.
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 slot `PHASE_1_FORK_SLOT`, where `PHASE_1_FORK_SLOT % SLOTS_PER_EPOCH == 0`.

### Upgrading the state

After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_GENESIS_SLOT`, an irregular state change is made to upgrade to Phase 1.
After `process_slots` of Phase 0 finishes, if `state.slot == PHASE_1_FORK_SLOT`, an irregular state change is made to upgrade to Phase 1.

```python
def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
Expand Down Expand Up @@ -102,7 +102,7 @@ def upgrade_to_phase1(pre: phase0.BeaconState) -> BeaconState:
current_epoch_start_shard=Shard(0),
shard_states=List[ShardState, MAX_SHARDS](
ShardState(
slot=pre.slot,
slot=compute_previous_slot(pre.slot),
gasprice=MIN_GASPRICE,
latest_block_root=Root(),
) for i in range(INITIAL_ACTIVE_SHARDS)
Expand Down
2 changes: 1 addition & 1 deletion specs/phase1/shard-fork-choice.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_forkchoice_shard_store(anchor_state: BeaconState, shard: Shard) -> Shard
shard=shard,
signed_blocks={
anchor_state.shard_states[shard].latest_block_root: SignedShardBlock(
message=ShardBlock(slot=anchor_state.slot, shard=shard)
message=ShardBlock(slot=compute_previous_slot(anchor_state.slot), shard=shard)
)
},
block_states={anchor_state.shard_states[shard].latest_block_root: anchor_state.copy().shard_states[shard]},
Expand Down
10 changes: 7 additions & 3 deletions specs/phase1/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
- [`FullAttestation`](#fullattestation)
- [Timing](#timing)
- [Attestation data](#attestation-data)
- [Head shard root](#head-shard-root)
- [Shard head root](#shard-head-root)
- [Shard transition](#shard-transition)
- [Construct attestation](#construct-attestation)
- [Attestation Aggregation](#attestation-aggregation)
Expand Down Expand Up @@ -267,9 +267,9 @@ A validator should create and broadcast the `attestation` to the associated atte

*Note*: We assume that the fork choice only follows branches with valid `offset_slots` with respect to the most recent beacon state shard transition for the queried shard.

##### Head shard root
##### Shard head root

Set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.
If `attestation_data.slot == GENESIS_SLOT`, set `attestation_data.shard_head_root = Root()`. Otherwise, set `attestation_data.shard_head_root = hash_tree_root(shard_head_block)`.

##### Shard transition

Expand Down Expand Up @@ -310,6 +310,10 @@ def get_shard_transition_fields(
def get_shard_transition(beacon_state: BeaconState,
shard: Shard,
shard_blocks: Sequence[SignedShardBlock]) -> ShardTransition:
# NOTE: We currently set `PHASE_1_FORK_SLOT` to `GENESIS_SLOT` for test vectors.
if beacon_state.slot == GENESIS_SLOT:
return ShardTransition()

offset_slots = compute_offset_slots(
get_latest_slot_for_shard(beacon_state, shard),
Slot(beacon_state.slot + 1),
Expand Down
3 changes: 0 additions & 3 deletions tests/core/pyspec/eth2spec/test/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Ca
# TODO: instead of upgrading a test phase0 genesis state we can also write a phase1 state helper.
# Decide based on performance/consistency results later.
state = phases[PHASE1].upgrade_to_phase1(state)
# Shard state slot must lag behind BeaconState slot by at least 1
# Will handle this more elegantly with fork mechanics
spec.process_slots(state, state.slot + 1)

return state

Expand Down
28 changes: 16 additions & 12 deletions tests/core/pyspec/eth2spec/test/helpers/attestations.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio
source_epoch = state.current_justified_checkpoint.epoch
source_root = state.current_justified_checkpoint.root

attestation_data = spec.AttestationData(
data = spec.AttestationData(
slot=slot,
index=index,
beacon_block_root=block_root,
Expand All @@ -79,23 +79,27 @@ def build_attestation_data(spec, state, slot, index, shard=None, shard_transitio

if spec.fork == PHASE1:
if shard is None:
shard = spec.compute_shard_from_committee_index(state, attestation_data.index, attestation_data.slot)
attestation_data.shard = shard
shard = spec.compute_shard_from_committee_index(state, data.index, data.slot)
data.shard = shard

if shard_transition is not None:
last_offset_index = len(shard_transition.shard_data_roots) - 1
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
data.shard_transition_root = shard_transition.hash_tree_root()
else:
if on_time:
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
last_offset_index = len(shard_transition.shard_data_roots) - 1
attestation_data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
attestation_data.shard_transition_root = shard_transition.hash_tree_root()
if data.slot == spec.GENESIS_SLOT:
data.shard_head_root = spec.Root()
data.shard_transition_root = spec.ShardTransition().hash_tree_root()
else:
shard_transition = spec.get_shard_transition(state, shard, shard_blocks=[])
last_offset_index = len(shard_transition.shard_data_roots) - 1
data.shard_head_root = shard_transition.shard_states[last_offset_index].latest_block_root
data.shard_transition_root = shard_transition.hash_tree_root()
else:
attestation_data.shard_head_root = state.shard_states[shard].latest_block_root
attestation_data.shard_transition_root = spec.Root()
return attestation_data
data.shard_head_root = state.shard_states[shard].latest_block_root
data.shard_transition_root = spec.Root()
return data


def get_valid_on_time_attestation(spec, state, slot=None, index=None, shard_transition=None, signed=False):
Expand Down
6 changes: 2 additions & 4 deletions tests/core/pyspec/eth2spec/test/helpers/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,10 @@ def transition_to_slot_via_block(spec, state, slot):

def transition_to_valid_shard_slot(spec, state):
"""
Transition to slot `spec.PHASE_1_GENESIS_SLOT + 1` and fork at `spec.PHASE_1_GENESIS_SLOT`.
Transition to slot `spec.PHASE_1_FORK_SLOT + 1` and fork at `spec.PHASE_1_FORK_SLOT`.
"""
transition_to(spec, state, spec.PHASE_1_GENESIS_SLOT)
state = spec.upgrade_to_phase1(state) # `upgrade_to_phase1` is a pure function
transition_to(spec, state, spec.PHASE_1_FORK_SLOT)
next_slot(spec, state)
return state


def next_epoch(spec, state):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from eth2spec.test.context import (
PHASE0,
spec_state_test, expect_assertion_error, always_bls, with_all_phases, with_phases
spec_state_test, expect_assertion_error, always_bls, with_all_phases
)
from eth2spec.test.helpers.attestations import sign_indexed_attestation
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing, \
Expand Down Expand Up @@ -197,7 +196,7 @@ def test_participants_already_slashed(spec, state):
# Some of the following tests are phase0 only: phase 1 lists participants with bitfields instead of index list.


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_high_index(spec, state):
Expand All @@ -210,7 +209,7 @@ def test_att1_high_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_high_index(spec, state):
Expand All @@ -223,7 +222,7 @@ def test_att2_high_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_empty_indices(spec, state):
Expand All @@ -235,7 +234,7 @@ def test_att1_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_empty_indices(spec, state):
Expand All @@ -247,7 +246,7 @@ def test_att2_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_all_empty_indices(spec, state):
Expand All @@ -262,7 +261,7 @@ def test_all_empty_indices(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_bad_extra_index(spec, state):
Expand All @@ -278,7 +277,7 @@ def test_att1_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_bad_replaced_index(spec, state):
Expand All @@ -294,7 +293,7 @@ def test_att1_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_bad_extra_index(spec, state):
Expand All @@ -310,7 +309,7 @@ def test_att2_bad_extra_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_bad_replaced_index(spec, state):
Expand All @@ -326,7 +325,7 @@ def test_att2_bad_replaced_index(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_duplicate_index_normal_signed(spec, state):
Expand All @@ -346,7 +345,7 @@ def test_att1_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_duplicate_index_normal_signed(spec, state):
Expand All @@ -366,7 +365,7 @@ def test_att2_duplicate_index_normal_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att1_duplicate_index_double_signed(spec, state):
Expand All @@ -381,7 +380,7 @@ def test_att1_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
@always_bls
def test_att2_duplicate_index_double_signed(spec, state):
Expand All @@ -396,7 +395,7 @@ def test_att2_duplicate_index_double_signed(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_unsorted_att_1(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True)
Expand All @@ -409,7 +408,7 @@ def test_unsorted_att_1(spec, state):
yield from run_attester_slashing_processing(spec, state, attester_slashing, False)


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_unsorted_att_2(spec, state):
attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from eth2spec.test.context import (
PHASE0,
spec_state_test, spec_test,
with_all_phases, with_phases, single_phase,
with_all_phases, single_phase,
with_custom_state,
zero_activation_threshold,
misc_balances, low_single_balance,
Expand All @@ -25,7 +24,7 @@ def run_process_rewards_and_penalties(spec, state):
yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties')


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_no_attestations_no_penalties(spec, state):
pre_state = state.copy()
Expand All @@ -38,7 +37,7 @@ def test_genesis_epoch_no_attestations_no_penalties(spec, state):
assert state.balances[index] == pre_state.balances[index]


@with_phases([PHASE0])
@with_all_phases
@spec_state_test
def test_genesis_epoch_full_attestations_no_rewards(spec, state):
attestations = []
Expand Down
Loading

0 comments on commit fc38fc1

Please sign in to comment.