eth2.0-specs/specs/lightclient/sync-protocol.md

5.8 KiB

Minimal Light Client Design

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

Table of contents

Introduction

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 that uses the concept of "sync committees" introduced in [./beacon-chain.md](the the light-client-friendliness beacon chain extension).

Constants

Name Value
SYNC_COMMITTEES_GENERALIZED_INDEX GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'current_sync_committee'))
FORK_GENERALIZED_INDEX GeneralizedIndexConcat(GeneralizedIndex(BeaconBlock, 'state_root'), GeneralizedIndex(BeaconState, 'fork'))

Containers

LightClientUpdate

class LightClientUpdate(Container):
    # Updated beacon header (and authenticating branch)
    header: BeaconBlockHeader
    # Sync committee signature to that header
    aggregation_bits: Bitlist[MAX_SYNC_COMMITTEE_SIZE]
    signature: BLSSignature
    header_branch: Vector[Bytes32, BEACON_CHAIN_ROOT_IN_SHARD_BLOCK_HEADER_DEPTH]
    # Updates fork version
    new_fork: Fork
    fork_branch: Vector[Bytes32, log_2(FORK_GENERALIZED_INDEX)]
    # Updated period committee (and authenticating branch)
    new_current_sync_committee: SyncCommittee
    new_next_sync_committee: SyncCommittee
    sync_committee_branch: Vector[Bytes32, log_2(SYNC_COMMITTEES_GENERALIZED_INDEX)]

Helpers

LightClientMemory

class LightClientMemory(Container):
    # Beacon header which is not expected to revert
    header: BeaconBlockHeader
    # Fork version data
    fork_version: Version
    # period committees corresponding to the beacon header
    current_sync_committee: SyncCommittee
    next_sync_committee: SyncCommittee

Light client state updates

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). It calls validate_update(memory, update) on each update that it receives in response. If sum(update.aggregate_bits) * 3 > len(update.aggregate_bits) * 2 for any valid update, it accepts that update immediately; otherwise, it waits around for some time and then finally calls update_memory(memory, update) on the valid update with the highest sum(update.aggregate_bits).

validate_update

def validate_update(memory: LightClientMemory, update: LightClientUpdate) -> bool:
    # Verify the update does not skip a period
    current_period = compute_epoch_at_slot(memory.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
    new_epoch = compute_epoch_of_shard_slot(update.header.slot)
    new_period = new_epoch // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
    assert new_period in (current_period, current_period + 1)  
    
    # Verify that it actually updates to a newer slot
    assert update.header.slot > memory.header.slot
    
    # Convenience as independent variable for convenience
    committee = memory.current_sync_committee if new_period == current_period else memory.next_sync_committee
    assert len(update.aggregation_bits) == len(committee)
    
    # Verify signature
    active_pubkeys = [p for (bit, p) in zip(update.aggregation_bits, committee.pubkeys) if bit]
    domain = compute_domain(DOMAIN_SYNC_COMMITTEE, memory.fork_version)
    signing_root = compute_signing_root(update.header, domain)
    assert bls.FastAggregateVerify(pubkeys, signing_root, update.signature)

    # Verify Merkle branches of new info
    assert is_valid_merkle_branch(
        leaf=hash_tree_root(update.new_fork),
        branch=update.fork_branch,
        depth=log2(FORK_GENERALIZED_INDEX),
        index=FORK_GENERALIZED_INDEX % 2**log2(FORK_GENERALIZED_INDEX),
        root=hash_tree_root(update.header),
    )
    assert is_valid_merkle_branch(
        leaf=hash_tree_root(update.current_sync_committee),
        branch=update.sync_committee_branch,
        depth=log2(SYNC_COMMITTEES_GENERALIZED_INDEX),
        index=SYNC_COMMITTEES_GENERALIZED_INDEX % 2**log2(SYNC_COMMITTEES_GENERALIZED_INDEX),
        root=hash_tree_root(update.header),
    )
    # Verify consistency of committees
    if new_period == current_period:
        assert update.new_current_sync_committee == memory.current_sync_committee
        assert update.new_next_sync_committee == memory.next_sync_committee
    else:
        assert update.new_current_sync_committee == memory.next_sync_committee

    return True

update_memory

def update_memory(memory: LightClientMemory, update: LightClientUpdate) -> None:
    memory.header = update.header
    epoch = compute_epoch_at_slot(update.header.slot)
    memory.fork_version = update.new_fork.previous_version if epoch < update.new_fork.epoch else update.new_fork.current_version
    memory.current_sync_committee = update.new_current_sync_committee
    memory.next_sync_committee == update.new_next_sync_committee