From 0fb0b26742e18ae86c545471e0cc98dab10f716a Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Tue, 6 Dec 2022 22:05:19 +0100 Subject: [PATCH 01/20] Add accessors for LC header Introduce `get_lc_beacon_slot` and `get_lc_beacon_root` accessors similar to `get_current_slot(state)` to account for future extensions to the light client header structure that may override how those fields are accessed. Idea is to extend with execution accessors in the future. --- specs/altair/light-client/full-node.md | 10 +-- specs/altair/light-client/light-client.md | 2 +- specs/altair/light-client/p2p-interface.md | 20 ++--- specs/altair/light-client/sync-protocol.md | 70 ++++++++++------ .../test/altair/light_client/test_sync.py | 80 +++++++++---------- .../light_client/test_update_ranking.py | 2 +- .../light_client/test_sync_protocol.py | 6 +- tests/formats/light_client/sync.md | 8 +- 8 files changed, 108 insertions(+), 90 deletions(-) diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md index 53ba4dc82c..0afbe54fbf 100644 --- a/specs/altair/light-client/full-node.md +++ b/specs/altair/light-client/full-node.md @@ -121,7 +121,7 @@ def create_light_client_update(state: BeaconState, state_root=finalized_block.message.state_root, body_root=hash_tree_root(finalized_block.message.body), ) - assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root + assert get_lc_beacon_root(update.finalized_header) == attested_state.finalized_checkpoint.root else: assert attested_state.finalized_checkpoint.root == Bytes32() finalized_header = BeaconBlockHeader() @@ -143,8 +143,8 @@ def create_light_client_update(state: BeaconState, Full nodes SHOULD provide the best derivable `LightClientUpdate` (according to `is_better_update`) for each sync committee period covering any epochs in range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientUpdate` for other sync committee periods. -- `LightClientUpdate` are assigned to sync committee periods based on their `attested_header.slot` -- `LightClientUpdate` are only considered if `compute_sync_committee_period_at_slot(update.attested_header.slot) == compute_sync_committee_period_at_slot(update.signature_slot)` +- `LightClientUpdate` are assigned to sync committee periods based on their `get_lc_beacon_slot(attested_header)` +- `LightClientUpdate` are only considered if `compute_sync_committee_period_at_slot(get_lc_beacon_slot(update.attested_header)) == compute_sync_committee_period_at_slot(update.signature_slot)` - Only `LightClientUpdate` with `next_sync_committee` as selected by fork choice are provided, regardless of ranking by `is_better_update`. To uniquely identify a non-finalized sync committee fork, all of `period`, `current_sync_committee` and `next_sync_committee` need to be incorporated, as sync committees may reappear over time. ### `create_light_client_finality_update` @@ -160,7 +160,7 @@ def create_light_client_finality_update(update: LightClientUpdate) -> LightClien ) ``` -Full nodes SHOULD provide the `LightClientFinalityUpdate` with the highest `attested_header.slot` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientFinalityUpdate` whenever `finalized_header` changes. +Full nodes SHOULD provide the `LightClientFinalityUpdate` with the highest `get_lc_beacon_slot(attested_header)` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientFinalityUpdate` whenever `finalized_header` changes. ### `create_light_client_optimistic_update` @@ -173,4 +173,4 @@ def create_light_client_optimistic_update(update: LightClientUpdate) -> LightCli ) ``` -Full nodes SHOULD provide the `LightClientOptimisticUpdate` with the highest `attested_header.slot` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientOptimisticUpdate` whenever `attested_header` changes. +Full nodes SHOULD provide the `LightClientOptimisticUpdate` with the highest `get_lc_beacon_slot(attested_header)` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientOptimisticUpdate` whenever `attested_header` changes. diff --git a/specs/altair/light-client/light-client.md b/specs/altair/light-client/light-client.md index 3189504375..15ed3112d9 100644 --- a/specs/altair/light-client/light-client.md +++ b/specs/altair/light-client/light-client.md @@ -23,7 +23,7 @@ This document explains how light clients MAY obtain light client data to sync wi 1. The light client MUST be configured out-of-band with a spec/preset (including fork schedule), with `genesis_state` (including `genesis_time` and `genesis_validators_root`), and with a trusted block root. The trusted block SHOULD be within the weak subjectivity period, and its root SHOULD be from a finalized `Checkpoint`. 2. The local clock is initialized based on the configured `genesis_time`, and the current fork digest is determined to browse for and connect to relevant light client data providers. 3. The light client fetches a [`LightClientBootstrap`](./sync-protocol.md#lightclientbootstrap) object for the configured trusted block root. The `bootstrap` object is passed to [`initialize_light_client_store`](./sync-protocol.md#initialize_light_client_store) to obtain a local [`LightClientStore`](./sync-protocol.md#lightclientstore). -4. The light client tracks the sync committee periods `finalized_period` from `store.finalized_header.slot`, `optimistic_period` from `store.optimistic_header.slot`, and `current_period` from `current_slot` based on the local clock. +4. The light client tracks the sync committee periods `finalized_period` from `get_lc_beacon_slot(store.finalized_header)`, `optimistic_period` from `get_lc_beacon_slot(store.optimistic_header)`, and `current_period` from `current_slot` based on the local clock. 1. When `finalized_period == optimistic_period` and [`is_next_sync_committee_known`](./sync-protocol.md#is_next_sync_committee_known) indicates `False`, the light client fetches a [`LightClientUpdate`](./sync-protocol.md#lightclientupdate) for `finalized_period`. If `finalized_period == current_period`, this fetch SHOULD be scheduled at a random time before `current_period` advances. 2. When `finalized_period + 1 < current_period`, the light client fetches a `LightClientUpdate` for each sync committee period in range `[finalized_period + 1, current_period)` (current period excluded) 3. When `finalized_period + 1 >= current_period`, the light client keeps observing [`LightClientFinalityUpdate`](./sync-protocol.md#lightclientfinalityupdate) and [`LightClientOptimisticUpdate`](./sync-protocol.md#lightclientoptimisticupdate). Received objects are passed to [`process_light_client_finality_update`](./sync-protocol.md#process_light_client_finality_update) and [`process_light_client_optimistic_update`](./sync-protocol.md#process_light_client_optimistic_update). This ensures that `finalized_header` and `optimistic_header` reflect the latest blocks. diff --git a/specs/altair/light-client/p2p-interface.md b/specs/altair/light-client/p2p-interface.md index 501269ee2a..13b8625b17 100644 --- a/specs/altair/light-client/p2p-interface.md +++ b/specs/altair/light-client/p2p-interface.md @@ -67,11 +67,11 @@ For full nodes, the following validations MUST additionally pass before forwardi For light clients, the following validations MUST additionally pass before forwarding the `finality_update` on the network. - _[REJECT]_ The `finality_update` is valid -- i.e. validate that `process_light_client_finality_update` does not indicate errors -- _[IGNORE]_ The `finality_update` advances the `finalized_header` of the local `LightClientStore` -- i.e. validate that processing `finality_update` increases `store.finalized_header.slot` +- _[IGNORE]_ The `finality_update` advances the `finalized_header` of the local `LightClientStore` -- i.e. validate that processing `finality_update` increases `get_lc_beacon_slot(store.finalized_header)` Light clients SHOULD call `process_light_client_finality_update` even if the message is ignored. -The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.slot))`. +The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(finality_update.attested_header)))`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -95,11 +95,11 @@ For full nodes, the following validations MUST additionally pass before forwardi For light clients, the following validations MUST additionally pass before forwarding the `optimistic_update` on the network. - _[REJECT]_ The `optimistic_update` is valid -- i.e. validate that `process_light_client_optimistic_update` does not indicate errors -- _[IGNORE]_ The `optimistic_update` either matches corresponding fields of the most recently forwarded `LightClientFinalityUpdate` (if any), or it advances the `optimistic_header` of the local `LightClientStore` -- i.e. validate that processing `optimistic_update` increases `store.optimistic_header.slot` +- _[IGNORE]_ The `optimistic_update` either matches corresponding fields of the most recently forwarded `LightClientFinalityUpdate` (if any), or it advances the `optimistic_header` of the local `LightClientStore` -- i.e. validate that processing `optimistic_update` increases `get_lc_beacon_slot(store.optimistic_header)` Light clients SHOULD call `process_light_client_optimistic_update` even if the message is ignored. -The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.slot))`. +The gossip `ForkDigest`-context is determined based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(optimistic_update.attested_header)))`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -142,7 +142,7 @@ Peers SHOULD provide results as defined in [`create_light_client_bootstrap`](./f When a `LightClientBootstrap` instance cannot be produced for a given block root, peers SHOULD respond with error code `3: ResourceUnavailable`. -A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(bootstrap.header.slot))` is used to select the fork namespace of the Response type. +A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(bootstrap.header)))` is used to select the fork namespace of the Response type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -180,7 +180,7 @@ The response MUST consist of zero or more `response_chunk`. Each _successful_ `r Peers SHOULD provide results as defined in [`create_light_client_update`](./full-node.md#create_light_client_update). They MUST respond with at least the earliest known result within the requested range, and MUST send results in consecutive order (by period). The response MUST NOT contain more than `min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count)` results. -For each `response_chunk`, a `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(update.attested_header.slot))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `update.sync_aggregate`, which is based on `update.signature_slot`. +For each `response_chunk`, a `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(update.attested_header)))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `update.sync_aggregate`, which is based on `update.signature_slot`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -211,7 +211,7 @@ Peers SHOULD provide results as defined in [`create_light_client_finality_update When no `LightClientFinalityUpdate` is available, peers SHOULD respond with error code `3: ResourceUnavailable`. -A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.slot))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `finality_update.sync_aggregate`, which is based on `finality_update.signature_slot`. +A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(finality_update.attested_header)))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `finality_update.sync_aggregate`, which is based on `finality_update.signature_slot`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -242,7 +242,7 @@ Peers SHOULD provide results as defined in [`create_light_client_optimistic_upda When no `LightClientOptimisticUpdate` is available, peers SHOULD respond with error code `3: ResourceUnavailable`. -A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.slot))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `optimistic_update.sync_aggregate`, which is based on `optimistic_update.signature_slot`. +A `ForkDigest`-context based on `compute_fork_version(compute_epoch_at_slot(get_lc_beacon_slot(optimistic_update.attested_header)))` is used to select the fork namespace of the Response type. Note that this `fork_version` may be different from the one used to verify the `optimistic_update.sync_aggregate`, which is based on `optimistic_update.signature_slot`. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: @@ -273,7 +273,7 @@ All full nodes SHOULD subscribe to and provide stability on the [`light_client_f Whenever fork choice selects a new head block with a sync aggregate participation `>= MIN_SYNC_COMMITTEE_PARTICIPANTS` and a post-Altair parent block, full nodes with at least one validator assigned to the current sync committee at the block's `slot` SHOULD broadcast derived light client data as follows: -- If `finalized_header.slot` increased, a `LightClientFinalityUpdate` SHOULD be broadcasted to the pubsub topic `light_client_finality_update` if no matching message has not yet been forwarded as part of gossip validation. -- If `attested_header.slot` increased, a `LightClientOptimisticUpdate` SHOULD be broadcasted to the pubsub topic `light_client_optimistic_update` if no matching message has not yet been forwarded as part of gossip validation. +- If `get_lc_beacon_slot(finalized_header)` increased, a `LightClientFinalityUpdate` SHOULD be broadcasted to the pubsub topic `light_client_finality_update` if no matching message has not yet been forwarded as part of gossip validation. +- If `get_lc_beacon_slot(attested_header)` increased, a `LightClientOptimisticUpdate` SHOULD be broadcasted to the pubsub topic `light_client_optimistic_update` if no matching message has not yet been forwarded as part of gossip validation. These messages SHOULD be broadcasted after one-third of `slot` has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot). To ensure that the corresponding block was given enough time to propagate through the network, they SHOULD NOT be sent earlier. Note that this is different from how other messages are handled, e.g., attestations, which may be sent early. diff --git a/specs/altair/light-client/sync-protocol.md b/specs/altair/light-client/sync-protocol.md index 793483bc0c..5ee0d66fe5 100644 --- a/specs/altair/light-client/sync-protocol.md +++ b/specs/altair/light-client/sync-protocol.md @@ -19,6 +19,8 @@ - [`LightClientOptimisticUpdate`](#lightclientoptimisticupdate) - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) + - [`get_lc_beacon_slot`](#get_lc_beacon_slot) + - [`get_lc_beacon_root`](#get_lc_beacon_root) - [`is_sync_committee_update`](#is_sync_committee_update) - [`is_finality_update`](#is_finality_update) - [`is_better_update`](#is_better_update) @@ -150,6 +152,20 @@ class LightClientStore(object): ## Helper functions +### `get_lc_beacon_slot` + +```python +def get_lc_beacon_slot(header: BeaconBlockHeader) -> Slot: + return header.slot +``` + +### `get_lc_beacon_root` + +```python +def get_lc_beacon_root(header: BeaconBlockHeader) -> Root: + return hash_tree_root(header) +``` + ### `is_sync_committee_update` ```python @@ -181,11 +197,11 @@ def is_better_update(new_update: LightClientUpdate, old_update: LightClientUpdat # Compare presence of relevant sync committee new_has_relevant_sync_committee = is_sync_committee_update(new_update) and ( - compute_sync_committee_period_at_slot(new_update.attested_header.slot) + compute_sync_committee_period_at_slot(get_lc_beacon_slot(new_update.attested_header)) == compute_sync_committee_period_at_slot(new_update.signature_slot) ) old_has_relevant_sync_committee = is_sync_committee_update(old_update) and ( - compute_sync_committee_period_at_slot(old_update.attested_header.slot) + compute_sync_committee_period_at_slot(get_lc_beacon_slot(old_update.attested_header)) == compute_sync_committee_period_at_slot(old_update.signature_slot) ) if new_has_relevant_sync_committee != old_has_relevant_sync_committee: @@ -200,12 +216,12 @@ def is_better_update(new_update: LightClientUpdate, old_update: LightClientUpdat # Compare sync committee finality if new_has_finality: new_has_sync_committee_finality = ( - compute_sync_committee_period_at_slot(new_update.finalized_header.slot) - == compute_sync_committee_period_at_slot(new_update.attested_header.slot) + compute_sync_committee_period_at_slot(get_lc_beacon_slot(new_update.finalized_header)) + == compute_sync_committee_period_at_slot(get_lc_beacon_slot(new_update.attested_header)) ) old_has_sync_committee_finality = ( - compute_sync_committee_period_at_slot(old_update.finalized_header.slot) - == compute_sync_committee_period_at_slot(old_update.attested_header.slot) + compute_sync_committee_period_at_slot(get_lc_beacon_slot(old_update.finalized_header)) + == compute_sync_committee_period_at_slot(get_lc_beacon_slot(old_update.attested_header)) ) if new_has_sync_committee_finality != old_has_sync_committee_finality: return new_has_sync_committee_finality @@ -215,8 +231,8 @@ def is_better_update(new_update: LightClientUpdate, old_update: LightClientUpdat return new_num_active_participants > old_num_active_participants # Tiebreaker 2: Prefer older data (fewer changes to best) - if new_update.attested_header.slot != old_update.attested_header.slot: - return new_update.attested_header.slot < old_update.attested_header.slot + if get_lc_beacon_slot(new_update.attested_header) != get_lc_beacon_slot(old_update.attested_header): + return get_lc_beacon_slot(new_update.attested_header) < get_lc_beacon_slot(old_update.attested_header) return new_update.signature_slot < old_update.signature_slot ``` @@ -260,7 +276,7 @@ A light client maintains its state in a `store` object of type `LightClientStore ```python def initialize_light_client_store(trusted_block_root: Root, bootstrap: LightClientBootstrap) -> LightClientStore: - assert hash_tree_root(bootstrap.header) == trusted_block_root + assert get_lc_beacon_root(bootstrap.header) == trusted_block_root assert is_valid_merkle_branch( leaf=hash_tree_root(bootstrap.current_sync_committee), @@ -301,8 +317,10 @@ def validate_light_client_update(store: LightClientStore, assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS # Verify update does not skip a sync committee period - assert current_slot >= update.signature_slot > update.attested_header.slot >= update.finalized_header.slot - store_period = compute_sync_committee_period_at_slot(store.finalized_header.slot) + update_attested_slot = get_lc_beacon_slot(update.attested_header) + update_finalized_slot = get_lc_beacon_slot(update.finalized_header) + assert current_slot >= update.signature_slot > update_attested_slot >= update_finalized_slot + store_period = compute_sync_committee_period_at_slot(get_lc_beacon_slot(store.finalized_header)) update_signature_period = compute_sync_committee_period_at_slot(update.signature_slot) if is_next_sync_committee_known(store): assert update_signature_period in (store_period, store_period + 1) @@ -310,12 +328,12 @@ def validate_light_client_update(store: LightClientStore, assert update_signature_period == store_period # Verify update is relevant - update_attested_period = compute_sync_committee_period_at_slot(update.attested_header.slot) + update_attested_period = compute_sync_committee_period_at_slot(update_attested_slot) update_has_next_sync_committee = not is_next_sync_committee_known(store) and ( is_sync_committee_update(update) and update_attested_period == store_period ) assert ( - update.attested_header.slot > store.finalized_header.slot + update_attested_slot > get_lc_beacon_slot(store.finalized_header) or update_has_next_sync_committee ) @@ -325,11 +343,11 @@ def validate_light_client_update(store: LightClientStore, if not is_finality_update(update): assert update.finalized_header == BeaconBlockHeader() else: - if update.finalized_header.slot == GENESIS_SLOT: + if update_finalized_slot == GENESIS_SLOT: assert update.finalized_header == BeaconBlockHeader() finalized_root = Bytes32() else: - finalized_root = hash_tree_root(update.finalized_header) + finalized_root = get_lc_beacon_root(update.finalized_header) assert is_valid_merkle_branch( leaf=finalized_root, branch=update.finality_branch, @@ -372,8 +390,8 @@ def validate_light_client_update(store: LightClientStore, ```python def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None: - store_period = compute_sync_committee_period_at_slot(store.finalized_header.slot) - update_finalized_period = compute_sync_committee_period_at_slot(update.finalized_header.slot) + store_period = compute_sync_committee_period_at_slot(get_lc_beacon_slot(store.finalized_header)) + update_finalized_period = compute_sync_committee_period_at_slot(get_lc_beacon_slot(update.finalized_header)) if not is_next_sync_committee_known(store): assert update_finalized_period == store_period store.next_sync_committee = update.next_sync_committee @@ -382,9 +400,9 @@ def apply_light_client_update(store: LightClientStore, update: LightClientUpdate store.next_sync_committee = update.next_sync_committee store.previous_max_active_participants = store.current_max_active_participants store.current_max_active_participants = 0 - if update.finalized_header.slot > store.finalized_header.slot: + if get_lc_beacon_slot(update.finalized_header) > get_lc_beacon_slot(store.finalized_header): store.finalized_header = update.finalized_header - if store.finalized_header.slot > store.optimistic_header.slot: + if get_lc_beacon_slot(store.finalized_header) > get_lc_beacon_slot(store.optimistic_header): store.optimistic_header = store.finalized_header ``` @@ -393,14 +411,14 @@ def apply_light_client_update(store: LightClientStore, update: LightClientUpdate ```python def process_light_client_store_force_update(store: LightClientStore, current_slot: Slot) -> None: if ( - current_slot > store.finalized_header.slot + UPDATE_TIMEOUT + current_slot > get_lc_beacon_slot(store.finalized_header) + UPDATE_TIMEOUT and store.best_valid_update is not None ): # Forced best update when the update timeout has elapsed. - # Because the apply logic waits for `finalized_header.slot` to indicate sync committee finality, + # Because the apply logic waits for `get_lc_beacon_slot(finalized_header)` to indicate sync committee finality, # the `attested_header` may be treated as `finalized_header` in extended periods of non-finality # to guarantee progression into later sync committee periods according to `is_better_update`. - if store.best_valid_update.finalized_header.slot <= store.finalized_header.slot: + if get_lc_beacon_slot(store.best_valid_update.finalized_header) <= get_lc_beacon_slot(store.finalized_header): store.best_valid_update.finalized_header = store.best_valid_update.attested_header apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None @@ -433,7 +451,7 @@ def process_light_client_update(store: LightClientStore, # Update the optimistic header if ( sum(sync_committee_bits) > get_safety_threshold(store) - and update.attested_header.slot > store.optimistic_header.slot + and get_lc_beacon_slot(update.attested_header) > get_lc_beacon_slot(store.optimistic_header) ): store.optimistic_header = update.attested_header @@ -441,14 +459,14 @@ def process_light_client_update(store: LightClientStore, update_has_finalized_next_sync_committee = ( not is_next_sync_committee_known(store) and is_sync_committee_update(update) and is_finality_update(update) and ( - compute_sync_committee_period_at_slot(update.finalized_header.slot) - == compute_sync_committee_period_at_slot(update.attested_header.slot) + compute_sync_committee_period_at_slot(get_lc_beacon_slot(update.finalized_header)) + == compute_sync_committee_period_at_slot(get_lc_beacon_slot(update.attested_header)) ) ) if ( sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2 and ( - update.finalized_header.slot > store.finalized_header.slot + get_lc_beacon_slot(update.finalized_header) > get_lc_beacon_slot(store.finalized_header) or update_has_finalized_next_sync_committee ) ): 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 1364c4bf10..cc6a070a38 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 @@ -56,18 +56,18 @@ def get_update_file_name(spec, update): suffix2 = "f" else: suffix2 = "x" - return f"update_{encode_hex(update.attested_header.hash_tree_root())}_{suffix1}{suffix2}" + return f"update_{encode_hex(spec.get_lc_beacon_root(update.attested_header))}_{suffix1}{suffix2}" -def get_checks(store): +def get_checks(spec, store): return { "finalized_header": { - 'slot': int(store.finalized_header.slot), - 'beacon_root': encode_hex(store.finalized_header.hash_tree_root()), + 'slot': int(spec.get_lc_beacon_slot(store.finalized_header)), + 'beacon_root': encode_hex(spec.get_lc_beacon_root(store.finalized_header)), }, "optimistic_header": { - 'slot': int(store.optimistic_header.slot), - 'beacon_root': encode_hex(store.optimistic_header.hash_tree_root()), + 'slot': int(spec.get_lc_beacon_slot(store.optimistic_header)), + 'beacon_root': encode_hex(spec.get_lc_beacon_root(store.optimistic_header)), }, } @@ -80,7 +80,7 @@ def emit_force_update(test, spec, state): test.steps.append({ "force_update": { "current_slot": int(current_slot), - "checks": get_checks(test.store), + "checks": get_checks(spec, test.store), } }) @@ -99,7 +99,7 @@ def emit_update(test, spec, state, block, attested_state, attested_block, finali "process_update": { "update": get_update_file_name(spec, update), "current_slot": int(current_slot), - "checks": get_checks(test.store), + "checks": get_checks(spec, test.store), } }) return update @@ -141,10 +141,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance to next sync committee period # ``` @@ -167,10 +167,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Signature in next period # ``` @@ -193,10 +193,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Finalized header not included # ``` @@ -214,10 +214,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block=None) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Non-finalized case: Attested `next_sync_committee` is not finalized # ``` @@ -236,10 +236,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Force-update using timeout # ``` @@ -256,10 +256,10 @@ def test_light_client_sync(spec, state): attested_state = state.copy() next_slots(spec, state, spec.UPDATE_TIMEOUT - 1) yield from emit_force_update(test, spec, state) - assert test.store.finalized_header.slot == store_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == store_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == store_state.slot # Edge case: Finalized header not included, after force-update # ``` @@ -275,10 +275,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block=None) - assert test.store.finalized_header.slot == store_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update == update - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Finalized header older than store # ``` @@ -296,15 +296,15 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == store_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update == update - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot yield from emit_force_update(test, spec, state) - assert test.store.finalized_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == attested_state.slot assert test.store.next_sync_committee == attested_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance to next sync committee period # ``` @@ -327,10 +327,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Finish test yield from finish_test(test) @@ -357,10 +357,10 @@ def test_supply_sync_committee_from_past_update(spec, state): # Apply `LightClientUpdate` from the past, populating `store.next_sync_committee` yield from emit_update(test, spec, past_state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == state.slot # Finish test yield from finish_test(test) @@ -383,10 +383,10 @@ def test_advance_finality_without_sync_committee(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance finality into next sync committee period, but omit `next_sync_committee` transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) @@ -402,10 +402,10 @@ def test_advance_finality_without_sync_committee(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance finality once more, with `next_sync_committee` still unknown past_state = finalized_state @@ -419,24 +419,24 @@ def test_advance_finality_without_sync_committee(spec, state): # Apply `LightClientUpdate` without `finalized_header` nor `next_sync_committee` update = yield from emit_update(test, spec, state, block, attested_state, attested_block, None, with_next=False) - assert test.store.finalized_header.slot == past_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == past_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update == update - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Apply `LightClientUpdate` with `finalized_header` but no `next_sync_committee` yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Apply full `LightClientUpdate`, supplying `next_sync_committee` yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert test.store.finalized_header.slot == finalized_state.slot + assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert test.store.optimistic_header.slot == attested_state.slot + assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Finish test yield from finish_test(test) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py index 23ad795847..96c3724350 100644 --- a/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py @@ -59,7 +59,7 @@ def test_update_ranking(spec, state): # - `sig_finalized` / `sig_attested` --> Only signature in next sync committee period # - `att_finalized` / `att_attested` --> Attested header also in next sync committee period # - `fin_finalized` / `fin_attested` --> Finalized header also in next sync committee period - # - `lat_finalized` / `lat_attested` --> Like `fin`, but at a later `attested_header.slot` + # - `lat_finalized` / `lat_attested` --> Like `fin`, but at a later `get_lc_beacon_slot(attested_header)` next_slots(spec, state, spec.compute_start_slot_at_epoch(spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 3) - 1) sig_finalized_block = state_transition_with_full_block(spec, state, True, True) _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py index bf09cc30ec..860091b323 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py @@ -68,7 +68,7 @@ def test_process_light_client_update_at_period_boundary(spec, state): # Forward to slot before next sync committee period so that next block is final one in period next_slots(spec, state, spec.UPDATE_TIMEOUT - 2) - store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + store_period = spec.compute_sync_committee_period_at_slot(spec.get_lc_beacon_slot(store.optimistic_header)) update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period == update_period @@ -112,7 +112,7 @@ def test_process_light_client_update_timeout(spec, state): # Forward to next sync committee period next_slots(spec, state, spec.UPDATE_TIMEOUT) - store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + store_period = spec.compute_sync_committee_period_at_slot(spec.get_lc_beacon_slot(store.optimistic_header)) update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period + 1 == update_period @@ -164,7 +164,7 @@ def test_process_light_client_update_finality_updated(spec, state): # Ensure that finality checkpoint has changed assert state.finalized_checkpoint.epoch == 3 # Ensure that it's same period - store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.slot) + store_period = spec.compute_sync_committee_period_at_slot(spec.get_lc_beacon_slot(store.optimistic_header)) update_period = spec.compute_sync_committee_period_at_slot(state.slot) assert store_period == update_period diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index 4d7162c3bc..3beeec0ddc 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -25,12 +25,12 @@ Each step includes checks to verify the expected impact on the `store` object. ```yaml finalized_header: { - slot: int, -- Integer value from store.finalized_header.slot - beacon_root: string, -- Encoded 32-byte value from store.finalized_header.hash_tree_root() + slot: int, -- Integer value from get_lc_beacon_slot(store.finalized_header) + beacon_root: string, -- Encoded 32-byte value from get_lc_beacon_root(store.finalized_header) } optimistic_header: { - slot: int, -- Integer value from store.optimistic_header.slot - beacon_root: string, -- Encoded 32-byte value from store.optimistic_header.hash_tree_root() + slot: int, -- Integer value from get_lc_beacon_slot(store.optimistic_header) + beacon_root: string, -- Encoded 32-byte value from get_lc_beacon_root(store.optimistic_header) } ``` From 14fd9370467c8bcbd3ed6131ca46739577dd1a7a Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Tue, 6 Dec 2022 23:21:06 +0100 Subject: [PATCH 02/20] Fix --- specs/altair/light-client/full-node.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md index 0afbe54fbf..139b3c941d 100644 --- a/specs/altair/light-client/full-node.md +++ b/specs/altair/light-client/full-node.md @@ -121,7 +121,7 @@ def create_light_client_update(state: BeaconState, state_root=finalized_block.message.state_root, body_root=hash_tree_root(finalized_block.message.body), ) - assert get_lc_beacon_root(update.finalized_header) == attested_state.finalized_checkpoint.root + assert get_lc_beacon_root(finalized_header) == attested_state.finalized_checkpoint.root else: assert attested_state.finalized_checkpoint.root == Bytes32() finalized_header = BeaconBlockHeader() From 2e97af262734c4ae5a619a6991b81100d9ff404e Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 00:43:07 +0100 Subject: [PATCH 03/20] Add `ExecutionPayloadHeader` to LC data While the light client sync protocol currently provides access to the latest `BeaconBlockHeader`, obtaining the matching execution data needs workarounds such as downloading the full block. Having ready access to the EL state root simplifies use cases that need a way to cross-check `eth_getProof` responses against LC data. Access to `block_hash` unlocks scenarios where a CL light client drives an EL without `engine_newPayload`. As of Altair, only the CL beacon block root is available, but the EL block hash is needed for engine API. Other fields in the `ExecutionPayloadHeader` such as `logs_bloom` may allow light client applications to monitor blocks for local interest, e.g. for transfers affecting a certain wallet. This enables to download only the few relevant blocks instead of every single one. A new `LightClientStore` is proposed into the Capella spec that may be used to sync LC data that includes execution data. Existing pre-Capella LC data will remain as is, but can be locally upgraded before feeding it into the new `LightClientStore` so that light clients do not need to run a potentially expensive fork transition at a specific time. This enables the `LightClientStore` to be upgraded at a use case dependent timing at any time before Capella hits. Smart contract and embedded deployments benefit from reduced code size and do not need synchronization with the beacon chain clock to perform the Capella fork. --- Makefile | 2 +- README.md | 2 +- setup.py | 20 + specs/capella/light-client/fork.md | 93 +++++ specs/capella/light-client/full-node.md | 72 ++++ specs/capella/light-client/p2p-interface.md | 99 +++++ specs/capella/light-client/sync-protocol.md | 289 +++++++++++++ .../test/altair/light_client/test_sync.py | 392 +++++++++++++++--- .../test/capella/light_client/__init__.py | 0 .../light_client/test_single_merkle_proof.py | 31 ++ tests/formats/light_client/sync.md | 25 +- tests/generators/light_client/main.py | 8 +- 12 files changed, 974 insertions(+), 59 deletions(-) create mode 100644 specs/capella/light-client/fork.md create mode 100644 specs/capella/light-client/full-node.md create mode 100644 specs/capella/light-client/p2p-interface.md create mode 100644 specs/capella/light-client/sync-protocol.md create mode 100644 tests/core/pyspec/eth2spec/test/capella/light_client/__init__.py create mode 100644 tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py diff --git a/Makefile b/Makefile index 73450562b5..728656cb6a 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ GENERATOR_VENVS = $(patsubst $(GENERATOR_DIR)/%, $(GENERATOR_DIR)/%venv, $(GENER MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) \ $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SPEC_DIR)/altair/**/*.md) \ $(wildcard $(SPEC_DIR)/bellatrix/*.md) \ - $(wildcard $(SPEC_DIR)/capella/*.md) \ + $(wildcard $(SPEC_DIR)/capella/*.md) $(wildcard $(SPEC_DIR)/capella/**/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ $(wildcard $(SPEC_DIR)/sharding/*.md) \ diff --git a/README.md b/README.md index 07b78c0d58..766b6eda7b 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Features are researched and developed in parallel, and then consolidated into se ### In-development Specifications | Code Name or Topic | Specs | Notes | | - | - | - | -| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Validator additions](specs/capella/validator.md)
    • [P2P networking](specs/capella/p2p-interface.md)
| +| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/altair/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
  • [Validator additions](specs/capella/validator.md)
  • [P2P networking](specs/capella/p2p-interface.md)
| | EIP4844 (tentative) |
  • Core
    • [Beacon Chain changes](specs/eip4844/beacon-chain.md)
    • [EIP-4844 fork](specs/eip4844/fork.md)
    • [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
  • Additions
    • [Honest validator guide changes](specs/eip4844/validator.md)
    • [P2P networking](specs/eip4844/p2p-interface.md)
| | Sharding (outdated) |
  • Core
    • [Beacon Chain changes](specs/sharding/beacon-chain.md)
  • Additions
    • [P2P networking](specs/sharding/p2p-interface.md)
| | Custody Game (outdated) |
  • Core
    • [Beacon Chain changes](specs/custody_game/beacon-chain.md)
  • Additions
    • [Honest validator guide changes](specs/custody_game/validator.md)
| Dependent on sharding | diff --git a/setup.py b/setup.py index 432a41fe4c..db0ad0cfc4 100644 --- a/setup.py +++ b/setup.py @@ -616,6 +616,21 @@ def imports(cls, preset_name: str): ''' + @classmethod + def sundry_functions(cls) -> str: + return super().sundry_functions() + '\n\n' + ''' +def compute_merkle_proof_for_block_body(body: BeaconBlockBody, + index: GeneralizedIndex) -> Sequence[Bytes32]: + return build_proof(body.get_backing(), index)''' + + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: + constants = { + 'EXECUTION_PAYLOAD_INDEX': 'GeneralizedIndex(25)', + } + return {**super().hardcoded_ssz_dep_constants(), **constants} + # # EIP4844SpecBuilder # @@ -718,6 +733,7 @@ def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str if k in [ "ceillog2", "floorlog2", + "compute_merkle_proof_for_block_body", "compute_merkle_proof_for_state", ]: del spec_object.functions[k] @@ -1010,6 +1026,10 @@ def finalize_options(self): """ if self.spec_fork in (CAPELLA, EIP4844): self.md_doc_paths += """ + specs/capella/light-client/fork.md + specs/capella/light-client/full-node.md + specs/capella/light-client/p2p-interface.md + specs/capella/light-client/sync-protocol.md specs/capella/beacon-chain.md specs/capella/fork.md specs/capella/fork-choice.md diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md new file mode 100644 index 0000000000..700f736c27 --- /dev/null +++ b/specs/capella/light-client/fork.md @@ -0,0 +1,93 @@ +# Capella Light Client -- Fork Logic + +## Table of contents + + + + + +- [Introduction](#introduction) + - [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 [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 + +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. + +```python +def upgrade_lc_header_to_capella(pre: BeaconBlockHeader) -> LightClientHeader: + return LightClientHeader( + beacon=pre, + execution=ExecutionPayloadHeader(), + ) +``` + +```python +def upgrade_lc_bootstrap_to_capella(pre: altair.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_capella(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=pre.current_sync_committee_branch, + ) +``` + +```python +def upgrade_lc_update_to_capella(pre: altair.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=pre.next_sync_committee_branch, + finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_capella(pre: altair.LightClientFinalityUpdate) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_capella(pre: altair.LightClientOptimisticUpdate) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +### 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`. + +```python +def upgrade_lc_store_to_capella(pre: altair.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_capella(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_capella(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_capella(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/capella/light-client/full-node.md b/specs/capella/light-client/full-node.md new file mode 100644 index 0000000000..104853f8e5 --- /dev/null +++ b/specs/capella/light-client/full-node.md @@ -0,0 +1,72 @@ +# Capella 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) + - [`compute_merkle_proof_for_block_body`](#compute_merkle_proof_for_block_body) + - [Modified `block_to_light_client_header`](#modified-block_to_light_client_header) + + + + +## Introduction + +This upgrade adds information about the execution payload to light client data as part of the Capella upgrade. + +## Helper functions + +### `compute_merkle_proof_for_block_body` + +```python +def compute_merkle_proof_for_block_body(body: BeaconBlockBody, + index: GeneralizedIndex) -> Sequence[Bytes32]: + ... +``` + +### Modified `block_to_light_client_header` + +```python +def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: + if compute_epoch_at_slot(block.message.slot) >= 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), + ) + execution_branch = compute_merkle_proof_for_block_body(block.message.body, EXECUTION_PAYLOAD_INDEX) + else: + execution_header = ExecutionPayloadHeader() + execution_branch = [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + + 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/capella/light-client/p2p-interface.md b/specs/capella/light-client/p2p-interface.md new file mode 100644 index 0000000000..b6c1ec0808 --- /dev/null +++ b/specs/capella/light-client/p2p-interface.md @@ -0,0 +1,99 @@ +# Capella 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 [Altair light client networking specification](../../altair/light-client/p2p-interface.md) is extended to exchange [Capella 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` and later | `capella.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` and later | `capella.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` and later | `capella.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` and later | `capella.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` and later | `capella.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` and later | `capella.LightClientOptimisticUpdate` | diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md new file mode 100644 index 0000000000..d8bfe3fd11 --- /dev/null +++ b/specs/capella/light-client/sync-protocol.md @@ -0,0 +1,289 @@ +# Capella Light Client -- Sync Protocol + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Constants](#constants) +- [Containers](#containers) + - [`LightClientHeader`](#lightclientheader) + - [Modified `LightClientBootstrap`](#modified-lightclientbootstrap) + - [Modified `LightClientUpdate`](#modified-lightclientupdate) + - [Modified `LightClientFinalityUpdate`](#modified-lightclientfinalityupdate) + - [Modified `LightClientOptimisticUpdate`](#modified-lightclientoptimisticupdate) + - [Modified `LightClientStore`](#modified-lightclientstore) +- [Helper functions](#helper-functions) + - [Modified `get_lc_beacon_slot`](#modified-get_lc_beacon_slot) + - [Modified `get_lc_beacon_root`](#modified-get_lc_beacon_root) + - [`get_lc_execution_root`](#get_lc_execution_root) + - [`is_valid_light_client_header`](#is_valid_light_client_header) +- [Light client initialization](#light-client-initialization) + - [Modified `initialize_light_client_store`](#modified-initialize_light_client_store) +- [Light client state updates](#light-client-state-updates) + - [Modified `validate_light_client_update`](#modified-validate_light_client_update) + + + + +## Introduction + +This upgrade adds information about the execution payload to light client data as part of the Capella upgrade. It extends the [Altair Light Client specifications](../../altair/light-client/sync-protocol.md). The [fork document](./fork.md) explains how to upgrade existing Altair based deployments to Capella. + +Additional documents describes the impact of the upgrade on certain roles: +- [Full node](./full-node.md) +- [Networking](./p2p-interface.md) + +## Constants + +| Name | Value | +| - | - | +| `EXECUTION_PAYLOAD_INDEX` | `get_generalized_index(BeaconBlockBody, 'execution_payload')` (= 25) | + +## Containers + +### `LightClientHeader` + +```python +class LightClientHeader(Container): + # Beacon block header + beacon: BeaconBlockHeader + # Execution payload header corresponding to `beacon.body_root` (from Capella onward) + execution: ExecutionPayloadHeader + execution_branch: Vector[Bytes32, floorlog2(EXECUTION_PAYLOAD_INDEX)] +``` + +### Modified `LightClientBootstrap` + +```python +class LightClientBootstrap(Container): + # Header matching the requested beacon block root + header: LightClientHeader # [Modified in Capella] + # Current sync committee corresponding to `header.beacon.state_root` + current_sync_committee: SyncCommittee + current_sync_committee_branch: Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_INDEX)] +``` + +### Modified `LightClientUpdate` + +```python +class LightClientUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader # [Modified in Capella] + # Next sync committee corresponding to `attested_header.beacon.state_root` + next_sync_committee: SyncCommittee + next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header: LightClientHeader # [Modified in Capella] + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### Modified `LightClientFinalityUpdate` + +```python +class LightClientFinalityUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader # [Modified in Capella] + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header: LightClientHeader # [Modified in Capella] + finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### Modified `LightClientOptimisticUpdate` + +```python +class LightClientOptimisticUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader # [Modified in Capella] + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### Modified `LightClientStore` + +```python +@dataclass +class LightClientStore(object): + # Header that is finalized + finalized_header: LightClientHeader # [Modified in Capella] + # Sync committees corresponding to the finalized header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Best available header to switch finalized head to if we see nothing else + best_valid_update: Optional[LightClientUpdate] + # Most recent available reasonably-safe header + optimistic_header: LightClientHeader # [Modified in Capella] + # Max number of active participants in a sync committee (used to calculate safety threshold) + previous_max_active_participants: uint64 + current_max_active_participants: uint64 +``` + +## Helper functions + +### Modified `get_lc_beacon_slot` + +```python +def get_lc_beacon_slot(header: LightClientHeader) -> Slot: + return header.beacon.slot +``` + +### Modified `get_lc_beacon_root` + +```python +def get_lc_beacon_root(header: LightClientHeader) -> Root: + return hash_tree_root(header.beacon) +``` + +### `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + if compute_epoch_at_slot(get_lc_beacon_slot(header)) >= CAPELLA_FORK_EPOCH: + return hash_tree_root(header.execution) + + return Root() +``` + +### `is_valid_light_client_header` + +```python +def is_valid_light_client_header(header: LightClientHeader) -> bool: + if compute_epoch_at_slot(get_lc_beacon_slot(header)) >= CAPELLA_FORK_EPOCH: + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_INDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_INDEX), + root=header.beacon.body_root, + ) + + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + ) +``` + +## Light client initialization + +### Modified `initialize_light_client_store` + +```python +def initialize_light_client_store(trusted_block_root: Root, + bootstrap: LightClientBootstrap) -> LightClientStore: + assert is_valid_light_client_header(bootstrap.header) # [New in Capella] + assert get_lc_beacon_root(bootstrap.header) == trusted_block_root + + assert is_valid_merkle_branch( + leaf=hash_tree_root(bootstrap.current_sync_committee), + branch=bootstrap.current_sync_committee_branch, + depth=floorlog2(CURRENT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(CURRENT_SYNC_COMMITTEE_INDEX), + root=bootstrap.header.beacon.state_root, # [Modified in Capella] + ) + + return LightClientStore( + finalized_header=bootstrap.header, + current_sync_committee=bootstrap.current_sync_committee, + next_sync_committee=SyncCommittee(), + best_valid_update=None, + optimistic_header=bootstrap.header, + previous_max_active_participants=0, + current_max_active_participants=0, + ) +``` + +## Light client state updates + +### Modified `validate_light_client_update` + +```python +def validate_light_client_update(store: LightClientStore, + update: LightClientUpdate, + current_slot: Slot, + genesis_validators_root: Root) -> None: + # Verify sync committee has sufficient participants + sync_aggregate = update.sync_aggregate + assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify update does not skip a sync committee period + assert is_valid_light_client_header(update.attested_header) # [New in Capella] + update_attested_slot = get_lc_beacon_slot(update.attested_header) + update_finalized_slot = get_lc_beacon_slot(update.finalized_header) + assert current_slot >= update.signature_slot > update_attested_slot >= update_finalized_slot + store_period = compute_sync_committee_period_at_slot(get_lc_beacon_slot(store.finalized_header)) + update_signature_period = compute_sync_committee_period_at_slot(update.signature_slot) + if is_next_sync_committee_known(store): + assert update_signature_period in (store_period, store_period + 1) + else: + assert update_signature_period == store_period + + # Verify update is relevant + update_attested_period = compute_sync_committee_period_at_slot(update_attested_slot) + update_has_next_sync_committee = not is_next_sync_committee_known(store) and ( + is_sync_committee_update(update) and update_attested_period == store_period + ) + assert update_attested_slot > update_finalized_slot or update_has_next_sync_committee + + # Verify that the `finality_branch`, if present, confirms `finalized_header.beacon` + # to match the finalized checkpoint root saved in the state of `attested_header.beacon`. + # Note that the genesis finalized checkpoint root is represented as a zero hash. + if not is_finality_update(update): + assert update.finalized_header == LightClientHeader() # [Modified in Capella] + else: + if update_finalized_slot == GENESIS_SLOT: + assert update.finalized_header == LightClientHeader() # [Modified in Capella] + finalized_root = Bytes32() + else: + assert is_valid_light_client_header(update.finalized_header) # [New in Capella] + finalized_root = get_lc_beacon_root(update.finalized_header) + assert is_valid_merkle_branch( + leaf=finalized_root, + branch=update.finality_branch, + depth=floorlog2(FINALIZED_ROOT_INDEX), + index=get_subtree_index(FINALIZED_ROOT_INDEX), + root=update.attested_header.beacon.state_root, # [Modified in Capella] + ) + + # Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the + # state of the `attested_header.beacon` + if not is_sync_committee_update(update): + assert update.next_sync_committee == SyncCommittee() + 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( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), + index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), + root=update.attested_header.beacon.state_root, # [Modified in Capella] + ) + + # Verify sync committee aggregate signature + if update_signature_period == store_period: + sync_committee = store.current_sync_committee + else: + sync_committee = store.next_sync_committee + participant_pubkeys = [ + pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) + if bit + ] + fork_version = compute_fork_version(compute_epoch_at_slot(update.signature_slot)) + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root) + signing_root = compute_signing_root(update.attested_header.beacon, domain) # [Modified in Capella] + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) +``` 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 cc6a070a38..53215670ae 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 @@ -3,14 +3,30 @@ from eth_utils import encode_hex from eth2spec.test.context import ( spec_state_test_with_matching_config, + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, with_presets, + with_state, with_altair_and_later, ) from eth2spec.test.helpers.attestations import ( next_slots_with_attestations, state_transition_with_full_block, ) -from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.constants import ( + PHASE0, ALTAIR, BELLATRIX, CAPELLA, + MINIMAL, + ALL_PHASES, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, +) +from eth2spec.test.helpers.forks import ( + is_post_capella, + is_post_fork, +) from eth2spec.test.helpers.light_client import ( get_sync_aggregate, ) @@ -20,25 +36,117 @@ ) -def setup_test(spec, state): - class LightClientSyncTest(object): - steps: List[Dict[str, Any]] - genesis_validators_root: spec.Root - store: spec.LightClientStore +def get_spec_for_fork_version(spec, fork_version, phases): + if phases is None: + return spec + for fork in [fork for fork in ALL_PHASES if is_post_fork(spec.fork, fork)]: + if fork == PHASE0: + fork_version_field = 'GENESIS_FORK_VERSION' + else: + fork_version_field = fork.upper() + '_FORK_VERSION' + if fork_version == getattr(spec.config, fork_version_field): + return phases[fork] + raise ValueError("Unknown fork version %s" % fork_version) + + +def needs_upgrade_to_capella(d_spec, s_spec): + return is_post_capella(s_spec) and not is_post_capella(d_spec) + + +def upgrade_lc_bootstrap_to_store(d_spec, s_spec, data): + if not needs_upgrade_to_capella(d_spec, s_spec): + return data + + upgraded = s_spec.upgrade_lc_bootstrap_to_capella(data) + assert s_spec.get_lc_beacon_slot(upgraded.header) == d_spec.get_lc_beacon_slot(data.header) + assert s_spec.get_lc_beacon_root(upgraded.header) == d_spec.get_lc_beacon_root(data.header) + assert s_spec.get_lc_execution_root(upgraded.header) == s_spec.Root() + assert upgraded.current_sync_committee == data.current_sync_committee + assert upgraded.current_sync_committee_branch == data.current_sync_committee_branch + return upgraded + + +def upgrade_lc_update_to_store(d_spec, s_spec, data): + if not needs_upgrade_to_capella(d_spec, s_spec): + return data + + upgraded = s_spec.upgrade_lc_update_to_capella(data) + assert s_spec.get_lc_beacon_slot(upgraded.attested_header) == d_spec.get_lc_beacon_slot(data.attested_header) + assert s_spec.get_lc_beacon_root(upgraded.attested_header) == d_spec.get_lc_beacon_root(data.attested_header) + assert s_spec.get_lc_execution_root(upgraded.attested_header) == s_spec.Root() + assert upgraded.next_sync_committee == data.next_sync_committee + assert upgraded.next_sync_committee_branch == data.next_sync_committee_branch + assert s_spec.get_lc_beacon_slot(upgraded.finalized_header) == d_spec.get_lc_beacon_slot(data.finalized_header) + assert s_spec.get_lc_beacon_root(upgraded.finalized_header) == d_spec.get_lc_beacon_root(data.finalized_header) + assert s_spec.get_lc_execution_root(upgraded.finalized_header) == s_spec.Root() + assert upgraded.sync_aggregate == data.sync_aggregate + assert upgraded.signature_slot == data.signature_slot + return upgraded + + +def upgrade_lc_store_to_new_spec(d_spec, s_spec, data): + if not needs_upgrade_to_capella(d_spec, s_spec): + return data + + upgraded = s_spec.upgrade_lc_store_to_capella(data) + assert s_spec.get_lc_beacon_slot(upgraded.finalized_header) == d_spec.get_lc_beacon_slot(data.finalized_header) + assert s_spec.get_lc_beacon_root(upgraded.finalized_header) == d_spec.get_lc_beacon_root(data.finalized_header) + assert s_spec.get_lc_execution_root(upgraded.finalized_header) == s_spec.Root() + assert upgraded.current_sync_committee == data.current_sync_committee + assert upgraded.next_sync_committee == data.next_sync_committee + if upgraded.best_valid_update is None: + assert data.best_valid_update is None + else: + assert upgraded.best_valid_update == upgrade_lc_update_to_store(d_spec, s_spec, data.best_valid_update) + assert s_spec.get_lc_beacon_slot(upgraded.optimistic_header) == d_spec.get_lc_beacon_slot(data.optimistic_header) + assert s_spec.get_lc_beacon_root(upgraded.optimistic_header) == d_spec.get_lc_beacon_root(data.optimistic_header) + assert s_spec.get_lc_execution_root(upgraded.optimistic_header) == s_spec.Root() + assert upgraded.previous_max_active_participants == data.previous_max_active_participants + assert upgraded.current_max_active_participants == data.current_max_active_participants + return upgraded + + +class LightClientSyncTest(object): + steps: List[Dict[str, Any]] + genesis_validators_root: Any + s_spec: Any + store: Any + +def get_store_fork_version(s_spec): + if is_post_capella(s_spec): + return s_spec.config.CAPELLA_FORK_VERSION + return s_spec.config.ALTAIR_FORK_VERSION + + +def setup_test(spec, state, s_spec=None, phases=None): test = LightClientSyncTest() test.steps = [] + if s_spec is None: + s_spec = spec + test.s_spec = s_spec + yield "genesis_validators_root", "meta", "0x" + state.genesis_validators_root.hex() test.genesis_validators_root = state.genesis_validators_root next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2 - 1) trusted_block = state_transition_with_full_block(spec, state, True, True) trusted_block_root = trusted_block.message.hash_tree_root() - bootstrap = spec.create_light_client_bootstrap(state, trusted_block) yield "trusted_block_root", "meta", "0x" + trusted_block_root.hex() - yield "bootstrap", bootstrap - test.store = spec.initialize_light_client_store(trusted_block_root, bootstrap) + + data_fork_version = spec.compute_fork_version(spec.compute_epoch_at_slot(trusted_block.message.slot)) + data_fork_digest = spec.compute_fork_digest(data_fork_version, test.genesis_validators_root) + d_spec = get_spec_for_fork_version(spec, data_fork_version, phases) + data = d_spec.create_light_client_bootstrap(state, trusted_block) + yield "bootstrap_fork_digest", "meta", encode_hex(data_fork_digest) + yield "bootstrap", data + + upgraded = upgrade_lc_bootstrap_to_store(d_spec, test.s_spec, data) + 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) + yield "store_fork_digest", "meta", encode_hex(store_fork_digest) return test @@ -47,62 +155,97 @@ def finish_test(test): yield "steps", test.steps -def get_update_file_name(spec, update): - if spec.is_sync_committee_update(update): +def get_update_file_name(d_spec, update): + if d_spec.is_sync_committee_update(update): suffix1 = "s" else: suffix1 = "x" - if spec.is_finality_update(update): + if d_spec.is_finality_update(update): suffix2 = "f" else: suffix2 = "x" - return f"update_{encode_hex(spec.get_lc_beacon_root(update.attested_header))}_{suffix1}{suffix2}" - + return f"update_{encode_hex(d_spec.get_lc_beacon_root(update.attested_header))}_{suffix1}{suffix2}" + + +def get_checks(s_spec, store): + if is_post_capella(s_spec): + return { + "finalized_header": { + 'slot': int(s_spec.get_lc_beacon_slot(store.finalized_header)), + 'beacon_root': encode_hex(s_spec.get_lc_beacon_root(store.finalized_header)), + 'execution_root': encode_hex(s_spec.get_lc_execution_root(store.finalized_header)), + }, + "optimistic_header": { + 'slot': int(s_spec.get_lc_beacon_slot(store.optimistic_header)), + 'beacon_root': encode_hex(s_spec.get_lc_beacon_root(store.optimistic_header)), + 'execution_root': encode_hex(s_spec.get_lc_execution_root(store.optimistic_header)), + }, + } -def get_checks(spec, store): return { "finalized_header": { - 'slot': int(spec.get_lc_beacon_slot(store.finalized_header)), - 'beacon_root': encode_hex(spec.get_lc_beacon_root(store.finalized_header)), + 'slot': int(s_spec.get_lc_beacon_slot(store.finalized_header)), + 'beacon_root': encode_hex(s_spec.get_lc_beacon_root(store.finalized_header)), }, "optimistic_header": { - 'slot': int(spec.get_lc_beacon_slot(store.optimistic_header)), - 'beacon_root': encode_hex(spec.get_lc_beacon_root(store.optimistic_header)), + 'slot': int(s_spec.get_lc_beacon_slot(store.optimistic_header)), + 'beacon_root': encode_hex(s_spec.get_lc_beacon_root(store.optimistic_header)), }, } def emit_force_update(test, spec, state): current_slot = state.slot - spec.process_light_client_store_force_update(test.store, current_slot) + test.s_spec.process_light_client_store_force_update(test.store, current_slot) yield from [] # Consistently enable `yield from` syntax in calling tests test.steps.append({ "force_update": { "current_slot": int(current_slot), - "checks": get_checks(spec, test.store), + "checks": get_checks(test.s_spec, test.store), } }) -def emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=True): - update = spec.create_light_client_update(state, block, attested_state, attested_block, finalized_block) +def emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=True, phases=None): + data_fork_version = spec.compute_fork_version(spec.compute_epoch_at_slot(attested_block.message.slot)) + data_fork_digest = spec.compute_fork_digest(data_fork_version, test.genesis_validators_root) + d_spec = get_spec_for_fork_version(spec, data_fork_version, phases) + data = d_spec.create_light_client_update(state, block, attested_state, attested_block, finalized_block) if not with_next: - update.next_sync_committee = spec.SyncCommittee() - update.next_sync_committee_branch = \ + data.next_sync_committee = spec.SyncCommittee() + data.next_sync_committee_branch = \ [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] current_slot = state.slot - spec.process_light_client_update(test.store, update, current_slot, test.genesis_validators_root) - yield get_update_file_name(spec, update), update + upgraded = upgrade_lc_update_to_store(d_spec, test.s_spec, data) + 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 test.steps.append({ "process_update": { - "update": get_update_file_name(spec, update), + "update_fork_digest": encode_hex(data_fork_digest), + "update": get_update_file_name(d_spec, data), "current_slot": int(current_slot), - "checks": get_checks(spec, test.store), + "checks": get_checks(test.s_spec, test.store), + } + }) + return upgraded + + +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.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) + + yield from [] # Consistently enable `yield from` syntax in calling tests + test.steps.append({ + "process_upgrade_store": { + "store_fork_digest": encode_hex(store_fork_digest), + "checks": get_checks(test.s_spec, test.store), } }) - return update def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): @@ -141,10 +284,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance to next sync committee period # ``` @@ -167,10 +310,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Signature in next period # ``` @@ -193,10 +336,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Finalized header not included # ``` @@ -214,10 +357,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block=None) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Non-finalized case: Attested `next_sync_committee` is not finalized # ``` @@ -236,10 +379,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) update = yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Force-update using timeout # ``` @@ -278,7 +421,7 @@ def test_light_client_sync(spec, state): assert spec.get_lc_beacon_slot(test.store.finalized_header) == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update == update - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Edge case: Finalized header older than store # ``` @@ -299,12 +442,12 @@ def test_light_client_sync(spec, state): assert spec.get_lc_beacon_slot(test.store.finalized_header) == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update == update - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot yield from emit_force_update(test, spec, state) assert spec.get_lc_beacon_slot(test.store.finalized_header) == attested_state.slot assert test.store.next_sync_committee == attested_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance to next sync committee period # ``` @@ -327,10 +470,10 @@ def test_light_client_sync(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Finish test yield from finish_test(test) @@ -383,10 +526,10 @@ def test_advance_finality_without_sync_committee(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance finality into next sync committee period, but omit `next_sync_committee` transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) @@ -402,10 +545,10 @@ def test_advance_finality_without_sync_committee(spec, state): sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Advance finality once more, with `next_sync_committee` still unknown past_state = finalized_state @@ -422,21 +565,162 @@ def test_advance_finality_without_sync_committee(spec, state): assert spec.get_lc_beacon_slot(test.store.finalized_header) == past_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update == update - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Apply `LightClientUpdate` with `finalized_header` but no `next_sync_committee` yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert not spec.is_next_sync_committee_known(test.store) assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Apply full `LightClientUpdate`, supplying `next_sync_committee` yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block) - assert spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + + # Finish test + yield from finish_test(test) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_config_overrides({ + 'CAPELLA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_fork(spec, phases, state): + # Start test + test = yield from setup_test(spec, state, phases=phases) + + # Initial `LightClientUpdate` + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + + # Jump to two slots before Capella + transition_to(spec, state, spec.compute_start_slot_at_epoch(phases[CAPELLA].config.CAPELLA_FORK_EPOCH) - 4) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = \ + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + + # Perform `LightClientStore` upgrade + yield from emit_upgrade_store(test, phases[CAPELLA], phases=phases) + update = test.store.best_valid_update + + # Final slot before Capella, check that importing the Altair format still works + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + + # Upgrade to Capella, attested block is still before the fork + attested_block = block.copy() + attested_state = state.copy() + state, _ = do_fork(state, spec, phases[CAPELLA], phases[CAPELLA].config.CAPELLA_FORK_EPOCH, with_block=False) + spec = phases[CAPELLA] + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) == test.s_spec.Root() + + # Another block in Capella, this time attested block is after the fork + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() + + # Jump to next epoch + transition_to(spec, state, spec.compute_start_slot_at_epoch(phases[CAPELLA].config.CAPELLA_FORK_EPOCH + 1) - 2) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_execution_root(test.store.finalized_header) == test.s_spec.Root() + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() + + # Finalize it + finalized_block = block.copy() + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.s_spec.get_lc_execution_root(test.store.finalized_header) != test.s_spec.Root() + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() + + # Finish test + yield from finish_test(test) + + +@with_phases(phases=[ALTAIR, BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_store_with_legacy_data(spec, phases, state): + # Start test (Altair bootstrap but with a Capella store) + test = yield from setup_test(spec, state, s_spec=phases[CAPELLA], phases=phases) + + # Initial `LightClientUpdate` (check that it works with Capella store) + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None - assert spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Finish test yield from finish_test(test) diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/__init__.py b/tests/core/pyspec/eth2spec/test/capella/light_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ca257a5a34 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py @@ -0,0 +1,31 @@ +from eth2spec.test.context import ( + spec_state_test, + with_capella_and_later, + with_test_suite_name, +) +from eth2spec.test.helpers.attestations import ( + state_transition_with_full_block, +) + + +@with_test_suite_name("BeaconBlockBody") +@with_capella_and_later +@spec_state_test +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_for_block_body(block.message.body, spec.EXECUTION_PAYLOAD_INDEX) + yield "proof", { + "leaf": "0x" + block.message.body.execution_payload.hash_tree_root().hex(), + "leaf_index": spec.EXECUTION_PAYLOAD_INDEX, + "branch": ['0x' + root.hex() for root in execution_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_INDEX), + index=spec.get_subtree_index(spec.EXECUTION_PAYLOAD_INDEX), + root=block.message.body.hash_tree_root(), + ) diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index 3beeec0ddc..5d6ac2b920 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -9,11 +9,15 @@ This series of tests provides reference test vectors for validating that a light ```yaml genesis_validators_root: Bytes32 -- string, hex encoded, with 0x prefix trusted_block_root: Bytes32 -- string, hex encoded, with 0x prefix +bootstrap_fork_digest: string -- Encoded `ForkDigest`-context of `bootstrap` +store_fork_digest: string -- Encoded `ForkDigest`-context of `store` object being tested ``` ### `bootstrap.ssz_snappy` -An SSZ-snappy encoded `bootstrap` object of type `LightClientBootstrap` to initialize a local `store` object of type `LightClientStore` using `initialize_light_client_store(trusted_block_rooot, bootstrap)`. +An SSZ-snappy encoded `bootstrap` object of type `LightClientBootstrap` to initialize a local `store` object of type `LightClientStore` with `store_fork_digest` using `initialize_light_client_store(trusted_block_rooot, bootstrap)`. The SSZ type can be determined from `bootstrap_fork_digest`. + +If `store_fork_digest` differs from `bootstrap_fork_digest`, the `bootstrap` object may need upgrading before initializing the store. ### `steps.yaml` @@ -27,10 +31,12 @@ Each step includes checks to verify the expected impact on the `store` object. finalized_header: { slot: int, -- Integer value from get_lc_beacon_slot(store.finalized_header) beacon_root: string, -- Encoded 32-byte value from get_lc_beacon_root(store.finalized_header) + execution_root: string, -- From Capella onward; get_lc_execution_root(store.finalized_header) } optimistic_header: { slot: int, -- Integer value from get_lc_beacon_slot(store.optimistic_header) beacon_root: string, -- Encoded 32-byte value from get_lc_beacon_root(store.optimistic_header) + execution_root: string, -- From Capella onward; get_lc_execution_root(store.optimistic_header) } ``` @@ -54,6 +60,7 @@ The function `process_light_client_update(store, update, current_slot, genesis_v ```yaml { + update_fork_digest: string -- Encoded `ForkDigest`-context of `update` update: string -- name of the `*.ssz_snappy` file to load as a `LightClientUpdate` object current_slot: int -- integer, decimal @@ -61,8 +68,24 @@ The function `process_light_client_update(store, update, current_slot, genesis_v } ``` +If `store_fork_digest` differs from `update_fork_digest`, the `update` object may need upgrading before initializing the store. + After this step, the `store` object may have been updated. +#### `process_upgrade_store` + +The `store` should be upgraded to reflect the new `store_fork_digest`: + +```yaml +{ + store_fork_digest: string -- Encoded `ForkDigest`-context of `store` + checks: {: value} -- the assertions. +} +``` + +After this step, the `store` object may have been updated. + + ## Condition A test-runner should initialize a local `LightClientStore` using the provided `bootstrap` object. It should then proceed to execute all the test steps in sequence. After each step, it should verify that the resulting `store` verifies against the provided `checks`. diff --git a/tests/generators/light_client/main.py b/tests/generators/light_client/main.py index 5d45bf39dd..54c09fae64 100644 --- a/tests/generators/light_client/main.py +++ b/tests/generators/light_client/main.py @@ -1,5 +1,5 @@ from eth2spec.test.helpers.constants import ALTAIR, BELLATRIX, CAPELLA, EIP4844 -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators +from eth2spec.gen_helpers.gen_from_tests.gen import combine_mods, run_state_test_generators if __name__ == "__main__": @@ -9,7 +9,11 @@ 'update_ranking', ]} bellatrix_mods = altair_mods - capella_mods = bellatrix_mods + + _new_capella_mods = {key: 'eth2spec.test.capella.light_client.test_' + key for key in [ + 'single_merkle_proof', + ]} + capella_mods = combine_mods(_new_capella_mods, bellatrix_mods) eip4844_mods = capella_mods all_mods = { From 2df8a559b8a20cee0a371d8185909c402fe0fe21 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 12:19:46 +0100 Subject: [PATCH 04/20] Fix LC references in main readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 766b6eda7b..7c9713a470 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Features are researched and developed in parallel, and then consolidated into se ### In-development Specifications | Code Name or Topic | Specs | Notes | | - | - | - | -| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/altair/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
  • [Validator additions](specs/capella/validator.md)
  • [P2P networking](specs/capella/p2p-interface.md)
| +| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/light-client/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/capella/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
  • [Validator additions](specs/capella/validator.md)
  • [P2P networking](specs/capella/p2p-interface.md)
| | EIP4844 (tentative) |
  • Core
    • [Beacon Chain changes](specs/eip4844/beacon-chain.md)
    • [EIP-4844 fork](specs/eip4844/fork.md)
    • [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
  • Additions
    • [Honest validator guide changes](specs/eip4844/validator.md)
    • [P2P networking](specs/eip4844/p2p-interface.md)
| | Sharding (outdated) |
  • Core
    • [Beacon Chain changes](specs/sharding/beacon-chain.md)
  • Additions
    • [P2P networking](specs/sharding/p2p-interface.md)
| | Custody Game (outdated) |
  • Core
    • [Beacon Chain changes](specs/custody_game/beacon-chain.md)
  • Additions
    • [Honest validator guide changes](specs/custody_game/validator.md)
| Dependent on sharding | From 11d2a5994899fc1513f15da98cefaa45700f2400 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 12:33:58 +0100 Subject: [PATCH 05/20] Flip `is_valid_light_client_header` logic for extensibility --- specs/capella/light-client/sync-protocol.md | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md index d8bfe3fd11..515b535825 100644 --- a/specs/capella/light-client/sync-protocol.md +++ b/specs/capella/light-client/sync-protocol.md @@ -162,18 +162,19 @@ def get_lc_execution_root(header: LightClientHeader) -> Root: ```python def is_valid_light_client_header(header: LightClientHeader) -> bool: - if compute_epoch_at_slot(get_lc_beacon_slot(header)) >= CAPELLA_FORK_EPOCH: - return is_valid_merkle_branch( - leaf=get_lc_execution_root(header), - branch=header.execution_branch, - depth=floorlog2(EXECUTION_PAYLOAD_INDEX), - index=get_subtree_index(EXECUTION_PAYLOAD_INDEX), - root=header.beacon.body_root, - ) - - return ( - header.execution == ExecutionPayloadHeader() - and header.execution_branch == [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + if compute_epoch_at_slot(get_lc_beacon_slot(header)) < CAPELLA_FORK_EPOCH: + if header.execution != ExecutionPayloadHeader(): + return False + + if header.execution_branch != [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))]: + return False + + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_INDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_INDEX), + root=header.beacon.body_root, ) ``` From d6da56cc195699babbe325b3f6093487d358aba5 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 12:37:57 +0100 Subject: [PATCH 06/20] Remove double mention of validator changes in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c9713a470..d7b6595d6c 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Features are researched and developed in parallel, and then consolidated into se ### In-development Specifications | Code Name or Topic | Specs | Notes | | - | - | - | -| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/light-client/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/capella/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
  • [Validator additions](specs/capella/validator.md)
  • [P2P networking](specs/capella/p2p-interface.md)
| +| Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/light-client/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/capella/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
    • [P2P networking](specs/capella/p2p-interface.md)
| | EIP4844 (tentative) |
  • Core
    • [Beacon Chain changes](specs/eip4844/beacon-chain.md)
    • [EIP-4844 fork](specs/eip4844/fork.md)
    • [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
  • Additions
    • [Honest validator guide changes](specs/eip4844/validator.md)
    • [P2P networking](specs/eip4844/p2p-interface.md)
| | Sharding (outdated) |
  • Core
    • [Beacon Chain changes](specs/sharding/beacon-chain.md)
  • Additions
    • [P2P networking](specs/sharding/p2p-interface.md)
| | Custody Game (outdated) |
  • Core
    • [Beacon Chain changes](specs/custody_game/beacon-chain.md)
  • Additions
    • [Honest validator guide changes](specs/custody_game/validator.md)
| Dependent on sharding | From 5028a806ad9f07822df2bc6a5be4796943f013ef Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 12:45:43 +0100 Subject: [PATCH 07/20] Implicit init during fork transition --- specs/capella/light-client/fork.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md index 700f736c27..ee2102b22d 100644 --- a/specs/capella/light-client/fork.md +++ b/specs/capella/light-client/fork.md @@ -25,7 +25,6 @@ A Capella `LightClientStore` can still process earlier light client data. In ord def upgrade_lc_header_to_capella(pre: BeaconBlockHeader) -> LightClientHeader: return LightClientHeader( beacon=pre, - execution=ExecutionPayloadHeader(), ) ``` From 4df86632b6c56f10cab8934e444e9fb16fea653b Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 12:56:34 +0100 Subject: [PATCH 08/20] Rename `legacy` --> `altair` in test name --- .../core/pyspec/eth2spec/test/altair/light_client/test_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 53215670ae..31bee9dd6a 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 @@ -704,7 +704,7 @@ def test_capella_fork(spec, phases, state): @with_state @with_matching_spec_config(emitted_fork=CAPELLA) @with_presets([MINIMAL], reason="too slow") -def test_capella_store_with_legacy_data(spec, phases, state): +def test_capella_store_with_altair_data(spec, phases, state): # Start test (Altair bootstrap but with a Capella store) test = yield from setup_test(spec, state, s_spec=phases[CAPELLA], phases=phases) From e67ca3d3098c476139ee4149442f0dc0e6761af9 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 13:09:18 +0100 Subject: [PATCH 09/20] Compute epoch only once for better readability --- specs/capella/light-client/full-node.md | 4 +++- specs/capella/light-client/sync-protocol.md | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/specs/capella/light-client/full-node.md b/specs/capella/light-client/full-node.md index 104853f8e5..6a2104a383 100644 --- a/specs/capella/light-client/full-node.md +++ b/specs/capella/light-client/full-node.md @@ -34,7 +34,9 @@ def compute_merkle_proof_for_block_body(body: BeaconBlockBody, ```python def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: - if compute_epoch_at_slot(block.message.slot) >= CAPELLA_FORK_EPOCH: + 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, diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md index 515b535825..060dea1b9d 100644 --- a/specs/capella/light-client/sync-protocol.md +++ b/specs/capella/light-client/sync-protocol.md @@ -152,7 +152,9 @@ def get_lc_beacon_root(header: LightClientHeader) -> Root: ```python def get_lc_execution_root(header: LightClientHeader) -> Root: - if compute_epoch_at_slot(get_lc_beacon_slot(header)) >= CAPELLA_FORK_EPOCH: + epoch = compute_epoch_at_slot(get_lc_beacon_slot(header)) + + if epoch >= CAPELLA_FORK_EPOCH: return hash_tree_root(header.execution) return Root() @@ -162,7 +164,9 @@ def get_lc_execution_root(header: LightClientHeader) -> Root: ```python def is_valid_light_client_header(header: LightClientHeader) -> bool: - if compute_epoch_at_slot(get_lc_beacon_slot(header)) < CAPELLA_FORK_EPOCH: + epoch = compute_epoch_at_slot(get_lc_beacon_slot(header)) + + if epoch < CAPELLA_FORK_EPOCH: if header.execution != ExecutionPayloadHeader(): return False From 8ad6810a44d86ca57fb37053cc299164d074795b Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 13:11:49 +0100 Subject: [PATCH 10/20] EIP4844 support (`excess_data_gas`), fork tests nyi --- Makefile | 2 +- README.md | 2 +- setup.py | 4 + specs/eip4844/light-client/fork.md | 110 ++++++++++++++++++++ specs/eip4844/light-client/full-node.md | 70 +++++++++++++ specs/eip4844/light-client/p2p-interface.md | 105 +++++++++++++++++++ specs/eip4844/light-client/sync-protocol.md | 88 ++++++++++++++++ 7 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 specs/eip4844/light-client/fork.md create mode 100644 specs/eip4844/light-client/full-node.md create mode 100644 specs/eip4844/light-client/p2p-interface.md create mode 100644 specs/eip4844/light-client/sync-protocol.md diff --git a/Makefile b/Makefile index 728656cb6a..f99f2ae9b0 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) \ $(wildcard $(SPEC_DIR)/custody/*.md) \ $(wildcard $(SPEC_DIR)/das/*.md) \ $(wildcard $(SPEC_DIR)/sharding/*.md) \ - $(wildcard $(SPEC_DIR)/eip4844/*.md) \ + $(wildcard $(SPEC_DIR)/eip4844/*.md) $(wildcard $(SPEC_DIR)/eip4844/**/*.md) \ $(wildcard $(SSZ_DIR)/*.md) COV_HTML_OUT=.htmlcov diff --git a/README.md b/README.md index d7b6595d6c..0827e06a44 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Features are researched and developed in parallel, and then consolidated into se | Code Name or Topic | Specs | Notes | | - | - | - | | Capella (tentative) |
  • Core
    • [Beacon chain changes](specs/capella/beacon-chain.md)
    • [Capella fork](specs/capella/fork.md)
  • Additions
    • [Light client sync protocol changes](specs/capella/light-client/sync-protocol.md) ([fork](specs/capella/light-client/fork.md), [full node](specs/capella/light-client/full-node.md), [networking](specs/capella/light-client/p2p-interface.md))
    • [Validator additions](specs/capella/validator.md)
    • [P2P networking](specs/capella/p2p-interface.md)
| -| EIP4844 (tentative) |
  • Core
    • [Beacon Chain changes](specs/eip4844/beacon-chain.md)
    • [EIP-4844 fork](specs/eip4844/fork.md)
    • [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
  • Additions
    • [Honest validator guide changes](specs/eip4844/validator.md)
    • [P2P networking](specs/eip4844/p2p-interface.md)
| +| EIP4844 (tentative) |
  • Core
    • [Beacon Chain changes](specs/eip4844/beacon-chain.md)
    • [EIP-4844 fork](specs/eip4844/fork.md)
    • [Polynomial commitments](specs/eip4844/polynomial-commitments.md)
  • Additions
    • [Light client sync protocol changes](specs/eip4844/light-client/sync-protocol.md) ([fork](specs/eip4844/light-client/fork.md), [full node](specs/eip4844/light-client/full-node.md), [networking](specs/eip4844/light-client/p2p-interface.md))
    • [Honest validator guide changes](specs/eip4844/validator.md)
    • [P2P networking](specs/eip4844/p2p-interface.md)
| | Sharding (outdated) |
  • Core
    • [Beacon Chain changes](specs/sharding/beacon-chain.md)
  • Additions
    • [P2P networking](specs/sharding/p2p-interface.md)
| | Custody Game (outdated) |
  • Core
    • [Beacon Chain changes](specs/custody_game/beacon-chain.md)
  • Additions
    • [Honest validator guide changes](specs/custody_game/validator.md)
| Dependent on sharding | | Data Availability Sampling (outdated) |
  • Core
    • [Core types and functions](specs/das/das-core.md)
    • [Fork choice changes](specs/das/fork-choice.md)
  • Additions
    • [P2P Networking](specs/das/p2p-interface.md)
    • [Sampling process](specs/das/sampling.md)
|
  • Dependent on sharding
  • [Technical explainer](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD)
| diff --git a/setup.py b/setup.py index db0ad0cfc4..83a2e71f34 100644 --- a/setup.py +++ b/setup.py @@ -1038,6 +1038,10 @@ def finalize_options(self): """ if self.spec_fork == EIP4844: self.md_doc_paths += """ + specs/eip4844/light-client/fork.md + specs/eip4844/light-client/full-node.md + specs/eip4844/light-client/p2p-interface.md + specs/eip4844/light-client/sync-protocol.md specs/eip4844/beacon-chain.md specs/eip4844/fork.md specs/eip4844/polynomial-commitments.md diff --git a/specs/eip4844/light-client/fork.md b/specs/eip4844/light-client/fork.md new file mode 100644 index 0000000000..2d5f74f467 --- /dev/null +++ b/specs/eip4844/light-client/fork.md @@ -0,0 +1,110 @@ +# EIP4844 Light Client -- Fork Logic + +## Table of contents + + + + + +- [Introduction](#introduction) + - [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 [Capella specification](../../capella/light-client/sync-protocol.md) to EIP4844. This is necessary when processing pre-EIP4844 data with a post-EIP4844 `LightClientStore`. Note that the data being exchanged over the network protocols uses the original format. + +### Upgrading light client data + +A EIP4844 `LightClientStore` can still process earlier light client data. In order to do so, that pre-EIP4844 data needs to be locally upgraded to EIP4844 before processing. + +```python +def upgrade_lc_header_to_eip4844(pre: capella.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, + ), + execution_branch=pre.execution_branch, + ) +``` + +```python +def upgrade_lc_bootstrap_to_eip4844(pre: capella.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_eip4844(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=pre.current_sync_committee_branch, + ) +``` + +```python +def upgrade_lc_update_to_eip4844(pre: capella.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_eip4844(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=pre.next_sync_committee_branch, + finalized_header=upgrade_lc_header_to_eip4844(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_eip4844(pre: capella.LightClientFinalityUpdate) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_eip4844(pre.attested_header), + finalized_header=upgrade_lc_header_to_eip4844(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_eip4844(pre: capella.LightClientOptimisticUpdate) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_eip4844(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +### Upgrading the store + +Existing `LightClientStore` objects based on Capella MUST be upgraded to EIP4844 before EIP4844 based light client data can be processed. The `LightClientStore` upgrade MAY be performed before `EIP4844_FORK_EPOCH`. + +```python +def upgrade_lc_store_to_eip4844(pre: capella.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_eip4844(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_eip4844(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_eip4844(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/eip4844/light-client/full-node.md b/specs/eip4844/light-client/full-node.md new file mode 100644 index 0000000000..e9d7340f6e --- /dev/null +++ b/specs/eip4844/light-client/full-node.md @@ -0,0 +1,70 @@ +# EIP4844 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 + +This upgrade adds information about the execution payload to light client data as part of the EIP4844 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), + ) + + # [New in EIP4844] + if epoch >= EIP4844_FORK_EPOCH: + execution_header.excess_data_gas = payload.excess_data_gas + + execution_branch = compute_merkle_proof_for_block_body(block.message.body, EXECUTION_PAYLOAD_INDEX) + else: + execution_header = ExecutionPayloadHeader() + execution_branch = [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + + 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/eip4844/light-client/p2p-interface.md b/specs/eip4844/light-client/p2p-interface.md new file mode 100644 index 0000000000..f3d89c130d --- /dev/null +++ b/specs/eip4844/light-client/p2p-interface.md @@ -0,0 +1,105 @@ +# EIP4844 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 [Capella light client networking specification](../../capella/light-client/p2p-interface.md) is extended to exchange [EIP4844 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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.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` | +| `EIP4844_FORK_VERSION` and later | `eip4844.LightClientOptimisticUpdate` | diff --git a/specs/eip4844/light-client/sync-protocol.md b/specs/eip4844/light-client/sync-protocol.md new file mode 100644 index 0000000000..e66b81ea1b --- /dev/null +++ b/specs/eip4844/light-client/sync-protocol.md @@ -0,0 +1,88 @@ +# EIP4844 Light Client -- Sync Protocol + +**Notice**: This document is a work-in-progress for researchers and implementers. + +## Table of contents + + + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [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 EIP4844 changes to the [`ExecutionPayload`](../beacon-chain.md) structure. It extends the [Capella Light Client specifications](../../capella/light-client/sync-protocol.md). The [fork document](./fork.md) explains how to upgrade existing Capella based deployments to EIP4844. + +Additional documents describes the impact of the upgrade on certain roles: +- [Full node](./full-node.md) +- [Networking](./p2p-interface.md) + +## Helper functions + +### Modified `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + epoch = compute_epoch_at_slot(get_lc_beacon_slot(header)) + + # [New in EIP4844] + if epoch >= EIP4844_FORK_EPOCH: + return hash_tree_root(header.execution) + + # [Modified in EIP4844] + 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(get_lc_beacon_slot(header)) + + # [New in EIP4844] + if epoch < EIP4844_FORK_EPOCH: + if header.execution.excess_data_gas != uint256(0): + return False + + if epoch < CAPELLA_FORK_EPOCH: + if header.execution != ExecutionPayloadHeader(): + return False + + if header.execution_branch != [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))]: + return False + + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_INDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_INDEX), + root=header.beacon.body_root, + ) +``` From dc05a3f19e34d95f3190f1fffc9edda9dec433e0 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 13:13:25 +0100 Subject: [PATCH 11/20] Rename test back --- .../pyspec/eth2spec/test/altair/light_client/test_sync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 31bee9dd6a..6ac2d250ee 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 @@ -704,8 +704,8 @@ def test_capella_fork(spec, phases, state): @with_state @with_matching_spec_config(emitted_fork=CAPELLA) @with_presets([MINIMAL], reason="too slow") -def test_capella_store_with_altair_data(spec, phases, state): - # Start test (Altair bootstrap but with a Capella store) +def test_capella_store_with_legacy_data(spec, phases, state): + # Start test (Legacy bootstrap but with a Capella store) test = yield from setup_test(spec, state, s_spec=phases[CAPELLA], phases=phases) # Initial `LightClientUpdate` (check that it works with Capella store) From 700bef7a45eb479002096383337c7d3c386e8feb Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 13:39:14 +0100 Subject: [PATCH 12/20] Fix `is_valid_light_client_header` --- specs/capella/light-client/sync-protocol.md | 9 ++++----- specs/eip4844/light-client/sync-protocol.md | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md index 060dea1b9d..41204331e1 100644 --- a/specs/capella/light-client/sync-protocol.md +++ b/specs/capella/light-client/sync-protocol.md @@ -167,11 +167,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool: epoch = compute_epoch_at_slot(get_lc_beacon_slot(header)) if epoch < CAPELLA_FORK_EPOCH: - if header.execution != ExecutionPayloadHeader(): - return False - - if header.execution_branch != [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))]: - return False + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + ) return is_valid_merkle_branch( leaf=get_lc_execution_root(header), diff --git a/specs/eip4844/light-client/sync-protocol.md b/specs/eip4844/light-client/sync-protocol.md index e66b81ea1b..61881bd381 100644 --- a/specs/eip4844/light-client/sync-protocol.md +++ b/specs/eip4844/light-client/sync-protocol.md @@ -72,11 +72,10 @@ def is_valid_light_client_header(header: LightClientHeader) -> bool: return False if epoch < CAPELLA_FORK_EPOCH: - if header.execution != ExecutionPayloadHeader(): - return False - - if header.execution_branch != [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))]: - return False + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] + ) return is_valid_merkle_branch( leaf=get_lc_execution_root(header), From a8dabc05a8cbd9868b7f4311a6d3979061bf76b8 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 12 Dec 2022 19:08:45 +0100 Subject: [PATCH 13/20] Add `test_eip4844_store_with_legacy_data` (fork test still nyi) --- .../test/altair/light_client/test_sync.py | 128 ++++++++++++------ 1 file changed, 87 insertions(+), 41 deletions(-) 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 6ac2d250ee..5f9fc983e1 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,7 +16,7 @@ state_transition_with_full_block, ) from eth2spec.test.helpers.constants import ( - PHASE0, ALTAIR, BELLATRIX, CAPELLA, + PHASE0, ALTAIR, BELLATRIX, CAPELLA, EIP4844, MINIMAL, ALL_PHASES, ) @@ -24,7 +24,7 @@ do_fork, ) from eth2spec.test.helpers.forks import ( - is_post_capella, + is_post_capella, is_post_eip4844, is_post_fork, ) from eth2spec.test.helpers.light_client import ( @@ -53,56 +53,87 @@ def needs_upgrade_to_capella(d_spec, s_spec): return is_post_capella(s_spec) and not is_post_capella(d_spec) -def upgrade_lc_bootstrap_to_store(d_spec, s_spec, data): - if not needs_upgrade_to_capella(d_spec, s_spec): - return data +def needs_upgrade_to_eip4844(d_spec, s_spec): + return is_post_eip4844(s_spec) and not is_post_eip4844(d_spec) + + +def check_lc_header_equal(d_spec, s_spec, data, upgraded): + assert s_spec.get_lc_beacon_slot(upgraded) == d_spec.get_lc_beacon_slot(data) + assert s_spec.get_lc_beacon_root(upgraded) == d_spec.get_lc_beacon_root(data) + if is_post_capella(s_spec): + if is_post_capella(d_spec): + assert s_spec.get_lc_execution_root(upgraded) == d_spec.get_lc_execution_root(data) + else: + assert s_spec.get_lc_execution_root(upgraded) == s_spec.Root() + - upgraded = s_spec.upgrade_lc_bootstrap_to_capella(data) - assert s_spec.get_lc_beacon_slot(upgraded.header) == d_spec.get_lc_beacon_slot(data.header) - assert s_spec.get_lc_beacon_root(upgraded.header) == d_spec.get_lc_beacon_root(data.header) - assert s_spec.get_lc_execution_root(upgraded.header) == s_spec.Root() +def check_lc_bootstrap_equal(d_spec, s_spec, data, upgraded): + check_lc_header_equal(d_spec, s_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 - return upgraded -def upgrade_lc_update_to_store(d_spec, s_spec, data): - if not needs_upgrade_to_capella(d_spec, s_spec): - return data +def upgrade_lc_bootstrap_to_store(d_spec, s_spec, data): + upgraded = data + + if needs_upgrade_to_capella(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_bootstrap_to_capella(upgraded) + check_lc_bootstrap_equal(d_spec, s_spec, data, upgraded) + + if needs_upgrade_to_eip4844(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_bootstrap_to_eip4844(upgraded) + check_lc_bootstrap_equal(d_spec, s_spec, data, upgraded) + + return upgraded + - upgraded = s_spec.upgrade_lc_update_to_capella(data) - assert s_spec.get_lc_beacon_slot(upgraded.attested_header) == d_spec.get_lc_beacon_slot(data.attested_header) - assert s_spec.get_lc_beacon_root(upgraded.attested_header) == d_spec.get_lc_beacon_root(data.attested_header) - assert s_spec.get_lc_execution_root(upgraded.attested_header) == s_spec.Root() +def check_lc_update_equal(d_spec, s_spec, data, upgraded): + check_lc_header_equal(d_spec, s_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 - assert s_spec.get_lc_beacon_slot(upgraded.finalized_header) == d_spec.get_lc_beacon_slot(data.finalized_header) - assert s_spec.get_lc_beacon_root(upgraded.finalized_header) == d_spec.get_lc_beacon_root(data.finalized_header) - assert s_spec.get_lc_execution_root(upgraded.finalized_header) == s_spec.Root() + check_lc_header_equal(d_spec, s_spec, data.finalized_header, upgraded.finalized_header) assert upgraded.sync_aggregate == data.sync_aggregate assert upgraded.signature_slot == data.signature_slot - return upgraded -def upgrade_lc_store_to_new_spec(d_spec, s_spec, data): - if not needs_upgrade_to_capella(d_spec, s_spec): - return data +def upgrade_lc_update_to_store(d_spec, s_spec, data): + upgraded = data + + if needs_upgrade_to_capella(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_update_to_capella(upgraded) + check_lc_update_equal(d_spec, s_spec, data, upgraded) - upgraded = s_spec.upgrade_lc_store_to_capella(data) - assert s_spec.get_lc_beacon_slot(upgraded.finalized_header) == d_spec.get_lc_beacon_slot(data.finalized_header) - assert s_spec.get_lc_beacon_root(upgraded.finalized_header) == d_spec.get_lc_beacon_root(data.finalized_header) - assert s_spec.get_lc_execution_root(upgraded.finalized_header) == s_spec.Root() + if needs_upgrade_to_eip4844(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_update_to_eip4844(upgraded) + check_lc_update_equal(d_spec, s_spec, data, upgraded) + + return upgraded + + +def check_lc_store_equal(d_spec, s_spec, data, upgraded): + check_lc_header_equal(d_spec, s_spec, data.finalized_header, upgraded.finalized_header) assert upgraded.current_sync_committee == data.current_sync_committee assert upgraded.next_sync_committee == data.next_sync_committee if upgraded.best_valid_update is None: assert data.best_valid_update is None else: - assert upgraded.best_valid_update == upgrade_lc_update_to_store(d_spec, s_spec, data.best_valid_update) - assert s_spec.get_lc_beacon_slot(upgraded.optimistic_header) == d_spec.get_lc_beacon_slot(data.optimistic_header) - assert s_spec.get_lc_beacon_root(upgraded.optimistic_header) == d_spec.get_lc_beacon_root(data.optimistic_header) - assert s_spec.get_lc_execution_root(upgraded.optimistic_header) == s_spec.Root() + check_lc_update_equal(d_spec, s_spec, data.best_valid_update, upgraded.best_valid_update) + check_lc_header_equal(d_spec, s_spec, data.optimistic_header, upgraded.optimistic_header) assert upgraded.previous_max_active_participants == data.previous_max_active_participants assert upgraded.current_max_active_participants == data.current_max_active_participants + + +def upgrade_lc_store_to_new_spec(d_spec, s_spec, data): + upgraded = data + + if needs_upgrade_to_capella(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_store_to_capella(upgraded) + check_lc_store_equal(d_spec, s_spec, data, upgraded) + + if needs_upgrade_to_eip4844(d_spec, s_spec): + upgraded = s_spec.upgrade_lc_store_to_eip4844(upgraded) + check_lc_store_equal(d_spec, s_spec, data, upgraded) + return upgraded @@ -114,6 +145,8 @@ class LightClientSyncTest(object): def get_store_fork_version(s_spec): + if is_post_eip4844(s_spec): + return s_spec.config.EIP4844_FORK_VERSION if is_post_capella(s_spec): return s_spec.config.CAPELLA_FORK_VERSION return s_spec.config.ALTAIR_FORK_VERSION @@ -699,16 +732,11 @@ def test_capella_fork(spec, phases, state): yield from finish_test(test) -@with_phases(phases=[ALTAIR, BELLATRIX], other_phases=[CAPELLA]) -@spec_test -@with_state -@with_matching_spec_config(emitted_fork=CAPELLA) -@with_presets([MINIMAL], reason="too slow") -def test_capella_store_with_legacy_data(spec, phases, state): - # Start test (Legacy bootstrap but with a Capella store) - test = yield from setup_test(spec, state, s_spec=phases[CAPELLA], phases=phases) +def run_test_upgraded_store_with_legacy_data(spec, state, s_spec, phases): + # Start test (Legacy bootstrap with an upgraded store) + test = yield from setup_test(spec, state, s_spec, phases) - # Initial `LightClientUpdate` (check that it works with Capella store) + # Initial `LightClientUpdate` (check that the upgraded store can process it) finalized_block = spec.SignedBeaconBlock() finalized_block.message.state_root = state.hash_tree_root() finalized_state = state.copy() @@ -724,3 +752,21 @@ def test_capella_store_with_legacy_data(spec, phases, state): # Finish test yield from finish_test(test) + + +@with_phases(phases=[ALTAIR, BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_store_with_legacy_data(spec, phases, state): + yield from run_test_upgraded_store_with_legacy_data(spec, state, phases[CAPELLA], phases) + + +@with_phases(phases=[ALTAIR, BELLATRIX, CAPELLA], other_phases=[EIP4844]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=EIP4844) +@with_presets([MINIMAL], reason="too slow") +def test_eip4844_store_with_legacy_data(spec, phases, state): + yield from run_test_upgraded_store_with_legacy_data(spec, state, phases[EIP4844], phases) From e5cda1745fa537d5b19403f2af5cbec0ebb2191a Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 4 Jan 2023 18:07:55 +0100 Subject: [PATCH 14/20] Run fork test for EIP4844 --- .../test/altair/light_client/test_sync.py | 69 +++++++++++-------- 1 file changed, 40 insertions(+), 29 deletions(-) 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 b3fd4ebe8a..9c90b63faf 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 @@ -618,15 +618,7 @@ def test_advance_finality_without_sync_committee(spec, state): yield from finish_test(test) -@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) -@spec_test -@with_config_overrides({ - 'CAPELLA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 -}, emit=False) -@with_state -@with_matching_spec_config(emitted_fork=CAPELLA) -@with_presets([MINIMAL], reason="too slow") -def test_capella_fork(spec, phases, state): +def run_test_single_fork(spec, phases, state, fork): # Start test test = yield from setup_test(spec, state, phases=phases) @@ -644,8 +636,9 @@ def test_capella_fork(spec, phases, state): assert test.store.best_valid_update is None assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - # Jump to two slots before Capella - transition_to(spec, state, spec.compute_start_slot_at_epoch(phases[CAPELLA].config.CAPELLA_FORK_EPOCH) - 4) + # Jump to two slots before fork + fork_epoch = getattr(phases[fork].config, fork.upper() + '_FORK_EPOCH') + transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_epoch) - 4) attested_block = state_transition_with_full_block(spec, state, True, True) attested_state = state.copy() sync_aggregate, _ = get_sync_aggregate(spec, state) @@ -658,10 +651,10 @@ def test_capella_fork(spec, phases, state): assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot # Perform `LightClientStore` upgrade - yield from emit_upgrade_store(test, phases[CAPELLA], phases=phases) + yield from emit_upgrade_store(test, phases[fork], phases=phases) update = test.store.best_valid_update - # Final slot before Capella, check that importing the Altair format still works + # Final slot before fork, check that importing the pre-fork format still works attested_block = block.copy() attested_state = state.copy() sync_aggregate, _ = get_sync_aggregate(spec, state) @@ -672,11 +665,11 @@ def test_capella_fork(spec, phases, state): assert test.store.best_valid_update == update assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - # Upgrade to Capella, attested block is still before the fork + # Upgrade to post-fork spec, attested block is still before the fork attested_block = block.copy() attested_state = state.copy() - state, _ = do_fork(state, spec, phases[CAPELLA], phases[CAPELLA].config.CAPELLA_FORK_EPOCH, with_block=False) - spec = phases[CAPELLA] + state, _ = do_fork(state, spec, phases[fork], fork_epoch, with_block=False) + spec = phases[fork] sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) @@ -684,9 +677,8 @@ def test_capella_fork(spec, phases, state): assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) == test.s_spec.Root() - # Another block in Capella, this time attested block is after the fork + # Another block after the fork, this time attested block is after the fork attested_block = block.copy() attested_state = state.copy() sync_aggregate, _ = get_sync_aggregate(spec, state) @@ -696,23 +688,20 @@ def test_capella_fork(spec, phases, state): assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() # Jump to next epoch - transition_to(spec, state, spec.compute_start_slot_at_epoch(phases[CAPELLA].config.CAPELLA_FORK_EPOCH + 1) - 2) + transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_epoch + 1) - 2) attested_block = state_transition_with_full_block(spec, state, True, True) attested_state = state.copy() sync_aggregate, _ = get_sync_aggregate(spec, state) block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot - assert test.s_spec.get_lc_execution_root(test.store.finalized_header) == test.s_spec.Root() assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update == update assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() - # Finalize it + # Finalize the fork finalized_block = block.copy() finalized_state = state.copy() _, _, state = next_slots_with_attestations(spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True) @@ -722,19 +711,41 @@ def test_capella_fork(spec, phases, state): block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) yield from emit_update(test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases) assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot - assert test.s_spec.get_lc_execution_root(test.store.finalized_header) != test.s_spec.Root() assert test.store.next_sync_committee == finalized_state.next_sync_committee assert test.store.best_valid_update is None assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot - assert test.s_spec.get_lc_execution_root(test.store.optimistic_header) != test.s_spec.Root() # Finish test yield from finish_test(test) -def run_test_upgraded_store_with_legacy_data(spec, state, s_spec, phases): +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_config_overrides({ + 'CAPELLA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_fork(spec, phases, state): + yield from run_test_single_fork(spec, phases, state, CAPELLA) + + +@with_phases(phases=[CAPELLA], other_phases=[EIP4844]) +@spec_test +@with_config_overrides({ + 'EIP4844_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=EIP4844) +@with_presets([MINIMAL], reason="too slow") +def test_eip4844_fork(spec, phases, state): + yield from run_test_single_fork(spec, phases, state, EIP4844) + + +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, s_spec, phases) + test = yield from setup_test(spec, state, phases[fork], phases) # Initial `LightClientUpdate` (check that the upgraded store can process it) finalized_block = spec.SignedBeaconBlock() @@ -760,7 +771,7 @@ def run_test_upgraded_store_with_legacy_data(spec, state, s_spec, phases): @with_matching_spec_config(emitted_fork=CAPELLA) @with_presets([MINIMAL], reason="too slow") def test_capella_store_with_legacy_data(spec, phases, state): - yield from run_test_upgraded_store_with_legacy_data(spec, state, phases[CAPELLA], phases) + yield from run_test_upgraded_store_with_legacy_data(spec, phases, state, CAPELLA) @with_phases(phases=[ALTAIR, BELLATRIX, CAPELLA], other_phases=[EIP4844]) @@ -769,4 +780,4 @@ def test_capella_store_with_legacy_data(spec, phases, state): @with_matching_spec_config(emitted_fork=EIP4844) @with_presets([MINIMAL], reason="too slow") def test_eip4844_store_with_legacy_data(spec, phases, state): - yield from run_test_upgraded_store_with_legacy_data(spec, state, phases[EIP4844], phases) + yield from run_test_upgraded_store_with_legacy_data(spec, phases, state, EIP4844) From ce7fd412cc9b29747690f5b44004e27feae86241 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 4 Jan 2023 19:44:10 +0100 Subject: [PATCH 15/20] Add test for LC data spanning 3 forks --- .../test/altair/light_client/test_sync.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) 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 9c90b63faf..beb9350e26 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 @@ -743,6 +743,55 @@ def test_eip4844_fork(spec, phases, state): yield from run_test_single_fork(spec, phases, state, EIP4844) +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) + + # Set up so that finalized is from `spec`, ... + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + + # ..., 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, _ = do_fork(state, spec, phases[fork_1], fork_1_epoch, with_block=False) + spec = phases[fork_1] + attested_block = state_transition_with_full_block(spec, state, True, 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) + state, _ = do_fork(state, spec, phases[fork_2], fork_2_epoch, with_block=False) + spec = phases[fork_2] + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, 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) + assert test.s_spec.get_lc_beacon_slot(test.store.finalized_header) == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.s_spec.get_lc_beacon_slot(test.store.optimistic_header) == attested_state.slot + + # Finish test + yield from finish_test(test) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, EIP4844]) +@spec_test +@with_config_overrides({ + 'CAPELLA_FORK_EPOCH': 3, # `setup_test` advances to epoch 2 + 'EIP4844_FORK_EPOCH': 4, +}, emit=False) +@with_state +@with_matching_spec_config(emitted_fork=EIP4844) +@with_presets([MINIMAL], reason="too slow") +def test_capella_eip4844_fork(spec, phases, state): + yield from run_test_multi_fork(spec, phases, state, CAPELLA, EIP4844) + + 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) From b7205813fd07c8f0e2038fe51364c2da16bdf94a Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 12 Jan 2023 19:01:57 +0100 Subject: [PATCH 16/20] Update sync test documentation --- tests/formats/light_client/sync.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index c42486c5cb..2f17dbe7a0 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -17,7 +17,7 @@ store_fork_digest: string -- Encoded `ForkDigest`-context of `store` obj An SSZ-snappy encoded `bootstrap` object of type `LightClientBootstrap` to initialize a local `store` object of type `LightClientStore` with `store_fork_digest` using `initialize_light_client_store(trusted_block_rooot, bootstrap)`. The SSZ type can be determined from `bootstrap_fork_digest`. -If `store_fork_digest` differs from `bootstrap_fork_digest`, the `bootstrap` object may need upgrading before initializing the store. +If `store_fork_digest` differs from `bootstrap_fork_digest`, the `bootstrap` object may need to be upgraded before initializing the store. ### `steps.yaml` @@ -68,7 +68,7 @@ The function `process_light_client_update(store, update, current_slot, genesis_v } ``` -If `store_fork_digest` differs from `update_fork_digest`, the `update` object may need upgrading before initializing the store. +If `store_fork_digest` differs from `update_fork_digest`, the `update` object may need to be upgraded before processing the update. After this step, the `store` object may have been updated. @@ -85,7 +85,6 @@ The `store` should be upgraded to reflect the new `store_fork_digest`: After this step, the `store` object may have been updated. - ## Condition A test-runner should initialize a local `LightClientStore` using the provided `bootstrap` object. It should then proceed to execute all the test steps in sequence. After each step, it should verify that the resulting `store` verifies against the provided `checks`. From ca32fe834782cc83e166007101c30beac17344ca Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 13 Jan 2023 11:19:53 +0100 Subject: [PATCH 17/20] Add docstrings to explain empty header --- specs/capella/light-client/full-node.md | 4 ++++ specs/eip4844/light-client/full-node.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/specs/capella/light-client/full-node.md b/specs/capella/light-client/full-node.md index 6a2104a383..a406cd771b 100644 --- a/specs/capella/light-client/full-node.md +++ b/specs/capella/light-client/full-node.md @@ -57,6 +57,10 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: ) execution_branch = compute_merkle_proof_for_block_body(block.message.body, EXECUTION_PAYLOAD_INDEX) 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 = [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] diff --git a/specs/eip4844/light-client/full-node.md b/specs/eip4844/light-client/full-node.md index e9d7340f6e..70983e1b39 100644 --- a/specs/eip4844/light-client/full-node.md +++ b/specs/eip4844/light-client/full-node.md @@ -53,6 +53,10 @@ def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: execution_branch = compute_merkle_proof_for_block_body(block.message.body, EXECUTION_PAYLOAD_INDEX) 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 = [Bytes32() for _ in range(floorlog2(EXECUTION_PAYLOAD_INDEX))] From a580f82c7d7b88d33b8c06b168447d46c3526b8f Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 13 Jan 2023 14:59:34 +0100 Subject: [PATCH 18/20] Use `beacon` wrapper in `upgrade_lc_header_to_capella` --- specs/capella/light-client/fork.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md index ee2102b22d..d5640e01b0 100644 --- a/specs/capella/light-client/fork.md +++ b/specs/capella/light-client/fork.md @@ -22,9 +22,9 @@ This document describes how to upgrade existing light client objects based on th 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. ```python -def upgrade_lc_header_to_capella(pre: BeaconBlockHeader) -> LightClientHeader: +def upgrade_lc_header_to_capella(pre: altair.LightClientHeader) -> LightClientHeader: return LightClientHeader( - beacon=pre, + beacon=pre.beacon, ) ``` From 514d4431caae8ecf158589c3052f64f6de4c37f6 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 13 Jan 2023 20:11:05 +0100 Subject: [PATCH 19/20] Use `bellatrix` in `upgrade_x_to_capella` helpers --- specs/capella/light-client/fork.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md index d5640e01b0..6dcc7578c2 100644 --- a/specs/capella/light-client/fork.md +++ b/specs/capella/light-client/fork.md @@ -22,14 +22,14 @@ This document describes how to upgrade existing light client objects based on th 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. ```python -def upgrade_lc_header_to_capella(pre: altair.LightClientHeader) -> LightClientHeader: +def upgrade_lc_header_to_capella(pre: bellatrix.LightClientHeader) -> LightClientHeader: return LightClientHeader( beacon=pre.beacon, ) ``` ```python -def upgrade_lc_bootstrap_to_capella(pre: altair.LightClientBootstrap) -> LightClientBootstrap: +def upgrade_lc_bootstrap_to_capella(pre: bellatrix.LightClientBootstrap) -> LightClientBootstrap: return LightClientBootstrap( header=upgrade_lc_header_to_capella(pre.header), current_sync_committee=pre.current_sync_committee, @@ -38,7 +38,7 @@ def upgrade_lc_bootstrap_to_capella(pre: altair.LightClientBootstrap) -> LightCl ``` ```python -def upgrade_lc_update_to_capella(pre: altair.LightClientUpdate) -> LightClientUpdate: +def upgrade_lc_update_to_capella(pre: bellatrix.LightClientUpdate) -> LightClientUpdate: return LightClientUpdate( attested_header=upgrade_lc_header_to_capella(pre.attested_header), next_sync_committee=pre.next_sync_committee, @@ -51,7 +51,7 @@ def upgrade_lc_update_to_capella(pre: altair.LightClientUpdate) -> LightClientUp ``` ```python -def upgrade_lc_finality_update_to_capella(pre: altair.LightClientFinalityUpdate) -> LightClientFinalityUpdate: +def upgrade_lc_finality_update_to_capella(pre: bellatrix.LightClientFinalityUpdate) -> LightClientFinalityUpdate: return LightClientFinalityUpdate( attested_header=upgrade_lc_header_to_capella(pre.attested_header), finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), @@ -62,7 +62,7 @@ def upgrade_lc_finality_update_to_capella(pre: altair.LightClientFinalityUpdate) ``` ```python -def upgrade_lc_optimistic_update_to_capella(pre: altair.LightClientOptimisticUpdate) -> LightClientOptimisticUpdate: +def upgrade_lc_optimistic_update_to_capella(pre: bellatrix.LightClientOptimisticUpdate) -> LightClientOptimisticUpdate: return LightClientOptimisticUpdate( attested_header=upgrade_lc_header_to_capella(pre.attested_header), sync_aggregate=pre.sync_aggregate, @@ -75,7 +75,7 @@ def upgrade_lc_optimistic_update_to_capella(pre: altair.LightClientOptimisticUpd 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`. ```python -def upgrade_lc_store_to_capella(pre: altair.LightClientStore) -> LightClientStore: +def upgrade_lc_store_to_capella(pre: bellatrix.LightClientStore) -> LightClientStore: if pre.best_valid_update is None: best_valid_update = None else: From ffd047c6fa19b331bb9265f8cf22ea616b1fe5c3 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Sat, 14 Jan 2023 14:51:59 +0100 Subject: [PATCH 20/20] Consistent test step naming --- .../core/pyspec/eth2spec/test/altair/light_client/test_sync.py | 2 +- tests/formats/light_client/sync.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 eb30ecb92a..664b4fb44f 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 @@ -274,7 +274,7 @@ def emit_upgrade_store(test, new_s_spec, phases=None): yield from [] # Consistently enable `yield from` syntax in calling tests test.steps.append({ - "process_upgrade_store": { + "upgrade_store": { "store_fork_digest": encode_hex(store_fork_digest), "checks": get_checks(test.s_spec, test.store), } diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index 2f17dbe7a0..1706b4c162 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -72,7 +72,7 @@ If `store_fork_digest` differs from `update_fork_digest`, the `update` object ma After this step, the `store` object may have been updated. -#### `process_upgrade_store` +#### `upgrade_store` The `store` should be upgraded to reflect the new `store_fork_digest`: