Skip to content

Commit 68dec8f

Browse files
authored
feat: electra process_registry_updates & slash_validators changes (#1420)
1 parent 0d40248 commit 68dec8f

File tree

8 files changed

+158
-84
lines changed

8 files changed

+158
-84
lines changed

config/networks/mainnet/config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,9 @@ WHISK_PROPOSER_SELECTION_GAP: 2
151151
# EIP7594
152152
EIP7594_FORK_VERSION: 0x06000001
153153
EIP7594_FORK_EPOCH: 18446744073709551615
154+
155+
# Electra
156+
# 2**7 * 10**9 (= 128,000,000,000) Gwei
157+
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000
158+
# 2**8 * 10**9) (= 256,000,000,000) Gwei
159+
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000

config/networks/minimal/config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,9 @@ WHISK_PROPOSER_SELECTION_GAP: 1
149149
# EIP7594
150150
EIP7594_FORK_VERSION: 0x06000001
151151
EIP7594_FORK_EPOCH: 18446744073709551615
152+
153+
# Electra
154+
# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei
155+
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000
156+
# [customized] 2**8 * 10**9) (= 256,000,000,000) Gwei
157+
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000

config/presets/mainnet/electra.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
4747
# Pending deposits processing
4848
# ---------------------------------------------------------------
4949
# 2**4 ( = 4) pending deposits
50-
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
50+
MAX_PENDING_DEPOSITS_PER_EPOCH: 16

config/presets/minimal/electra.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2
4747
# Pending deposits processing
4848
# ---------------------------------------------------------------
4949
# 2**4 ( = 4) pending deposits
50-
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
50+
MAX_PENDING_DEPOSITS_PER_EPOCH: 16

lib/lambda_ethereum_consensus/state_transition/accessors.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,4 +657,29 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
657657

658658
for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index
659659
end
660+
661+
@doc """
662+
Return the churn limit for the current epoch.
663+
"""
664+
@spec get_balance_churn_limit(Types.BeaconState.t()) :: Types.gwei()
665+
def get_balance_churn_limit(state) do
666+
churn =
667+
max(
668+
ChainSpec.get("MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA"),
669+
div(get_total_active_balance(state), ChainSpec.get("CHURN_LIMIT_QUOTIENT"))
670+
)
671+
672+
churn - rem(churn, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"))
673+
end
674+
675+
@doc """
676+
Return the churn limit for the current epoch dedicated to activations and exits.
677+
"""
678+
@spec get_activation_exit_churn_limit(Types.BeaconState.t()) :: Types.gwei()
679+
def get_activation_exit_churn_limit(state) do
680+
min(
681+
ChainSpec.get("MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT"),
682+
get_balance_churn_limit(state)
683+
)
684+
end
660685
end

lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -144,49 +144,75 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
144144
current_epoch = Accessors.get_current_epoch(state)
145145
activation_exit_epoch = Misc.compute_activation_exit_epoch(current_epoch)
146146

147-
churn_limit = Accessors.get_validator_activation_churn_limit(state)
147+
validators
148+
|> Enum.with_index()
149+
|> Enum.reduce_while(state, fn {validator, idx}, state ->
150+
handle_validator_registry_update(
151+
state,
152+
validator,
153+
idx,
154+
current_epoch,
155+
activation_exit_epoch,
156+
ejection_balance
157+
)
158+
end)
159+
|> then(fn
160+
%BeaconState{} = state -> {:ok, state}
161+
{:error, reason} -> {:error, reason}
162+
end)
163+
end
148164

149-
result =
150-
validators
151-
|> Stream.with_index()
152-
|> Stream.map(fn {v, i} ->
153-
{{v, i}, Predicates.eligible_for_activation_queue?(v),
154-
Predicates.active_validator?(v, current_epoch) and
155-
v.effective_balance <= ejection_balance}
156-
end)
157-
|> Stream.filter(&(elem(&1, 1) or elem(&1, 2)))
158-
|> Stream.map(fn
159-
{{v, i}, true, b} -> {{%{v | activation_eligibility_epoch: current_epoch + 1}, i}, b}
160-
{{v, i}, false = _is_eligible, b} -> {{v, i}, b}
161-
end)
162-
|> Enum.reduce({:ok, state}, fn
163-
_, {:error, _} = err -> err
164-
{{v, i}, should_be_ejected}, {:ok, st} -> eject_validator(st, v, i, should_be_ejected)
165-
{err, _}, _ -> err
166-
end)
165+
defp handle_validator_registry_update(
166+
state,
167+
validator,
168+
idx,
169+
current_epoch,
170+
activation_exit_epoch,
171+
ejection_balance
172+
) do
173+
cond do
174+
Predicates.eligible_for_activation_queue?(validator) ->
175+
updated_validator = %Validator{
176+
validator
177+
| activation_eligibility_epoch: current_epoch + 1
178+
}
167179

168-
with {:ok, new_state} <- result do
169-
new_state.validators
170-
|> Stream.with_index()
171-
|> Stream.filter(fn {v, _} -> Predicates.eligible_for_activation?(state, v) end)
172-
|> Enum.sort_by(fn {%{activation_eligibility_epoch: ep}, i} -> {ep, i} end)
173-
|> Enum.take(churn_limit)
174-
|> Enum.reduce(new_state.validators, fn {v, i}, acc ->
175-
%{v | activation_epoch: activation_exit_epoch}
176-
|> then(&Aja.Vector.replace_at!(acc, i, &1))
177-
end)
178-
|> then(&{:ok, %BeaconState{new_state | validators: &1}})
179-
end
180-
end
180+
{:cont,
181+
%BeaconState{
182+
state
183+
| validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
184+
}}
181185

182-
defp eject_validator(state, validator, index, false) do
183-
{:ok, %{state | validators: Aja.Vector.replace_at!(state.validators, index, validator)}}
184-
end
186+
Predicates.active_validator?(validator, current_epoch) &&
187+
validator.effective_balance <= ejection_balance ->
188+
case Mutators.initiate_validator_exit(state, validator) do
189+
{:ok, {state, ejected_validator}} ->
190+
updated_state = %{
191+
state
192+
| validators: Aja.Vector.replace_at!(state.validators, idx, ejected_validator)
193+
}
194+
195+
{:cont, updated_state}
196+
197+
{:error, msg} ->
198+
{:halt, {:error, msg}}
199+
end
200+
201+
Predicates.eligible_for_activation?(state, validator) ->
202+
updated_validator = %Validator{
203+
validator
204+
| activation_epoch: activation_exit_epoch
205+
}
206+
207+
updated_state = %BeaconState{
208+
state
209+
| validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
210+
}
211+
212+
{:cont, updated_state}
185213

186-
defp eject_validator(state, validator, index, true) do
187-
with {:ok, ejected_validator} <- Mutators.initiate_validator_exit(state, validator) do
188-
{:ok,
189-
%{state | validators: Aja.Vector.replace_at!(state.validators, index, ejected_validator)}}
214+
true ->
215+
{:cont, state}
190216
end
191217
end
192218

lib/lambda_ethereum_consensus/state_transition/mutators.ex

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,59 +11,33 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
1111
Initiate the exit of the validator with index ``index``.
1212
"""
1313
@spec initiate_validator_exit(BeaconState.t(), integer()) ::
14-
{:ok, Validator.t()} | {:error, String.t()}
14+
{:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
1515
def initiate_validator_exit(%BeaconState{} = state, index) when is_integer(index) do
1616
initiate_validator_exit(state, Aja.Vector.at!(state.validators, index))
1717
end
1818

1919
@spec initiate_validator_exit(BeaconState.t(), Validator.t()) ::
20-
{:ok, Validator.t()} | {:error, String.t()}
20+
{:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
2121
def initiate_validator_exit(%BeaconState{} = state, %Validator{} = validator) do
2222
far_future_epoch = Constants.far_future_epoch()
2323
min_validator_withdrawability_delay = ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY")
2424

2525
if validator.exit_epoch != far_future_epoch do
26-
{:ok, validator}
26+
{:ok, {state, validator}}
2727
else
28-
exit_epochs =
29-
state.validators
30-
|> Stream.filter(fn validator ->
31-
validator.exit_epoch != far_future_epoch
32-
end)
33-
|> Stream.map(fn validator -> validator.exit_epoch end)
34-
|> Enum.to_list()
35-
36-
exit_queue_epoch =
37-
Enum.max(
38-
exit_epochs ++ [Misc.compute_activation_exit_epoch(Accessors.get_current_epoch(state))]
39-
)
40-
41-
exit_queue_churn =
42-
state.validators
43-
|> Stream.filter(fn validator ->
44-
validator.exit_epoch == exit_queue_epoch
45-
end)
46-
|> Enum.to_list()
47-
|> length()
48-
49-
exit_queue_epoch =
50-
if exit_queue_churn >= Accessors.get_validator_churn_limit(state) do
51-
exit_queue_epoch + 1
52-
else
53-
exit_queue_epoch
54-
end
55-
56-
next_withdrawable_epoch = exit_queue_epoch + min_validator_withdrawability_delay
28+
state = compute_exit_epoch_and_update_churn(state, validator.effective_balance)
29+
exit_queue_epoch = state.earliest_exit_epoch
5730

58-
if next_withdrawable_epoch > Constants.far_future_epoch() do
59-
{:error, "withdrawable_epoch_too_large"}
31+
if exit_queue_epoch + min_validator_withdrawability_delay > 2 ** 64 do
32+
{:error, "withdrawable_epoch overflow"}
6033
else
6134
{:ok,
62-
%{
63-
validator
64-
| exit_epoch: exit_queue_epoch,
65-
withdrawable_epoch: next_withdrawable_epoch
66-
}}
35+
{state,
36+
%{
37+
validator
38+
| exit_epoch: exit_queue_epoch,
39+
withdrawable_epoch: exit_queue_epoch + min_validator_withdrawability_delay
40+
}}}
6741
end
6842
end
6943
end
@@ -78,17 +52,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
7852
) ::
7953
{:ok, BeaconState.t()} | {:error, String.t()}
8054
def slash_validator(state, slashed_index, whistleblower_index \\ nil) do
81-
with {:ok, validator} <- initiate_validator_exit(state, slashed_index),
55+
with {:ok, {state, validator}} <- initiate_validator_exit(state, slashed_index),
8256
state = add_slashing(state, validator, slashed_index),
8357
{:ok, proposer_index} <- Accessors.get_beacon_proposer_index(state) do
8458
slashing_penalty =
8559
validator.effective_balance
86-
|> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX"))
60+
|> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA"))
8761

8862
whistleblower_index = whistleblower_index(whistleblower_index, proposer_index)
8963

9064
whistleblower_reward =
91-
div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT"))
65+
div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA"))
9266

9367
proposer_reward =
9468
(whistleblower_reward * Constants.proposer_weight())
@@ -187,4 +161,41 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
187161
)
188162
|> then(&{:ok, &1})
189163
end
164+
165+
@spec compute_exit_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) ::
166+
Types.BeaconState.t()
167+
def compute_exit_epoch_and_update_churn(state, exit_balance) do
168+
current_epoch = Accessors.get_current_epoch(state)
169+
170+
earliest_exit_epoch =
171+
max(state.earliest_exit_epoch, Misc.compute_activation_exit_epoch(current_epoch))
172+
173+
per_epoch_churn = Accessors.get_activation_exit_churn_limit(state)
174+
175+
exit_balance_to_consume =
176+
if state.earliest_exit_epoch < earliest_exit_epoch do
177+
per_epoch_churn
178+
else
179+
state.exit_balance_to_consume
180+
end
181+
182+
{earliest_exit_epoch, exit_balance_to_consume} =
183+
if exit_balance > exit_balance_to_consume do
184+
balance_to_process = exit_balance - exit_balance_to_consume
185+
additional_epochs = div(balance_to_process - 1, per_epoch_churn) + 1
186+
187+
{
188+
earliest_exit_epoch + additional_epochs,
189+
exit_balance_to_consume + additional_epochs * per_epoch_churn
190+
}
191+
else
192+
{earliest_exit_epoch, exit_balance_to_consume}
193+
end
194+
195+
%BeaconState{
196+
state
197+
| exit_balance_to_consume: exit_balance_to_consume - exit_balance,
198+
earliest_exit_epoch: earliest_exit_epoch
199+
}
200+
end
190201
end

lib/lambda_ethereum_consensus/state_transition/operations.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
571571
{:error, "invalid signature"}
572572

573573
true ->
574-
with {:ok, validator} <- Mutators.initiate_validator_exit(state, validator_index) do
574+
with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do
575575
Aja.Vector.replace_at!(state.validators, validator_index, validator)
576576
|> then(&{:ok, %BeaconState{state | validators: &1}})
577577
end

0 commit comments

Comments
 (0)