Simplify sync protocol and update to calculate optimistic heads

1. Simplify `valid_updates` to `best_valid_update` so the `LightClientStore` only needs to store O(1) data
2. Track an optimistic head, by looking for the highest-slot header which passes a safety threshold
This commit is contained in:
vbuterin 2021-11-26 15:11:19 -06:00 committed by GitHub
parent e1356aec83
commit 25f2efab19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 71 additions and 17 deletions

View File

@ -18,7 +18,10 @@
- [`LightClientStore`](#lightclientstore) - [`LightClientStore`](#lightclientstore)
- [Helper functions](#helper-functions) - [Helper functions](#helper-functions)
- [`get_subtree_index`](#get_subtree_index) - [`get_subtree_index`](#get_subtree_index)
- [`get_signed_header`](#get_signed_header)
- [`get_safety_threshold`](#get_safety_threshold)
- [Light client state updates](#light-client-state-updates) - [Light client state updates](#light-client-state-updates)
- [`process_slot`](#process_slot)
- [`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)
- [`process_light_client_update`](#process_light_client_update) - [`process_light_client_update`](#process_light_client_update)
@ -47,9 +50,10 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain.
### Misc ### Misc
| Name | Value | | Name | Value | Notes |
| - | - | | - | - | - |
| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | |
| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours |
## Containers ## Containers
@ -86,10 +90,12 @@ class LightClientUpdate(Container):
### `LightClientStore` ### `LightClientStore`
```python ```python
@dataclass
class LightClientStore(object): class LightClientStore(object):
snapshot: LightClientSnapshot snapshot: LightClientSnapshot
valid_updates: Set[LightClientUpdate] best_valid_update: Optional[LightClientUpdate]
optimistic_header: BeaconBlockHeader
previous_period_max_attendance: uint64
current_period_max_attendance: uint64
``` ```
## Helper functions ## Helper functions
@ -101,9 +107,38 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64:
return uint64(generalized_index % 2**(floorlog2(generalized_index))) return uint64(generalized_index % 2**(floorlog2(generalized_index)))
``` ```
### `get_signed_header`
```python
def get_signed_header(update: LightClientUpdate):
if update.finality_header is None:
return update.header
else:
return update.finality_header
```
### `get_safety_threshold`
```python
def get_safety_threshold(store: LightClientStore):
return max(
store.previous_period_max_attendance,
store.current_period_max_attendance
) // 2
```
## Light client state updates ## Light client state updates
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. 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
```
#### `validate_light_client_update` #### `validate_light_client_update`
@ -172,24 +207,43 @@ def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClient
#### `process_light_client_update` #### `process_light_client_update`
```python ```python
def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, def process_light_client_update(store: LightClientStore,
update: LightClientUpdate,
current_slot: Slot,
genesis_validators_root: Root) -> None: genesis_validators_root: Root) -> None:
validate_light_client_update(store.snapshot, update, genesis_validators_root) validate_light_client_update(store.snapshot, update, genesis_validators_root)
store.valid_updates.add(update)
# Update the best update in case we have to force-update to it if the timeout elapses
update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD 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 ( if (
sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2
and update.finality_header != BeaconBlockHeader() and update.finality_header != BeaconBlockHeader()
): ):
# Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. # Normal update through 2/3 threshold
# Note that (2) means that the current light client design needs finality.
# It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182.
apply_light_client_update(store.snapshot, update) apply_light_client_update(store.snapshot, update)
store.valid_updates = set() store.best_valid_update = None
elif current_slot > store.snapshot.header.slot + update_timeout: elif current_slot > store.snapshot.header.slot + update_timeout:
# Forced best update when the update timeout has elapsed # Forced best update when the update timeout has elapsed
apply_light_client_update(store.snapshot, apply_light_client_update(store.snapshot, store.best_valid_update)
max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) store.best_valid_update = None
store.valid_updates = set()
``` ```