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.
This commit is contained in:
parent
78b035abf9
commit
f9d866eb28
|
@ -29,9 +29,9 @@
|
||||||
- [Light client initialization](#light-client-initialization)
|
- [Light client initialization](#light-client-initialization)
|
||||||
- [`initialize_light_client_store`](#initialize_light_client_store)
|
- [`initialize_light_client_store`](#initialize_light_client_store)
|
||||||
- [Light client state updates](#light-client-state-updates)
|
- [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)
|
- [`validate_light_client_update`](#validate_light_client_update)
|
||||||
- [`apply_light_client_update`](#apply_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_update`](#process_light_client_update)
|
||||||
- [`process_light_client_finality_update`](#process_light_client_finality_update)
|
- [`process_light_client_finality_update`](#process_light_client_finality_update)
|
||||||
- [`process_light_client_optimistic_update`](#process_light_client_optimistic_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.
|
- **`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)`.
|
- **`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)`.
|
- **`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.
|
- `try_light_client_store_force_update` MAY be called based on use case dependent heuristics if light client sync appears stuck.
|
||||||
|
|
||||||
### `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
|
|
||||||
```
|
|
||||||
|
|
||||||
### `validate_light_client_update`
|
### `validate_light_client_update`
|
||||||
|
|
||||||
|
@ -399,12 +378,32 @@ def apply_light_client_update(store: LightClientStore, update: LightClientUpdate
|
||||||
elif update_finalized_period == store_period + 1:
|
elif update_finalized_period == store_period + 1:
|
||||||
store.current_sync_committee = store.next_sync_committee
|
store.current_sync_committee = store.next_sync_committee
|
||||||
store.next_sync_committee = update.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:
|
if update.finalized_header.slot > store.finalized_header.slot:
|
||||||
store.finalized_header = update.finalized_header
|
store.finalized_header = update.finalized_header
|
||||||
if store.finalized_header.slot > store.optimistic_header.slot:
|
if store.finalized_header.slot > store.optimistic_header.slot:
|
||||||
store.optimistic_header = store.finalized_header
|
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`
|
### `process_light_client_update`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
|
@ -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
|
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
|
yield from [] # Consistently enable `yield from` syntax in calling tests
|
||||||
test.steps.append({
|
test.steps.append({
|
||||||
"process_slot": {
|
"force_update": {
|
||||||
"current_slot": int(current_slot),
|
"current_slot": int(current_slot),
|
||||||
"checks": get_checks(test.store),
|
"checks": get_checks(test.store),
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ def test_light_client_sync(spec, state):
|
||||||
# ```
|
# ```
|
||||||
attested_state = state.copy()
|
attested_state = state.copy()
|
||||||
next_slots(spec, state, spec.UPDATE_TIMEOUT - 1)
|
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.finalized_header.slot == store_state.slot
|
||||||
assert test.store.next_sync_committee == store_state.next_sync_committee
|
assert test.store.next_sync_committee == store_state.next_sync_committee
|
||||||
assert test.store.best_valid_update is None
|
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.next_sync_committee == store_state.next_sync_committee
|
||||||
assert test.store.best_valid_update == update
|
assert test.store.best_valid_update == update
|
||||||
assert test.store.optimistic_header.slot == attested_state.slot
|
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.finalized_header.slot == attested_state.slot
|
||||||
assert test.store.next_sync_committee == attested_state.next_sync_committee
|
assert test.store.next_sync_committee == attested_state.next_sync_committee
|
||||||
assert test.store.best_valid_update is None
|
assert test.store.best_valid_update is None
|
||||||
|
|
|
@ -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:
|
should be executed with the specified parameters:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
Loading…
Reference in New Issue