From 09ec58131d41792ac4479881ad89573331adcb38 Mon Sep 17 00:00:00 2001 From: Justin Date: Wed, 18 Nov 2020 10:33:42 +0000 Subject: [PATCH] Optimised updates as suggested by @vbuterin --- specs/lightclient/sync-protocol.md | 101 +++++++++++++++-------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/specs/lightclient/sync-protocol.md b/specs/lightclient/sync-protocol.md index d35c9821d..f78e6f090 100644 --- a/specs/lightclient/sync-protocol.md +++ b/specs/lightclient/sync-protocol.md @@ -20,6 +20,7 @@ - [`LightClientStore`](#lightclientstore) - [Light client state updates](#light-client-state-updates) - [`is_valid_light_client_update`](#is_valid_light_client_update) + - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -35,8 +36,8 @@ This document suggests a minimal light client design for the beacon chain that u | Name | Value | | - | - | -| `NEXT_SYNC_COMMITTEE_INDEX` | `Index(BeaconState, 'next_sync_committee')` | | `FINALIZED_ROOT_INDEX` | `Index(BeaconState, 'finalized_checkpoint', 'root')` | +| `NEXT_SYNC_COMMITTEE_INDEX` | `Index(BeaconState, 'next_sync_committee')` | ## Configuration @@ -70,14 +71,14 @@ class LightClientSnapshot(Container): ```python class LightClientUpdate(Container): - # Updated snapshot - snapshot: LightClientSnapshot - # Header that the new snapshot is a finalized ancestor of - signed_header: BeaconBlockHeader - # Merkle branch proving ancestry of the header in the snapshot - ancestry_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] - # Merkle branch for the next sync committee + # Update beacon block header + header: BeaconBlockHeader + # Next sync committee corresponding to the header + next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, log2(NEXT_SYNC_COMMITTEE_INDEX)] + # Finality proof for the update header + finality_header: BeaconBlockHeader + finality_branch: Vector[Bytes32, log2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] sync_committee_signature: BLSSignature @@ -100,75 +101,81 @@ A light client maintains its state in a `store` object of type `LightClientStore #### `is_valid_light_client_update` ```python -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 +def is_valid_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> bool: + # Verify update slot is larger than snapshot slot + assert update.header.slot > 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 relationship between signed header and ancestor header - if update.signed_header == new_snapshot.header: - assert update.ancestry_branch == [ZERO_HASH for _ in range(log2(FINALIZED_ROOT_INDEX))] + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (snapshot_period, snapshot_period + 1) + + # Verify update header root is the finalized root of the finality header, if specified + if update.finality_header == BeaconBlockHeader(): + signed_header = update.header + assert update.finality_branch == [ZERO_HASH for _ in range(log2(FINALIZED_ROOT_INDEX))] else: + signed_header = update.finality_header assert is_valid_merkle_branch( - leaf=hash_tree_root(new_snapshot.header), - branch=update.ancestry_branch, + leaf=hash_tree_root(update.header), + branch=update.finality_branch, depth=log2(FINALIZED_ROOT_INDEX), index=FINALIZED_ROOT_INDEX % 2**log2(FINALIZED_ROOT_INDEX), - root=update.signed_header.state_root, - ) + root=update.finality_header.state_root, + ) - # 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 + # Verify update next sync committee if the update period incremented + if update_period == snapshot_period: + sync_committee = snapshot.current_sync_committee + assert update.next_sync_committee_branch == [ZERO_HASH for _ in range(log2(NEXT_SYNC_COMMITTEE_INDEX))] else: - assert new_snapshot.current_sync_committee == old_snapshot.next_sync_committee + sync_committee = snapshot.next_sync_committee assert is_valid_merkle_branch( - leaf=hash_tree_root(new_snapshot.next_sync_committee), + leaf=hash_tree_root(update.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=new_snapshot.header.state_root, + root=update.header.state_root, ) - # Verify sync committee bitfield length - sync_committee = new_snapshot.current_sync_committee - assert len(update.sync_committee_bits) == len(sync_committee) + # Verify sync committee has sufficient participants 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(update.signed_header, domain) + signing_root = compute_signing_root(signed_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) return True ``` -#### `process_update` +#### `apply_light_client_update` + +```python +def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: + snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == snapshot_period + 1: + snapshot.current_sync_committee = snapshot.next_sync_committee + snapshot.next_sync_committee = update.next_sync_committee + snapshot.header = update.header +``` + +#### `process_light_client_update` ```python 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) + assert is_valid_light_client_update(store.snapshot, update) + store.valid_updates.append(update) - # Immediate update "happy path" requires: - # (i) 2/3 participation - # (ii) an update that refers to the finalized ancestor of a signed block, and not the signed block directly - - if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.snapshot.header != update.signed_header: - # Immediate update when quorum is reached - store.snapshot = update.snapshot + if sum(update.sync_committee_bits) * 3 > len(update.sync_committee_bits) * 2 and update.header != update.finality_header: + # Apply update if 2/3 quorum is reached and we have a finality proof + apply_light_client_update(store, update) store.valid_updates = [] - elif current_slot > old_snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: + elif current_slot > snapshot.header.slot + LIGHT_CLIENT_UPDATE_TIMEOUT: # Forced best update when the update timeout has elapsed - store.snapshot = max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits)).snapshot + apply_light_client_update(store, max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) store.valid_updates = [] ```