Skip to content

feat: electra slashing, proposer_index & compute_sync_committees changes #1417

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 5 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 8 additions & 5 deletions lib/lambda_ethereum_consensus/state_transition/accessors.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
alias Types.SyncCommittee
alias Types.Validator

@max_random_byte 2 ** 8 - 1
@max_random_byte 2 ** 16 - 1

@doc """
Compute the correct sync committee for a given `epoch`.
Expand Down Expand Up @@ -118,13 +118,16 @@ defmodule LambdaEthereumConsensus.StateTransition.Accessors do
|> Misc.compute_shuffled_index(active_validator_count, seed) do
candidate_index = Aja.Vector.at!(active_validator_indices, shuffled_index)

<<_::binary-size(rem(index, 32)), random_byte, _::binary>> =
SszEx.hash(seed <> Misc.uint64_to_bytes(div(index, 32)))
random_bytes = SszEx.hash(seed <> Misc.uint_to_bytes(div(index, 16), 64))
offset = rem(index, 16) * 2

max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
bytes = binary_part(random_bytes, offset, 2) <> <<0::48>>
random_value = Misc.bytes_to_uint64(bytes)

max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA")
effective_balance = Aja.Vector.at!(validators, candidate_index).effective_balance

if effective_balance * @max_random_byte >= max_effective_balance * random_byte do
if effective_balance * @max_random_byte >= max_effective_balance * random_value do
{:ok, sync_committee_indices |> List.insert_at(0, candidate_index)}
else
{:ok, sync_committee_indices}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,17 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
adjusted_total_slashing_balance =
min(slashed_sum * proportional_slashing_multiplier, total_balance)

penalty_per_effective_balance_increment =
div(adjusted_total_slashing_balance, div(total_balance, increment))

new_state =
validators
|> Stream.with_index()
|> Enum.reduce(state, fn {validator, index}, acc ->
if validator.slashed and
epoch + div(epochs_per_slashings_vector, 2) == validator.withdrawable_epoch do
# increment factored out from penalty numerator to avoid uint64 overflow
penalty_numerator =
div(validator.effective_balance, increment) * adjusted_total_slashing_balance

penalty = div(penalty_numerator, total_balance) * increment
effective_balance_increments = div(validator.effective_balance, increment)
penalty = penalty_per_effective_balance_increment * effective_balance_increments

BeaconState.decrease_balance(acc, index, penalty)
else
Expand Down
17 changes: 10 additions & 7 deletions lib/lambda_ethereum_consensus/state_transition/misc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
alias LambdaEthereumConsensus.Utils
alias Types.BeaconState

@max_random_byte 2 ** 8 - 1
@max_random_byte 2 ** 16 - 1

@doc """
Returns the Unix timestamp at the start of the given slot
Expand Down Expand Up @@ -130,23 +130,26 @@ defmodule LambdaEthereumConsensus.StateTransition.Misc do
) ::
{:ok, Types.validator_index()}
def compute_proposer_index(state, indices, seed) do
max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE")
max_effective_balance = ChainSpec.get("MAX_EFFECTIVE_BALANCE_ELECTRA")
total = Aja.Vector.size(indices)

Stream.iterate(0, &(&1 + 1))
|> Stream.map(fn i ->
{:ok, index} = compute_shuffled_index(rem(i, total), total, seed)
candidate_index = Aja.Vector.at!(indices, index)

<<_::binary-size(rem(i, 32)), random_byte, _::binary>> =
SszEx.hash(seed <> uint_to_bytes(div(i, 32), 64))
random_bytes = SszEx.hash(seed <> uint_to_bytes(div(i, 16), 64))
offset = rem(i, 16) * 2

bytes = binary_part(random_bytes, offset, 2) <> <<0::48>>
random_value = bytes_to_uint64(bytes)

effective_balance = Aja.Vector.at(state.validators, candidate_index).effective_balance

{effective_balance, random_byte, candidate_index}
{effective_balance, random_value, candidate_index}
end)
|> Stream.filter(fn {effective_balance, random_byte, _} ->
effective_balance * @max_random_byte >= max_effective_balance * random_byte
|> Stream.filter(fn {effective_balance, random_value, _} ->
effective_balance * @max_random_byte >= max_effective_balance * random_value
end)
|> Enum.take(1)
|> then(fn [{_, _, candidate_index}] -> {:ok, candidate_index} end)
Expand Down
15 changes: 8 additions & 7 deletions test/spec/runners/sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ defmodule SyncTestRunner do
def run_test_case(%SpecTestCase{} = testcase) do
original_engine_api_config = Application.fetch_env!(:lambda_ethereum_consensus, EngineApi)

on_exit(fn ->
Application.put_env(
:lambda_ethereum_consensus,
EngineApi,
original_engine_api_config
)
end)

Comment on lines +35 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

Application.put_env(
:lambda_ethereum_consensus,
EngineApi,
Expand All @@ -41,13 +49,6 @@ defmodule SyncTestRunner do
{:ok, _pid} = SyncTestRunner.EngineApiMock.start_link([])

ForkChoiceTestRunner.run_test_case(testcase)

# TODO: we should do this cleanup even if the test crashes/fails
Application.put_env(
:lambda_ethereum_consensus,
EngineApi,
original_engine_api_config
)
end
end

Expand Down
Loading