diff --git a/.circleci/config.yml b/.circleci/config.yml index 5958a2fc6..b41699f71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -168,6 +168,20 @@ jobs: command: make citest fork=eip6110 - store_test_results: path: tests/core/pyspec/test-reports + test-attslotrange: + docker: + - image: circleci/python:3.8 + working_directory: ~/specs-repo + steps: + - restore_cache: + key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} + - restore_pyspec_cached_venv + - run: + name: Run py-tests + command: make citest fork=attslotrange + - store_test_results: + path: tests/core/pyspec/test-reports + table_of_contents: docker: - image: circleci/node:10.16.3 diff --git a/.gitignore b/.gitignore index 82026c27b..b64dbde43 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ tests/core/pyspec/eth2spec/bellatrix/ tests/core/pyspec/eth2spec/capella/ tests/core/pyspec/eth2spec/deneb/ tests/core/pyspec/eth2spec/eip6110/ +tests/core/pyspec/eth2spec/attslotrange/ # coverage reports .htmlcov diff --git a/Makefile b/Makefile index ab5521663..b0990bd51 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/*/*.md) \ $(wildcard $(SPEC_DIR)/_features/*/*/*.md) \ $(wildcard $(SSZ_DIR)/*.md) -ALL_EXECUTABLE_SPECS = phase0 altair bellatrix capella deneb eip6110 +ALL_EXECUTABLE_SPECS = phase0 altair bellatrix capella deneb eip6110 attslotrange # The parameters for commands. Use `foreach` to avoid listing specs again. COVERAGE_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPECS), --cov=eth2spec.$S.$(TEST_PRESET_TYPE)) PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPECS), ./eth2spec/$S) diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index 5ad394c08..1fee9abf6 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -53,6 +53,9 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000000 # temporary stub EIP6110_FORK_EPOCH: 18446744073709551615 +# AttSlotRange +ATTSLOTRANGE_FORK_VERSION: 0x05000000 # temporary stub +ATTSLOTRANGE_FORK_EPOCH: 18446744073709551615 # Time parameters diff --git a/configs/minimal.yaml b/configs/minimal.yaml index 5895cfc70..acc685480 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -52,6 +52,11 @@ DENEB_FORK_EPOCH: 18446744073709551615 # EIP6110 EIP6110_FORK_VERSION: 0x05000001 EIP6110_FORK_EPOCH: 18446744073709551615 +# AttSlotRange +ATTSLOTRANGE_FORK_VERSION: 0x05000001 +ATTSLOTRANGE_FORK_EPOCH: 18446744073709551615 + + # Time parameters diff --git a/setup.py b/setup.py index b2316ed95..23c4ac19c 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ BELLATRIX = 'bellatrix' CAPELLA = 'capella' DENEB = 'deneb' EIP6110 = 'eip6110' +ATTSLOTRANGE= 'attslotrange' # The helper functions that are used when defining constants @@ -680,11 +681,23 @@ class EIP6110SpecBuilder(DenebSpecBuilder): from eth2spec.deneb import {preset_name} as deneb ''' +# +# AttSlotRangeSpecBuilder +# +class AttSlotRangeSpecBuilder(DenebSpecBuilder): + fork: str = ATTSLOTRANGE -spec_builders = { - builder.fork: builder - for builder in (Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder, EIP6110SpecBuilder) -} + @classmethod + def imports(cls, preset_name: str): + return super().imports(preset_name) + f''' +from eth2spec.deneb import {preset_name} as deneb +''' + +all_builders = ( + Phase0SpecBuilder, AltairSpecBuilder, BellatrixSpecBuilder, CapellaSpecBuilder, DenebSpecBuilder, + EIP6110SpecBuilder, AttSlotRangeSpecBuilder, +) +spec_builders = {builder.fork: builder for builder in all_builders} def is_byte_vector(value: str) -> bool: @@ -982,14 +995,14 @@ class PySpecCommand(Command): if len(self.md_doc_paths) == 0: print("no paths were specified, using default markdown file paths for pyspec" " build (spec fork: %s)" % self.spec_fork) - if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110): + if self.spec_fork in (PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, ATTSLOTRANGE): self.md_doc_paths = """ specs/phase0/beacon-chain.md specs/phase0/fork-choice.md specs/phase0/validator.md specs/phase0/weak-subjectivity.md """ - if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110): + if self.spec_fork in (ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, ATTSLOTRANGE): self.md_doc_paths += """ specs/altair/light-client/full-node.md specs/altair/light-client/light-client.md @@ -1001,7 +1014,7 @@ class PySpecCommand(Command): specs/altair/validator.md specs/altair/p2p-interface.md """ - if self.spec_fork in (BELLATRIX, CAPELLA, DENEB, EIP6110): + if self.spec_fork in (BELLATRIX, CAPELLA, DENEB, EIP6110, ATTSLOTRANGE): self.md_doc_paths += """ specs/bellatrix/beacon-chain.md specs/bellatrix/fork.md @@ -1010,7 +1023,7 @@ class PySpecCommand(Command): specs/bellatrix/p2p-interface.md sync/optimistic.md """ - if self.spec_fork in (CAPELLA, DENEB, EIP6110): + if self.spec_fork in (CAPELLA, DENEB, EIP6110, ATTSLOTRANGE): self.md_doc_paths += """ specs/capella/light-client/fork.md specs/capella/light-client/full-node.md @@ -1022,7 +1035,7 @@ class PySpecCommand(Command): specs/capella/validator.md specs/capella/p2p-interface.md """ - if self.spec_fork in (DENEB, EIP6110): + if self.spec_fork in (DENEB, EIP6110, ATTSLOTRANGE): self.md_doc_paths += """ specs/deneb/light-client/fork.md specs/deneb/light-client/full-node.md @@ -1044,6 +1057,12 @@ class PySpecCommand(Command): specs/_features/eip6110/beacon-chain.md specs/_features/eip6110/fork.md """ + if self.spec_fork == ATTSLOTRANGE: + self.md_doc_paths += """ + specs/_features/attslotrange/beacon-chain.md + specs/_features/attslotrange/fork.md + """ + if len(self.md_doc_paths) == 0: raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) diff --git a/specs/_features/attslotrange/beacon-chain.md b/specs/_features/attslotrange/beacon-chain.md new file mode 100644 index 000000000..cc0acc61e --- /dev/null +++ b/specs/_features/attslotrange/beacon-chain.md @@ -0,0 +1,74 @@ +# Deneb -- The Beacon Chain + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Preset](#preset) +- [Configuration](#configuration) +- [Containers](#containers) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Modified `process_attestation`](#modified-process_attestation) + + + + +## Introduction + +This feature allows for inclusion of attestations created during epoch `N` to be included in slots from epoch `N` as well as all slots in epoch `N+1` rather than the current `SLOTS_PER_EPOCH` slot restricted range. This is an extension of the Deneb upgrade. + +## Preset + +## Configuration + +## Containers + +## Beacon chain state transition function + +### Block processing + +#### Modified `process_attestation` + +*Note*: The function `process_attestation` is modified to expand valid slots for inclusion tothose in the `target.epoch` epoch as well as those in the `target.epoch + 1` epoch. + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot # [Modified in AttSlotRange] + assert data.index < get_committee_count_per_slot(state, data.target.epoch) + + committee = get_beacon_committee(state, data.slot, data.index) + assert len(attestation.aggregation_bits) == len(committee) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +``` diff --git a/specs/_features/attslotrange/fork.md b/specs/_features/attslotrange/fork.md new file mode 100644 index 000000000..3d8454c2a --- /dev/null +++ b/specs/_features/attslotrange/fork.md @@ -0,0 +1,123 @@ +# Att-Slot-Range -- Fork Logic + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to AttSlotRange](#fork-to-attslotrange) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of Att-Slot-Range upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| - | - | +| `ATTSLOTRANGE_FORK_VERSION` | `Version('0x05000000')` | +| `ATTSLOTRANGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= ATTSLOTRANGE_FORK_EPOCH: + return ATTSLOTRANGE_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to AttSlotRange + +### Fork trigger + +TBD. This fork is defined for testing purposes, the EIP may be combined with other consensus-layer upgrade. +For now, we assume the condition will be triggered at epoch `ATTSLOTRANGE_FORK_EPOCH`. + +Note that for the pure AttSlotRange networks, we don't apply `upgrade_to_attslotrange` since it starts with AttSlotRange version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == ATTSLOTRANGE_FORK_EPOCH, +an irregular state change is made to upgrade to AttSlotRange. + +```python +def upgrade_to_attslotrange(pre: deneb.BeaconState) -> BeaconState: + 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=ATTSLOTRANGE_FORK_VERSION, # [Modified in Att-Slot-Range] + epoch=deneb.get_current_epoch(pre), + ), + # 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, + # Participation + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + # 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, + # Inactivity + inactivity_scores=pre.inactivity_scores, + # Sync + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # Execution-layer + latest_execution_payload_header=pre.latest_execution_payload_header, + # Withdrawals + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + # Deep history valid from Capella onwards + historical_summaries=pre.historical_summaries, + ) + + return post +``` diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index 901fd273a..4ffae34af 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -9,12 +9,13 @@ from eth2spec.bellatrix import mainnet as spec_bellatrix_mainnet, minimal as spe from eth2spec.capella import mainnet as spec_capella_mainnet, minimal as spec_capella_minimal from eth2spec.deneb import mainnet as spec_deneb_mainnet, minimal as spec_deneb_minimal from eth2spec.eip6110 import mainnet as spec_eip6110_mainnet, minimal as spec_eip6110_minimal +from eth2spec.attslotrange import mainnet as spec_attslotrange_mainnet, minimal as spec_attslotrange_minimal from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, - EIP6110, + EIP6110, ATTSLOTRANGE, MINIMAL, MAINNET, ALL_PHASES, ALL_FORK_UPGRADES, @@ -82,6 +83,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { CAPELLA: spec_capella_minimal, DENEB: spec_deneb_minimal, EIP6110: spec_eip6110_minimal, + ATTSLOTRANGE: spec_attslotrange_minimal, }, MAINNET: { PHASE0: spec_phase0_mainnet, @@ -90,6 +92,7 @@ spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { CAPELLA: spec_capella_mainnet, DENEB: spec_deneb_mainnet, EIP6110: spec_eip6110_mainnet, + ATTSLOTRANGE: spec_attslotrange_mainnet, }, } @@ -433,6 +436,7 @@ with_bellatrix_and_later = with_all_phases_from(BELLATRIX) with_capella_and_later = with_all_phases_from(CAPELLA) with_deneb_and_later = with_all_phases_from(DENEB) with_eip6110_and_later = with_all_phases_from(EIP6110) +with_attslotrange_and_later = with_all_phases_from(ATTSLOTRANGE) def _get_preset_targets(kw): diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 2140c96e4..a3339305e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -16,13 +16,14 @@ SHARDING = SpecForkName('sharding') CUSTODY_GAME = SpecForkName('custody_game') DAS = SpecForkName('das') EIP6110 = SpecForkName('eip6110') +ATTSLOTRANGE = SpecForkName('attslotrange') # The forks that pytest can run with. ALL_PHASES = ( # Formal forks PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, # Experimental patches - EIP6110, + EIP6110, ATTSLOTRANGE, ) # The forks that output to the test vectors. TESTGEN_FORKS = (PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py index 68444c472..76b332728 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -16,6 +16,7 @@ from eth2spec.test.helpers.constants import ( CAPELLA, DENEB, EIP6110, + ATTSLOTRANGE, ) from eth2spec.test.helpers.deposits import ( prepare_state_and_deposit, @@ -161,6 +162,8 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate= state = post_spec.upgrade_to_deneb(state) elif post_spec.fork == EIP6110: state = post_spec.upgrade_to_eip6110(state) + elif post_spec.fork == ATTSLOTRANGE: + state = post_spec.upgrade_to_attslotrange(state) assert state.fork.epoch == fork_epoch @@ -179,6 +182,9 @@ def do_fork(state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate= elif post_spec.fork == EIP6110: assert state.fork.previous_version == post_spec.config.DENEB_FORK_VERSION assert state.fork.current_version == post_spec.config.EIP6110_FORK_VERSION + elif post_spec.fork == ATTSLOTRANGE: + assert state.fork.previous_version == post_spec.config.DENEB_FORK_VERSION + assert state.fork.current_version == post_spec.config.ATTSLOTRANGE_FORK_VERSION if with_block: return state, _state_transition_and_sign_block_at_slot( diff --git a/tests/core/pyspec/eth2spec/test/helpers/forks.py b/tests/core/pyspec/eth2spec/test/helpers/forks.py index 5e97522db..35c265ebb 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/forks.py +++ b/tests/core/pyspec/eth2spec/test/helpers/forks.py @@ -1,10 +1,12 @@ from .constants import ( PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, - EIP6110, + EIP6110, ATTSLOTRANGE, ) def is_post_fork(a, b): + if a == ATTSLOTRANGE: + return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ATTSLOTRANGE] if a == EIP6110: return b in [PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110] if a == DENEB: @@ -38,3 +40,7 @@ def is_post_deneb(spec): def is_post_eip6110(spec): return is_post_fork(spec.fork, EIP6110) + + +def is_post_attslotrange(spec): + return is_post_fork(spec.fork, ATTSLOTRANGE) diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index fea259013..e3649b8e9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.constants import ( - ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, + ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP6110, ATTSLOTRANGE, ) from eth2spec.test.helpers.execution_payload import ( compute_el_header_block_hash, @@ -86,6 +86,9 @@ def create_genesis_state(spec, validator_balances, activation_threshold): elif spec.fork == EIP6110: previous_version = spec.config.DENEB_FORK_VERSION current_version = spec.config.EIP6110_FORK_VERSION + elif spec.fork == ATTSLOTRANGE: + previous_version = spec.config.DENEB_FORK_VERSION + current_version = spec.config.ATTSLOTRANGE_FORK_VERSION state = spec.BeaconState( genesis_time=0,