From 25f2efab198ee41dd31bcd4500eb5cc39dab4a70 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 26 Nov 2021 15:11:19 -0600 Subject: [PATCH 01/15] Simplify sync protocol and update to calculate optimistic heads 1. Simplify `valid_updates` to `best_valid_update` so the `LightClientStore` only needs to store O(1) data 2. Track an optimistic head, by looking for the highest-slot header which passes a safety threshold --- specs/altair/sync-protocol.md | 88 ++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 24c35f8912..b309b61f45 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -18,7 +18,10 @@ - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) - [`get_subtree_index`](#get_subtree_index) + - [`get_signed_header`](#get_signed_header) + - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) + - [`process_slot`](#process_slot) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -47,9 +50,10 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. ### Misc -| Name | Value | -| - | - | -| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | +| Name | Value | Notes | +| - | - | - | +| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | +| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours | ## Containers @@ -86,10 +90,12 @@ class LightClientUpdate(Container): ### `LightClientStore` ```python -@dataclass class LightClientStore(object): snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] + best_valid_update: Optional[LightClientUpdate] + optimistic_header: BeaconBlockHeader + previous_period_max_attendance: uint64 + current_period_max_attendance: uint64 ``` ## Helper functions @@ -101,9 +107,38 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` +### `get_signed_header` + +```python +def get_signed_header(update: LightClientUpdate): + if update.finality_header is None: + return update.header + else: + return update.finality_header +``` + +### `get_safety_threshold` + +```python +def get_safety_threshold(store: LightClientStore): + return max( + store.previous_period_max_attendance, + store.current_period_max_attendance + ) // 2 +``` + ## 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 current slot based on some local clock. +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 current slot based on some local clock. `process_slot` is processed every time the current slot increments. + +### `process_slot` + +```python +def process_slot(store: LightClientStore, current_slot: Slot): + if current_slot % SAFETY_THRESHOLD_CALCULATION_PERIOD == 0: + store.previous_period_max_attendance = store.current_period_max_attendance + store.current_period_max_attendance = 0 +``` #### `validate_light_client_update` @@ -172,24 +207,43 @@ def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClient #### `process_light_client_update` ```python -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, +def process_light_client_update(store: LightClientStore, + update: LightClientUpdate, + current_slot: Slot, genesis_validators_root: Root) -> None: + validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD + + # Update the best update in case we have to force-update to it if the timeout elapses + if ( + sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits) and + get_signed_header(update).slot > store.snapshot.header.slot + ): + store.best_finalization_update = update + + # Track the maximum attendance in the committee signatures + store.current_period_max_attendance = max( + store.current_period_max_attendance, + update.sync_committee_bits.count(1) + ) + + # Update the optimistic header + if ( + sum(update.sync_committee_bits) > get_safety_threshold(store) and + update.header.slot > store.optimistic_header.slot + ): + store.optimistic_header = update.header + + # Update finalized header if ( sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 and update.finality_header != BeaconBlockHeader() ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. + # Normal update through 2/3 threshold apply_light_client_update(store.snapshot, update) - store.valid_updates = set() + store.best_valid_update = None elif current_slot > store.snapshot.header.slot + update_timeout: # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() + apply_light_client_update(store.snapshot, store.best_valid_update) + store.best_valid_update = None ``` From 013e814d2d2e0c9f46eb31e36f9f7e27c2cdf39e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 26 Nov 2021 15:32:37 -0600 Subject: [PATCH 02/15] Update sync-protocol.md --- specs/altair/sync-protocol.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index b309b61f45..b077d50211 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -156,10 +156,8 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # 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 == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: - signed_header = update.finality_header assert is_valid_merkle_branch( leaf=hash_tree_root(update.header), branch=update.finality_branch, @@ -188,7 +186,7 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # 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, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) + signing_root = compute_signing_root(get_signed_header(update), domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) ``` From e1041649608be5fafe58f42a726948ca64064170 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 27 Nov 2021 07:25:27 -0600 Subject: [PATCH 03/15] Rework data structures (#2747) 1. Replace `header` and `finality_header` with `attested_header` (always the header signed by the committee) and `finailzed_header` (always the header verified by the Merkle branch) 2. Remove `LightClientSnapshot`, fold its fields into `LightClientStore` for simplicity --- specs/altair/sync-protocol.md | 115 +++++++++++++++++----------------- 1 file changed, 56 insertions(+), 59 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index b077d50211..f90469f987 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -13,7 +13,6 @@ - [Preset](#preset) - [Misc](#misc) - [Containers](#containers) - - [`LightClientSnapshot`](#lightclientsnapshot) - [`LightClientUpdate`](#lightclientupdate) - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) @@ -22,6 +21,7 @@ - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - [`process_slot`](#process_slot) + - [`get_active_header`](#get_active_header) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -57,28 +57,17 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. ## Containers -### `LightClientSnapshot` - -```python -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee -``` - ### `LightClientUpdate` ```python class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader + # The beacon block header that is attested to by the sync committee + attested_header: BeaconBlockHeader # Next sync committee corresponding to the header next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader + # The finalized beacon block header attested to by Merkle branch + finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] @@ -91,9 +80,16 @@ class LightClientUpdate(Container): ```python class LightClientStore(object): - snapshot: LightClientSnapshot + # Beacon block header that is finalized + finalized_header: BeaconBlockHeader + # Sync committees corresponding to the header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Best available header to switch finalized head to if we see nothing else best_valid_update: Optional[LightClientUpdate] + # Most recent available reasonably-safe header optimistic_header: BeaconBlockHeader + # Max number of participants in a sync committee (used to calculate safety threshold) previous_period_max_attendance: uint64 current_period_max_attendance: uint64 ``` @@ -107,16 +103,6 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` -### `get_signed_header` - -```python -def get_signed_header(update: LightClientUpdate): - if update.finality_header is None: - return update.header - else: - return update.finality_header -``` - ### `get_safety_threshold` ```python @@ -140,44 +126,57 @@ def process_slot(store: LightClientStore, current_slot: Slot): store.current_period_max_attendance = 0 ``` +### `get_active_header` + +```python +def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: + # Is the update trying to convince us of a finalized header or an optimistic header? + if update.finalized_header BeaconBlockHeader(): + return update.finalized_header + else: + return update.attested_header +``` + #### `validate_light_client_update` ```python -def validate_light_client_update(snapshot: LightClientSnapshot, +def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot + + # Verify update slot is larger than slot of current best finalized header + active_header = get_active_header(update) + assert active_header.slot > store.finalized_header.slot # Verify update does not skip a sync committee period - 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) + finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + assert update_period in (finalized_period, finalized_period + 1) # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): + if update.finalized_header == BeaconBlockHeader(): assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), + leaf=hash_tree_root(update.finalized_header), branch=update.finality_branch, depth=floorlog2(FINALIZED_ROOT_INDEX), index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, + root=update.attested_header.state_root, ) # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee + if update_period == finalized_period: + sync_committee = store.current_sync_committee assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] else: - sync_committee = snapshot.next_sync_committee + sync_committee = store.next_sync_committee assert is_valid_merkle_branch( leaf=hash_tree_root(update.next_sync_committee), branch=update.next_sync_committee_branch, depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, + root=active_header.state_root, ) # Verify sync committee has sufficient participants @@ -186,20 +185,21 @@ def validate_light_client_update(snapshot: LightClientSnapshot, # 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, genesis_validators_root) - signing_root = compute_signing_root(get_signed_header(update), domain) + signing_root = compute_signing_root(update.attested_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) ``` #### `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 +def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None: + active_header = get_active_header(update) + finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD + if update_period == finalized_period + 1: + store.current_sync_committee = store.next_sync_committee + store.next_sync_committee = update.next_sync_committee + store.finalized_header = active_header ``` #### `process_light_client_update` @@ -210,13 +210,10 @@ def process_light_client_update(store: LightClientStore, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) + validate_light_client_update(store, update, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if ( - sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits) and - get_signed_header(update).slot > store.snapshot.header.slot - ): + if sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits): store.best_finalization_update = update # Track the maximum attendance in the committee signatures @@ -228,20 +225,20 @@ def process_light_client_update(store: LightClientStore, # Update the optimistic header if ( sum(update.sync_committee_bits) > get_safety_threshold(store) and - update.header.slot > store.optimistic_header.slot + update.attested_header.slot > store.optimistic_header.slot ): - store.optimistic_header = update.header + store.optimistic_header = update.attested_header # Update finalized header if ( sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() + and update.finalized_header != BeaconBlockHeader() ): # Normal update through 2/3 threshold - apply_light_client_update(store.snapshot, update) + apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.snapshot.header.slot + update_timeout: + elif current_slot > store.finalized_header.slot + update_timeout: # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, store.best_valid_update) + apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None ``` From 77188726de0b71a89afe40e5e5c97c85a96a163c Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sat, 27 Nov 2021 07:27:16 -0600 Subject: [PATCH 04/15] Fixed ToC and get_active_header positioninf --- specs/altair/sync-protocol.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index f90469f987..ca7aa74512 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -17,11 +17,10 @@ - [`LightClientStore`](#lightclientstore) - [Helper functions](#helper-functions) - [`get_subtree_index`](#get_subtree_index) - - [`get_signed_header`](#get_signed_header) + - [`get_active_header`](#get_active_header) - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - [`process_slot`](#process_slot) - - [`get_active_header`](#get_active_header) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -103,6 +102,17 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: return uint64(generalized_index % 2**(floorlog2(generalized_index))) ``` +### `get_active_header` + +```python +def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: + # Is the update trying to convince us of a finalized header or an optimistic header? + if update.finalized_header BeaconBlockHeader(): + return update.finalized_header + else: + return update.attested_header +``` + ### `get_safety_threshold` ```python @@ -126,17 +136,6 @@ def process_slot(store: LightClientStore, current_slot: Slot): store.current_period_max_attendance = 0 ``` -### `get_active_header` - -```python -def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: - # Is the update trying to convince us of a finalized header or an optimistic header? - if update.finalized_header BeaconBlockHeader(): - return update.finalized_header - else: - return update.attested_header -``` - #### `validate_light_client_update` ```python From c4f70970c41205b874c289f2f4f2e2554a18f78e Mon Sep 17 00:00:00 2001 From: vbuterin Date: Sun, 28 Nov 2021 08:31:48 -0600 Subject: [PATCH 05/15] Update specs/altair/sync-protocol.md Co-authored-by: terence tsao --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index ca7aa74512..dd61b6545a 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -107,7 +107,7 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: ```python def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: # Is the update trying to convince us of a finalized header or an optimistic header? - if update.finalized_header BeaconBlockHeader(): + if update.finalized_header != BeaconBlockHeader(): return update.finalized_header else: return update.attested_header From 06af6296c5727e4e0faedb54da0938ecfc75aae7 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Nov 2021 07:04:05 -0600 Subject: [PATCH 06/15] Updated in response to comments --- specs/altair/sync-protocol.md | 40 +++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index dd61b6545a..6ce46bf6b0 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -52,7 +52,8 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | Name | Value | Notes | | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | -| `SAFETY_THRESHOLD_CALCULATION_PERIOD` | `4096` | ~13.6 hours | +| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~13.6 hours | +| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | ## Containers @@ -88,9 +89,9 @@ class LightClientStore(object): best_valid_update: Optional[LightClientUpdate] # Most recent available reasonably-safe header optimistic_header: BeaconBlockHeader - # Max number of participants in a sync committee (used to calculate safety threshold) - previous_period_max_attendance: uint64 - current_period_max_attendance: uint64 + # Max number of active participants in a sync committee (used to calculate safety threshold) + previous_max_active_participants: uint64 + current_max_active_participants: uint64 ``` ## Helper functions @@ -106,7 +107,9 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: ```python def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: - # Is the update trying to convince us of a finalized header or an optimistic header? + # The "active header" is the header that the update is trying to convince us + # to accept. If a finalized header is present, it's the finalized header, + # otherwise it's the attested header if update.finalized_header != BeaconBlockHeader(): return update.finalized_header else: @@ -118,8 +121,8 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ```python def get_safety_threshold(store: LightClientStore): return max( - store.previous_period_max_attendance, - store.current_period_max_attendance + store.previous_max_active_participants, + store.current_max_active_participants ) // 2 ``` @@ -131,9 +134,9 @@ A light client maintains its state in a `store` object of type `LightClientStore ```python def process_slot(store: LightClientStore, current_slot: Slot): - if current_slot % SAFETY_THRESHOLD_CALCULATION_PERIOD == 0: - store.previous_period_max_attendance = store.current_period_max_attendance - store.current_period_max_attendance = 0 + if current_slot % SAFETY_THRESHOLD_PERIOD == 0: + store.previous_max_active_participants = store.current_max_active_participants + store.current_max_active_participants = 0 ``` #### `validate_light_client_update` @@ -141,11 +144,12 @@ def process_slot(store: LightClientStore, current_slot: Slot): ```python def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, + current_slot: Slot, genesis_validators_root: Root) -> None: # Verify update slot is larger than slot of current best finalized header active_header = get_active_header(update) - assert active_header.slot > store.finalized_header.slot + assert current_slot >= active_header.slot > store.finalized_header.slot # Verify update does not skip a sync committee period finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD @@ -209,15 +213,15 @@ def process_light_client_update(store: LightClientStore, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store, update, genesis_validators_root) + validate_light_client_update(store, update, current_slot, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if sum(update.sync_committee_bits) > sum(store.best_finalization_update.sync_committee_bits): - store.best_finalization_update = update + if sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits): + store.best_valid_update = update - # Track the maximum attendance in the committee signatures - store.current_period_max_attendance = max( - store.current_period_max_attendance, + # Track the maximum numebr of active participants in the committee signatures + store.current_max_active_participants = max( + store.current_max_active_participants, update.sync_committee_bits.count(1) ) @@ -236,7 +240,7 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.finalized_header.slot + update_timeout: + elif current_slot > store.finalized_header.slot + UPDATE_TIMEOUT: # Forced best update when the update timeout has elapsed apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None From 6fa19705fb5f1414592d6f3cb66117a738285950 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Mon, 29 Nov 2021 07:05:01 -0600 Subject: [PATCH 07/15] Clarified next sync committee comment --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 6ce46bf6b0..0af20cbca1 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -63,7 +63,7 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. class LightClientUpdate(Container): # The beacon block header that is attested to by the sync committee attested_header: BeaconBlockHeader - # Next sync committee corresponding to the header + # Next sync committee corresponding to the active header next_sync_committee: SyncCommittee next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] # The finalized beacon block header attested to by Merkle branch From 7de1495a42f89a876fb84ac1701a4cbe3e3eab6b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Nov 2021 20:38:42 +0800 Subject: [PATCH 08/15] Fix lint (#2750) --- presets/mainnet/altair.yaml | 4 ++++ presets/minimal/altair.yaml | 4 ++++ setup.py | 1 + specs/altair/sync-protocol.md | 25 ++++++++++++++++--------- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/presets/mainnet/altair.yaml b/presets/mainnet/altair.yaml index 9a17b78032..21e3cc3285 100644 --- a/presets/mainnet/altair.yaml +++ b/presets/mainnet/altair.yaml @@ -22,3 +22,7 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 # --------------------------------------------------------------- # 1 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) +UPDATE_TIMEOUT: 8192 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 32 * 256 // 2) +SAFETY_THRESHOLD_PERIOD: 4096 diff --git a/presets/minimal/altair.yaml b/presets/minimal/altair.yaml index 88d78bea36..7cdbd58ea7 100644 --- a/presets/minimal/altair.yaml +++ b/presets/minimal/altair.yaml @@ -22,3 +22,7 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 # --------------------------------------------------------------- # 1 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) +UPDATE_TIMEOUT: 64 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 8 * 8 // 2) +SAFETY_THRESHOLD_PERIOD: 32 diff --git a/setup.py b/setup.py index 0ced87be2e..7b74fc1557 100644 --- a/setup.py +++ b/setup.py @@ -683,6 +683,7 @@ def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', 'bytes', 'byte', 'ByteList', 'ByteVector', 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', + 'Optional', ] diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 0af20cbca1..ce7ae62523 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -52,8 +52,8 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | Name | Value | Notes | | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | -| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~13.6 hours | | `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | +| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2` | ~13.6 hours | ## Containers @@ -79,6 +79,7 @@ class LightClientUpdate(Container): ### `LightClientStore` ```python +@dataclass class LightClientStore(object): # Beacon block header that is finalized finalized_header: BeaconBlockHeader @@ -119,7 +120,7 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ### `get_safety_threshold` ```python -def get_safety_threshold(store: LightClientStore): +def get_safety_threshold(store: LightClientStore) -> uint64: return max( store.previous_max_active_participants, store.current_max_active_participants @@ -130,10 +131,10 @@ def get_safety_threshold(store: LightClientStore): 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 current slot based on some local clock. `process_slot` is processed every time the current slot increments. -### `process_slot` +#### `process_slot` ```python -def process_slot(store: LightClientStore, current_slot: Slot): +def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: if current_slot % SAFETY_THRESHOLD_PERIOD == 0: store.previous_max_active_participants = store.current_max_active_participants store.current_max_active_participants = 0 @@ -216,13 +217,16 @@ def process_light_client_update(store: LightClientStore, validate_light_client_update(store, update, current_slot, genesis_validators_root) # Update the best update in case we have to force-update to it if the timeout elapses - if sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits): + if ( + store.best_valid_update is None + or sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits) + ): store.best_valid_update = update - # Track the maximum numebr of active participants in the committee signatures + # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( - store.current_max_active_participants, - update.sync_committee_bits.count(1) + store.current_max_active_participants, + update.sync_committee_bits.count(1), ) # Update the optimistic header @@ -240,7 +244,10 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif current_slot > store.finalized_header.slot + UPDATE_TIMEOUT: + elif ( + current_slot > store.finalized_header.slot + UPDATE_TIMEOUT + and store.best_valid_update is not None + ): # Forced best update when the update timeout has elapsed apply_light_client_update(store, store.best_valid_update) store.best_valid_update = None From c30662b696e5ae3e8c4b0afb0bcfc736bbf547ed Mon Sep 17 00:00:00 2001 From: vbuterin Date: Tue, 30 Nov 2021 06:39:25 -0600 Subject: [PATCH 09/15] Consistently use sum instead of count(1) --- specs/altair/sync-protocol.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index ce7ae62523..401effc16e 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -226,7 +226,7 @@ def process_light_client_update(store: LightClientStore, # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, - update.sync_committee_bits.count(1), + sum(update.sync_committee_bits), ) # Update the optimistic header From 402c663b51805f725955356f5b9091502bb0505b Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 30 Nov 2021 21:57:43 +0800 Subject: [PATCH 10/15] Fix function name leftover --- specs/altair/sync-protocol.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 401effc16e..8da4a4d988 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -20,7 +20,7 @@ - [`get_active_header`](#get_active_header) - [`get_safety_threshold`](#get_safety_threshold) - [Light client state updates](#light-client-state-updates) - - [`process_slot`](#process_slot) + - [`process_slot_for_light_client_store`](#process_slot_for_light_client_store) - [`validate_light_client_update`](#validate_light_client_update) - [`apply_light_client_update`](#apply_light_client_update) - [`process_light_client_update`](#process_light_client_update) @@ -129,9 +129,9 @@ def get_safety_threshold(store: LightClientStore) -> uint64: ## 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 current slot based on some local clock. `process_slot` is processed every time the current slot increments. +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 current slot based on some local clock. `process_slot_for_light_client_store` is processed every time the current slot increments. -#### `process_slot` +#### `process_slot_for_light_client_store` ```python def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: From 916193bd69848ca80981458226acc2bb9f4840a6 Mon Sep 17 00:00:00 2001 From: vbuterin Date: Fri, 10 Dec 2021 07:48:14 -0600 Subject: [PATCH 11/15] Updates in response to comments --- specs/altair/sync-protocol.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 8da4a4d988..a36bf782b6 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -53,7 +53,6 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain. | - | - | - | | `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | | | `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | ~27.3 hours | -| `SAFETY_THRESHOLD_PERIOD` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2` | ~13.6 hours | ## Containers @@ -70,8 +69,7 @@ class LightClientUpdate(Container): finalized_header: BeaconBlockHeader finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature + sync_committee_aggregate: SyncAggregate # Fork version for the aggregate signature fork_version: Version ``` @@ -135,9 +133,16 @@ A light client maintains its state in a `store` object of type `LightClientStore ```python def process_slot_for_light_client_store(store: LightClientStore, current_slot: Slot) -> None: - if current_slot % SAFETY_THRESHOLD_PERIOD == 0: + if current_slot % UPDATE_TIMEOUT == 0: store.previous_max_active_participants = store.current_max_active_participants store.current_max_active_participants = 0 + if ( + current_slot > store.finalized_header.slot + UPDATE_TIMEOUT + and store.best_valid_update is not None + ): + # Forced best update when the update timeout has elapsed + apply_light_client_update(store, store.best_valid_update) + store.best_valid_update = None ``` #### `validate_light_client_update` @@ -157,7 +162,8 @@ def validate_light_client_update(store: LightClientStore, update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert update_period in (finalized_period, finalized_period + 1) - # Verify update header root is the finalized root of the finality header, if specified + # Verify that the `finalized_header`, if present, actually is the finalized header saved in the + # state of the `attested header` if update.finalized_header == BeaconBlockHeader(): assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] else: @@ -182,15 +188,17 @@ def validate_light_client_update(store: LightClientStore, index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), root=active_header.state_root, ) + + sync_aggregate = update.sync_committee_aggregate # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + assert sum(sync_aggregate.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] + participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) signing_root = compute_signing_root(update.attested_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) + assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) ``` #### `apply_light_client_update` @@ -244,11 +252,4 @@ def process_light_client_update(store: LightClientStore, # Normal update through 2/3 threshold apply_light_client_update(store, update) store.best_valid_update = None - elif ( - current_slot > store.finalized_header.slot + UPDATE_TIMEOUT - and store.best_valid_update is not None - ): - # Forced best update when the update timeout has elapsed - apply_light_client_update(store, store.best_valid_update) - store.best_valid_update = None ``` From 2f618f7b483310303525dfe592d176f3fc91f087 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 21:38:58 +0800 Subject: [PATCH 12/15] Fix lint and presets --- presets/mainnet/altair.yaml | 2 -- presets/minimal/altair.yaml | 2 -- specs/altair/sync-protocol.md | 5 ++++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/presets/mainnet/altair.yaml b/presets/mainnet/altair.yaml index 21e3cc3285..813ef72122 100644 --- a/presets/mainnet/altair.yaml +++ b/presets/mainnet/altair.yaml @@ -24,5 +24,3 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) UPDATE_TIMEOUT: 8192 -# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 32 * 256 // 2) -SAFETY_THRESHOLD_PERIOD: 4096 diff --git a/presets/minimal/altair.yaml b/presets/minimal/altair.yaml index 7cdbd58ea7..5e472c49cf 100644 --- a/presets/minimal/altair.yaml +++ b/presets/minimal/altair.yaml @@ -24,5 +24,3 @@ EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 # SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) UPDATE_TIMEOUT: 64 -# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD // 2 (= 8 * 8 // 2) -SAFETY_THRESHOLD_PERIOD: 32 diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index a36bf782b6..6dae145b95 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -195,7 +195,10 @@ def validate_light_client_update(store: LightClientStore, assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) if bit] + participant_pubkeys = [ + pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) + if bit + ] domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) signing_root = compute_signing_root(update.attested_header, domain) assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) From 25d88fee244cc687b0cd98a5a2b7cadb9d01f94a Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 22:05:09 +0800 Subject: [PATCH 13/15] Fix `process_light_client_update` --- specs/altair/sync-protocol.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index 6dae145b95..fb86c2bf11 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -226,30 +226,32 @@ def process_light_client_update(store: LightClientStore, genesis_validators_root: Root) -> None: validate_light_client_update(store, update, current_slot, genesis_validators_root) - + + sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits + # Update the best update in case we have to force-update to it if the timeout elapses if ( store.best_valid_update is None - or sum(update.sync_committee_bits) > sum(store.best_valid_update.sync_committee_bits) + or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits) ): store.best_valid_update = update # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, - sum(update.sync_committee_bits), + sum(sync_committee_bits), ) # Update the optimistic header if ( - sum(update.sync_committee_bits) > get_safety_threshold(store) and + sum(sync_committee_bits) > get_safety_threshold(store) and update.attested_header.slot > store.optimistic_header.slot ): store.optimistic_header = update.attested_header # Update finalized header if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 + sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2 and update.finalized_header != BeaconBlockHeader() ): # Normal update through 2/3 threshold From 257c2413a57ec4b311f462931b47861be8ec9ada Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 14 Dec 2021 22:06:25 +0800 Subject: [PATCH 14/15] Update test_sync_protocol.py per the new optimistic_header and data structure --- .../altair/unittests/test_sync_protocol.py | 110 +++++++++--------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py index 15444df819..30444c4ce4 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py @@ -1,3 +1,5 @@ +from copy import deepcopy + from eth2spec.test.context import ( spec_state_test, with_presets, @@ -19,20 +21,24 @@ from eth2spec.test.helpers.merkle import build_proof -@with_altair_and_later -@spec_state_test -def test_process_light_client_update_not_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), +def _initialize_light_client_store(spec, state): + return spec.LightClientStore( + finalized_header=spec.BeaconBlockHeader(), current_sync_committee=state.current_sync_committee, next_sync_committee=state.next_sync_committee, + best_valid_update=None, + optimistic_header=spec.BeaconBlockHeader(), + previous_max_active_participants=0, + current_max_active_participants=0, ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) - # Block at slot 1 doesn't increase sync committee period, so it won't update snapshot + +@with_altair_and_later +@spec_state_test +def test_process_light_client_update_not_timeout(spec, state): + store = _initialize_light_client_store(spec, state) + + # Block at slot 1 doesn't increase sync committee period, so it won't force update store.finalized_header block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) block_header = spec.BeaconBlockHeader( @@ -52,6 +58,10 @@ def test_process_light_client_update_not_updated(spec, state): block_header.slot, committee, ) + sync_committee_aggregate = spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] # Ensure that finality checkpoint is genesis @@ -61,40 +71,34 @@ def test_process_light_client_update_not_updated(spec, state): finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( - header=block_header, + attested_header=block_header, next_sync_committee=state.next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, + finalized_header=finality_header, finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) + pre_store = deepcopy(store) + spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - assert len(store.valid_updates) == 1 - assert store.valid_updates.pop() == update - assert store.snapshot == pre_snapshot + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.finalized_header == pre_store.finalized_header + assert store.best_valid_update == update @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_timeout(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) + store = _initialize_light_client_store(spec, state) # Forward to next sync committee period - next_slots(spec, state, spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD)) - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + next_slots(spec, state, spec.UPDATE_TIMEOUT) + snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert snapshot_period + 1 == update_period @@ -119,6 +123,10 @@ def test_process_light_client_update_timeout(spec, state): committee, block_root=spec.Root(block_header.hash_tree_root()), ) + sync_committee_aggregate = spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) # Sync committee is updated next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) @@ -127,36 +135,30 @@ def test_process_light_client_update_timeout(spec, state): finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] update = spec.LightClientUpdate( - header=block_header, + attested_header=block_header, next_sync_committee=state.next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, + finalized_header=finality_header, finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) + pre_store = deepcopy(store) + spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.best_valid_update == update + assert store.finalized_header == pre_store.finalized_header @with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_process_light_client_update_finality_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) + store = _initialize_light_client_store(spec, state) # Change finality blocks = [] @@ -167,7 +169,7 @@ def test_process_light_client_update_finality_updated(spec, state): # Ensure that finality checkpoint has changed assert state.finalized_checkpoint.epoch == 3 # Ensure that it's same period - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + snapshot_period = spec.compute_epoch_at_slot(store.optimistic_header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD assert snapshot_period == update_period @@ -199,20 +201,24 @@ def test_process_light_client_update_finality_updated(spec, state): committee, block_root=spec.Root(block_header.hash_tree_root()), ) + sync_committee_aggregate = spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) update = spec.LightClientUpdate( - header=finalized_block_header, + attested_header=block_header, next_sync_committee=state.next_sync_committee, next_sync_committee_branch=next_sync_committee_branch, - finality_header=block_header, # block_header is the signed header + finalized_header=finalized_block_header, finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, + sync_committee_aggregate=sync_committee_aggregate, fork_version=state.fork.current_version, ) spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header + assert store.current_max_active_participants > 0 + assert store.optimistic_header == update.attested_header + assert store.finalized_header == update.finalized_header + assert store.best_valid_update is None From de892382db6135197c68dadb205e834a8b44ed51 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Wed, 15 Dec 2021 23:43:54 +0800 Subject: [PATCH 15/15] Minor style fixes --- specs/altair/sync-protocol.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md index fb86c2bf11..c8c7c3d4dc 100644 --- a/specs/altair/sync-protocol.md +++ b/specs/altair/sync-protocol.md @@ -120,8 +120,8 @@ def get_active_header(update: LightClientUpdate) -> BeaconBlockHeader: ```python def get_safety_threshold(store: LightClientStore) -> uint64: return max( - store.previous_max_active_participants, - store.current_max_active_participants + store.previous_max_active_participants, + store.current_max_active_participants, ) // 2 ``` @@ -152,7 +152,6 @@ def validate_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, genesis_validators_root: Root) -> None: - # Verify update slot is larger than slot of current best finalized header active_header = get_active_header(update) assert current_slot >= active_header.slot > store.finalized_header.slot @@ -224,7 +223,6 @@ def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, genesis_validators_root: Root) -> None: - validate_light_client_update(store, update, current_slot, genesis_validators_root) sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits @@ -235,20 +233,20 @@ def process_light_client_update(store: LightClientStore, or sum(sync_committee_bits) > sum(store.best_valid_update.sync_committee_aggregate.sync_committee_bits) ): store.best_valid_update = update - + # Track the maximum number of active participants in the committee signatures store.current_max_active_participants = max( store.current_max_active_participants, sum(sync_committee_bits), ) - + # Update the optimistic header if ( - sum(sync_committee_bits) > get_safety_threshold(store) and - update.attested_header.slot > store.optimistic_header.slot + sum(sync_committee_bits) > get_safety_threshold(store) + and update.attested_header.slot > store.optimistic_header.slot ): store.optimistic_header = update.attested_header - + # Update finalized header if ( sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2