diff --git a/scripts/build_spec.py b/scripts/build_spec.py index 9232cf00b..a16fa79ac 100644 --- a/scripts/build_spec.py +++ b/scripts/build_spec.py @@ -25,8 +25,6 @@ from dataclasses import ( field, ) -from copy import deepcopy - from eth2spec.utils.ssz.ssz_impl import ( hash_tree_root, signing_root, @@ -60,8 +58,6 @@ from dataclasses import ( field, ) -from copy import deepcopy - from eth2spec.utils.ssz.ssz_impl import ( hash_tree_root, signing_root, diff --git a/specs/core/0_fork-choice.md b/specs/core/0_fork-choice.md index 668b20714..ee318412d 100644 --- a/specs/core/0_fork-choice.md +++ b/specs/core/0_fork-choice.md @@ -47,10 +47,11 @@ The head block root associated with a `store` is defined as `get_head(store)`. A *Notes*: -1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. +1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time). 2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other. 3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](https://github.com/ethereum/eth2.0-specs/blob/dev/specs/validator/0_beacon-chain-validator.md) should ensure that `state.latest_eth1_data` of the canonical Ethereum 2.0 chain remains consistent with the canonical Ethereum 1.0 chain. If not, emergency manual intervention will be required. 4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`. +5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost). ### Helpers @@ -95,7 +96,13 @@ class Store(object): def get_genesis_store(genesis_state: BeaconState) -> Store: genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state)) root = signing_root(genesis_block) - return Store(blocks={root: genesis_block}, states={root: genesis_state}, justified_root=root, finalized_root=root) + return Store( + blocks={root: genesis_block}, + states={root: genesis_state}, + time=genesis_state.genesis_time, + justified_root=root, + finalized_root=root, + ) ``` #### `get_ancestor` @@ -150,16 +157,17 @@ def on_tick(store: Store, time: int) -> None: ```python def on_block(store: Store, block: BeaconBlock) -> None: + # Make a copy of the state to avoid mutability issues + parent_block = store.blocks[block.parent_root] + pre_state = store.states[RootSlot(signing_root(parent_block), parent_block.slot)].copy() + # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT # Add new block to the store store.blocks[signing_root(block)] = block # Check block is a descendant of the finalized block assert get_ancestor(store, signing_root(block), store.blocks[store.finalized_root].slot) == store.finalized_root # Check that block is later than the finalized epoch slot assert blocks.slot > get_epoch_start_slot(store.finalized_epoch) - # Check block slot against Unix time - parent_block = store.blocks[block.parent_root] - pre_state = deepcopy(store.states[RootSlot(signing_root(parent_block), parent_block.slot)]) - assert store.time >= pre_state.genesis_time + block.slot * SECONDS_PER_SLOT # Check the block is valid and compute the post-state state = state_transition(pre_state, block) # Add new state for this block to the store diff --git a/test_libs/pyspec/eth2spec/test/test_fork_choice.py b/test_libs/pyspec/eth2spec/test/test_fork_choice.py index ab1728251..4bc7e8b0a 100644 --- a/test_libs/pyspec/eth2spec/test/test_fork_choice.py +++ b/test_libs/pyspec/eth2spec/test/test_fork_choice.py @@ -2,7 +2,7 @@ from typing import List from eth2spec.utils.ssz.ssz_impl import signing_root, hash_tree_root -from eth2spec.test.context import with_all_phases, spec_state_test +from eth2spec.test.context import with_all_phases, with_state, bls_switch from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.attestations import get_valid_attestation @@ -10,10 +10,10 @@ from eth2spec.test.helpers.state import next_slot @with_all_phases -@spec_state_test +@with_state +@bls_switch def test_basic(spec, state): state.latest_block_header = spec.BeaconBlockHeader(body_root=hash_tree_root(spec.BeaconBlockBody())) - yield 'pre', state # Initialization store = spec.get_genesis_store(state) @@ -36,17 +36,14 @@ def test_basic(spec, state): spec.on_block(store, block) assert store.blocks[signing_root(block)] == block - yield 'blocks', blocks, List[spec.BeaconBlock] # TODO: add tests for justified_root and finalized_root - yield 'post', state @with_all_phases -@spec_state_test +@with_state +@bls_switch def test_on_attestation(spec, state): - yield 'pre', state - store = spec.get_genesis_store(state) time = 100 spec.on_tick(store, time) @@ -54,7 +51,6 @@ def test_on_attestation(spec, state): next_slot(spec, state) attestation = get_valid_attestation(spec, state, slot=1) - yield 'attestation', attestation indexed_attestation = spec.convert_to_indexed(state, attestation) spec.on_attestation(store, attestation) assert ( @@ -64,5 +60,3 @@ def test_on_attestation(spec, state): root=attestation.data.target_root, ) ) - - yield 'post', state diff --git a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py index 64d50b579..077f94b49 100644 --- a/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/test_libs/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -1,3 +1,4 @@ +import copy from types import GeneratorType from typing import ( List, @@ -151,6 +152,9 @@ class Container(object): def __hash__(self): return hash(self.hash_tree_root()) + def copy(self): + return copy.deepcopy(self) + @classmethod def get_fields_dict(cls): return dict(cls.__annotations__)