13
13
- [ Preset] ( #preset )
14
14
- [ Misc] ( #misc )
15
15
- [ Containers] ( #containers )
16
- - [ ` LightClientSnapshot ` ] ( #lightclientsnapshot )
17
16
- [ ` LightClientUpdate ` ] ( #lightclientupdate )
18
17
- [ ` LightClientStore ` ] ( #lightclientstore )
19
18
- [ Helper functions] ( #helper-functions )
20
19
- [ ` get_subtree_index ` ] ( #get_subtree_index )
20
+ - [ ` get_active_header ` ] ( #get_active_header )
21
+ - [ ` get_safety_threshold ` ] ( #get_safety_threshold )
21
22
- [ Light client state updates] ( #light-client-state-updates )
23
+ - [ ` process_slot_for_light_client_store ` ] ( #process_slot_for_light_client_store )
22
24
- [ ` validate_light_client_update ` ] ( #validate_light_client_update )
23
25
- [ ` apply_light_client_update ` ] ( #apply_light_client_update )
24
26
- [ ` process_light_client_update ` ] ( #process_light_client_update )
@@ -47,38 +49,27 @@ uses sync committees introduced in [this beacon chain extension](./beacon-chain.
47
49
48
50
### Misc
49
51
50
- | Name | Value |
51
- | - | - |
52
- | ` MIN_SYNC_COMMITTEE_PARTICIPANTS ` | ` 1 ` |
52
+ | Name | Value | Notes |
53
+ | - | - | - |
54
+ | ` MIN_SYNC_COMMITTEE_PARTICIPANTS ` | ` 1 ` | |
55
+ | ` UPDATE_TIMEOUT ` | ` SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD ` | ~ 27.3 hours |
53
56
54
57
## Containers
55
58
56
- ### ` LightClientSnapshot `
57
-
58
- ``` python
59
- class LightClientSnapshot (Container ):
60
- # Beacon block header
61
- header: BeaconBlockHeader
62
- # Sync committees corresponding to the header
63
- current_sync_committee: SyncCommittee
64
- next_sync_committee: SyncCommittee
65
- ```
66
-
67
59
### ` LightClientUpdate `
68
60
69
61
``` python
70
62
class LightClientUpdate (Container ):
71
- # Update beacon block header
72
- header : BeaconBlockHeader
73
- # Next sync committee corresponding to the header
63
+ # The beacon block header that is attested to by the sync committee
64
+ attested_header : BeaconBlockHeader
65
+ # Next sync committee corresponding to the active header
74
66
next_sync_committee: SyncCommittee
75
67
next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX )]
76
- # Finality proof for the update header
77
- finality_header : BeaconBlockHeader
68
+ # The finalized beacon block header attested to by Merkle branch
69
+ finalized_header : BeaconBlockHeader
78
70
finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX )]
79
71
# Sync committee aggregate signature
80
- sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE ]
81
- sync_committee_signature: BLSSignature
72
+ sync_committee_aggregate: SyncAggregate
82
73
# Fork version for the aggregate signature
83
74
fork_version: Version
84
75
```
@@ -88,8 +79,18 @@ class LightClientUpdate(Container):
88
79
``` python
89
80
@dataclass
90
81
class LightClientStore (object ):
91
- snapshot: LightClientSnapshot
92
- valid_updates: Set[LightClientUpdate]
82
+ # Beacon block header that is finalized
83
+ finalized_header: BeaconBlockHeader
84
+ # Sync committees corresponding to the header
85
+ current_sync_committee: SyncCommittee
86
+ next_sync_committee: SyncCommittee
87
+ # Best available header to switch finalized head to if we see nothing else
88
+ best_valid_update: Optional[LightClientUpdate]
89
+ # Most recent available reasonably-safe header
90
+ optimistic_header: BeaconBlockHeader
91
+ # Max number of active participants in a sync committee (used to calculate safety threshold)
92
+ previous_max_active_participants: uint64
93
+ current_max_active_participants: uint64
93
94
```
94
95
95
96
## Helper functions
@@ -101,95 +102,157 @@ def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64:
101
102
return uint64(generalized_index % 2 ** (floorlog2(generalized_index)))
102
103
```
103
104
105
+ ### ` get_active_header `
106
+
107
+ ``` python
108
+ def get_active_header (update : LightClientUpdate) -> BeaconBlockHeader:
109
+ # The "active header" is the header that the update is trying to convince us
110
+ # to accept. If a finalized header is present, it's the finalized header,
111
+ # otherwise it's the attested header
112
+ if update.finalized_header != BeaconBlockHeader():
113
+ return update.finalized_header
114
+ else :
115
+ return update.attested_header
116
+ ```
117
+
118
+ ### ` get_safety_threshold `
119
+
120
+ ``` python
121
+ def get_safety_threshold (store : LightClientStore) -> uint64:
122
+ return max (
123
+ store.previous_max_active_participants,
124
+ store.current_max_active_participants,
125
+ ) // 2
126
+ ```
127
+
104
128
## Light client state updates
105
129
106
- 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.
130
+ 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.
131
+
132
+ #### ` process_slot_for_light_client_store `
133
+
134
+ ``` python
135
+ def process_slot_for_light_client_store (store : LightClientStore, current_slot : Slot) -> None :
136
+ if current_slot % UPDATE_TIMEOUT == 0 :
137
+ store.previous_max_active_participants = store.current_max_active_participants
138
+ store.current_max_active_participants = 0
139
+ if (
140
+ current_slot > store.finalized_header.slot + UPDATE_TIMEOUT
141
+ and store.best_valid_update is not None
142
+ ):
143
+ # Forced best update when the update timeout has elapsed
144
+ apply_light_client_update(store, store.best_valid_update)
145
+ store.best_valid_update = None
146
+ ```
107
147
108
148
#### ` validate_light_client_update `
109
149
110
150
``` python
111
- def validate_light_client_update (snapshot : LightClientSnapshot ,
151
+ def validate_light_client_update (store : LightClientStore ,
112
152
update : LightClientUpdate,
153
+ current_slot : Slot,
113
154
genesis_validators_root : Root) -> None :
114
- # Verify update slot is larger than snapshot slot
115
- assert update.header.slot > snapshot.header.slot
155
+ # Verify update slot is larger than slot of current best finalized header
156
+ active_header = get_active_header(update)
157
+ assert current_slot >= active_header.slot > store.finalized_header.slot
116
158
117
159
# Verify update does not skip a sync committee period
118
- snapshot_period = compute_epoch_at_slot(snapshot.header .slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
119
- update_period = compute_epoch_at_slot(update.header .slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
120
- assert update_period in (snapshot_period, snapshot_period + 1 )
160
+ finalized_period = compute_epoch_at_slot(store.finalized_header .slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
161
+ update_period = compute_epoch_at_slot(active_header .slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
162
+ assert update_period in (finalized_period, finalized_period + 1 )
121
163
122
- # Verify update header root is the finalized root of the finality header, if specified
123
- if update.finality_header == BeaconBlockHeader():
124
- signed_header = update.header
164
+ # Verify that the `finalized_header`, if present, actually is the finalized header saved in the
165
+ # state of the `attested header`
166
+ if update.finalized_header == BeaconBlockHeader():
125
167
assert update.finality_branch == [Bytes32() for _ in range (floorlog2(FINALIZED_ROOT_INDEX ))]
126
168
else :
127
- signed_header = update.finality_header
128
169
assert is_valid_merkle_branch(
129
- leaf = hash_tree_root(update.header ),
170
+ leaf = hash_tree_root(update.finalized_header ),
130
171
branch = update.finality_branch,
131
172
depth = floorlog2(FINALIZED_ROOT_INDEX ),
132
173
index = get_subtree_index(FINALIZED_ROOT_INDEX ),
133
- root = update.finality_header .state_root,
174
+ root = update.attested_header .state_root,
134
175
)
135
176
136
177
# Verify update next sync committee if the update period incremented
137
- if update_period == snapshot_period :
138
- sync_committee = snapshot .current_sync_committee
178
+ if update_period == finalized_period :
179
+ sync_committee = store .current_sync_committee
139
180
assert update.next_sync_committee_branch == [Bytes32() for _ in range (floorlog2(NEXT_SYNC_COMMITTEE_INDEX ))]
140
181
else :
141
- sync_committee = snapshot .next_sync_committee
182
+ sync_committee = store .next_sync_committee
142
183
assert is_valid_merkle_branch(
143
184
leaf = hash_tree_root(update.next_sync_committee),
144
185
branch = update.next_sync_committee_branch,
145
186
depth = floorlog2(NEXT_SYNC_COMMITTEE_INDEX ),
146
187
index = get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX ),
147
- root = update.header .state_root,
188
+ root = active_header .state_root,
148
189
)
190
+
191
+ sync_aggregate = update.sync_committee_aggregate
149
192
150
193
# Verify sync committee has sufficient participants
151
- assert sum (update .sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
194
+ assert sum (sync_aggregate .sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS
152
195
153
196
# Verify sync committee aggregate signature
154
- participant_pubkeys = [pubkey for (bit, pubkey) in zip (update.sync_committee_bits, sync_committee.pubkeys) if bit]
197
+ participant_pubkeys = [
198
+ pubkey for (bit, pubkey) in zip (sync_aggregate.sync_committee_bits, sync_committee.pubkeys)
199
+ if bit
200
+ ]
155
201
domain = compute_domain(DOMAIN_SYNC_COMMITTEE , update.fork_version, genesis_validators_root)
156
- signing_root = compute_signing_root(signed_header , domain)
157
- assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update .sync_committee_signature)
202
+ signing_root = compute_signing_root(update.attested_header , domain)
203
+ assert bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate .sync_committee_signature)
158
204
```
159
205
160
206
#### ` apply_light_client_update `
161
207
162
208
``` python
163
- def apply_light_client_update (snapshot : LightClientSnapshot, update : LightClientUpdate) -> None :
164
- snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
165
- update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
166
- if update_period == snapshot_period + 1 :
167
- snapshot.current_sync_committee = snapshot.next_sync_committee
168
- snapshot.next_sync_committee = update.next_sync_committee
169
- snapshot.header = update.header
209
+ def apply_light_client_update (store : LightClientStore, update : LightClientUpdate) -> None :
210
+ active_header = get_active_header(update)
211
+ finalized_period = compute_epoch_at_slot(store.finalized_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
212
+ update_period = compute_epoch_at_slot(active_header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD
213
+ if update_period == finalized_period + 1 :
214
+ store.current_sync_committee = store.next_sync_committee
215
+ store.next_sync_committee = update.next_sync_committee
216
+ store.finalized_header = active_header
170
217
```
171
218
172
219
#### ` process_light_client_update `
173
220
174
221
``` python
175
- def process_light_client_update (store : LightClientStore, update : LightClientUpdate, current_slot : Slot,
222
+ def process_light_client_update (store : LightClientStore,
223
+ update : LightClientUpdate,
224
+ current_slot : Slot,
176
225
genesis_validators_root : Root) -> None :
177
- validate_light_client_update(store.snapshot, update, genesis_validators_root)
178
- store.valid_updates.add(update)
226
+ validate_light_client_update(store, update, current_slot, genesis_validators_root)
179
227
180
- update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD
228
+ sync_committee_bits = update.sync_committee_aggregate.sync_committee_bits
229
+
230
+ # Update the best update in case we have to force-update to it if the timeout elapses
181
231
if (
182
- sum (update.sync_committee_bits) * 3 >= len (update.sync_committee_bits) * 2
183
- and update.finality_header != BeaconBlockHeader( )
232
+ store.best_valid_update is None
233
+ or sum (sync_committee_bits) > sum (store.best_valid_update.sync_committee_aggregate.sync_committee_bits )
184
234
):
185
- # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof.
186
- # Note that (2) means that the current light client design needs finality.
187
- # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182.
188
- apply_light_client_update(store.snapshot, update)
189
- store.valid_updates = set ()
190
- elif current_slot > store.snapshot.header.slot + update_timeout:
191
- # Forced best update when the update timeout has elapsed
192
- apply_light_client_update(store.snapshot,
193
- max (store.valid_updates, key = lambda update : sum (update.sync_committee_bits)))
194
- store.valid_updates = set ()
235
+ store.best_valid_update = update
236
+
237
+ # Track the maximum number of active participants in the committee signatures
238
+ store.current_max_active_participants = max (
239
+ store.current_max_active_participants,
240
+ sum (sync_committee_bits),
241
+ )
242
+
243
+ # Update the optimistic header
244
+ if (
245
+ sum (sync_committee_bits) > get_safety_threshold(store)
246
+ and update.attested_header.slot > store.optimistic_header.slot
247
+ ):
248
+ store.optimistic_header = update.attested_header
249
+
250
+ # Update finalized header
251
+ if (
252
+ sum (sync_committee_bits) * 3 >= len (sync_committee_bits) * 2
253
+ and update.finalized_header != BeaconBlockHeader()
254
+ ):
255
+ # Normal update through 2/3 threshold
256
+ apply_light_client_update(store, update)
257
+ store.best_valid_update = None
195
258
```
0 commit comments