Files
consensus-specs/specs/lightclient/sync-protocol.md
2020-11-17 15:30:09 +00:00

5.8 KiB

Minimal Light Client

Notice: This document is a work-in-progress for researchers and implementers.

Table of contents

Introduction

Eth2 is designed to be light client friendly for constrained environments to access Eth2 with reasonable satefy and liveness. Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) and metered VMs (e.g. blockchain VMs for cross-chain bridges).

This document suggests a minimal light client design for the beacon chain that uses sync committees introduced in this beacon chain extension.

Constants

Name Value
NEXT_SYNC_COMMITTEE_INDEX IndexConcat(Index(BeaconBlock, 'state_root'), Index(BeaconState, 'next_sync_committee'))

Configuration

Misc

Name Value
MIN_SYNC_COMMITTEE_PARTICIPANTS 1
MAX_VALID_LIGHT_CLIENT_UPDATES uint64(2**64 - 1)

Time parameters

Name Value Unit Duration
LIGHT_CLIENT_UPDATE_TIMEOUT Slot(2**13) slots ~27 hours

Containers

LightClientSnapshot

class LightClientSnapshot(Container):
    # Beacon block header
    header: BeaconBlockHeader
    # Sync committees corresponding to the header
    current_sync_committee: SyncCommittee
    next_sync_committee: SyncCommittee

LightClientUpdate

class LightClientUpdate(Container):
    # Updated snapshot
    snapshot: LightClientSnapshot
    # Merkle branches for the next sync committee
    next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)]
    # Sync committee aggregate signature
    sync_committee_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE]
    sync_committee_signature: BLSSignature
    # Fork version for the aggregate signature
    fork_version

LightClientStore

class LightClientStore(Container):
    snapshot: LightClientSnapshot
    valid_updates: List[LightClientUpdate, MAX_VALID_LIGHT_CLIENT_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 currect slot based on some local clock.

is_valid_light_client_update

def is_valid_light_client_update(store: LightClientStore, update: LightClientUpdate) -> bool:
    # Verify new slot is larger than old slot
    old_snapshot = store.snapshot
    new_snapshot = update.snapshot
    assert new_snapshot.header.slot > old_snapshot.header.slot

    # Verify update does not skip a sync committee period
    old_period = compute_epoch_at_slot(old_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
    new_period = compute_epoch_at_slot(new_snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
    assert new_period in (old_period, old_period + 1)

    # Verify new snapshot sync committees
    if new_period == old_period:
        assert new_snapshot.current_sync_committee == old_snapshot.current_sync_committee
        assert new_snapshot.next_sync_committee == old_snapshot.next_sync_committee
    else new_period == old_period + 1:
        assert new_snapshot.current_sync_committee == old_snapshot.next_sync_committee
        assert is_valid_merkle_branch(
            leaf=hash_tree_root(new_snapshot.next_sync_committee),
            branch=update.next_sync_committee_branch,
            depth=log2(NEXT_SYNC_COMMITTEE_INDEX),
            index=NEXT_SYNC_COMMITTEE_INDEX % 2**log2(NEXT_SYNC_COMMITTEE_INDEX),
            root=hash_tree_root(new_snapshot.header),
        )

    # Verify sync committee bitfield length 
    sync_committee = new_snapshot.current_sync_committee
    assert len(update.sync_committee_bits) == len(sync_committee)
    assert sum(update.sync_committee_bits) > MIN_SYNC_COMMITTEE_PARTICIPANTS

    # Verify sync committee aggregate signature
    participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit]
    domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version)
    signing_root = compute_signing_root(new_snapshot.header, domain)
    assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature)

    return True

process_update

def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot) -> None:
    # Validate update
    assert is_valid_light_client_update(store, update)
    valid_updates.append(update)

    if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2:
        # Immediate update when quorum is reached
        store.snapshot = update.snapshot
        valid_updates = []
    elif current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT:
        # Forced best update when the update timeout has elapsed
        store.snapshot = max(valid_updates, key=lambda update: sum(update.sync_committee_bits)).new_snapshot
        valid_updates = []