# Extended light client protocol This document defined an extension to the [light client sync protocol](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/sync-protocol.md) that allows for clients to determine non-finalized optimistic heads, so they can sync up to the tip of the chain. ## Terminology note A `LightClientUpdate` usually has two headers: the **finality header**, the header that is closer to the tip of the chain and is being signed by the sync committee, and the **header**, which is finalized and verified via Merkle branch as an ancestor of the finality header. In the exceptional case where there has been no finalized header for a long time, the **header** is what is being signed by the sync committee. (Yes, these names are confusing and should be changed) We will use the term **signed header** to refer to the finality header in the first case, and the header in the second case. ```python def get_signed_header(update: LightClientUpdate): if update.finality_header is None: return update.header else: return update.finality_header ``` ## Extension to light client protocol: dynamic safety threshold | Parameter | Value | | - | - | | `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4,096` (slots) | ### Updated `LightClientStore` ```python @dataclass @dataclass class LightClientStore(object): snapshot: LightClientSnapshot best_valid_update: Optional[LightClientUpdate] optimistic_header: BeaconBlockHeader previous_period_max_attendance: int current_period_max_attendance: int ``` ### `get_safety_threshold` ```python def get_safety_threshold(store: LightClientStore): return max( store.previous_period_max_attendance, store.current_period_max_attendance ) // 2 ``` ### Updated `process_light_client_update` ```python def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, genesis_validators_root: Root) -> None: validate_light_client_update(store.snapshot, update, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses if ( sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits) and get_signed_header(update).slot > store.snapshot.header.slot ): store.best_finalization_update = update # Track the maximum attendance in the committee signatures store.current_period_max_attendance = max( store.current_period_max_attendance, update.sync_committee_bits.count(1) ) # Update the optimistic header if ( sum(update.sync_committee_bits) > get_safety_threshold(store) and update.header.slot > store.optimistic_header.slot ): store.optimistic_header = update.header # Update finalized header if ( sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 and update.finality_header != BeaconBlockHeader() ): # Normal update through 2/3 threshold apply_light_client_update(store.snapshot, update) store.best_valid_update = None elif current_slot > store.snapshot.header.slot + update_timeout: # Forced best update when the update timeout has elapsed apply_light_client_update(store.snapshot, store.best_valid_update) store.best_valid_update = None ``` ### `process_slot` ```python def process_slot(store: LightClientStore, current_slot: Slot): if current_slot % SAFETY_THRESHOLD_CALCULATION_PERIOD == 0: store.previous_period_max_attendance = store.current_period_max_attendance store.current_period_max_attendance = 0 ``` ## N-server light client protocol Suppose that a light client talks to `N` servers, which are expected to be full nodes of the beacon chain. This protocol helps the light client keep up with the chain, as long as at least one of the servers is online and honest. Every `k` seconds (eg. 4 seconds into each slot), the light client must send a **`UpdateRequest(finalized_header_epoch: Epoch, safety_threshold: int)`** message, passing along the epoch of its finalized header (remember, this could come from "actual" finalization _or_ the 1-day-delayed auto-finalization) and the light client's safety threshold. The safety threshold is calculated by the same method as above: Each server must respond with a valid `LightClientUpdate` with `update.sync_committee_bits.count(1) >= safety_threshold` and `get_signed_header(update).slot` as high as possible. Note particularly that `get_signed_header(update).slot` cannot exceed the last slot in the sync committee period _after_ the period that the `update_request.finalized_header_epoch` is contained in. The client receives each update and processes it as defined by [the sync protocol](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/sync-protocol.md).