@@ -35,7 +35,9 @@ defmodule LambdaEthereumConsensus.Validator do
35
35
slot: slot ,
36
36
root: head_root ,
37
37
duties: % {
38
- attester: { :not_computed , :not_computed }
38
+ # Order is: previous epoch, current epoch, next epoch
39
+ attester: [ :not_computed , :not_computed , :not_computed ] ,
40
+ proposer: :not_computed
39
41
} ,
40
42
validator: validator
41
43
}
@@ -90,23 +92,30 @@ defmodule LambdaEthereumConsensus.Validator do
90
92
# TODO: this doesn't take into account reorgs or empty slots
91
93
new_state = update_state ( state , slot , head_root )
92
94
93
- if should_attest? ( state , slot ) , do: attest ( state )
94
- maybe_publish_aggregate ( state , slot )
95
+ case get_current_attester_duty ( new_state , slot ) do
96
+ nil -> :ok
97
+ duty -> attest ( new_state , duty )
98
+ end
99
+
100
+ new_state = maybe_publish_aggregate ( new_state , slot )
101
+
102
+ if should_propose? ( new_state , slot ) , do: propose ( new_state )
95
103
96
104
{ :noreply , new_state }
97
105
end
98
106
99
107
defp update_state ( % { slot: last_slot , root: last_root } = state , slot , head_root ) do
100
- last_epoch = Misc . compute_epoch_at_slot ( last_slot )
101
- epoch = Misc . compute_epoch_at_slot ( slot )
108
+ last_epoch = Misc . compute_epoch_at_slot ( last_slot + 1 )
109
+ epoch = Misc . compute_epoch_at_slot ( slot + 1 )
102
110
103
111
if last_epoch == epoch do
104
112
state
105
113
else
106
- target_root =
107
- if slot == Misc . compute_start_slot_at_epoch ( epoch ) , do: head_root , else: last_root
114
+ start_slot = Misc . compute_start_slot_at_epoch ( epoch )
115
+ target_root = if slot == start_slot , do: head_root , else: last_root
108
116
109
- new_beacon = fetch_target_state ( epoch , target_root )
117
+ # Process the start of the new epoch
118
+ new_beacon = fetch_target_state ( epoch , target_root ) |> process_slots ( start_slot )
110
119
111
120
new_duties =
112
121
shift_duties ( state . duties , epoch , last_epoch )
@@ -124,48 +133,48 @@ defmodule LambdaEthereumConsensus.Validator do
124
133
state
125
134
end
126
135
127
- defp shift_duties ( % { attester: { _ , ep1 } } = duties , epoch , current_epoch )
128
- when epoch + 1 == current_epoch do
129
- % { duties | attester: { ep1 , :not_computed } }
136
+ defp shift_duties ( % { attester: [ _ep0 , ep1 , ep2 ] } = duties , epoch , current_epoch ) do
137
+ case current_epoch - epoch do
138
+ 1 -> % { duties | attester: [ ep1 , ep2 , :not_computed ] }
139
+ 2 -> % { duties | attester: [ ep2 , :not_computed , :not_computed ] }
140
+ _ -> % { duties | attester: [ :not_computed , :not_computed , :not_computed ] }
141
+ end
130
142
end
131
143
132
- defp shift_duties ( duties , _ , _ ) , do: % { duties | attester: { :not_computed , :not_computed } }
133
-
134
144
defp maybe_update_duties ( duties , beacon_state , epoch , validator ) do
135
145
attester_duties =
136
146
maybe_update_attester_duties ( duties . attester , beacon_state , epoch , validator )
137
147
138
- % { duties | attester: attester_duties }
139
- end
148
+ proposer_duties = compute_proposer_duties ( beacon_state , epoch , validator . index )
140
149
141
- defp maybe_update_attester_duties ( { :not_computed , _ } = duties , beacon_state , epoch , validator ) do
142
- compute_attester_duty ( duties , 0 , beacon_state , epoch , validator )
143
- |> maybe_update_attester_duties ( beacon_state , epoch , validator )
150
+ % { duties | attester: attester_duties , proposer: proposer_duties }
144
151
end
145
152
146
- defp maybe_update_attester_duties ( { _ , :not_computed } = duties , beacon_state , epoch , validator ) do
147
- compute_attester_duty ( duties , 1 , beacon_state , epoch , validator )
148
- |> maybe_update_attester_duties ( beacon_state , epoch , validator )
149
- end
153
+ defp maybe_update_attester_duties ( [ epp , ep0 , ep1 ] , beacon_state , epoch , validator ) do
154
+ duties =
155
+ Stream . with_index ( [ ep0 , ep1 ] )
156
+ |> Enum . map ( fn
157
+ { :not_computed , i } -> compute_attester_duty ( beacon_state , epoch + i , validator )
158
+ { d , _ } -> d
159
+ end )
150
160
151
- defp maybe_update_attester_duties ( duties , _ , _ , _ ) , do: duties
161
+ [ epp | duties ]
162
+ end
152
163
153
- defp compute_attester_duty ( duties , index , beacon_state , base_epoch , validator )
154
- when index in 0 .. 1 do
155
- epoch = base_epoch + index
164
+ defp compute_attester_duty ( beacon_state , epoch , validator ) do
156
165
# Can't fail
157
166
{ :ok , duty } = Utils . get_committee_assignment ( beacon_state , epoch , validator . index )
158
167
159
- duty =
160
- update_with_aggregation_duty ( duty , beacon_state , validator . privkey )
161
- |> update_with_subnet_id ( beacon_state , epoch )
162
-
163
- put_elem ( duties , index , duty )
168
+ update_with_aggregation_duty ( duty , beacon_state , validator . privkey )
169
+ |> update_with_subnet_id ( beacon_state , epoch )
164
170
end
165
171
166
- defp move_subnets ( % { attester: { old_ep0 , old_ep1 } } , % { attester: { ep0 , ep1 } } ) do
167
- old_subnets = MapSet . new ( [ old_ep0 . subnet_id , old_ep1 . subnet_id ] )
168
- new_subnets = MapSet . new ( [ ep0 . subnet_id , ep1 . subnet_id ] )
172
+ defp get_subnet_ids ( duties ) ,
173
+ do: duties |> Stream . reject ( & ( & 1 == :not_computed ) ) |> Enum . map ( & & 1 . subnet_id )
174
+
175
+ defp move_subnets ( % { attester: old_duties } , % { attester: new_duties } ) do
176
+ old_subnets = old_duties |> get_subnet_ids ( ) |> MapSet . new ( )
177
+ new_subnets = new_duties |> get_subnet_ids ( ) |> MapSet . new ( )
169
178
170
179
# leave old subnets (except for recurring ones)
171
180
MapSet . difference ( old_subnets , new_subnets ) |> leave ( )
@@ -174,8 +183,8 @@ defmodule LambdaEthereumConsensus.Validator do
174
183
MapSet . difference ( new_subnets , old_subnets ) |> join ( )
175
184
end
176
185
177
- defp join_subnets_for_duties ( % { attester: { ep0 , ep1 } } ) do
178
- join ( [ ep0 . subnet_id , ep1 . subnet_id ] )
186
+ defp join_subnets_for_duties ( % { attester: duties } ) do
187
+ duties |> get_subnet_ids ( ) |> join ( )
179
188
end
180
189
181
190
defp join ( subnets ) do
@@ -192,54 +201,80 @@ defmodule LambdaEthereumConsensus.Validator do
192
201
end
193
202
end
194
203
195
- defp log_duties ( % { attester: attester_duties } , validator_index ) do
196
- { % { index_in_committee: i0 , committee_index: ci0 , slot: slot0 } ,
197
- % { index_in_committee: i1 , committee_index: ci1 , slot: slot1 } } = attester_duties
198
-
199
- Logger . info (
200
- "Validator #{ validator_index } has to attest in committee #{ ci0 } of slot #{ slot0 } with index #{ i0 } ," <>
201
- " and in committee #{ ci1 } of slot #{ slot1 } with index #{ i1 } "
202
- )
204
+ defp log_duties ( % { attester: attester_duties , proposer: proposer_duties } , validator_index ) do
205
+ attester_duties
206
+ # Drop the first element, which is the previous epoch's duty
207
+ |> Stream . drop ( 1 )
208
+ |> Enum . each ( fn % { index_in_committee: i , committee_index: ci , slot: slot } ->
209
+ Logger . info (
210
+ "[Validator] #{ validator_index } has to attest in committee #{ ci } of slot #{ slot } with index #{ i } "
211
+ )
212
+ end )
213
+
214
+ Enum . each ( proposer_duties , fn slot ->
215
+ Logger . info ( "[Validator] #{ validator_index } has to propose a block in slot #{ slot } !" )
216
+ end )
203
217
end
204
218
205
- defp should_attest? ( % { duties: % { attester: { % { slot: duty_slot } , _ } } } , slot ) ,
206
- do: duty_slot == slot
219
+ defp get_current_attester_duty ( state , current_slot ) do
220
+ find_attester_duty ( state , & ( & 1 . slot == current_slot ) )
221
+ end
207
222
208
- defp attest ( state ) do
209
- { current_duty , _ } = state . duties . attester
223
+ defp find_attester_duty ( % { duties: % { attester: attester_duties } } , search_fn ) do
224
+ Enum . find ( attester_duties , fn
225
+ :not_computed -> false
226
+ duty -> search_fn . ( duty )
227
+ end )
228
+ end
210
229
230
+ defp attest ( state , current_duty ) do
211
231
{ subnet_id , attestation } =
212
232
produce_attestation ( current_duty , state . root , state . validator . privkey )
213
233
214
234
Logger . info ( "[Validator] Attesting in slot #{ attestation . data . slot } on subnet #{ subnet_id } " )
215
235
Gossip.Attestation . publish ( subnet_id , attestation )
216
236
217
- if current_duty . is_aggregator do
237
+ if current_duty . should_aggregate? do
218
238
Logger . info ( "[Validator] Collecting messages for future aggregation..." )
219
239
Gossip.Attestation . collect ( subnet_id , attestation )
220
240
end
221
241
end
222
242
223
243
# We publish our aggregate on the next slot, and when we're an aggregator
224
244
# TODO: we should publish it two-thirds of the way through the slot
225
- def maybe_publish_aggregate ( % { duties: % { attester: { duty , _ } } , validator: validator } , slot )
226
- when duty . slot == slot + 1 and duty . is_aggregator do
245
+ defp maybe_publish_aggregate ( % { duties: duties , validator: validator } = state , slot ) do
246
+ case find_attester_duty ( state , & ( & 1 . slot < slot and & 1 . should_aggregate? ) ) do
247
+ nil ->
248
+ state
249
+
250
+ duty ->
251
+ publish_aggregate ( duty , validator )
252
+
253
+ attester_duties =
254
+ Enum . map ( duties . attester , fn
255
+ ^ duty -> % { duty | should_aggregate?: false }
256
+ d -> d
257
+ end )
258
+
259
+ % { state | duties: % { duties | attester: attester_duties } }
260
+ end
261
+ end
262
+
263
+ defp publish_aggregate ( duty , validator ) do
227
264
case Gossip.Attestation . stop_collecting ( duty . subnet_id ) do
228
265
{ :ok , attestations } ->
229
- Logger . info ( "[Validator] Publishing aggregate of slot #{ slot } " )
266
+ Logger . info ( "[Validator] Publishing aggregate of slot #{ duty . slot } " )
230
267
231
268
aggregate_attestations ( attestations )
232
269
|> append_proof ( duty . selection_proof , validator )
233
270
|> append_signature ( duty . signing_domain , validator )
234
271
|> Gossip.Attestation . publish_aggregate ( )
235
272
236
273
_ ->
237
- Logger . error ( "[Validator] Failed to publish aggregate" )
274
+ :ok
238
275
end
239
276
end
240
277
241
- def maybe_publish_aggregate ( _ , _ ) , do: :ok
242
-
243
278
defp aggregate_attestations ( attestations ) do
244
279
aggregation_bits =
245
280
attestations
@@ -325,11 +360,11 @@ defmodule LambdaEthereumConsensus.Validator do
325
360
epoch = Misc . compute_epoch_at_slot ( duty . slot )
326
361
domain = Accessors . get_domain ( beacon_state , Constants . domain_aggregate_and_proof ( ) , epoch )
327
362
328
- Map . put ( duty , :is_aggregator , true )
363
+ Map . put ( duty , :should_aggregate? , true )
329
364
|> Map . put ( :selection_proof , proof )
330
365
|> Map . put ( :signing_domain , domain )
331
366
else
332
- Map . put ( duty , :is_aggregator , false )
367
+ Map . put ( duty , :should_aggregate? , false )
333
368
end
334
369
end
335
370
@@ -345,4 +380,22 @@ defmodule LambdaEthereumConsensus.Validator do
345
380
defp fetch_validator_index ( beacon , % { index: nil , pubkey: pk } ) do
346
381
beacon . validators |> Enum . find_index ( & ( & 1 . pubkey == pk ) )
347
382
end
383
+
384
+ defp compute_proposer_duties ( beacon_state , epoch , validator_index ) do
385
+ start_slot = Misc . compute_start_slot_at_epoch ( epoch )
386
+
387
+ start_slot .. ( start_slot + ChainSpec . get ( "SLOTS_PER_EPOCH" ) - 1 )
388
+ |> Enum . flat_map ( fn slot ->
389
+ # Can't fail
390
+ { :ok , proposer_index } = Accessors . get_beacon_proposer_index ( beacon_state , slot )
391
+ if proposer_index == validator_index , do: [ slot ] , else: [ ]
392
+ end )
393
+ end
394
+
395
+ # If we are the proposer for the next slot, we should propose a block now
396
+ defp should_propose? ( % { duties: % { proposer: slots } } , slot ) , do: Enum . member? ( slots , slot + 1 )
397
+
398
+ defp propose ( _state ) do
399
+ # TODO: implement block proposal
400
+ end
348
401
end
0 commit comments