mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-13 02:56:24 +00:00
ff0e525ecc
Future light client protocol extensions may include data from the block in addition to data from the state, e.g., `ExecutionPayloadHeader`. To prepare for this, also pass the block to the corresponding functions. In practice, blocks access is easier than historic state access, meaning there are no practical limitations due to this change.
177 lines
9.2 KiB
Markdown
177 lines
9.2 KiB
Markdown
# Altair Light Client -- Full Node
|
|
|
|
**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)
|
|
- [Helper functions](#helper-functions)
|
|
- [`compute_merkle_proof_for_state`](#compute_merkle_proof_for_state)
|
|
- [Deriving light client data](#deriving-light-client-data)
|
|
- [`create_light_client_bootstrap`](#create_light_client_bootstrap)
|
|
- [`create_light_client_update`](#create_light_client_update)
|
|
- [`create_light_client_finality_update`](#create_light_client_finality_update)
|
|
- [`create_light_client_optimistic_update`](#create_light_client_optimistic_update)
|
|
|
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
<!-- /TOC -->
|
|
|
|
## Introduction
|
|
|
|
This document provides helper functions to enable full nodes to serve light client data. Full nodes SHOULD implement the described functionality to enable light clients to sync with the network.
|
|
|
|
## Helper functions
|
|
|
|
### `compute_merkle_proof_for_state`
|
|
|
|
```python
|
|
def compute_merkle_proof_for_state(state: BeaconState,
|
|
index: GeneralizedIndex) -> Sequence[Bytes32]:
|
|
...
|
|
```
|
|
|
|
## Deriving light client data
|
|
|
|
Full nodes are expected to derive light client data from historic blocks and states and provide it to other clients.
|
|
|
|
### `create_light_client_bootstrap`
|
|
|
|
To form a `LightClientBootstrap`, the following objects are needed:
|
|
- `state`: the post state of any post-Altair block
|
|
- `block`: the corresponding block
|
|
|
|
```python
|
|
def create_light_client_bootstrap(state: BeaconState,
|
|
block: SignedBeaconBlock) -> LightClientBootstrap:
|
|
assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH
|
|
|
|
assert state.slot == state.latest_block_header.slot
|
|
header = state.latest_block_header.copy()
|
|
header.state_root = hash_tree_root(state)
|
|
assert hash_tree_root(header) == hash_tree_root(block.message)
|
|
|
|
return LightClientBootstrap(
|
|
header=BeaconBlockHeader(
|
|
slot=state.latest_block_header.slot,
|
|
proposer_index=state.latest_block_header.proposer_index,
|
|
parent_root=state.latest_block_header.parent_root,
|
|
state_root=hash_tree_root(state),
|
|
body_root=state.latest_block_header.body_root,
|
|
),
|
|
current_sync_committee=state.current_sync_committee,
|
|
current_sync_committee_branch=compute_merkle_proof_for_state(state, CURRENT_SYNC_COMMITTEE_INDEX),
|
|
)
|
|
```
|
|
|
|
Full nodes SHOULD provide `LightClientBootstrap` for all finalized epoch boundary blocks in the epoch range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientBootstrap` for other blocks.
|
|
|
|
Blocks are considered to be epoch boundary blocks if their block root can occur as part of a valid `Checkpoint`, i.e., if their slot is the initial slot of an epoch, or if all following slots through the initial slot of the next epoch are empty (no block proposed / orphaned).
|
|
|
|
`LightClientBootstrap` is computed from the block's immediate post state (without applying empty slots).
|
|
|
|
### `create_light_client_update`
|
|
|
|
To form a `LightClientUpdate`, the following historical states and blocks are needed:
|
|
- `state`: the post state of any block with a post-Altair parent block
|
|
- `block`: the corresponding block
|
|
- `attested_state`: the post state of `attested_block`
|
|
- `attested_block`: the block referred to by `block.parent_root`
|
|
- `finalized_block`: the block referred to by `attested_state.finalized_checkpoint.root`, if locally available (may be unavailable, e.g., when using checkpoint sync, or if it was pruned locally)
|
|
|
|
```python
|
|
def create_light_client_update(state: BeaconState,
|
|
block: SignedBeaconBlock,
|
|
attested_state: BeaconState,
|
|
attested_block: SignedBeaconBlock,
|
|
finalized_block: Optional[SignedBeaconBlock]) -> LightClientUpdate:
|
|
assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH
|
|
assert sum(block.message.body.sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
|
|
|
|
assert state.slot == state.latest_block_header.slot
|
|
header = state.latest_block_header.copy()
|
|
header.state_root = hash_tree_root(state)
|
|
assert hash_tree_root(header) == hash_tree_root(block.message)
|
|
update_signature_period = compute_sync_committee_period_at_slot(block.message.slot)
|
|
|
|
assert attested_state.slot == attested_state.latest_block_header.slot
|
|
attested_header = attested_state.latest_block_header.copy()
|
|
attested_header.state_root = hash_tree_root(attested_state)
|
|
assert hash_tree_root(attested_header) == hash_tree_root(attested_block.message) == block.message.parent_root
|
|
update_attested_period = compute_sync_committee_period_at_slot(attested_block.message.slot)
|
|
|
|
# `next_sync_committee` is only useful if the message is signed by the current sync committee
|
|
if update_attested_period == update_signature_period:
|
|
next_sync_committee = attested_state.next_sync_committee
|
|
next_sync_committee_branch = compute_merkle_proof_for_state(attested_state, NEXT_SYNC_COMMITTEE_INDEX)
|
|
else:
|
|
next_sync_committee = SyncCommittee()
|
|
next_sync_committee_branch = [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
|
|
|
|
# Indicate finality whenever possible
|
|
if finalized_block is not None:
|
|
if finalized_block.message.slot != GENESIS_SLOT:
|
|
finalized_header = BeaconBlockHeader(
|
|
slot=finalized_block.message.slot,
|
|
proposer_index=finalized_block.message.proposer_index,
|
|
parent_root=finalized_block.message.parent_root,
|
|
state_root=finalized_block.message.state_root,
|
|
body_root=hash_tree_root(finalized_block.message.body),
|
|
)
|
|
assert hash_tree_root(finalized_header) == attested_state.finalized_checkpoint.root
|
|
else:
|
|
assert attested_state.finalized_checkpoint.root == Bytes32()
|
|
finalized_header = BeaconBlockHeader()
|
|
finality_branch = compute_merkle_proof_for_state(attested_state, FINALIZED_ROOT_INDEX)
|
|
else:
|
|
finalized_header = BeaconBlockHeader()
|
|
finality_branch = [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
|
|
|
|
return LightClientUpdate(
|
|
attested_header=attested_header,
|
|
next_sync_committee=next_sync_committee,
|
|
next_sync_committee_branch=next_sync_committee_branch,
|
|
finalized_header=finalized_header,
|
|
finality_branch=finality_branch,
|
|
sync_aggregate=block.message.body.sync_aggregate,
|
|
signature_slot=block.message.slot,
|
|
)
|
|
```
|
|
|
|
Full nodes SHOULD provide the best derivable `LightClientUpdate` (according to `is_better_update`) for each sync committee period covering any epochs in range `[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` where `current_epoch` is defined by the current wall-clock time. Full nodes MAY also provide `LightClientUpdate` for other sync committee periods.
|
|
|
|
- `LightClientUpdate` are assigned to sync committee periods based on their `attested_header.slot`
|
|
- `LightClientUpdate` are only considered if `compute_sync_committee_period_at_slot(update.attested_header.slot) == compute_sync_committee_period_at_slot(update.signature_slot)`
|
|
- Only `LightClientUpdate` with `next_sync_committee` as selected by fork choice are provided, regardless of ranking by `is_better_update`. To uniquely identify a non-finalized sync committee fork, all of `period`, `current_sync_committee` and `next_sync_committee` need to be incorporated, as sync committees may reappear over time.
|
|
|
|
### `create_light_client_finality_update`
|
|
|
|
```python
|
|
def create_light_client_finality_update(update: LightClientUpdate) -> LightClientFinalityUpdate:
|
|
return LightClientFinalityUpdate(
|
|
attested_header=update.attested_header,
|
|
finalized_header=update.finalized_header,
|
|
finality_branch=update.finality_branch,
|
|
sync_aggregate=update.sync_aggregate,
|
|
signature_slot=update.signature_slot,
|
|
)
|
|
```
|
|
|
|
Full nodes SHOULD provide the `LightClientFinalityUpdate` with the highest `attested_header.slot` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientFinalityUpdate` whenever `finalized_header` changes.
|
|
|
|
### `create_light_client_optimistic_update`
|
|
|
|
```python
|
|
def create_light_client_optimistic_update(update: LightClientUpdate) -> LightClientOptimisticUpdate:
|
|
return LightClientOptimisticUpdate(
|
|
attested_header=update.attested_header,
|
|
sync_aggregate=update.sync_aggregate,
|
|
signature_slot=update.signature_slot,
|
|
)
|
|
```
|
|
|
|
Full nodes SHOULD provide the `LightClientOptimisticUpdate` with the highest `attested_header.slot` (if multiple, highest `signature_slot`) as selected by fork choice, and SHOULD support a push mechanism to deliver new `LightClientOptimisticUpdate` whenever `attested_header` changes.
|