Skip to content

feat: electra process_registry_updates & slash_validators changes #1420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ed74379
feat EpochProcessing.process_slashings electra changes
LeanSerra Apr 3, 2025
71b3674
feat compute_proposer_index changes
LeanSerra Apr 4, 2025
9aa4202
feat compute_sync_committees Electra changes
LeanSerra Apr 4, 2025
f09b71b
fix fork_choice.ex tests were flaky because env var cleanup was not done
LeanSerra Apr 4, 2025
df45ee4
feat eligible_for_activation_queue Electra changes
LeanSerra Apr 7, 2025
8fb4d63
feat new compounding_withdrawal_credential? Electra function
LeanSerra Apr 7, 2025
6e34108
feat new has_compounding_withdrawal_credential Electra function
LeanSerra Apr 7, 2025
620e700
feat new has_execution_withdrawal_credential Electra function
LeanSerra Apr 7, 2025
d904593
feat fully_withdrawable_validator? Electra changes
LeanSerra Apr 7, 2025
5445583
feat new get_max_effective_balance Electra function
LeanSerra Apr 7, 2025
5cbc5af
feat partially_withdrawable_validator? Electra changes
LeanSerra Apr 7, 2025
753badb
fix typo in struct field in has_compounding_withdrawal_credential
LeanSerra Apr 7, 2025
29bf08a
feat new get_committee_indices Electra function
LeanSerra Apr 7, 2025
17bf479
feat get_attesting_indices Electra changes
LeanSerra Apr 7, 2025
42b0bfb
lint remove unnecessary parentheses in if
LeanSerra Apr 7, 2025
6df1b36
feat slash_validator Electra changes
LeanSerra Apr 7, 2025
9e13ad5
feat new get_balance_churn_limit Electra function
LeanSerra Apr 7, 2025
abc2585
feat new get_activation_exit_churn_limit Electra function
LeanSerra Apr 7, 2025
5b7e5dc
feat compute_exit_epoch_and_churn Electra function
LeanSerra Apr 7, 2025
4eddb4b
feat initiate_validator_exit Electra changes
LeanSerra Apr 7, 2025
44a00ec
feat process_registry_updates Electra changes
LeanSerra Apr 8, 2025
9bc3a0b
refactor remove unused eject_validator(_,_,_, false) variant
LeanSerra Apr 8, 2025
ed8be8a
fix compute_exit_epoch_and_update_churn wrong exit_balance calculation
LeanSerra Apr 8, 2025
c084913
Merge branch 'electra-support' into electra_predicates
LeanSerra Apr 8, 2025
6ec0216
fix additional_epochs calculation in compute_exit_epoch_and_update_churn
LeanSerra Apr 8, 2025
7da3d5a
fix wrong values for constants in minimal config
LeanSerra Apr 8, 2025
70d40aa
Merge branch 'electra_predicates' into electra_validator_exit
LeanSerra Apr 8, 2025
88c7308
refactor move registry_update for each validator to own function inli…
LeanSerra Apr 9, 2025
f8c443a
Merge branch 'electra-support' into electra_validator_exit
LeanSerra Apr 10, 2025
b4a9d15
Merge branch 'electra-support' into electra_validator_exit
LeanSerra Apr 10, 2025
27a4063
fix move config constants from presets to config
LeanSerra Apr 10, 2025
0dc56f9
fix remove added constants from gnosis file as they are not in the spec
LeanSerra Apr 10, 2025
6fbae2f
Revert "fix remove added constants from gnosis file as they are not i…
LeanSerra Apr 10, 2025
e5a3214
fix diff from revert last commit
LeanSerra Apr 10, 2025
2c572b4
refactor initiate_validator_exit now returns a tuple from code review
LeanSerra Apr 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/networks/mainnet/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@ WHISK_PROPOSER_SELECTION_GAP: 2
# EIP7594
EIP7594_FORK_VERSION: 0x06000001
EIP7594_FORK_EPOCH: 18446744073709551615

# Electra
# 2**7 * 10**9 (= 128,000,000,000) Gwei
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000
# 2**8 * 10**9) (= 256,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000
6 changes: 6 additions & 0 deletions config/networks/minimal/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,9 @@ WHISK_PROPOSER_SELECTION_GAP: 1
# EIP7594
EIP7594_FORK_VERSION: 0x06000001
EIP7594_FORK_EPOCH: 18446744073709551615

# Electra
# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000
# [customized] 2**8 * 10**9) (= 256,000,000,000) Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000
2 changes: 1 addition & 1 deletion config/presets/mainnet/electra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8
# Pending deposits processing
# ---------------------------------------------------------------
# 2**4 ( = 4) pending deposits
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
2 changes: 1 addition & 1 deletion config/presets/minimal/electra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2
# Pending deposits processing
# ---------------------------------------------------------------
# 2**4 ( = 4) pending deposits
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
MAX_PENDING_DEPOSITS_PER_EPOCH: 16
25 changes: 25 additions & 0 deletions lib/lambda_ethereum_consensus/state_transition/accessors.ex
Original file line number Diff line number Diff line change
Expand Up @@ -657,4 +657,29 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do

for {bit, index} <- Enum.with_index(bitlist), bit == 1, do: index
end

@doc """
Return the churn limit for the current epoch.
"""
@spec get_balance_churn_limit(Types.BeaconState.t()) :: Types.gwei()
def get_balance_churn_limit(state) do
churn =
max(
ChainSpec.get("MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA"),
div(get_total_active_balance(state), ChainSpec.get("CHURN_LIMIT_QUOTIENT"))
)

churn - rem(churn, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT"))
end

@doc """
Return the churn limit for the current epoch dedicated to activations and exits.
"""
@spec get_activation_exit_churn_limit(Types.BeaconState.t()) :: Types.gwei()
def get_activation_exit_churn_limit(state) do
min(
ChainSpec.get("MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT"),
get_balance_churn_limit(state)
)
end
end
104 changes: 65 additions & 39 deletions lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -144,49 +144,75 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
current_epoch = Accessors.get_current_epoch(state)
activation_exit_epoch = Misc.compute_activation_exit_epoch(current_epoch)

churn_limit = Accessors.get_validator_activation_churn_limit(state)
validators
|> Enum.with_index()
|> Enum.reduce_while(state, fn {validator, idx}, state ->
handle_validator_registry_update(
state,
validator,
idx,
current_epoch,
activation_exit_epoch,
ejection_balance
)
end)
|> then(fn
%BeaconState{} = state -> {:ok, state}
{:error, reason} -> {:error, reason}
end)
end

result =
validators
|> Stream.with_index()
|> Stream.map(fn {v, i} ->
{{v, i}, Predicates.eligible_for_activation_queue?(v),
Predicates.active_validator?(v, current_epoch) and
v.effective_balance <= ejection_balance}
end)
|> Stream.filter(&(elem(&1, 1) or elem(&1, 2)))
|> Stream.map(fn
{{v, i}, true, b} -> {{%{v | activation_eligibility_epoch: current_epoch + 1}, i}, b}
{{v, i}, false = _is_eligible, b} -> {{v, i}, b}
end)
|> Enum.reduce({:ok, state}, fn
_, {:error, _} = err -> err
{{v, i}, should_be_ejected}, {:ok, st} -> eject_validator(st, v, i, should_be_ejected)
{err, _}, _ -> err
end)
defp handle_validator_registry_update(
state,
validator,
idx,
current_epoch,
activation_exit_epoch,
ejection_balance
) do
cond do
Predicates.eligible_for_activation_queue?(validator) ->
updated_validator = %Validator{
validator
| activation_eligibility_epoch: current_epoch + 1
}

with {:ok, new_state} <- result do
new_state.validators
|> Stream.with_index()
|> Stream.filter(fn {v, _} -> Predicates.eligible_for_activation?(state, v) end)
|> Enum.sort_by(fn {%{activation_eligibility_epoch: ep}, i} -> {ep, i} end)
|> Enum.take(churn_limit)
|> Enum.reduce(new_state.validators, fn {v, i}, acc ->
%{v | activation_epoch: activation_exit_epoch}
|> then(&Aja.Vector.replace_at!(acc, i, &1))
end)
|> then(&{:ok, %BeaconState{new_state | validators: &1}})
end
end
{:cont,
%BeaconState{
state
| validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
}}

defp eject_validator(state, validator, index, false) do
{:ok, %{state | validators: Aja.Vector.replace_at!(state.validators, index, validator)}}
end
Predicates.active_validator?(validator, current_epoch) &&
validator.effective_balance <= ejection_balance ->
case Mutators.initiate_validator_exit(state, validator) do
{:ok, {state, ejected_validator}} ->
updated_state = %{
state
| validators: Aja.Vector.replace_at!(state.validators, idx, ejected_validator)
}

{:cont, updated_state}

{:error, msg} ->
{:halt, {:error, msg}}
end

Predicates.eligible_for_activation?(state, validator) ->
updated_validator = %Validator{
validator
| activation_epoch: activation_exit_epoch
}

updated_state = %BeaconState{
state
| validators: Aja.Vector.replace_at!(state.validators, idx, updated_validator)
}

{:cont, updated_state}

defp eject_validator(state, validator, index, true) do
with {:ok, ejected_validator} <- Mutators.initiate_validator_exit(state, validator) do
{:ok,
%{state | validators: Aja.Vector.replace_at!(state.validators, index, ejected_validator)}}
true ->
{:cont, state}
end
end

Expand Down
95 changes: 53 additions & 42 deletions lib/lambda_ethereum_consensus/state_transition/mutators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,33 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
Initiate the exit of the validator with index ``index``.
"""
@spec initiate_validator_exit(BeaconState.t(), integer()) ::
{:ok, Validator.t()} | {:error, String.t()}
{:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
def initiate_validator_exit(%BeaconState{} = state, index) when is_integer(index) do
initiate_validator_exit(state, Aja.Vector.at!(state.validators, index))
end

@spec initiate_validator_exit(BeaconState.t(), Validator.t()) ::
{:ok, Validator.t()} | {:error, String.t()}
{:ok, {BeaconState.t(), Validator.t()}} | {:error, String.t()}
def initiate_validator_exit(%BeaconState{} = state, %Validator{} = validator) do
far_future_epoch = Constants.far_future_epoch()
min_validator_withdrawability_delay = ChainSpec.get("MIN_VALIDATOR_WITHDRAWABILITY_DELAY")

if validator.exit_epoch != far_future_epoch do
{:ok, validator}
{:ok, {state, validator}}
else
exit_epochs =
state.validators
|> Stream.filter(fn validator ->
validator.exit_epoch != far_future_epoch
end)
|> Stream.map(fn validator -> validator.exit_epoch end)
|> Enum.to_list()

exit_queue_epoch =
Enum.max(
exit_epochs ++ [Misc.compute_activation_exit_epoch(Accessors.get_current_epoch(state))]
)

exit_queue_churn =
state.validators
|> Stream.filter(fn validator ->
validator.exit_epoch == exit_queue_epoch
end)
|> Enum.to_list()
|> length()

exit_queue_epoch =
if exit_queue_churn >= Accessors.get_validator_churn_limit(state) do
exit_queue_epoch + 1
else
exit_queue_epoch
end

next_withdrawable_epoch = exit_queue_epoch + min_validator_withdrawability_delay
state = compute_exit_epoch_and_update_churn(state, validator.effective_balance)
exit_queue_epoch = state.earliest_exit_epoch

if next_withdrawable_epoch > Constants.far_future_epoch() do
{:error, "withdrawable_epoch_too_large"}
if exit_queue_epoch + min_validator_withdrawability_delay > 2 ** 64 do
{:error, "withdrawable_epoch overflow"}
else
{:ok,
%{
validator
| exit_epoch: exit_queue_epoch,
withdrawable_epoch: next_withdrawable_epoch
}}
{state,
%{
validator
| exit_epoch: exit_queue_epoch,
withdrawable_epoch: exit_queue_epoch + min_validator_withdrawability_delay
}}}
end
end
end
Expand All @@ -78,17 +52,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
) ::
{:ok, BeaconState.t()} | {:error, String.t()}
def slash_validator(state, slashed_index, whistleblower_index \\ nil) do
with {:ok, validator} <- initiate_validator_exit(state, slashed_index),
with {:ok, {state, validator}} <- initiate_validator_exit(state, slashed_index),
state = add_slashing(state, validator, slashed_index),
{:ok, proposer_index} <- Accessors.get_beacon_proposer_index(state) do
slashing_penalty =
validator.effective_balance
|> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX"))
|> div(ChainSpec.get("MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA"))

whistleblower_index = whistleblower_index(whistleblower_index, proposer_index)

whistleblower_reward =
div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT"))
div(validator.effective_balance, ChainSpec.get("WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA"))

proposer_reward =
(whistleblower_reward * Constants.proposer_weight())
Expand Down Expand Up @@ -187,4 +161,41 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
)
|> then(&{:ok, &1})
end

@spec compute_exit_epoch_and_update_churn(Types.BeaconState.t(), Types.gwei()) ::
Types.BeaconState.t()
def compute_exit_epoch_and_update_churn(state, exit_balance) do
current_epoch = Accessors.get_current_epoch(state)

earliest_exit_epoch =
max(state.earliest_exit_epoch, Misc.compute_activation_exit_epoch(current_epoch))

per_epoch_churn = Accessors.get_activation_exit_churn_limit(state)

exit_balance_to_consume =
if state.earliest_exit_epoch < earliest_exit_epoch do
per_epoch_churn
else
state.exit_balance_to_consume
end

{earliest_exit_epoch, exit_balance_to_consume} =
if exit_balance > exit_balance_to_consume do
balance_to_process = exit_balance - exit_balance_to_consume
additional_epochs = div(balance_to_process - 1, per_epoch_churn) + 1

{
earliest_exit_epoch + additional_epochs,
exit_balance_to_consume + additional_epochs * per_epoch_churn
}
else
{earliest_exit_epoch, exit_balance_to_consume}
end

%BeaconState{
state
| exit_balance_to_consume: exit_balance_to_consume - exit_balance,
earliest_exit_epoch: earliest_exit_epoch
}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
{:error, "invalid signature"}

true ->
with {:ok, validator} <- Mutators.initiate_validator_exit(state, validator_index) do
with {:ok, {state, validator}} <- Mutators.initiate_validator_exit(state, validator_index) do
Aja.Vector.replace_at!(state.validators, validator_index, validator)
|> then(&{:ok, %BeaconState{state | validators: &1}})
end
Expand Down
Loading