2021-08-18 23:11:38 +00:00
# Altair -- Minimal Light Client
2020-11-16 07:02:11 +00:00
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents
<!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE - RUN doctoc TO UPDATE -->
- [Introduction ](#introduction )
- [Constants ](#constants )
2021-05-06 13:52:52 +00:00
- [Preset ](#preset )
2020-11-17 12:42:09 +00:00
- [Misc ](#misc )
2020-11-16 07:02:11 +00:00
- [Containers ](#containers )
2021-03-11 06:27:23 +00:00
- [`LightClientUpdate` ](#lightclientupdate )
- [`LightClientStore` ](#lightclientstore )
2020-12-10 09:04:11 +00:00
- [Helper functions ](#helper-functions )
- [`get_subtree_index` ](#get_subtree_index )
2021-11-26 21:11:19 +00:00
- [`get_signed_header` ](#get_signed_header )
- [`get_safety_threshold` ](#get_safety_threshold )
2020-11-16 07:02:11 +00:00
- [Light client state updates ](#light-client-state-updates )
2021-11-26 21:11:19 +00:00
- [`process_slot` ](#process_slot )
2021-11-27 13:25:27 +00:00
- [`get_active_header` ](#get_active_header )
2021-01-19 12:44:21 +00:00
- [`validate_light_client_update` ](#validate_light_client_update )
2020-11-18 10:33:42 +00:00
- [`apply_light_client_update` ](#apply_light_client_update )
2020-11-17 12:42:09 +00:00
- [`process_light_client_update` ](#process_light_client_update )
2020-11-16 07:02:11 +00:00
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
## Introduction
2021-08-18 23:11:38 +00:00
The beacon chain is designed to be light client friendly for constrained environments to
access Ethereum with reasonable safety and liveness.
2020-12-07 15:10:39 +00:00
Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets)
and metered VMs (e.g. blockchain VMs for cross-chain bridges).
2020-11-17 12:42:09 +00:00
2020-12-07 15:10:39 +00:00
This document suggests a minimal light client design for the beacon chain that
uses sync committees introduced in [this beacon chain extension ](./beacon-chain.md ).
2020-11-16 07:02:11 +00:00
## Constants
| Name | Value |
| - | - |
2020-12-09 09:35:22 +00:00
| `FINALIZED_ROOT_INDEX` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` |
| `NEXT_SYNC_COMMITTEE_INDEX` | `get_generalized_index(BeaconState, 'next_sync_committee')` |
2020-11-17 12:42:09 +00:00
2021-05-06 13:52:52 +00:00
## Preset
2020-11-17 12:42:09 +00:00
### Misc
2021-11-26 21:11:19 +00:00
| Name | Value | Notes |
| - | - | - |
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | |
| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours |
2020-11-17 12:42:09 +00:00
2020-11-16 07:02:11 +00:00
## Containers
2021-03-11 06:27:23 +00:00
### `LightClientUpdate`
2020-11-16 07:02:11 +00:00
2020-11-17 12:42:09 +00:00
```python
class LightClientUpdate(Container):
2021-11-27 13:25:27 +00:00
# The beacon block header that is attested to by the sync committee
attested_header: BeaconBlockHeader
2020-11-18 10:33:42 +00:00
# Next sync committee corresponding to the header
next_sync_committee: SyncCommittee
2020-12-16 07:00:06 +00:00
next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)]
2021-11-27 13:25:27 +00:00
# The finalized beacon block header attested to by Merkle branch
finalized_header: BeaconBlockHeader
2020-12-16 07:00:06 +00:00
finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)]
2020-11-17 12:42:09 +00:00
# Sync committee aggregate signature
2020-11-18 09:31:41 +00:00
sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE]
2020-11-17 12:42:09 +00:00
sync_committee_signature: BLSSignature
2020-11-17 15:30:09 +00:00
# Fork version for the aggregate signature
2020-11-18 08:27:19 +00:00
fork_version: Version
2020-11-17 12:42:09 +00:00
```
2021-03-11 06:27:23 +00:00
### `LightClientStore`
2020-11-16 07:02:11 +00:00
```python
2021-05-06 17:00:04 +00:00
class LightClientStore(object):
2021-11-27 13:25:27 +00:00
# Beacon block header that is finalized
finalized_header: BeaconBlockHeader
# Sync committees corresponding to the header
current_sync_committee: SyncCommittee
next_sync_committee: SyncCommittee
# Best available header to switch finalized head to if we see nothing else
2021-11-26 21:11:19 +00:00
best_valid_update: Optional[LightClientUpdate]
2021-11-27 13:25:27 +00:00
# Most recent available reasonably-safe header
2021-11-26 21:11:19 +00:00
optimistic_header: BeaconBlockHeader
2021-11-27 13:25:27 +00:00
# Max number of participants in a sync committee (used to calculate safety threshold)
2021-11-26 21:11:19 +00:00
previous_period_max_attendance: uint64
current_period_max_attendance: uint64
2020-11-16 07:02:11 +00:00
```
2020-12-10 09:04:11 +00:00
## Helper functions
### `get_subtree_index`
```python
def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64:
2020-12-16 07:00:06 +00:00
return uint64(generalized_index % 2**(floorlog2(generalized_index)))
2020-12-10 09:04:11 +00:00
```
2021-11-26 21:11:19 +00:00
### `get_safety_threshold`
```python
def get_safety_threshold(store: LightClientStore):
return max(
store.previous_period_max_attendance,
store.current_period_max_attendance
) // 2
```
2020-11-16 07:02:11 +00:00
## Light client state updates
2021-11-26 21:11:19 +00:00
A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate` . Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. `process_slot` is processed every time the current slot increments.
### `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
```
2020-11-16 07:02:11 +00:00
2021-11-27 13:25:27 +00:00
### `get_active_header`
```python
def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader:
# Is the update trying to convince us of a finalized header or an optimistic header?
if update.finalized_header BeaconBlockHeader():
return update.finalized_header
else:
return update.attested_header
```
2021-01-19 12:44:21 +00:00
#### `validate_light_client_update`
2020-11-16 07:02:11 +00:00
```python
2021-11-27 13:25:27 +00:00
def validate_light_client_update(store: LightClientStore,
2021-05-04 13:39:22 +00:00
update: LightClientUpdate,
2021-03-11 13:02:05 +00:00
genesis_validators_root: Root) -> None:
2021-11-27 13:25:27 +00:00
# Verify update slot is larger than slot of current best finalized header
active_header = get_active_header(update)
assert active_header.slot > store.finalized_header.slot
2020-11-17 12:42:09 +00:00
# Verify update does not skip a sync committee period
2021-11-27 13:25:27 +00:00
finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
assert update_period in (finalized_period, finalized_period + 1)
2020-11-18 10:33:42 +00:00
# Verify update header root is the finalized root of the finality header, if specified
2021-11-27 13:25:27 +00:00
if update.finalized_header == BeaconBlockHeader():
2020-12-16 07:00:06 +00:00
assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
2020-11-18 08:27:19 +00:00
else:
assert is_valid_merkle_branch(
2021-11-27 13:25:27 +00:00
leaf=hash_tree_root(update.finalized_header),
2020-11-18 10:33:42 +00:00
branch=update.finality_branch,
2020-12-16 07:00:06 +00:00
depth=floorlog2(FINALIZED_ROOT_INDEX),
2020-12-10 09:04:11 +00:00
index=get_subtree_index(FINALIZED_ROOT_INDEX),
2021-11-27 13:25:27 +00:00
root=update.attested_header.state_root,
2020-11-18 10:33:42 +00:00
)
2020-11-17 12:42:09 +00:00
2020-11-18 10:33:42 +00:00
# Verify update next sync committee if the update period incremented
2021-11-27 13:25:27 +00:00
if update_period == finalized_period:
sync_committee = store.current_sync_committee
2020-12-16 07:00:06 +00:00
assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
2020-11-18 09:31:41 +00:00
else:
2021-11-27 13:25:27 +00:00
sync_committee = store.next_sync_committee
2020-11-17 12:42:09 +00:00
assert is_valid_merkle_branch(
2020-11-18 10:33:42 +00:00
leaf=hash_tree_root(update.next_sync_committee),
2020-11-17 12:42:09 +00:00
branch=update.next_sync_committee_branch,
2020-12-16 07:00:06 +00:00
depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX),
2020-12-10 09:04:11 +00:00
index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX),
2021-11-27 13:25:27 +00:00
root=active_header.state_root,
2020-11-17 12:42:09 +00:00
)
2020-11-18 10:33:42 +00:00
# Verify sync committee has sufficient participants
2020-11-18 09:31:41 +00:00
assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
2020-11-17 12:42:09 +00:00
# Verify sync committee aggregate signature
participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit]
2021-03-11 13:02:05 +00:00
domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root)
2021-11-27 13:25:27 +00:00
signing_root = compute_signing_root(update.attested_header, domain)
2020-11-17 12:42:09 +00:00
assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature)
2020-11-16 07:02:11 +00:00
```
2020-11-18 10:33:42 +00:00
#### `apply_light_client_update`
```python
2021-11-27 13:25:27 +00:00
def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None:
active_header = get_active_header(update)
finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
if update_period == finalized_period + 1:
store.current_sync_committee = store.next_sync_committee
store.next_sync_committee = update.next_sync_committee
store.finalized_header = active_header
2020-11-18 10:33:42 +00:00
```
#### `process_light_client_update`
2020-11-16 07:02:11 +00:00
2020-11-16 07:07:40 +00:00
```python
2021-11-26 21:11:19 +00:00
def process_light_client_update(store: LightClientStore,
update: LightClientUpdate,
current_slot: Slot,
2021-03-11 13:02:05 +00:00
genesis_validators_root: Root) -> None:
2021-11-26 21:11:19 +00:00
2021-11-27 13:25:27 +00:00
validate_light_client_update(store, update, genesis_validators_root)
2021-11-26 21:11:19 +00:00
# Update the best update in case we have to force-update to it if the timeout elapses
2021-11-27 13:25:27 +00:00
if sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits):
2021-11-26 21:11:19 +00:00
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
2021-11-27 13:25:27 +00:00
update.attested_header.slot > store.optimistic_header.slot
2021-11-26 21:11:19 +00:00
):
2021-11-27 13:25:27 +00:00
store.optimistic_header = update.attested_header
2021-11-26 21:11:19 +00:00
# Update finalized header
2020-12-09 09:35:22 +00:00
if (
2021-05-04 20:25:34 +00:00
sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2
2021-11-27 13:25:27 +00:00
and update.finalized_header != BeaconBlockHeader()
2020-12-09 09:35:22 +00:00
):
2021-11-26 21:11:19 +00:00
# Normal update through 2/3 threshold
2021-11-27 13:25:27 +00:00
apply_light_client_update(store, update)
2021-11-26 21:11:19 +00:00
store.best_valid_update = None
2021-11-27 13:25:27 +00:00
elif current_slot > store.finalized_header.slot + update_timeout:
2020-11-17 15:30:09 +00:00
# Forced best update when the update timeout has elapsed
2021-11-27 13:25:27 +00:00
apply_light_client_update(store, store.best_valid_update)
2021-11-26 21:11:19 +00:00
store.best_valid_update = None
2020-11-16 07:02:11 +00:00
```