diff --git a/README.md b/README.md index 58bff5b9e4..c62a4171dc 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Features are researched and developed in parallel, and then consolidated into se ### In-development Specifications | Code Name or Topic | Specs | Notes | | - | - | - | -| Electra | | +| Electra | | | Sharding (outdated) | | | Custody Game (outdated) | | Dependent on sharding | | Data Availability Sampling (outdated) | | | diff --git a/pysetup/spec_builders/electra.py b/pysetup/spec_builders/electra.py index 1f968a817d..ca02ee927c 100644 --- a/pysetup/spec_builders/electra.py +++ b/pysetup/spec_builders/electra.py @@ -12,12 +12,10 @@ def imports(cls, preset_name: str): from eth2spec.deneb import {preset_name} as deneb ''' -## TODO: deal with changed gindices - @classmethod def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: return { - 'FINALIZED_ROOT_GINDEX': 'GeneralizedIndex(169)', - 'CURRENT_SYNC_COMMITTEE_GINDEX': 'GeneralizedIndex(86)', - 'NEXT_SYNC_COMMITTEE_GINDEX': 'GeneralizedIndex(87)', + 'FINALIZED_ROOT_GINDEX_ELECTRA': 'GeneralizedIndex(169)', + 'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(86)', + 'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA': 'GeneralizedIndex(87)', } diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md index cea38613db..d887691c67 100644 --- a/specs/altair/light-client/full-node.md +++ b/specs/altair/light-client/full-node.md @@ -76,7 +76,7 @@ def create_light_client_bootstrap(state: BeaconState, header=block_to_light_client_header(block), current_sync_committee=state.current_sync_committee, current_sync_committee_branch=CurrentSyncCommitteeBranch( - compute_merkle_proof(state, CURRENT_SYNC_COMMITTEE_GINDEX)), + compute_merkle_proof(state, current_sync_committee_gindex_at_slot(state.slot))), ) ``` @@ -124,7 +124,7 @@ def create_light_client_update(state: BeaconState, if update_attested_period == update_signature_period: update.next_sync_committee = attested_state.next_sync_committee update.next_sync_committee_branch = NextSyncCommitteeBranch( - compute_merkle_proof(attested_state, NEXT_SYNC_COMMITTEE_GINDEX)) + compute_merkle_proof(attested_state, next_sync_committee_gindex_at_slot(attested_state.slot))) # Indicate finality whenever possible if finalized_block is not None: @@ -134,7 +134,7 @@ def create_light_client_update(state: BeaconState, else: assert attested_state.finalized_checkpoint.root == Bytes32() update.finality_branch = FinalityBranch( - compute_merkle_proof(attested_state, FINALIZED_ROOT_GINDEX)) + compute_merkle_proof(attested_state, finalized_root_gindex_at_slot(attested_state.slot))) update.sync_aggregate = block.message.body.sync_aggregate update.signature_slot = block.message.slot diff --git a/specs/altair/light-client/sync-protocol.md b/specs/altair/light-client/sync-protocol.md index 2585889bf9..f7f38d1041 100644 --- a/specs/altair/light-client/sync-protocol.md +++ b/specs/altair/light-client/sync-protocol.md @@ -21,6 +21,9 @@ - [`LightClientOptimisticUpdate`](#lightclientoptimisticupdate) - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) + - [`finalized_root_gindex_at_slot`](#finalized_root_gindex_at_slot) + - [`current_sync_committee_gindex_at_slot`](#current_sync_committee_gindex_at_slot) + - [`next_sync_committee_gindex_at_slot`](#next_sync_committee_gindex_at_slot) - [`is_valid_light_client_header`](#is_valid_light_client_header) - [`is_sync_committee_update`](#is_sync_committee_update) - [`is_finality_update`](#is_finality_update) @@ -28,6 +31,7 @@ - [`is_next_sync_committee_known`](#is_next_sync_committee_known) - [`get_safety_threshold`](#get_safety_threshold) - [`get_subtree_index`](#get_subtree_index) + - [`is_valid_normalized_merkle_branch`](#is_valid_normalized_merkle_branch) - [`compute_sync_committee_period_at_slot`](#compute_sync_committee_period_at_slot) - [Light client initialization](#light-client-initialization) - [`initialize_light_client_store`](#initialize_light_client_store) @@ -171,6 +175,30 @@ class LightClientStore(object): ## Helper functions +### `finalized_root_gindex_at_slot` + +```python +def finalized_root_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + # pylint: disable=unused-argument + return FINALIZED_ROOT_GINDEX +``` + +### `current_sync_committee_gindex_at_slot` + +```python +def current_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + # pylint: disable=unused-argument + return CURRENT_SYNC_COMMITTEE_GINDEX +``` + +### `next_sync_committee_gindex_at_slot` + +```python +def next_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + # pylint: disable=unused-argument + return NEXT_SYNC_COMMITTEE_GINDEX +``` + ### `is_valid_light_client_header` ```python @@ -273,6 +301,22 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` +### `is_valid_normalized_merkle_branch` + +```python +def is_valid_normalized_merkle_branch(leaf: Bytes32, + branch: Sequence[Bytes32], + gindex: GeneralizedIndex, + root: Root) -> bool: + depth = floorlog2(gindex) + index = get_subtree_index(gindex) + num_extra = len(branch) - depth + for i in range(num_extra): + if branch[i] != Bytes32(): + return False + return is_valid_merkle_branch(leaf, branch[num_extra:], depth, index, root) +``` + ### `compute_sync_committee_period_at_slot` ```python @@ -292,11 +336,10 @@ def initialize_light_client_store(trusted_block_root: Root, assert is_valid_light_client_header(bootstrap.header) assert hash_tree_root(bootstrap.header.beacon) == trusted_block_root - assert is_valid_merkle_branch( + assert is_valid_normalized_merkle_branch( leaf=hash_tree_root(bootstrap.current_sync_committee), branch=bootstrap.current_sync_committee_branch, - depth=floorlog2(CURRENT_SYNC_COMMITTEE_GINDEX), - index=get_subtree_index(CURRENT_SYNC_COMMITTEE_GINDEX), + gindex=current_sync_committee_gindex_at_slot(bootstrap.header.beacon.slot), root=bootstrap.header.beacon.state_root, ) @@ -364,11 +407,10 @@ def validate_light_client_update(store: LightClientStore, else: assert is_valid_light_client_header(update.finalized_header) finalized_root = hash_tree_root(update.finalized_header.beacon) - assert is_valid_merkle_branch( + assert is_valid_normalized_merkle_branch( leaf=finalized_root, branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_GINDEX), - index=get_subtree_index(FINALIZED_ROOT_GINDEX), + gindex=finalized_root_gindex_at_slot(update.attested_header.beacon.slot), root=update.attested_header.beacon.state_root, ) @@ -379,11 +421,10 @@ def validate_light_client_update(store: LightClientStore, else: if update_attested_period == store_period and is_next_sync_committee_known(store): assert update.next_sync_committee == store.next_sync_committee - assert is_valid_merkle_branch( + assert is_valid_normalized_merkle_branch( leaf=hash_tree_root(update.next_sync_committee), branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_GINDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_GINDEX), + gindex=next_sync_committee_gindex_at_slot(update.attested_header.beacon.slot), root=update.attested_header.beacon.state_root, ) diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md index 6dcc7578c2..6fcb6e3147 100644 --- a/specs/capella/light-client/fork.md +++ b/specs/capella/light-client/fork.md @@ -7,8 +7,8 @@ - [Introduction](#introduction) - - [Upgrading light client data](#upgrading-light-client-data) - - [Upgrading the store](#upgrading-the-store) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) @@ -17,7 +17,7 @@ This document describes how to upgrade existing light client objects based on the [Altair specification](../../altair/light-client/sync-protocol.md) to Capella. This is necessary when processing pre-Capella data with a post-Capella `LightClientStore`. Note that the data being exchanged over the network protocols uses the original format. -### Upgrading light client data +## Upgrading light client data A Capella `LightClientStore` can still process earlier light client data. In order to do so, that pre-Capella data needs to be locally upgraded to Capella before processing. @@ -70,7 +70,7 @@ def upgrade_lc_optimistic_update_to_capella(pre: bellatrix.LightClientOptimistic ) ``` -### Upgrading the store +## Upgrading the store Existing `LightClientStore` objects based on Altair MUST be upgraded to Capella before Capella based light client data can be processed. The `LightClientStore` upgrade MAY be performed before `CAPELLA_FORK_EPOCH`. diff --git a/specs/deneb/light-client/fork.md b/specs/deneb/light-client/fork.md index 2dce4778ed..07230a21c7 100644 --- a/specs/deneb/light-client/fork.md +++ b/specs/deneb/light-client/fork.md @@ -7,8 +7,8 @@ - [Introduction](#introduction) - - [Upgrading light client data](#upgrading-light-client-data) - - [Upgrading the store](#upgrading-the-store) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) @@ -17,7 +17,7 @@ This document describes how to upgrade existing light client objects based on the [Capella specification](../../capella/light-client/sync-protocol.md) to Deneb. This is necessary when processing pre-Deneb data with a post-Deneb `LightClientStore`. Note that the data being exchanged over the network protocols uses the original format. -### Upgrading light client data +## Upgrading light client data A Deneb `LightClientStore` can still process earlier light client data. In order to do so, that pre-Deneb data needs to be locally upgraded to Deneb before processing. @@ -90,7 +90,7 @@ def upgrade_lc_optimistic_update_to_deneb(pre: capella.LightClientOptimisticUpda ) ``` -### Upgrading the store +## Upgrading the store Existing `LightClientStore` objects based on Capella MUST be upgraded to Deneb before Deneb based light client data can be processed. The `LightClientStore` upgrade MAY be performed before `DENEB_FORK_EPOCH`. diff --git a/specs/deneb/light-client/full-node.md b/specs/deneb/light-client/full-node.md index db081b8e43..424723667c 100644 --- a/specs/deneb/light-client/full-node.md +++ b/specs/deneb/light-client/full-node.md @@ -17,7 +17,7 @@ ## Introduction -This upgrade adds information about the execution payload to light client data as part of the Deneb upgrade. +Execution payload data is updated to account for the Deneb upgrade. ## Helper functions diff --git a/specs/electra/light-client/fork.md b/specs/electra/light-client/fork.md new file mode 100644 index 0000000000..d613df56a9 --- /dev/null +++ b/specs/electra/light-client/fork.md @@ -0,0 +1,133 @@ +# Electra Light Client -- Fork Logic + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [`normalize_merkle_branch`](#normalize_merkle_branch) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) + + + + +## Introduction + +This document describes how to upgrade existing light client objects based on the [Deneb specification](../../deneb/light-client/sync-protocol.md) to Electra. This is necessary when processing pre-Electra data with a post-Electra `LightClientStore`. Note that the data being exchanged over the network protocols uses the original format. + +## Helper functions + +### `normalize_merkle_branch` + +```python +def normalize_merkle_branch(branch: Sequence[Bytes32], + gindex: GeneralizedIndex) -> Sequence[Bytes32]: + depth = floorlog2(gindex) + num_extra = depth - len(branch) + return [Bytes32()] * num_extra + [*branch] +``` + +## Upgrading light client data + +A Electra `LightClientStore` can still process earlier light client data. In order to do so, that pre-Electra data needs to be locally upgraded to Electra before processing. + +```python +def upgrade_lc_header_to_electra(pre: deneb.LightClientHeader) -> LightClientHeader: + return LightClientHeader( + beacon=pre.beacon, + execution=ExecutionPayloadHeader( + parent_hash=pre.execution.parent_hash, + fee_recipient=pre.execution.fee_recipient, + state_root=pre.execution.state_root, + receipts_root=pre.execution.receipts_root, + logs_bloom=pre.execution.logs_bloom, + prev_randao=pre.execution.prev_randao, + block_number=pre.execution.block_number, + gas_limit=pre.execution.gas_limit, + gas_used=pre.execution.gas_used, + timestamp=pre.execution.timestamp, + extra_data=pre.execution.extra_data, + base_fee_per_gas=pre.execution.base_fee_per_gas, + block_hash=pre.execution.block_hash, + transactions_root=pre.execution.transactions_root, + withdrawals_root=pre.execution.withdrawals_root, + blob_gas_used=pre.execution.blob_gas_used, + excess_blob_gas=pre.execution.blob_gas_used, + deposit_requests_root=Root(), # [New in Electra:EIP6110] + withdrawal_requests_root=Root(), # [New in Electra:EIP7002:EIP7251] + consolidation_requests_root=Root(), # [New in Electra:EIP7251] + ), + execution_branch=pre.execution_branch, + ) +``` + +```python +def upgrade_lc_bootstrap_to_electra(pre: deneb.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_electra(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=normalize_merkle_branch( + pre.current_sync_committee_branch, CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA), + ) +``` + +```python +def upgrade_lc_update_to_electra(pre: deneb.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=normalize_merkle_branch( + pre.next_sync_committee_branch, NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA), + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch=normalize_merkle_branch( + pre.finality_branch, FINALIZED_ROOT_GINDEX_ELECTRA), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_electra(pre: deneb.LightClientFinalityUpdate) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch=normalize_merkle_branch( + pre.finality_branch, FINALIZED_ROOT_GINDEX_ELECTRA), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_electra(pre: deneb.LightClientOptimisticUpdate) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +## Upgrading the store + +Existing `LightClientStore` objects based on Deneb MUST be upgraded to Electra before Electra based light client data can be processed. The `LightClientStore` upgrade MAY be performed before `ELECTRA_FORK_EPOCH`. + +```python +def upgrade_lc_store_to_electra(pre: deneb.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_electra(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + best_valid_update=best_valid_update, + optimistic_header=upgrade_lc_header_to_electra(pre.optimistic_header), + previous_max_active_participants=pre.previous_max_active_participants, + current_max_active_participants=pre.current_max_active_participants, + ) +``` diff --git a/specs/electra/light-client/full-node.md b/specs/electra/light-client/full-node.md new file mode 100644 index 0000000000..f08a2cc5ed --- /dev/null +++ b/specs/electra/light-client/full-node.md @@ -0,0 +1,80 @@ +# Electra Light Client -- Full Node + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [Modified `block_to_light_client_header`](#modified-block_to_light_client_header) + + + + +## Introduction + +Execution payload data is updated to account for the Electra upgrade. + +## Helper functions + +### Modified `block_to_light_client_header` + +```python +def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: + epoch = compute_epoch_at_slot(block.message.slot) + + if epoch >= CAPELLA_FORK_EPOCH: + payload = block.message.body.execution_payload + execution_header = ExecutionPayloadHeader( + parent_hash=payload.parent_hash, + fee_recipient=payload.fee_recipient, + state_root=payload.state_root, + receipts_root=payload.receipts_root, + logs_bloom=payload.logs_bloom, + prev_randao=payload.prev_randao, + block_number=payload.block_number, + gas_limit=payload.gas_limit, + gas_used=payload.gas_used, + timestamp=payload.timestamp, + extra_data=payload.extra_data, + base_fee_per_gas=payload.base_fee_per_gas, + block_hash=payload.block_hash, + transactions_root=hash_tree_root(payload.transactions), + withdrawals_root=hash_tree_root(payload.withdrawals), + ) + if epoch >= DENEB_FORK_EPOCH: + execution_header.blob_gas_used = payload.blob_gas_used + execution_header.excess_blob_gas = payload.excess_blob_gas + + # [New in Electra:EIP6110:EIP7002:EIP7251] + if epoch >= ELECTRA_FORK_EPOCH: + execution_header.deposit_requests_root = hash_tree_root(payload.deposit_requests) + execution_header.withdrawal_requests_root = hash_tree_root(payload.withdrawal_requests) + execution_header.consolidation_requests_root = hash_tree_root(payload.consolidation_requests) + + execution_branch = ExecutionBranch( + compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX)) + else: + # Note that during fork transitions, `finalized_header` may still point to earlier forks. + # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), + # it was not included in the corresponding light client data. To ensure compatibility + # with legacy data going through `upgrade_lc_header_to_capella`, leave out execution data. + execution_header = ExecutionPayloadHeader() + execution_branch = ExecutionBranch() + + return LightClientHeader( + beacon=BeaconBlockHeader( + slot=block.message.slot, + proposer_index=block.message.proposer_index, + parent_root=block.message.parent_root, + state_root=block.message.state_root, + body_root=hash_tree_root(block.message.body), + ), + execution=execution_header, + execution_branch=execution_branch, + ) +``` diff --git a/specs/electra/light-client/p2p-interface.md b/specs/electra/light-client/p2p-interface.md new file mode 100644 index 0000000000..3cbd5dd28f --- /dev/null +++ b/specs/electra/light-client/p2p-interface.md @@ -0,0 +1,111 @@ +# Electra Light Client -- Networking + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Networking](#networking) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`light_client_finality_update`](#light_client_finality_update) + - [`light_client_optimistic_update`](#light_client_optimistic_update) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [GetLightClientBootstrap](#getlightclientbootstrap) + - [LightClientUpdatesByRange](#lightclientupdatesbyrange) + - [GetLightClientFinalityUpdate](#getlightclientfinalityupdate) + - [GetLightClientOptimisticUpdate](#getlightclientoptimisticupdate) + + + + +## Networking + +The [Deneb light client networking specification](../../deneb/light-client/p2p-interface.md) is extended to exchange [Electra light client data](./sync-protocol.md). + +### The gossip domain: gossipsub + +#### Topics and messages + +##### Global topics + +###### `light_client_finality_update` + +[0]: # (eth2spec: skip) + +| `fork_version` | Message SSZ type | +|--------------------------------------------------------|-------------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientFinalityUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientFinalityUpdate` | + +###### `light_client_optimistic_update` + +[0]: # (eth2spec: skip) + +| `fork_version` | Message SSZ type | +|--------------------------------------------------------|---------------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientOptimisticUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientOptimisticUpdate` | + +### The Req/Resp domain + +#### Messages + +##### GetLightClientBootstrap + +[0]: # (eth2spec: skip) + +| `fork_version` | Response SSZ type | +|--------------------------------------------------------|------------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientBootstrap` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientBootstrap` | +| `DENEB_FORK_VERSION` | `deneb.LightClientBootstrap` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientBootstrap` | + +##### LightClientUpdatesByRange + +[0]: # (eth2spec: skip) + +| `fork_version` | Response chunk SSZ type | +|--------------------------------------------------------|----------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientUpdate` | + +##### GetLightClientFinalityUpdate + +[0]: # (eth2spec: skip) + +| `fork_version` | Response SSZ type | +|--------------------------------------------------------|-------------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientFinalityUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientFinalityUpdate` | + +##### GetLightClientOptimisticUpdate + +[0]: # (eth2spec: skip) + +| `fork_version` | Response SSZ type | +|--------------------------------------------------------|---------------------------------------| +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientOptimisticUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientOptimisticUpdate` | diff --git a/specs/electra/light-client/sync-protocol.md b/specs/electra/light-client/sync-protocol.md new file mode 100644 index 0000000000..ef9dcd5987 --- /dev/null +++ b/specs/electra/light-client/sync-protocol.md @@ -0,0 +1,188 @@ +# Electra Light Client -- Sync Protocol + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) + - [Frozen constants](#frozen-constants) + - [New constants](#new-constants) +- [Helper functions](#helper-functions) + - [Modified `finalized_root_gindex_at_slot`](#modified-finalized_root_gindex_at_slot) + - [Modified `current_sync_committee_gindex_at_slot`](#modified-current_sync_committee_gindex_at_slot) + - [Modified `next_sync_committee_gindex_at_slot`](#modified-next_sync_committee_gindex_at_slot) + - [Modified `get_lc_execution_root`](#modified-get_lc_execution_root) + - [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header) + + + + +## Introduction + +This upgrade updates light client data to include the Electra changes to the [`ExecutionPayload`](../beacon-chain.md) structure and to the generalized indices of surrounding containers. It extends the [Deneb Light Client specifications](../../deneb/light-client/sync-protocol.md). The [fork document](./fork.md) explains how to upgrade existing Deneb based deployments to Electra. + +Additional documents describes the impact of the upgrade on certain roles: +- [Full node](./full-node.md) +- [Networking](./p2p-interface.md) + +## Custom types + +| Name | SSZ equivalent | Description | +| - | - | - | +| `FinalityBranch` | `Vector[Bytes32, floorlog2(FINALIZED_ROOT_GINDEX_ELECTRA)]` | Merkle branch of `finalized_checkpoint.root` within `BeaconState` | +| `CurrentSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `current_sync_committee` within `BeaconState` | +| `NextSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `next_sync_committee` within `BeaconState` | + +## Constants + +### Frozen constants + +Existing `GeneralizedIndex` constants are frozen at their [Altair](../../altair/light-client/sync-protocol.md#constants) values. + +| Name | Value | +| - | - | +| `FINALIZED_ROOT_GINDEX` | `get_generalized_index(altair.BeaconState, 'finalized_checkpoint', 'root')` (= 105) | +| `CURRENT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'current_sync_committee')` (= 54) | +| `NEXT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'next_sync_committee')` (= 55) | + +### New constants + +| Name | Value | +| - | - | +| `FINALIZED_ROOT_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 169) | +| `CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'current_sync_committee')` (= 86) | +| `NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 87) | + +## Helper functions + +### Modified `finalized_root_gindex_at_slot` + +```python +def finalized_root_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return FINALIZED_ROOT_GINDEX_ELECTRA + return FINALIZED_ROOT_GINDEX +``` + +### Modified `current_sync_committee_gindex_at_slot` + +```python +def current_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA + return CURRENT_SYNC_COMMITTEE_GINDEX +``` + +### Modified `next_sync_committee_gindex_at_slot` + +```python +def next_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA + return NEXT_SYNC_COMMITTEE_GINDEX +``` + +### Modified `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + epoch = compute_epoch_at_slot(header.beacon.slot) + + # [New in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return hash_tree_root(header.execution) + + # [Modified in Electra] + if epoch >= DENEB_FORK_EPOCH: + execution_header = deneb.ExecutionPayloadHeader( + parent_hash=header.execution.parent_hash, + fee_recipient=header.execution.fee_recipient, + state_root=header.execution.state_root, + receipts_root=header.execution.receipts_root, + logs_bloom=header.execution.logs_bloom, + prev_randao=header.execution.prev_randao, + block_number=header.execution.block_number, + gas_limit=header.execution.gas_limit, + gas_used=header.execution.gas_used, + timestamp=header.execution.timestamp, + extra_data=header.execution.extra_data, + base_fee_per_gas=header.execution.base_fee_per_gas, + block_hash=header.execution.block_hash, + transactions_root=header.execution.transactions_root, + withdrawals_root=header.execution.withdrawals_root, + blob_gas_used=header.execution.blob_gas_used, + excess_blob_gas=header.execution.excess_blob_gas, + ) + return hash_tree_root(execution_header) + + if epoch >= CAPELLA_FORK_EPOCH: + execution_header = capella.ExecutionPayloadHeader( + parent_hash=header.execution.parent_hash, + fee_recipient=header.execution.fee_recipient, + state_root=header.execution.state_root, + receipts_root=header.execution.receipts_root, + logs_bloom=header.execution.logs_bloom, + prev_randao=header.execution.prev_randao, + block_number=header.execution.block_number, + gas_limit=header.execution.gas_limit, + gas_used=header.execution.gas_used, + timestamp=header.execution.timestamp, + extra_data=header.execution.extra_data, + base_fee_per_gas=header.execution.base_fee_per_gas, + block_hash=header.execution.block_hash, + transactions_root=header.execution.transactions_root, + withdrawals_root=header.execution.withdrawals_root, + ) + return hash_tree_root(execution_header) + + return Root() +``` + +### Modified `is_valid_light_client_header` + +```python +def is_valid_light_client_header(header: LightClientHeader) -> bool: + epoch = compute_epoch_at_slot(header.beacon.slot) + + # [New in Electra:EIP6110:EIP7002:EIP7251] + if epoch < ELECTRA_FORK_EPOCH: + if ( + header.execution.deposit_requests_root != Root() + or header.execution.withdrawal_requests_root != Root() + or header.execution.consolidation_requests_root != Root() + ): + return False + + if epoch < DENEB_FORK_EPOCH: + if header.execution.blob_gas_used != uint64(0) or header.execution.excess_blob_gas != uint64(0): + return False + + if epoch < CAPELLA_FORK_EPOCH: + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == ExecutionBranch() + ) + + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + root=header.beacon.body_root, + ) +``` diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py index 6418caafd9..dd2f4a7164 100644 --- a/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py @@ -3,6 +3,11 @@ with_light_client, with_test_suite_name, ) +from eth2spec.test.helpers.light_client import ( + latest_current_sync_committee_gindex, + latest_finalized_root_gindex, + latest_next_sync_committee_gindex, +) @with_test_suite_name("BeaconState") @@ -10,17 +15,18 @@ @spec_state_test def test_current_sync_committee_merkle_proof(spec, state): yield "object", state - current_sync_committee_branch = spec.compute_merkle_proof(state, spec.CURRENT_SYNC_COMMITTEE_GINDEX) + gindex = latest_current_sync_committee_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) yield "proof", { "leaf": "0x" + state.current_sync_committee.hash_tree_root().hex(), - "leaf_index": spec.CURRENT_SYNC_COMMITTEE_GINDEX, - "branch": ['0x' + root.hex() for root in current_sync_committee_branch] + "leaf_index": gindex, + "branch": ['0x' + root.hex() for root in branch] } assert spec.is_valid_merkle_branch( leaf=state.current_sync_committee.hash_tree_root(), - branch=current_sync_committee_branch, - depth=spec.floorlog2(spec.CURRENT_SYNC_COMMITTEE_GINDEX), - index=spec.get_subtree_index(spec.CURRENT_SYNC_COMMITTEE_GINDEX), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), root=state.hash_tree_root(), ) @@ -30,17 +36,18 @@ def test_current_sync_committee_merkle_proof(spec, state): @spec_state_test def test_next_sync_committee_merkle_proof(spec, state): yield "object", state - next_sync_committee_branch = spec.compute_merkle_proof(state, spec.NEXT_SYNC_COMMITTEE_GINDEX) + gindex = latest_next_sync_committee_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) yield "proof", { "leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(), - "leaf_index": spec.NEXT_SYNC_COMMITTEE_GINDEX, - "branch": ['0x' + root.hex() for root in next_sync_committee_branch] + "leaf_index": gindex, + "branch": ['0x' + root.hex() for root in branch] } assert spec.is_valid_merkle_branch( leaf=state.next_sync_committee.hash_tree_root(), - branch=next_sync_committee_branch, - depth=spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_GINDEX), - index=spec.get_subtree_index(spec.NEXT_SYNC_COMMITTEE_GINDEX), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), root=state.hash_tree_root(), ) @@ -50,17 +57,18 @@ def test_next_sync_committee_merkle_proof(spec, state): @spec_state_test def test_finality_root_merkle_proof(spec, state): yield "object", state - finality_branch = spec.compute_merkle_proof(state, spec.FINALIZED_ROOT_GINDEX) + gindex = latest_finalized_root_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) yield "proof", { "leaf": "0x" + state.finalized_checkpoint.root.hex(), - "leaf_index": spec.FINALIZED_ROOT_GINDEX, - "branch": ['0x' + root.hex() for root in finality_branch] + "leaf_index": gindex, + "branch": ['0x' + root.hex() for root in branch] } assert spec.is_valid_merkle_branch( leaf=state.finalized_checkpoint.root, - branch=finality_branch, - depth=spec.floorlog2(spec.FINALIZED_ROOT_GINDEX), - index=spec.get_subtree_index(spec.FINALIZED_ROOT_GINDEX), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), root=state.hash_tree_root(), ) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py index b1bb13ee93..45c7d77887 100644 --- a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py @@ -16,15 +16,16 @@ state_transition_with_full_block, ) from eth2spec.test.helpers.constants import ( - ALTAIR, BELLATRIX, CAPELLA, DENEB, + ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA, MINIMAL, ) from eth2spec.test.helpers.fork_transition import ( do_fork, + transition_across_forks, ) from eth2spec.test.helpers.forks import ( get_spec_for_fork_version, - is_post_capella, is_post_deneb, + is_post_capella, is_post_deneb, is_post_electra, ) from eth2spec.test.helpers.light_client import ( compute_start_slot_at_next_sync_committee_period, @@ -47,6 +48,8 @@ class LightClientSyncTest(object): def get_store_fork_version(s_spec): + if is_post_electra(s_spec): + return s_spec.config.ELECTRA_FORK_VERSION if is_post_deneb(s_spec): return s_spec.config.DENEB_FORK_VERSION if is_post_capella(s_spec): @@ -60,6 +63,11 @@ def setup_test(spec, state, s_spec=None, phases=None): if s_spec is None: s_spec = spec + if phases is None: + phases = { + spec.fork: spec, + s_spec.fork: s_spec, + } test.s_spec = s_spec yield "genesis_validators_root", "meta", "0x" + state.genesis_validators_root.hex() @@ -77,7 +85,7 @@ def setup_test(spec, state, s_spec=None, phases=None): yield "bootstrap_fork_digest", "meta", encode_hex(data_fork_digest) yield "bootstrap", data - upgraded = upgrade_lc_bootstrap_to_new_spec(d_spec, test.s_spec, data) + upgraded = upgrade_lc_bootstrap_to_new_spec(d_spec, test.s_spec, data, phases) test.store = test.s_spec.initialize_light_client_store(trusted_block_root, upgraded) store_fork_version = get_store_fork_version(test.s_spec) store_fork_digest = test.s_spec.compute_fork_digest(store_fork_version, test.genesis_validators_root) @@ -149,11 +157,10 @@ def emit_update(test, spec, state, block, attested_state, attested_block, finali data = d_spec.create_light_client_update(state, block, attested_state, attested_block, finalized_block) if not with_next: data.next_sync_committee = spec.SyncCommittee() - data.next_sync_committee_branch = \ - [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_GINDEX))] + data.next_sync_committee_branch = spec.NextSyncCommitteeBranch() current_slot = state.slot - upgraded = upgrade_lc_update_to_new_spec(d_spec, test.s_spec, data) + upgraded = upgrade_lc_update_to_new_spec(d_spec, test.s_spec, data, phases) test.s_spec.process_light_client_update(test.store, upgraded, current_slot, test.genesis_validators_root) yield get_update_file_name(d_spec, data), data @@ -169,7 +176,7 @@ def emit_update(test, spec, state, block, attested_state, attested_block, finali def emit_upgrade_store(test, new_s_spec, phases=None): - test.store = upgrade_lc_store_to_new_spec(test.s_spec, new_s_spec, test.store) + test.store = upgrade_lc_store_to_new_spec(test.s_spec, new_s_spec, test.store, phases) test.s_spec = new_s_spec store_fork_version = get_store_fork_version(test.s_spec) store_fork_digest = test.s_spec.compute_fork_digest(store_fork_version, test.genesis_validators_root) @@ -561,7 +568,7 @@ def run_test_single_fork(spec, phases, state, fork): # Upgrade to post-fork spec, attested block is still before the fork attested_block = block.copy() attested_state = state.copy() - sync_aggregate, _ = get_sync_aggregate(phases[fork], state, phases=phases) + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) state, block = do_fork(state, spec, phases[fork], fork_epoch, sync_aggregate=sync_aggregate) spec = phases[fork] yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) @@ -635,6 +642,18 @@ def test_deneb_fork(spec, phases, state): yield from run_test_single_fork(spec, phases, state, DENEB) +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_config_overrides({ + 'ELECTRA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_electra_fork(spec, phases, state): + yield from run_test_single_fork(spec, phases, state, ELECTRA) + + def run_test_multi_fork(spec, phases, state, fork_1, fork_2): # Start test test = yield from setup_test(spec, state, phases[fork_2], phases) @@ -646,17 +665,28 @@ def run_test_multi_fork(spec, phases, state, fork_1, fork_2): # ..., attested is from `fork_1`, ... fork_1_epoch = getattr(phases[fork_1].config, fork_1.upper() + '_FORK_EPOCH') - transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_1_epoch) - 1) - state, attested_block = do_fork(state, spec, phases[fork_1], fork_1_epoch) - spec = phases[fork_1] + spec, state, attested_block = transition_across_forks( + spec, + state, + spec.compute_start_slot_at_epoch(fork_1_epoch), + phases, + with_block=True, + ) attested_state = state.copy() # ..., and signature is from `fork_2` fork_2_epoch = getattr(phases[fork_2].config, fork_2.upper() + '_FORK_EPOCH') - transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_2_epoch) - 1) - sync_aggregate, _ = get_sync_aggregate(phases[fork_2], state) - state, block = do_fork(state, spec, phases[fork_2], fork_2_epoch, sync_aggregate=sync_aggregate) - spec = phases[fork_2] + spec, state, _ = transition_across_forks( + spec, state, spec.compute_start_slot_at_epoch(fork_2_epoch) - 1, phases) + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + spec, state, block = transition_across_forks( + spec, + state, + spec.compute_start_slot_at_epoch(fork_2_epoch), + phases, + with_block=True, + sync_aggregate=sync_aggregate, + ) # Check that update applies yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) @@ -682,6 +712,33 @@ def test_capella_deneb_fork(spec, phases, state): yield from run_test_multi_fork(spec, phases, state, CAPELLA, DENEB) +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, DENEB, ELECTRA]) +@spec_test +@with_config_overrides({ + 'CAPELLA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 + 'DENEB_FORK_EPOCH': 4, + 'ELECTRA_FORK_EPOCH': 5, +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_electra_fork(spec, phases, state): + yield from run_test_multi_fork(spec, phases, state, CAPELLA, ELECTRA) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB, ELECTRA]) +@spec_test +@with_config_overrides({ + 'DENEB_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 + 'ELECTRA_FORK_EPOCH': 4, +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_electra_fork(spec, phases, state): + yield from run_test_multi_fork(spec, phases, state, DENEB, ELECTRA) + + def run_test_upgraded_store_with_legacy_data(spec, phases, state, fork): # Start test (Legacy bootstrap with an upgraded store) test = yield from setup_test(spec, state, phases[fork], phases) @@ -713,10 +770,19 @@ def test_capella_store_with_legacy_data(spec, phases, state): yield from run_test_upgraded_store_with_legacy_data(spec, phases, state, CAPELLA) -@with_phases(phases=[ALTAIR, BELLATRIX, CAPELLA], other_phases=[DENEB]) +@with_phases(phases=[ALTAIR, BELLATRIX, CAPELLA], other_phases=[CAPELLA, DENEB]) @spec_test @with_state @with_matching_spec_config(emitted_fork=DENEB) @with_presets([MINIMAL], reason="too slow") def test_deneb_store_with_legacy_data(spec, phases, state): yield from run_test_upgraded_store_with_legacy_data(spec, phases, state, DENEB) + + +@with_phases(phases=[ALTAIR, BELLATRIX, CAPELLA, DENEB], other_phases=[CAPELLA, DENEB, ELECTRA]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_electra_store_with_legacy_data(spec, phases, state): + yield from run_test_upgraded_store_with_legacy_data(spec, phases, state, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py index 41bb3f307e..7f414ab285 100644 --- a/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py @@ -15,16 +15,17 @@ def test_execution_merkle_proof(spec, state): block = state_transition_with_full_block(spec, state, True, False) yield "object", block.message.body - execution_branch = spec.compute_merkle_proof(block.message.body, spec.EXECUTION_PAYLOAD_GINDEX) + gindex = spec.EXECUTION_PAYLOAD_GINDEX + branch = spec.compute_merkle_proof(block.message.body, gindex) yield "proof", { "leaf": "0x" + block.message.body.execution_payload.hash_tree_root().hex(), - "leaf_index": spec.EXECUTION_PAYLOAD_GINDEX, - "branch": ['0x' + root.hex() for root in execution_branch] + "leaf_index": gindex, + "branch": ['0x' + root.hex() for root in branch] } assert spec.is_valid_merkle_branch( leaf=block.message.body.execution_payload.hash_tree_root(), - branch=execution_branch, - depth=spec.floorlog2(spec.EXECUTION_PAYLOAD_GINDEX), - index=spec.get_subtree_index(spec.EXECUTION_PAYLOAD_GINDEX), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), root=block.message.body.hash_tree_root(), ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index ed398516cd..6987ec9728 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -39,7 +39,7 @@ EIP7594, ) # The forks that have light client specs -LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0],) +LIGHT_CLIENT_TESTING_FORKS = (*[item for item in MAINNET_FORKS if item != PHASE0], ELECTRA) # The forks that output to the test vectors. TESTGEN_FORKS = (*MAINNET_FORKS, ELECTRA, EIP7594, WHISK) # Forks allowed in the test runner `--fork` flag, to fail fast in case of typos diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client.py b/tests/core/pyspec/eth2spec/test/helpers/light_client.py index 3bdaa46497..4638c988b5 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/light_client.py +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client.py @@ -1,8 +1,11 @@ +from eth2spec.test.helpers.constants import ( + CAPELLA, DENEB, ELECTRA, +) from eth2spec.test.helpers.fork_transition import ( transition_across_forks, ) from eth2spec.test.helpers.forks import ( - is_post_capella, is_post_deneb, + is_post_capella, is_post_deneb, is_post_electra ) from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, @@ -11,6 +14,24 @@ from math import floor +def latest_finalized_root_gindex(spec): + if hasattr(spec, 'FINALIZED_ROOT_GINDEX_ELECTRA'): + return spec.FINALIZED_ROOT_GINDEX_ELECTRA + return spec.FINALIZED_ROOT_GINDEX + + +def latest_current_sync_committee_gindex(spec): + if hasattr(spec, 'CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA'): + return spec.CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA + return spec.CURRENT_SYNC_COMMITTEE_GINDEX + + +def latest_next_sync_committee_gindex(spec): + if hasattr(spec, 'NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA'): + return spec.NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA + return spec.NEXT_SYNC_COMMITTEE_GINDEX + + def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): return spec.compute_start_slot_at_epoch(sync_committee_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) @@ -68,11 +89,13 @@ def create_update(spec, if with_next: update.next_sync_committee = attested_state.next_sync_committee - update.next_sync_committee_branch = spec.compute_merkle_proof(attested_state, spec.NEXT_SYNC_COMMITTEE_GINDEX) + update.next_sync_committee_branch = spec.compute_merkle_proof( + attested_state, latest_next_sync_committee_gindex(spec)) if with_finality: update.finalized_header = spec.block_to_light_client_header(finalized_block) - update.finality_branch = spec.compute_merkle_proof(attested_state, spec.FINALIZED_ROOT_GINDEX) + update.finality_branch = spec.compute_merkle_proof( + attested_state, latest_finalized_root_gindex(spec)) update.sync_aggregate, update.signature_slot = get_sync_aggregate( spec, attested_state, num_participants) @@ -88,6 +111,20 @@ def needs_upgrade_to_deneb(spec, new_spec): return is_post_deneb(new_spec) and not is_post_deneb(spec) +def needs_upgrade_to_electra(spec, new_spec): + return is_post_electra(new_spec) and not is_post_electra(spec) + + +def check_merkle_branch_equal(spec, new_spec, data, upgraded, gindex): + if is_post_electra(new_spec): + assert ( + new_spec.normalize_merkle_branch(upgraded, gindex) + == new_spec.normalize_merkle_branch(data, gindex) + ) + else: + assert upgraded == data + + def check_lc_header_equal(spec, new_spec, data, upgraded): assert upgraded.beacon.slot == data.beacon.slot assert upgraded.beacon.hash_tree_root() == data.beacon.hash_tree_root() @@ -98,15 +135,19 @@ def check_lc_header_equal(spec, new_spec, data, upgraded): assert new_spec.get_lc_execution_root(upgraded) == new_spec.Root() -def upgrade_lc_header_to_new_spec(spec, new_spec, data): +def upgrade_lc_header_to_new_spec(spec, new_spec, data, phases): upgraded = data if needs_upgrade_to_capella(spec, new_spec): - upgraded = new_spec.upgrade_lc_header_to_capella(upgraded) + upgraded = phases[CAPELLA].upgrade_lc_header_to_capella(upgraded) check_lc_header_equal(spec, new_spec, data, upgraded) if needs_upgrade_to_deneb(spec, new_spec): - upgraded = new_spec.upgrade_lc_header_to_deneb(upgraded) + upgraded = phases[DENEB].upgrade_lc_header_to_deneb(upgraded) + check_lc_header_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_header_to_electra(upgraded) check_lc_header_equal(spec, new_spec, data, upgraded) return upgraded @@ -115,18 +156,28 @@ def upgrade_lc_header_to_new_spec(spec, new_spec, data): def check_lc_bootstrap_equal(spec, new_spec, data, upgraded): check_lc_header_equal(spec, new_spec, data.header, upgraded.header) assert upgraded.current_sync_committee == data.current_sync_committee - assert upgraded.current_sync_committee_branch == data.current_sync_committee_branch + check_merkle_branch_equal( + spec, + new_spec, + data.current_sync_committee_branch, + upgraded.current_sync_committee_branch, + latest_current_sync_committee_gindex(new_spec), + ) -def upgrade_lc_bootstrap_to_new_spec(spec, new_spec, data): +def upgrade_lc_bootstrap_to_new_spec(spec, new_spec, data, phases): upgraded = data if needs_upgrade_to_capella(spec, new_spec): - upgraded = new_spec.upgrade_lc_bootstrap_to_capella(upgraded) + upgraded = phases[CAPELLA].upgrade_lc_bootstrap_to_capella(upgraded) check_lc_bootstrap_equal(spec, new_spec, data, upgraded) if needs_upgrade_to_deneb(spec, new_spec): - upgraded = new_spec.upgrade_lc_bootstrap_to_deneb(upgraded) + upgraded = phases[DENEB].upgrade_lc_bootstrap_to_deneb(upgraded) + check_lc_bootstrap_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_bootstrap_to_electra(upgraded) check_lc_bootstrap_equal(spec, new_spec, data, upgraded) return upgraded @@ -135,21 +186,38 @@ def upgrade_lc_bootstrap_to_new_spec(spec, new_spec, data): def check_lc_update_equal(spec, new_spec, data, upgraded): check_lc_header_equal(spec, new_spec, data.attested_header, upgraded.attested_header) assert upgraded.next_sync_committee == data.next_sync_committee - assert upgraded.next_sync_committee_branch == data.next_sync_committee_branch + check_merkle_branch_equal( + spec, + new_spec, + data.next_sync_committee_branch, + upgraded.next_sync_committee_branch, + latest_next_sync_committee_gindex(new_spec), + ) check_lc_header_equal(spec, new_spec, data.finalized_header, upgraded.finalized_header) + check_merkle_branch_equal( + spec, + new_spec, + data.finality_branch, + upgraded.finality_branch, + latest_finalized_root_gindex(new_spec), + ) assert upgraded.sync_aggregate == data.sync_aggregate assert upgraded.signature_slot == data.signature_slot -def upgrade_lc_update_to_new_spec(spec, new_spec, data): +def upgrade_lc_update_to_new_spec(spec, new_spec, data, phases): upgraded = data if needs_upgrade_to_capella(spec, new_spec): - upgraded = new_spec.upgrade_lc_update_to_capella(upgraded) + upgraded = phases[CAPELLA].upgrade_lc_update_to_capella(upgraded) check_lc_update_equal(spec, new_spec, data, upgraded) if needs_upgrade_to_deneb(spec, new_spec): - upgraded = new_spec.upgrade_lc_update_to_deneb(upgraded) + upgraded = phases[DENEB].upgrade_lc_update_to_deneb(upgraded) + check_lc_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_update_to_electra(upgraded) check_lc_update_equal(spec, new_spec, data, upgraded) return upgraded @@ -158,19 +226,30 @@ def upgrade_lc_update_to_new_spec(spec, new_spec, data): def check_lc_finality_update_equal(spec, new_spec, data, upgraded): check_lc_header_equal(spec, new_spec, data.attested_header, upgraded.attested_header) check_lc_header_equal(spec, new_spec, data.finalized_header, upgraded.finalized_header) + check_merkle_branch_equal( + spec, + new_spec, + data.finality_branch, + upgraded.finality_branch, + latest_finalized_root_gindex(new_spec), + ) assert upgraded.sync_aggregate == data.sync_aggregate assert upgraded.signature_slot == data.signature_slot -def upgrade_lc_finality_update_to_new_spec(spec, new_spec, data): +def upgrade_lc_finality_update_to_new_spec(spec, new_spec, data, phases): upgraded = data if needs_upgrade_to_capella(spec, new_spec): - upgraded = new_spec.upgrade_lc_finality_update_to_capella(upgraded) + upgraded = phases[CAPELLA].upgrade_lc_finality_update_to_capella(upgraded) check_lc_finality_update_equal(spec, new_spec, data, upgraded) if needs_upgrade_to_deneb(spec, new_spec): - upgraded = new_spec.upgrade_lc_finality_update_to_deneb(upgraded) + upgraded = phases[DENEB].upgrade_lc_finality_update_to_deneb(upgraded) + check_lc_finality_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_finality_update_to_electra(upgraded) check_lc_finality_update_equal(spec, new_spec, data, upgraded) return upgraded @@ -189,15 +268,19 @@ def check_lc_store_equal(spec, new_spec, data, upgraded): assert upgraded.current_max_active_participants == data.current_max_active_participants -def upgrade_lc_store_to_new_spec(spec, new_spec, data): +def upgrade_lc_store_to_new_spec(spec, new_spec, data, phases): upgraded = data if needs_upgrade_to_capella(spec, new_spec): - upgraded = new_spec.upgrade_lc_store_to_capella(upgraded) + upgraded = phases[CAPELLA].upgrade_lc_store_to_capella(upgraded) check_lc_store_equal(spec, new_spec, data, upgraded) if needs_upgrade_to_deneb(spec, new_spec): - upgraded = new_spec.upgrade_lc_store_to_deneb(upgraded) + upgraded = phases[DENEB].upgrade_lc_store_to_deneb(upgraded) + check_lc_store_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_store_to_electra(upgraded) check_lc_store_equal(spec, new_spec, data, upgraded) return upgraded diff --git a/tests/generators/light_client/main.py b/tests/generators/light_client/main.py index cfe34aee4b..a3cdfd62fd 100644 --- a/tests/generators/light_client/main.py +++ b/tests/generators/light_client/main.py @@ -1,4 +1,4 @@ -from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB +from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA from eth2spec.gen_helpers.gen_from_tests.gen import combine_mods, run_state_test_generators @@ -15,12 +15,14 @@ ]} capella_mods = combine_mods(_new_capella_mods, bellatrix_mods) deneb_mods = capella_mods + electra_mods = deneb_mods all_mods = { ALTAIR: altair_mods, BELLATRIX: bellatrix_mods, CAPELLA: capella_mods, DENEB: deneb_mods, + ELECTRA: electra_mods, } run_state_test_generators(runner_name="light_client", all_mods=all_mods)