From f9d866eb28db8c5b59dbd166c13d3721ed615323 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Tue, 3 May 2022 12:31:26 +0200 Subject: [PATCH] Manually trigger `LightClientStore` force updates Replaces `process_slot_for_light_client_store` which force updates the `LightClientStore` automatically based on `finalized_header` age with `try_light_client_store_force_update` which may be manually called based on use case dependent heuristics if light client sync appears stuck. Not all use cases share the same risk profile. --- specs/altair/light-client/sync-protocol.md | 45 +++++++++---------- .../test/altair/light_client/test_sync.py | 10 ++--- tests/formats/light_client/sync.md | 4 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/specs/altair/light-client/sync-protocol.md b/specs/altair/light-client/sync-protocol.md index 627049a43..058183fc4 100644 --- a/specs/altair/light-client/sync-protocol.md +++ b/specs/altair/light-client/sync-protocol.md @@ -29,9 +29,9 @@ - [Light client initialization](#light-client-initialization) - [`initialize_light_client_store`](#initialize_light_client_store) - [Light client state updates](#light-client-state-updates) - - [`process_slot_for_light_client_store`](#process_slot_for_light_client_store) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) + - [`try_light_client_store_force_update`](#try_light_client_store_force_update) - [`process_light_client_update`](#process_light_client_update) - [`process_light_client_finality_update`](#process_light_client_finality_update) - [`process_light_client_optimistic_update`](#process_light_client_optimistic_update) @@ -285,28 +285,7 @@ def initialize_light_client_store(trusted_block_root: Root, - **`update: LightClientUpdate`**: Every `update` triggers `process_light_client_update(store, update, current_slot, genesis_validators_root)` where `current_slot` is the current slot based on a local clock. - **`finality_update: LightClientFinalityUpdate`**: Every `finality_update` triggers `process_light_client_finality_update(store, finality_update, current_slot, genesis_validators_root)`. - **`optimistic_update: LightClientOptimisticUpdate`**: Every `optimistic_update` triggers `process_light_client_optimistic_update(store, optimistic_update, current_slot, genesis_validators_root)`. -- `process_slot_for_light_client_store` is triggered every time the current slot increments. - -### `process_slot_for_light_client_store` - -```python -def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: - if current_slot % UPDATE_TIMEOUT == 0: - store.previous_max_active_participants = store.current_max_active_participants - store.current_max_active_participants = 0 - if ( - current_slot > store.finalized_header.slot + 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, - # 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: - 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 -``` +- `try_light_client_store_force_update` MAY be called based on use case dependent heuristics if light client sync appears stuck. ### `validate_light_client_update` @@ -399,12 +378,32 @@ def apply_light_client_update(store: LightClientStore, update: LightClientUpdate elif update_finalized_period == store_period + 1: store.current_sync_committee = store.next_sync_committee 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: store.finalized_header = update.finalized_header if store.finalized_header.slot > store.optimistic_header.slot: store.optimistic_header = store.finalized_header ``` +### `try_light_client_store_force_update` + +```python +def try_light_client_store_force_update(store: LightClientStore, current_slot: Slot) -> None: + if ( + current_slot > store.finalized_header.slot + 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, + # 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: + 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 +``` + ### `process_light_client_update` ```python 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 9448302e7..ed2d04b4d 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 @@ -72,13 +72,13 @@ def get_checks(store): } -def emit_slot(test, spec, state): +def emit_force_update(test, spec, state): current_slot = state.slot - spec.process_slot_for_light_client_store(test.store, current_slot) + spec.try_light_client_store_force_update(test.store, current_slot) yield from [] # Consistently enable `yield from` syntax in calling tests test.steps.append({ - "process_slot": { + "force_update": { "current_slot": int(current_slot), "checks": get_checks(test.store), } @@ -249,7 +249,7 @@ def test_light_client_sync(spec, state): # ``` attested_state = state.copy() next_slots(spec, state, spec.UPDATE_TIMEOUT - 1) - yield from emit_slot(test, spec, state) + yield from emit_force_update(test, spec, state) assert test.store.finalized_header.slot == store_state.slot assert test.store.next_sync_committee == store_state.next_sync_committee assert test.store.best_valid_update is None @@ -293,7 +293,7 @@ def test_light_client_sync(spec, state): 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 - yield from emit_slot(test, spec, state) + yield from emit_force_update(test, spec, state) assert test.store.finalized_header.slot == attested_state.slot assert test.store.next_sync_committee == attested_state.next_sync_committee assert test.store.best_valid_update is None diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md index 31ecf6036..aa0d593b6 100644 --- a/tests/formats/light_client/sync.md +++ b/tests/formats/light_client/sync.md @@ -34,9 +34,9 @@ optimistic_header: { } ``` -#### `process_slot` execution step +#### `force_update` execution step -The function `process_slot_for_light_client_store(store, current_slot)` +The function `try_light_client_store_force_update(store, current_slot)` should be executed with the specified parameters: ```yaml