Notice: This document is a work-in-progress for researchers and implementers.
This upgrade updates light client data to include the Electra changes to the ExecutionPayload
structure and to the generalized indices of surrounding containers. It extends the Deneb Light Client specifications. The fork document explains how to upgrade existing Deneb based deployments to Electra.
Additional documents describes the impact of the upgrade on certain roles:
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 |
Existing GeneralizedIndex
constants are frozen at their Altair 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) |
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) |
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
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
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
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()
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,
)