From 0b8ab23bd4fdb38a1bc38e0f242fc892cee71663 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 27 Apr 2022 09:50:27 +0200 Subject: [PATCH] Remove `fork_version` from `LightClientUpdate` The `fork_version` field in `LightClientUpdate` can be derived from the `update.signature_slot` value by consulting the locally configured fork schedule. The light client already needs access to the fork schedule to determine the `GeneralizedIndex` values used for merkle proofs, and the memory layouts of the structures (including `LightClientUpdate`). The `fork_version` itself is network dependent and doesn't reveal that info. --- specs/altair/beacon-chain.md | 13 +++++++ specs/altair/sync-protocol.md | 5 ++- specs/bellatrix/beacon-chain.md | 15 ++++++++ specs/capella/beacon-chain.md | 20 +++++++++++ .../altair/unittests/test_sync_protocol.py | 33 +++++++++++++----- .../eth2spec/test/helpers/light_client.py | 34 +++++++++++++++++-- 6 files changed, 107 insertions(+), 13 deletions(-) diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index a14ddb4f6..fadce93ef 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -28,6 +28,7 @@ - [Helper functions](#helper-functions) - [Crypto](#crypto) - [Misc](#misc-1) + - [`compute_fork_version`](#compute_fork_version) - [`add_flag`](#add_flag) - [`has_flag`](#has_flag) - [Beacon state accessors](#beacon-state-accessors) @@ -226,6 +227,18 @@ the functionality described in those documents. ### Misc +#### `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + #### `add_flag` ```python diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 1aa9316d3..a3b10efa2 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -71,8 +71,6 @@ class LightClientUpdate(Container): finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature sync_aggregate: SyncAggregate - # Fork version for the aggregate signature - fork_version: Version # Slot at which the aggregate signature was created (untrusted) signature_slot: Slot ``` @@ -211,7 +209,8 @@ def validate_light_client_update(store: LightClientStore, pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit ] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) + fork_version = compute_fork_version(compute_epoch_at_slot(update.signature_slot)) + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root) signing_root = compute_signing_root(update.attested_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) ``` diff --git a/specs/bellatrix/beacon-chain.md b/specs/bellatrix/beacon-chain.md index b37a8ab71..f92c4cbf5 100644 --- a/specs/bellatrix/beacon-chain.md +++ b/specs/bellatrix/beacon-chain.md @@ -29,6 +29,7 @@ - [`is_execution_enabled`](#is_execution_enabled) - [Misc](#misc) - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) + - [Modified `compute_fork_version`](#modified-compute_fork_version) - [Beacon state accessors](#beacon-state-accessors) - [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas) - [Beacon state mutators](#beacon-state-mutators) @@ -242,6 +243,20 @@ def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + ### Beacon state accessors #### Modified `get_inactivity_penalty_deltas` diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md index da67a8fcb..e2b47dd1f 100644 --- a/specs/capella/beacon-chain.md +++ b/specs/capella/beacon-chain.md @@ -34,6 +34,8 @@ - [`has_eth1_withdrawal_credential`](#has_eth1_withdrawal_credential) - [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator) - [`is_partially_withdrawable_validator`](#is_partially_withdrawable_validator) + - [Misc](#misc-1) + - [Modified `compute_fork_version`](#modified-compute_fork_version) - [Beacon chain state transition function](#beacon-chain-state-transition-function) - [Epoch processing](#epoch-processing) - [Full withdrawals](#full-withdrawals) @@ -318,6 +320,24 @@ def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> return has_eth1_withdrawal_credential(validator) and has_max_effective_balance and has_excess_balance ``` +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + 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 +``` + ## Beacon chain state transition function ### Epoch processing diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py index 6411ecb6a..4acfa6424 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py @@ -16,6 +16,7 @@ from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.light_client import ( get_sync_aggregate, initialize_light_client_store, + override_config_fork_epochs, ) from eth2spec.test.helpers.state import ( next_slots, @@ -27,6 +28,9 @@ from eth2spec.test.helpers.merkle import build_proof @with_altair_and_later @spec_state_test def test_process_light_client_update_not_timeout(spec, state): + old_config = spec.config + override_config_fork_epochs(spec, state) + store = initialize_light_client_store(spec, state) # Block at slot 1 doesn't increase sync committee period, so it won't force update store.finalized_header @@ -41,7 +45,7 @@ def test_process_light_client_update_not_timeout(spec, state): ) # Sync committee signing the block_header - sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header) + sync_aggregate, signature_slot = get_sync_aggregate(spec, state, block_header) next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] # Ensure that finality checkpoint is genesis @@ -57,7 +61,6 @@ def test_process_light_client_update_not_timeout(spec, state): finalized_header=finality_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, - fork_version=fork_version, signature_slot=signature_slot, ) @@ -70,11 +73,16 @@ def test_process_light_client_update_not_timeout(spec, state): assert store.finalized_header == pre_store.finalized_header assert store.best_valid_update == update + spec.config = old_config + @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_at_period_boundary(spec, state): + old_config = spec.config + override_config_fork_epochs(spec, state) + store = initialize_light_client_store(spec, state) # Forward to slot before next sync committee period so that next block is final one in period @@ -94,7 +102,7 @@ def test_process_light_client_update_at_period_boundary(spec, state): ) # Sync committee signing the block_header - sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header) + sync_aggregate, signature_slot = get_sync_aggregate(spec, state, block_header) next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] # Finality is unchanged @@ -108,7 +116,6 @@ def test_process_light_client_update_at_period_boundary(spec, state): finalized_header=finality_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, - fork_version=fork_version, signature_slot=signature_slot, ) @@ -121,11 +128,16 @@ def test_process_light_client_update_at_period_boundary(spec, state): assert store.best_valid_update == update assert store.finalized_header == pre_store.finalized_header + spec.config = old_config + @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_timeout(spec, state): + old_config = spec.config + override_config_fork_epochs(spec, state) + store = initialize_light_client_store(spec, state) # Forward to next sync committee period @@ -145,7 +157,7 @@ def test_process_light_client_update_timeout(spec, state): ) # Sync committee signing the block_header - sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header) + sync_aggregate, signature_slot = get_sync_aggregate(spec, state, block_header) # Sync committee is updated next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) @@ -160,7 +172,6 @@ def test_process_light_client_update_timeout(spec, state): finalized_header=finality_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, - fork_version=fork_version, signature_slot=signature_slot, ) @@ -173,11 +184,16 @@ def test_process_light_client_update_timeout(spec, state): assert store.best_valid_update == update assert store.finalized_header == pre_store.finalized_header + spec.config = old_config + @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_finality_updated(spec, state): + old_config = spec.config + override_config_fork_epochs(spec, state) + store = initialize_light_client_store(spec, state) # Change finality @@ -211,7 +227,7 @@ def test_process_light_client_update_finality_updated(spec, state): ) # Sync committee signing the block_header - sync_aggregate, fork_version, signature_slot = get_sync_aggregate(spec, state, block_header) + sync_aggregate, signature_slot = get_sync_aggregate(spec, state, block_header) update = spec.LightClientUpdate( attested_header=block_header, @@ -220,7 +236,6 @@ def test_process_light_client_update_finality_updated(spec, state): finalized_header=finalized_block_header, finality_branch=finality_branch, sync_aggregate=sync_aggregate, - fork_version=fork_version, signature_slot=signature_slot, ) @@ -230,3 +245,5 @@ def test_process_light_client_update_finality_updated(spec, state): assert store.optimistic_header == update.attested_header assert store.finalized_header == update.finalized_header assert store.best_valid_update is None + + spec.config = old_config diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client.py b/tests/core/pyspec/eth2spec/test/helpers/light_client.py index b35c6c687..6868cad86 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/light_client.py +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from eth2spec.test.helpers.state import ( transition_to, ) @@ -7,6 +9,35 @@ from eth2spec.test.helpers.sync_committee import ( ) +def override_config_fork_epochs(spec, state): + # Test framework adjusts state fork but leaves spec config constants inconsistent + config_overrides = {} + if state.fork.current_version == spec.config.GENESIS_FORK_VERSION: + pass + elif state.fork.current_version == spec.config.ALTAIR_FORK_VERSION: + config_overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH + elif state.fork.current_version == spec.config.BELLATRIX_FORK_VERSION: + config_overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['BELLATRIX_FORK_EPOCH'] = spec.GENESIS_EPOCH + elif state.fork.current_version == spec.config.CAPELLA_FORK_VERSION: + config_overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['BELLATRIX_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['CAPELLA_FORK_EPOCH'] = spec.GENESIS_EPOCH + elif state.fork.current_version == spec.config.SHARDING_FORK_VERSION: + config_overrides['ALTAIR_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['BELLATRIX_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['CAPELLA_FORK_EPOCH'] = spec.GENESIS_EPOCH + config_overrides['SHARDING_FORK_EPOCH'] = spec.GENESIS_EPOCH + else: + assert False + + tmp_config = deepcopy(spec.config._asdict()) + tmp_config.update(config_overrides) + config_types = spec.Configuration.__annotations__ + test_config = {k: config_types[k](v) for k, v in tmp_config.items()} + spec.config = spec.Configuration(**test_config) + + def initialize_light_client_store(spec, state): return spec.LightClientStore( finalized_header=spec.BeaconBlockHeader(), @@ -45,5 +76,4 @@ def get_sync_aggregate(spec, state, block_header, signature_slot=None): sync_committee_bits=sync_committee_bits, sync_committee_signature=sync_committee_signature, ) - fork_version = signature_state.fork.current_version - return sync_aggregate, fork_version, signature_slot + return sync_aggregate, signature_slot