From 0fb0b26742e18ae86c545471e0cc98dab10f716a Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Tue, 6 Dec 2022 22:05:19 +0100 Subject: [PATCH 1/2] 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 53ba4dc82..0afbe54fb 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 318950437..15ed3112d 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 501269ee2..13b8625b1 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 793483bc0..5ee0d66fe 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 1364c4bf1..cc6a070a3 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 23ad79584..96c372435 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 bf09cc30e..860091b32 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 4d7162c3b..3beeec0dd 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 2/2] 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 0afbe54fb..139b3c941 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()