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 |
- Core
- [Beacon Chain changes](specs/electra/beacon-chain.md)
- [EIP-6110 fork](specs/electra/fork.md)
- Additions
- [Honest validator guide changes](specs/electra/validator.md)
|
+| Electra | - Core
- [Beacon Chain changes](specs/electra/beacon-chain.md)
- [EIP-6110 fork](specs/electra/fork.md)
- Additions
- [Light client sync protocol changes](specs/electra/light-client/sync-protocol.md) ([fork](specs/electra/light-client/fork.md), [full node](specs/electra/light-client/full-node.md), [networking](specs/electra/light-client/p2p-interface.md))
- [Honest validator guide changes](specs/electra/validator.md)
|
| Sharding (outdated) | - Core
- [Beacon Chain changes](specs/_features/sharding/beacon-chain.md)
- Additions
- [P2P networking](specs/_features/sharding/p2p-interface.md)
|
| Custody Game (outdated) | - Core
- [Beacon Chain changes](specs/_features/custody_game/beacon-chain.md)
- Additions
- [Honest validator guide changes](specs/_features/custody_game/validator.md)
| Dependent on sharding |
| Data Availability Sampling (outdated) | - Core
- [Core types and functions](specs/_features/das/das-core.md)
- [Fork choice changes](specs/_features/das/fork-choice.md)
- Additions
- [P2P Networking](specs/_features/das/p2p-interface.md)
- [Sampling process](specs/_features/das/sampling.md)
| - Dependent on sharding
- [Technical explainer](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD)
|
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)