Skip to content

Commit 7675b4b

Browse files
committed
Calculate next epoch duties ahead of time
1 parent 14ce12f commit 7675b4b

File tree

2 files changed

+42
-26
lines changed

2 files changed

+42
-26
lines changed

lib/lambda_ethereum_consensus/validator/duties.ex

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
2424

2525
@type proposer_duty :: Types.slot()
2626

27-
@type attester_duties :: %{Types.slot() => [attester_duty()]}
28-
@type proposer_duties :: %{Types.slot() => [proposer_duty()]}
27+
@type attester_duties :: [attester_duty()]
28+
@type proposer_duties :: [proposer_duty()]
29+
30+
@type attester_duties_per_slot :: %{Types.slot() => attester_duties()}
31+
@type proposer_duties_per_slot :: %{Types.slot() => proposer_duties()}
2932

3033
@type kind :: :proposers | :attesters
31-
@type duties :: %{attesters: attester_duties(), proposers: proposer_duties()}
34+
@type duties :: %{kind() => attester_duties_per_slot() | proposer_duties_per_slot()}
3235

3336
############################
3437
# Accessors
@@ -37,14 +40,14 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
3740
def current_proposer(duties, epoch, slot),
3841
do: get_in(duties, [epoch, :proposers, slot])
3942

40-
@spec current_attesters(duties(), Types.epoch(), Types.slot()) :: [attester_duty()]
43+
@spec current_attesters(duties(), Types.epoch(), Types.slot()) :: attester_duties()
4144
def current_attesters(duties, epoch, slot) do
4245
for %{attested?: false} = duty <- attesters(duties, epoch, slot) do
4346
duty
4447
end
4548
end
4649

47-
@spec current_aggregators(duties(), Types.epoch(), Types.slot()) :: [attester_duty()]
50+
@spec current_aggregators(duties(), Types.epoch(), Types.slot()) :: attester_duties()
4851
def current_aggregators(duties, epoch, slot) do
4952
for %{should_aggregate?: true} = duty <- attesters(duties, epoch, slot) do
5053
duty
@@ -56,9 +59,13 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
5659
############################
5760
# Update functions
5861

59-
@spec update_duties!(duties(), kind(), Types.epoch(), Types.slot(), [
60-
attester_duty() | proposer_duties()
61-
]) :: duties()
62+
@spec update_duties!(
63+
duties(),
64+
kind(),
65+
Types.epoch(),
66+
Types.slot(),
67+
attester_duties() | proposer_duties()
68+
) :: duties()
6269
def update_duties!(duties, kind, epoch, slot, updated),
6370
do: put_in(duties, [epoch, kind, slot], updated)
6471

@@ -72,7 +79,7 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
7279
# Main functions
7380

7481
@spec compute_proposers_for_epoch(BeaconState.t(), Types.epoch(), ValidatorSet.validators()) ::
75-
proposer_duties()
82+
proposer_duties_per_slot()
7683
def compute_proposers_for_epoch(%BeaconState{} = state, epoch, validators) do
7784
with {:ok, epoch} <- check_valid_epoch(state, epoch),
7885
{start_slot, end_slot} <- boundary_slots(epoch) do
@@ -86,7 +93,7 @@ defmodule LambdaEthereumConsensus.Validator.Duties do
8693
end
8794

8895
@spec compute_attesters_for_epoch(BeaconState.t(), Types.epoch(), ValidatorSet.validators()) ::
89-
attester_duties()
96+
attester_duties_per_slot()
9097
def compute_attesters_for_epoch(%BeaconState{} = state, epoch, validators) do
9198
with {:ok, epoch} <- check_valid_epoch(state, epoch),
9299
{start_slot, end_slot} <- boundary_slots(epoch) do

lib/lambda_ethereum_consensus/validator/validator_set.ex

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
2626
validators: validators()
2727
}
2828

29+
@doc "Check if the duties for the given epoch are already computed."
30+
defguard is_duties_computed(set, epoch)
31+
when is_map(set.duties) and not is_nil(:erlang.map_get(epoch, set.duties))
32+
2933
@doc """
3034
Initiate the set of validators, given the slot and head root.
3135
"""
@@ -75,7 +79,7 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
7579
set
7680
|> update_state(epoch, slot, head_root)
7781
|> attests(epoch, slot, head_root)
78-
|> build_next_payload(epoch, slot, head_root)
82+
|> build_payload(slot + 1, head_root)
7983
end
8084

8185
@doc """
@@ -99,7 +103,7 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
99103
defp process_tick(%{head_root: head_root} = set, epoch, {slot, :second_third}) do
100104
set
101105
|> attests(epoch, slot, head_root)
102-
|> build_next_payload(epoch, slot, head_root)
106+
|> build_payload(slot + 1, head_root)
103107
end
104108

105109
defp process_tick(set, epoch, {slot, :last_third}) do
@@ -119,19 +123,23 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
119123
defp update_head(set, head_root), do: %{set | head_root: head_root}
120124

121125
defp compute_duties(set, epoch, _slot, _head_root)
122-
when not is_nil(:erlang.map_get(epoch, set.duties)),
126+
when is_duties_computed(set, epoch) and is_duties_computed(set, epoch + 1),
123127
do: set
124128

125129
defp compute_duties(set, epoch, slot, head_root) do
126-
epoch
127-
|> Validator.fetch_target_state_and_go_to_slot(slot, head_root)
128-
|> compute_duties_for_epoch!(epoch, set.validators)
130+
epochs_to_calculate =
131+
[{epoch, slot}, {epoch + 1, Misc.compute_start_slot_at_epoch(epoch + 1)}]
132+
|> Enum.reject(&Map.has_key?(set.duties, elem(&1, 0)))
133+
134+
epochs_to_calculate
135+
|> Map.new(&compute_duties_for_epoch!(set, &1, head_root))
129136
|> merge_duties_and_prune(epoch, set)
130137
end
131138

132-
defp compute_duties_for_epoch!(beacon, epoch, validators) do
133-
proposers = Duties.compute_proposers_for_epoch(beacon, epoch, validators)
134-
attesters = Duties.compute_attesters_for_epoch(beacon, epoch, validators)
139+
defp compute_duties_for_epoch!(set, {epoch, slot}, head_root) do
140+
beacon = Validator.fetch_target_state_and_go_to_slot(epoch, slot, head_root)
141+
proposers = Duties.compute_proposers_for_epoch(beacon, epoch, set.validators)
142+
attesters = Duties.compute_attesters_for_epoch(beacon, epoch, set.validators)
135143

136144
Logger.info(
137145
"[Validator] Proposer duties for epoch #{epoch} are: #{inspect(proposers, pretty: true)}"
@@ -141,29 +149,30 @@ defmodule LambdaEthereumConsensus.ValidatorSet do
141149
"[Validator] Attester duties for epoch #{epoch} are: #{inspect(attesters, pretty: true)}"
142150
)
143151

144-
%{epoch => %{proposers: proposers, attesters: attesters}}
152+
{epoch, %{proposers: proposers, attesters: attesters}}
145153
end
146154

147-
defp merge_duties_and_prune(new_duties, epoch, set) do
155+
defp merge_duties_and_prune(new_duties, current_epoch, set) do
148156
set.duties
149157
# Remove duties from epoch - 2 or older
150-
|> Map.reject(fn {old_epoch, _} -> old_epoch < epoch - 2 end)
158+
|> Map.reject(fn {old_epoch, _} -> old_epoch < current_epoch - 1 end)
151159
|> Map.merge(new_duties)
152160
|> then(fn current_duties -> %{set | duties: current_duties} end)
153161
end
154162

155163
##############################
156164
# Block proposal
157165

158-
defp build_next_payload(%{validators: validators} = set, epoch, slot, head_root) 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
166+
defp build_payload(%{validators: validators} = set, slot, head_root) do
167+
epoch = Misc.compute_epoch_at_slot(slot)
168+
169+
case Duties.current_proposer(set.duties, epoch, slot) do
161170
nil ->
162171
set
163172

164173
validator_index ->
165174
validators
166-
|> Map.update!(validator_index, &Validator.start_payload_builder(&1, slot + 1, head_root))
175+
|> Map.update!(validator_index, &Validator.start_payload_builder(&1, slot, head_root))
167176
|> update_validators(set)
168177
end
169178
end

0 commit comments

Comments
 (0)