-
-
Published
Linked with GitHub
# 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).