2019-08-20 12:33:29 +00:00
# Minimal Light Client Design
2019-03-15 11:25:15 +00:00
2019-08-20 12:33:29 +00:00
**Notice**: This document is a work-in-progress for researchers and implementers.
2019-03-21 18:33:36 +00:00
2019-05-06 15:30:32 +00:00
## Table of contents
2019-03-21 18:33:36 +00:00
<!-- TOC -->
2019-04-11 07:13:45 +00:00
2019-08-20 12:33:29 +00:00
- [Minimal Light Client Design ](#minimal-light-client-design )
2019-05-06 15:30:32 +00:00
- [Table of contents ](#table-of-contents )
2019-08-20 12:33:29 +00:00
- [Introduction ](#introduction )
- [Custom types ](#custom-types )
- [Constants ](#constants )
- [Containers ](#containers )
- [`LightClientUpdate` ](#lightclientupdate )
- [`LightClientMemory` ](#lightclientmemory )
2019-09-04 16:00:59 +00:00
- [Helpers ](#helpers )
2019-08-20 12:33:29 +00:00
- [`get_persistent_committee_pubkeys_and_balances` ](#get_persistent_committee_pubkeys_and_balances )
- [Light client state updates ](#light-client-state-updates )
- [Data overhead ](#data-overhead )
2019-04-11 07:13:45 +00:00
2019-03-21 18:33:36 +00:00
<!-- /TOC -->
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
## Introduction
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
Ethereum 2.0 is designed to be light client friendly. This allows low-resource clients such as mobile phones to access Ethereum 2.0 with reasonable safety and liveness. It also facilitates the development of "bridges" to external blockchains. This document suggests a minimal light client design for the beacon chain.
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
## Custom types
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
We define the following Python custom types for type hinting and readability:
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
| Name | SSZ equivalent | Description |
| - | - | - |
| `CompactValidator` | `uint64` | compact representation of a validator for light clients |
2019-04-14 08:13:43 +00:00
2019-08-20 12:33:29 +00:00
## Constants
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
| Name | Value |
| - | - |
| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH` | `4` |
| `BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX` | **TBD** |
| `PERSISTENT_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH` | `5` |
| `PERSISTENT_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX` | **TBD** |
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
## Containers
2019-04-14 08:13:43 +00:00
2019-08-20 12:33:29 +00:00
### `LightClientUpdate`
2019-03-13 07:54:27 +00:00
```python
2019-08-20 12:33:29 +00:00
class LightClientUpdate(container):
# Shard block root (and authenticating signature data)
shard_block_root: Hash
fork_version: Version
aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
signature: BLSSignature
# Updated beacon header (and authenticating branch)
header: BeaconBlockHeader
header_branch: Vector[Hash, BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH]
# Updated persistent committee (and authenticating branch)
committee: CompactCommittee
committee_branch: Vector[Hash, PERSISTENT_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH + log_2(SHARD_COUNT)]
2019-03-13 07:54:27 +00:00
```
2019-08-20 12:33:29 +00:00
### `LightClientMemory`
2019-04-14 08:13:43 +00:00
```python
2019-08-20 12:33:29 +00:00
class LightClientMemory(object):
shard: Shard # Randomly initialized and retained forever
header: BeaconBlockHeader # Beacon header which is not expected to revert
# Persistent committees corresponding to the beacon header
previous_committee: CompactCommittee
current_committee: CompactCommittee
next_committee: CompactCommittee
2019-04-14 08:13:43 +00:00
```
2019-09-04 16:00:59 +00:00
## Helpers
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
### `get_persistent_committee_pubkeys_and_balances`
2019-03-13 07:54:27 +00:00
```python
2019-08-20 12:33:29 +00:00
def get_persistent_committee_pubkeys_and_balances(memory: LightClientMemory,
epoch: Epoch) -> Tuple[Sequence[BLSPubkey], Sequence[uint64]]:
"""
Return pubkeys and balances for the persistent committee at ``epoch``.
"""
current_period = compute_epoch_of_slot(memory.header.slot) // EPOCHS_PER_SHARD_PERIOD
next_period = epoch // EPOCHS_PER_SHARD_PERIOD
assert next_period in (current_period, current_period + 1)
if next_period == current_period:
earlier_committee, later_committee = memory.previous_committee, memory.current_committee
else:
earlier_committee, later_committee = memory.current_committee, memory.next_committee
pubkeys = []
balances = []
for pubkey, compact_validator in zip(earlier_committee.pubkeys, earlier_committee.compact_validators):
index, slashed, balance = unpack_compact_validator(compact_validator)
if epoch % EPOCHS_PER_SHARD_PERIOD < index % EPOCHS_PER_SHARD_PERIOD:
pubkeys.append(pubkey)
balances.append(balance)
for pubkey, compact_validator in zip(later_committee.pubkeys, later_committee.compact_validators):
index, slashed, balance = unpack_compact_validator(compact_validator)
if epoch % EPOCHS_PER_SHARD_PERIOD >= index % EPOCHS_PER_SHARD_PERIOD:
pubkeys.append(pubkey)
balances.append(balance)
return pubkeys, balances
2019-03-13 07:54:27 +00:00
```
2019-08-20 12:33:29 +00:00
## Light client state updates
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
The state of a light client is stored in a `memory` object of type `LightClientMemory` . To advance its state a light client requests an `update` object of type `LightClientUpdate` from the network by sending a request containing `(memory.shard, memory.header.slot, slot_range_end)` and calls `update_memory(memory, update)` .
2019-03-13 07:54:27 +00:00
```python
2019-08-20 12:33:29 +00:00
def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None:
# Verify the update does not skip a period
current_period = compute_epoch_of_slot(memory.header.slot) // EPOCHS_PER_SHARD_PERIOD
next_epoch = compute_epoch_of_shard_slot(update.header.slot)
next_period = next_epoch // EPOCHS_PER_SHARD_PERIOD
assert next_period in (current_period, current_period + 1)
# Verify update header against shard block root and header branch
assert is_valid_merkle_branch(
leaf=hash_tree_root(update.header),
branch=update.header_branch,
depth=BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH,
index=BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_INDEX,
root=update.shard_block_root,
)
# Verify persistent committee votes pass 2/3 threshold
pubkeys, balances = get_persistent_committee_pubkeys_and_balances(memory, next_epoch)
assert 3 * sum(filter(lambda i: update.aggregation_bits[i], balances)) > 2 * sum(balances)
2019-04-14 08:17:43 +00:00
2019-08-20 12:33:29 +00:00
# Verify shard attestations
pubkey = bls_aggregate_pubkeys(filter(lambda i: update.aggregation_bits[i], pubkeys))
domain = compute_domain(DOMAIN_SHARD_ATTESTER, update.fork_version)
assert bls_verify(pubkey, update.shard_block_root, update.signature, domain)
# Update persistent committees if entering a new period
if next_period == current_period + 1:
assert is_valid_merkle_branch(
leaf=hash_tree_root(update.committee),
branch=update.committee_branch,
depth=PERSISTENT_COMMITTEE_ROOT_IN_BEACON_STATE_DEPTH + log_2(SHARD_COUNT),
index=PERSISTENT_COMMITTEE_ROOT_IN_BEACON_STATE_INDEX < < log_2 ( SHARD_COUNT ) + memory . shard ,
root=hash_tree_root(update.header),
2019-03-13 07:54:27 +00:00
)
2019-08-20 12:33:29 +00:00
memory.previous_committee = memory.current_committee
memory.current_committee = memory.next_committee
memory.next_committee = update.committee
2019-03-13 07:47:29 +00:00
2019-08-20 12:33:29 +00:00
# Update header
memory.header = update.header
2019-03-13 07:54:27 +00:00
```
2019-08-20 12:33:29 +00:00
## Data overhead
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
Once every `EPOCHS_PER_SHARD_PERIOD` epochs (~27 hours) a light client downloads a `LightClientUpdate` object:
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
* `shard_block_root` : 32 bytes
* `fork_version` : 4 bytes
* `aggregation_bits` : 16 bytes
* `signature` : 96 bytes
* `header` : 8 + 32 + 32 + 32 + 96 = 200 bytes
* `header_branch` : 4 * 32 = 128 bytes
* `committee` : 128 * (48 + 8) = 7,168 bytes
* `committee_branch` : (5 + 10) * 32 = 480 bytes
2019-03-13 07:54:27 +00:00
2019-08-20 12:33:29 +00:00
The total overhead is 8,124 bytes, or ~0.083 bytes per second. The Bitcoin SPV equivalent is 80 bytes per ~560 seconds, or ~0.143 bytes per second. Various compression optimisations (similar to [these ](https://github.com/RCasatta/compressedheaders )) are possible.
2019-03-13 07:47:29 +00:00
2019-08-20 12:33:29 +00:00
A light client can choose to update the header (without updating the committee) more frequently than once every `EPOCHS_PER_SHARD_PERIOD` epochs at a cost of 32 + 4 + 16 + 96 + 200 + 128 = 476 bytes per update.