# 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#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 GeneralizedIndex(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 GeneralizedIndex(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 GeneralizedIndex(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, ) ```