Skip to content

Commit 14ce12f

Browse files
committed
Refactored Duties functions out of the ValidatorSet
1 parent d9e8d21 commit 14ce12f

File tree

3 files changed

+159
-115
lines changed

3 files changed

+159
-115
lines changed

lib/lambda_ethereum_consensus/validator/duties.ex

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,50 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
2727
@type attester_duties :: %{Types.slot() => [attester_duty()]}
2828
@type proposer_duties :: %{Types.slot() => [proposer_duty()]}
2929

30+
@type kind :: :proposers | :attesters
3031
@type duties :: %{attesters: attester_duties(), proposers: proposer_duties()}
3132

33+
############################
34+
# Accessors
35+
36+
@spec current_proposer(duties(), Types.epoch(), Types.slot()) :: proposer_duty() | nil
37+
def current_proposer(duties, epoch, slot),
38+
do: get_in(duties, [epoch, :proposers, slot])
39+
40+
@spec current_attesters(duties(), Types.epoch(), Types.slot()) :: [attester_duty()]
41+
def current_attesters(duties, epoch, slot) do
42+
for %{attested?: false} = duty <- attesters(duties, epoch, slot) do
43+
duty
44+
end
45+
end
46+
47+
@spec current_aggregators(duties(), Types.epoch(), Types.slot()) :: [attester_duty()]
48+
def current_aggregators(duties, epoch, slot) do
49+
for %{should_aggregate?: true} = duty <- attesters(duties, epoch, slot) do
50+
duty
51+
end
52+
end
53+
54+
defp attesters(duties, epoch, slot), do: get_in(duties, [epoch, :attesters, slot]) || []
55+
56+
############################
57+
# Update functions
58+
59+
@spec update_duties!(duties(), kind(), Types.epoch(), Types.slot(), [
60+
attester_duty() | proposer_duties()
61+
]) :: duties()
62+
def update_duties!(duties, kind, epoch, slot, updated),
63+
do: put_in(duties, [epoch, kind, slot], updated)
64+
65+
@spec attested(attester_duty()) :: attester_duty()
66+
def attested(duty), do: Map.put(duty, :attested?, true)
67+
68+
@spec aggregated(attester_duty()) :: attester_duty()
69+
def aggregated(duty), do: Map.put(duty, :should_aggregate?, false)
70+
71+
############################
72+
# Main functions
73+
3274
@spec compute_proposers_for_epoch(BeaconState.t(), Types.epoch(), ValidatorSet.validators()) ::
3375
proposer_duties()
3476
def compute_proposers_for_epoch(%BeaconState{} = state, epoch, validators) do
@@ -64,18 +106,16 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
64106
case Accessors.get_beacon_committee(state, slot, committee_index) do
65107
{:ok, committee} ->
66108
for {validator_index, index_in_committee} <- Enum.with_index(committee),
67-
validator = Map.get(validators, validator_index),
68-
duty =
69-
%{
70-
validator_index: validator_index,
71-
index_in_committee: index_in_committee,
72-
committee_length: length(committee),
73-
committee_index: committee_index,
74-
attested?: false
75-
}
76-
|> update_with_aggregation_duty(state, slot, validator.keystore.privkey)
77-
|> update_with_subnet_id(state, epoch, slot) do
78-
duty
109+
validator = Map.get(validators, validator_index) do
110+
%{
111+
validator_index: validator_index,
112+
index_in_committee: index_in_committee,
113+
committee_length: length(committee),
114+
committee_index: committee_index,
115+
attested?: false
116+
}
117+
|> update_with_aggregation_duty(state, slot, validator.keystore.privkey)
118+
|> update_with_subnet_id(state, epoch, slot)
79119
end
80120

81121
{:error, _} ->

lib/lambda_ethereum_consensus/validator/validator.ex

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,24 @@ defmodule LambdaEthereumConsensus.Validator do
4141
epoch = Misc.compute_epoch_at_slot(head_slot)
4242
beacon = fetch_target_state_and_go_to_slot(epoch, head_slot, head_root)
4343

44-
new(keystore, beacon)
44+
state = %__MODULE__{
45+
index: nil,
46+
keystore: keystore,
47+
payload_builder: nil
48+
}
49+
50+
case fetch_validator_index(beacon, state.keystore.pubkey) do
51+
nil ->
52+
Logger.warning(
53+
"[Validator] Public key #{state.keystore.pubkey} not found in the validator set"
54+
)
55+
56+
state
57+
58+
validator_index ->
59+
log_debug(validator_index, "Setup completed")
60+
%{state | index: validator_index}
61+
end
4562
end
4663

4764
@spec new(Keystore.t(), Types.BeaconState.t()) :: t()
@@ -115,9 +132,9 @@ defmodule LambdaEthereumConsensus.Validator do
115132
end
116133
end
117134

118-
@spec publish_aggregate(Duties.attester_duty(), Types.slot(), non_neg_integer(), Keystore.t()) ::
135+
@spec publish_aggregate(t(), Duties.attester_duty(), Types.slot()) ::
119136
:ok
120-
def publish_aggregate(duty, slot, validator_index, keystore) do
137+
def publish_aggregate(%{index: validator_index, keystore: keystore}, duty, slot) do
121138
case Gossip.Attestation.stop_collecting(duty.subnet_id) do
122139
{:ok, attestations} ->
123140
log_md = [slot: slot, attestations: attestations]

lib/lambda_ethereum_consensus/validator/validator_set.ex

Lines changed: 87 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,43 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
3838
setup_validators(slot, head_root, keystore_dir, keystore_pass_dir)
3939
end
4040

41+
defp setup_validators(_s, _r, keystore_dir, keystore_pass_dir)
42+
when is_nil(keystore_dir) or is_nil(keystore_pass_dir) do
43+
Logger.warning(
44+
"[Validator] No keystore_dir or keystore_pass_dir provided. Validators won't start."
45+
)
46+
47+
%__MODULE__{}
48+
end
49+
50+
defp setup_validators(slot, head_root, keystore_dir, keystore_pass_dir) do
51+
validator_keystores = decode_validator_keystores(keystore_dir, keystore_pass_dir)
52+
epoch = Misc.compute_epoch_at_slot(slot)
53+
54+
validators =
55+
Map.new(validator_keystores, fn keystore ->
56+
validator = Validator.new(keystore, slot, head_root)
57+
{validator.index, validator}
58+
end)
59+
60+
Logger.info("[Validator] Initialized #{Enum.count(validators)} validators")
61+
62+
%__MODULE__{validators: validators}
63+
|> update_state(epoch, slot, head_root)
64+
end
65+
4166
@doc """
4267
Notify all validators of a new head.
4368
"""
4469
@spec notify_head(t(), Types.slot(), Types.root()) :: t()
4570
def notify_head(set, slot, head_root) do
4671
# TODO: Just for testing purposes, remove it later
47-
Logger.info("[Validator] Notifying all Validators with new_head", root: head_root, slot: slot)
72+
Logger.info("[ValidatorSet] New Head", root: head_root, slot: slot)
4873
epoch = Misc.compute_epoch_at_slot(slot)
4974

5075
set
5176
|> update_state(epoch, slot, head_root)
52-
|> attest(epoch, slot, head_root)
77+
|> attests(epoch, slot, head_root)
5378
|> build_next_payload(epoch, slot, head_root)
5479
end
5580

@@ -59,59 +84,26 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
5984
@spec notify_tick(t(), tuple()) :: t()
6085
def notify_tick(%{head_root: head_root} = set, {slot, third} = slot_data) do
6186
# TODO: Just for testing purposes, remove it later
62-
Logger.info("[Validator] Notifying all Validators with notify_tick: #{inspect(third)}",
63-
root: head_root,
64-
slot: slot
65-
)
66-
87+
Logger.info("[ValidatorSet] Tick #{inspect(third)}", root: head_root, slot: slot)
6788
epoch = Misc.compute_epoch_at_slot(slot)
6889

6990
set
7091
|> update_state(epoch, slot, head_root)
7192
|> process_tick(epoch, slot_data)
7293
end
7394

74-
defp process_tick(%{head_root: head_root} = set, epoch, {slot, :first_third}),
75-
do: propose(set, epoch, slot, head_root)
95+
defp process_tick(%{head_root: head_root} = set, epoch, {slot, :first_third}) do
96+
propose(set, epoch, slot, head_root)
97+
end
7698

7799
defp process_tick(%{head_root: head_root} = set, epoch, {slot, :second_third}) do
78100
set
79-
|> attest(epoch, slot, head_root)
101+
|> attests(epoch, slot, head_root)
80102
|> build_next_payload(epoch, slot, head_root)
81103
end
82104

83-
defp process_tick(set, epoch, {slot, :last_third}),
84-
do: publish_aggregate(set, epoch, slot)
85-
86-
##############################
87-
# Setup
88-
89-
defp setup_validators(_s, _r, keystore_dir, keystore_pass_dir)
90-
when is_nil(keystore_dir) or is_nil(keystore_pass_dir) do
91-
Logger.warning(
92-
"[Validator] No keystore_dir or keystore_pass_dir provided. Validators won't start."
93-
)
94-
95-
%__MODULE__{}
96-
end
97-
98-
defp setup_validators(slot, head_root, keystore_dir, keystore_pass_dir) do
99-
validator_keystores = decode_validator_keystores(keystore_dir, keystore_pass_dir)
100-
epoch = Misc.compute_epoch_at_slot(slot)
101-
102-
# This will be removed later when refactoring Validator new
103-
beacon = Validator.fetch_target_state_and_go_to_slot(epoch, slot, head_root)
104-
105-
validators =
106-
Map.new(validator_keystores, fn keystore ->
107-
validator = Validator.new(keystore, beacon)
108-
{validator.index, validator}
109-
end)
110-
111-
Logger.info("[Validator] Initialized #{Enum.count(validators)} validators")
112-
113-
%__MODULE__{validators: validators}
114-
|> update_state(epoch, slot, head_root)
105+
defp process_tick(set, epoch, {slot, :last_third}) do
106+
publish_aggregates(set, epoch, slot)
115107
end
116108

117109
##############################
@@ -161,91 +153,86 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
161153
end
162154

163155
##############################
164-
# Attestation and proposal
165-
166-
defp attest(set, epoch, slot, head_root) do
167-
case current_attesters(set, epoch, slot) do
168-
[] ->
169-
set
170-
171-
attesters ->
172-
Enum.map(attesters, fn {validator, duty} ->
173-
Validator.attest(validator, duty, slot, head_root)
174-
175-
# Duty.attested(duty)
176-
%{duty | attested?: true}
177-
end)
178-
|> then(&%{set | duties: put_in(set.duties, [epoch, :attesters, slot], &1)})
179-
end
180-
end
181-
182-
defp publish_aggregate(set, epoch, slot) do
183-
case current_aggregators(set, epoch, slot) do
184-
[] ->
185-
set
186-
187-
aggregators ->
188-
Enum.map(aggregators, fn {validator, duty} ->
189-
Validator.publish_aggregate(duty, slot, validator.index, validator.keystore)
190-
191-
# Duty.aggregated(duty)
192-
%{duty | should_aggregate?: false}
193-
end)
194-
|> then(&%{set | duties: put_in(set.duties, [epoch, :attesters, slot], &1)})
195-
end
196-
end
156+
# Block proposal
197157

198158
defp build_next_payload(%{validators: validators} = set, epoch, slot, head_root) do
199-
set
200-
|> proposer(epoch, slot + 1)
201-
|> case do
159+
# FIXME: At a boundary slot epoch here is incorrect, we need to alway have the next epoch calculated
160+
case Duties.current_proposer(set.duties, epoch, slot + 1) do
202161
nil ->
203162
set
204163

205164
validator_index ->
206165
validators
207166
|> Map.update!(validator_index, &Validator.start_payload_builder(&1, slot + 1, head_root))
208-
|> then(&%{set | validators: &1})
167+
|> update_validators(set)
209168
end
210169
end
211170

212171
defp propose(%{validators: validators} = set, epoch, slot, head_root) do
213-
set
214-
|> proposer(epoch, slot)
215-
|> case do
172+
case Duties.current_proposer(set.duties, epoch, slot) do
216173
nil ->
217174
set
218175

219176
validator_index ->
220177
validators
221178
|> Map.update!(validator_index, &Validator.propose(&1, slot, head_root))
222-
|> then(&%{set | validators: &1})
179+
|> update_validators(set)
223180
end
224181
end
225182

183+
defp update_validators(new_validators, set), do: %{set | validators: new_validators}
184+
226185
##############################
227-
# Helpers
186+
# Attestation
228187

229-
defp current_attesters(set, epoch, slot) do
230-
set
231-
|> attesters(epoch, slot)
232-
|> Enum.flat_map(fn
233-
%{attested?: false} = duty -> [{Map.get(set.validators, duty.validator_index), duty}]
234-
_ -> []
235-
end)
188+
defp attests(set, epoch, slot, head_root) do
189+
case Duties.current_attesters(set.duties, epoch, slot) do
190+
[] ->
191+
set
192+
193+
attester_duties ->
194+
attester_duties
195+
|> Enum.map(&attest(&1, slot, head_root, set.validators))
196+
|> update_duties(set, epoch, :attesters, slot)
197+
end
236198
end
237199

238-
defp current_aggregators(set, epoch, slot) do
239-
set
240-
|> attesters(epoch, slot)
241-
|> Enum.flat_map(fn
242-
%{should_aggregate?: true} = duty -> [{Map.get(set.validators, duty.validator_index), duty}]
243-
_ -> []
244-
end)
200+
defp publish_aggregates(set, epoch, slot) do
201+
case Duties.current_aggregators(set.duties, epoch, slot) do
202+
[] ->
203+
set
204+
205+
aggregator_duties ->
206+
aggregator_duties
207+
|> Enum.map(&publish_aggregate(&1, slot, set.validators))
208+
|> update_duties(set, epoch, :attesters, slot)
209+
end
210+
end
211+
212+
defp attest(duty, slot, head_root, validators) do
213+
validators
214+
|> Map.get(duty.validator_index)
215+
|> Validator.attest(duty, slot, head_root)
216+
217+
Duties.attested(duty)
245218
end
246219

247-
defp proposer(set, epoch, slot), do: get_in(set.duties, [epoch, :proposers, slot])
248-
defp attesters(set, epoch, slot), do: get_in(set.duties, [epoch, :attesters, slot]) || []
220+
defp publish_aggregate(duty, slot, validators) do
221+
validators
222+
|> Map.get(duty.validator_index)
223+
|> Validator.publish_aggregate(duty, slot)
224+
225+
Duties.aggregated(duty)
226+
end
227+
228+
defp update_duties(new_duties, set, epoch, kind, slot) do
229+
set.duties
230+
|> Duties.update_duties!(kind, epoch, slot, new_duties)
231+
|> then(&%{set | duties: &1})
232+
end
233+
234+
##############################
235+
# Key management
249236

250237
@doc """
251238
Get validator keystores from the keystore directory.

0 commit comments

Comments
 (0)