Skip to content

feat: implement electra deposits #1424

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 60 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 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
e4e8607
feat process_epoch changes + deposits/consolidation scaffolding
LeanSerra Apr 8, 2025
0e53f57
feat new is_valid_deposit_signature Electra function
LeanSerra Apr 9, 2025
12ed2d3
feat get_validator_from_deposit Electra changes
LeanSerra Apr 9, 2025
a73c061
feat apply_deposit Electra changes
LeanSerra Apr 9, 2025
6ad6e4d
feat new apply_pending_deposit Electra function
LeanSerra Apr 9, 2025
3eef3e7
feat new process_pending_deposits Electra function
LeanSerra Apr 9, 2025
88c7308
refactor move registry_update for each validator to own function inli…
LeanSerra Apr 9, 2025
14a0539
Merge branch 'electra_validator_exit' into electra_deposits
LeanSerra Apr 9, 2025
ebc73e0
refactor avoid negation in if when calculating pending_deposits in ap…
LeanSerra Apr 9, 2025
f8c443a
Merge branch 'electra-support' into electra_validator_exit
LeanSerra Apr 10, 2025
8f72d6b
Merge branch 'electra_validator_exit' into electra_deposits
LeanSerra Apr 10, 2025
b4a9d15
Merge branch 'electra-support' into electra_validator_exit
LeanSerra Apr 10, 2025
280669a
Merge branch 'electra_validator_exit' into electra_deposits
LeanSerra Apr 10, 2025
c2db045
refactor move logic to handle an individual pending_deposit to its ow…
LeanSerra Apr 10, 2025
7bc38b9
feat process_operations Electra changes
LeanSerra Apr 10, 2025
6d74e53
feat new process_deposit_request Electra function
LeanSerra Apr 10, 2025
40b0a5c
fix conditional logic for verify_deposits was incorrect
LeanSerra Apr 10, 2025
5b9721c
Merge branch 'electra-support' into electra_deposits
LeanSerra Apr 11, 2025
38c5a4e
docs update electra-gap.md with changes from #1424
LeanSerra Apr 11, 2025
ff7ab9f
Merge branch 'electra-support' into electra_deposits
LeanSerra Apr 15, 2025
c4008f2
fix apply_deposits, apply_initial_deposit amount should be 0, returne…
LeanSerra Apr 16, 2025
ffbd715
fix apply_deposits using Enum.member when it should be using Enum.any
LeanSerra Apr 16, 2025
a9a9d2f
fix process_deposit_request was overwritting the updated_state
LeanSerra Apr 16, 2025
3346f10
fix add new Containers to operations.ex runner
LeanSerra Apr 16, 2025
b85fcbd
Merge branch 'electra-support' into electra_deposits
LeanSerra Apr 16, 2025
c049a33
Merge branch 'electra-support' into electra_deposits
LeanSerra Apr 16, 2025
5b7528f
add TODO comments for process_withdrawal_request and process_consolid…
LeanSerra Apr 16, 2025
096d71f
refactor process_deposit_request
LeanSerra Apr 16, 2025
c166d30
fix diff remove unnecessary added lines
LeanSerra Apr 16, 2025
fe117dc
refactor Mutators.apply_deposit
LeanSerra Apr 16, 2025
566221f
refactor EpochProcessing.apply_pending_deposit
LeanSerra Apr 16, 2025
b044e6d
add TODO comment for process_pending_consolidations
LeanSerra Apr 16, 2025
762bdfb
refactor rename apply_initial_deposit to add_validator_to_registry
LeanSerra Apr 16, 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
15 changes: 9 additions & 6 deletions electra-gap.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,26 @@ This document outlines the gaps in the current implementation of the Electra. It
- [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch))
- [x] Modified `process_registry_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_registry_updates), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1420))
- [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417))
- [ ] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit))
- [ ] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits))
- [x] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [x] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [ ] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations))
- [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates))
- [x] Modified `get_validator_from_deposit` ([Spec](docs/specs/electra/beacon-chains.md#modified-get_validator_from_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))

## Block Processing

- [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals))
- [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload))
- [ ] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations))
- [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [ ] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation))
- [ ] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit))
- [x] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [ ] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit))
- [ ] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request))
- [ ] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request))
- [x] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [ ] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request))

- [x] New `is_valid_deposit_signature` ([Spec](docs/specs/electra/beacon-chain.md#new-is_valid_deposit_signature), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [x] Modified `add_validator_to_registry` ([Spec](docs/specs/electra/beacon-chain.md#modified-add_validator_to_registry), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
- [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
## Execution Engine

- [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash))
Expand Down
151 changes: 151 additions & 0 deletions lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
alias LambdaEthereumConsensus.Utils.BitVector
alias LambdaEthereumConsensus.Utils.Randao
alias Types.BeaconState
alias Types.DepositMessage
alias Types.HistoricalSummary
alias Types.Validator

Expand Down Expand Up @@ -454,4 +455,154 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
max(balance + delta, 0)
end)
end

@spec process_pending_deposits(BeaconState.t()) :: {:ok, BeaconState.t()}
def process_pending_deposits(%BeaconState{} = state) do
available_for_processing =
state.deposit_balance_to_consume + Accessors.get_activation_exit_churn_limit(state)

finalized_slot = Misc.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)

{state, churn_limit_reached, processed_amount, deposits_to_postpone, last_processed_index} =
state.pending_deposits
|> Enum.with_index()
|> Enum.reduce_while({state, false, 0, [], 0}, fn {deposit, index},
{state, churn_limit_reached,
processed_amount, deposits_to_postpone,
_last_processed_index} ->
cond do
# Do not process deposit requests if Eth1 bridge deposits are not yet applied.
deposit.slot > Constants.genesis_slot() &&
state.eth1_deposit_index < state.deposit_requests_start_index ->
{:halt,
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}

# Check if deposit has been finalized, otherwise, stop processing.
deposit.slot > finalized_slot ->
{:halt,
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}

# Check if number of processed deposits has not reached the limit, otherwise, stop processing.
index >= ChainSpec.get("MAX_PENDING_DEPOSITS_PER_EPOCH") ->
{:halt,
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}

true ->
handle_pending_deposit(
deposit,
state,
churn_limit_reached,
processed_amount,
deposits_to_postpone,
index,
available_for_processing
)
end
end)

deposit_balance_to_consume =
if churn_limit_reached do
available_for_processing - processed_amount
else
0
end

{:ok,
%BeaconState{
state
| pending_deposits:
Enum.drop(state.pending_deposits, last_processed_index + 1)
|> Enum.concat(deposits_to_postpone),
deposit_balance_to_consume: deposit_balance_to_consume
}}
end

defp handle_pending_deposit(
deposit,
state,
churn_limit_reached,
processed_amount,
deposits_to_postpone,
index,
available_for_processing
) do
far_future_epoch = Constants.far_future_epoch()
next_epoch = Accessors.get_current_epoch(state)

{is_validator_exited, is_validator_withdrawn} =
case Enum.find(state.validators, fn v -> v.pubkey == deposit.pubkey end) do
%Validator{} = validator ->
{validator.exit_epoch < far_future_epoch, validator.withdrawable_epoch < next_epoch}

_ ->
{false, false}
end

cond do
# Deposited balance will never become active. Increase balance but do not consume churn
is_validator_withdrawn ->
{:ok, state} = apply_pending_deposit(state, deposit)

{:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}

# Validator is exiting, postpone the deposit until after withdrawable epoch
is_validator_exited ->
deposits_to_postpone = Enum.concat(deposits_to_postpone, [deposit])

{:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}

true ->
# Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
is_churn_limit_reached =
processed_amount + deposit.amount > available_for_processing

if is_churn_limit_reached do
{:halt, {state, true, processed_amount, deposits_to_postpone, index - 1}}
else
# Consume churn and apply deposit.
processed_amount = processed_amount + deposit.amount
{:ok, state} = apply_pending_deposit(state, deposit)
{:cont, {state, false, processed_amount, deposits_to_postpone, index}}
end
end
end

@spec process_pending_consolidations(BeaconState.t()) :: {:ok, BeaconState.t()}
def process_pending_consolidations(%BeaconState{} = state) do
# TODO: Not implemented yet
{:ok, state}
end

defp apply_pending_deposit(state, deposit) do
index =
Enum.find_index(state.validators, fn validator -> validator.pubkey == deposit.pubkey end)

current_validator? = is_number(index)

valid_signature? =
current_validator? ||
DepositMessage.valid_deposit_signature?(
deposit.pubkey,
deposit.withdrawal_credentials,
deposit.amount,
deposit.signature
)

cond do
current_validator? ->
{:ok, BeaconState.increase_balance(state, index, deposit.amount)}

!current_validator? && valid_signature? ->
Mutators.add_validator_to_registry(
state,
deposit.pubkey,
deposit.withdrawal_credentials,
deposit.amount
)

true ->
# Neither a validator nor have a valid signature, we do not apply the deposit
{:ok, state}
end
end
end
56 changes: 38 additions & 18 deletions lib/lambda_ethereum_consensus/state_transition/mutators.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
alias LambdaEthereumConsensus.StateTransition.Accessors
alias LambdaEthereumConsensus.StateTransition.Misc
alias Types.BeaconState
alias Types.DepositMessage
alias Types.PendingDeposit
alias Types.Validator

@doc """
Expand Down Expand Up @@ -122,31 +124,49 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
Types.uint64(),
Types.bls_signature()
) :: {:ok, BeaconState.t()} | {:error, String.t()}
def apply_deposit(state, pubkey, withdrawal_credentials, amount, signature) do
case Enum.find_index(state.validators, fn validator -> validator.pubkey == pubkey end) do
index when is_number(index) ->
{:ok, BeaconState.increase_balance(state, index, amount)}

_ ->
deposit_message = %Types.DepositMessage{
pubkey: pubkey,
withdrawal_credentials: withdrawal_credentials,
amount: amount
}

domain = Misc.compute_domain(Constants.domain_deposit())
def apply_deposit(state, pubkey, withdrawal_credentials, amount, sig) do
current_validator? = Enum.any?(state.validators, &(&1.pubkey == pubkey))

signing_root = Misc.compute_signing_root(deposit_message, domain)
valid_signature? =
current_validator? ||
DepositMessage.valid_deposit_signature?(pubkey, withdrawal_credentials, amount, sig)

if Bls.valid?(pubkey, signing_root, signature) do
apply_initial_deposit(state, pubkey, withdrawal_credentials, amount)
if !current_validator? && !valid_signature? do
# Neither a validator nor have a valid signature, we do not apply the deposit
{:ok, state}
else
updated_state =
if current_validator? do
state
else
{:ok, state}
{:ok, state} = add_validator_to_registry(state, pubkey, withdrawal_credentials, 0)
state
end

deposit = %PendingDeposit{
pubkey: pubkey,
withdrawal_credentials: withdrawal_credentials,
amount: amount,
signature: sig,
slot: Constants.genesis_slot()
}

{:ok,
%BeaconState{
updated_state
| pending_deposits: updated_state.pending_deposits ++ [deposit]
}}
end
end

defp apply_initial_deposit(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
@spec add_validator_to_registry(
BeaconState.t(),
Types.bls_pubkey(),
Types.bytes32(),
Types.gwei()
) ::
{:ok, BeaconState.t()}
def add_validator_to_registry(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
Types.Deposit.get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
|> then(&Aja.Vector.append(state.validators, &1))
|> then(
Expand Down
73 changes: 67 additions & 6 deletions lib/lambda_ethereum_consensus/state_transition/operations.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
alias LambdaEthereumConsensus.Utils.BitList
alias LambdaEthereumConsensus.Utils.BitVector
alias LambdaEthereumConsensus.Utils.Randao
alias Types.PendingDeposit

alias Types.Attestation
alias Types.BeaconBlock
alias Types.BeaconBlockBody
alias Types.BeaconBlockHeader
alias Types.BeaconState
alias Types.ConsolidationRequest
alias Types.DepositRequest
alias Types.ExecutionPayload
alias Types.ExecutionPayloadHeader
alias Types.SyncAggregate
alias Types.Validator
alias Types.Withdrawal
alias Types.WithdrawalRequest

@spec process_block_header(BeaconState.t(), BeaconBlock.t()) ::
{:ok, BeaconState.t()} | {:error, String.t()}
Expand Down Expand Up @@ -918,6 +922,43 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
end
end

@spec process_deposit_request(BeaconState.t(), DepositRequest.t()) :: {:ok, BeaconState.t()}
def process_deposit_request(state, deposit_request) do
start_index =
if state.deposit_requests_start_index == Constants.unset_deposit_requests_start_index(),
do: deposit_request.index,
else: state.deposit_requests_start_index

deposit = %PendingDeposit{
pubkey: deposit_request.pubkey,
withdrawal_credentials: deposit_request.withdrawal_credentials,
amount: deposit_request.amount,
signature: deposit_request.signature,
slot: state.slot
}

{:ok,
%BeaconState{
state
| deposit_requests_start_index: start_index,
pending_deposits: state.pending_deposits ++ [deposit]
}}
end

@spec process_withdrawal_request(BeaconState.t(), WithdrawalRequest.t()) ::
{:ok, BeaconState.t()}
def process_withdrawal_request(state, _withdrawal_request) do
# TODO: Not implemented yet
{:ok, state}
end

@spec process_consolidation_request(BeaconState.t(), ConsolidationRequest.t()) ::
{:ok, BeaconState.t()}
def process_consolidation_request(state, _consolidation_request) do
# TODO: Not implemented yet
{:ok, state}
end

@spec process_operations(BeaconState.t(), BeaconBlockBody.t()) ::
{:ok, BeaconState.t()} | {:error, String.t()}
def process_operations(state, body) do
Expand All @@ -934,6 +975,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
body.bls_to_execution_changes,
&process_bls_to_execution_change/2
)
|> for_ops(:deposit_request, body.execution_requests.deposits, &process_deposit_request/2)
|> for_ops(
:withdrawal_request,
body.execution_requests.withdrawals,
&process_withdrawal_request/2
)
|> for_ops(
:consolidation_request,
body.execution_requests.consolidations,
&process_consolidation_request/2
)
end
end

Expand All @@ -954,13 +1006,22 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do

@spec verify_deposits(BeaconState.t(), BeaconBlockBody.t()) :: :ok | {:error, String.t()}
defp verify_deposits(state, body) do
deposit_count = state.eth1_data.deposit_count - state.eth1_deposit_index
deposit_limit = min(ChainSpec.get("MAX_DEPOSITS"), deposit_count)
eth1_deposit_index_limit =
min(state.eth1_data.deposit_count, state.deposit_requests_start_index)

if length(body.deposits) == deposit_limit do
:ok
else
{:error, "deposits length mismatch"}
max_deposits = ChainSpec.get("MAX_DEPOSITS")

cond do
state.eth1_deposit_index < eth1_deposit_index_limit and
length(body.deposits) ==
min(max_deposits, eth1_deposit_index_limit - state.eth1_deposit_index) ->
:ok

state.eth1_deposit_index >= eth1_deposit_index_limit and Enum.empty?(body.deposits) ->
:ok

true ->
{:error, "Invalid deposits"}
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ defmodule LambdaEthereumConsensus.StateTransition do
|> epoch_op(:registry_updates, &EpochProcessing.process_registry_updates/1)
|> epoch_op(:slashings, &EpochProcessing.process_slashings/1)
|> epoch_op(:eth1_data_reset, &EpochProcessing.process_eth1_data_reset/1)
|> epoch_op(:pending_deposits, &EpochProcessing.process_pending_deposits/1)
|> epoch_op(:pending_consolidations, &EpochProcessing.process_pending_consolidations/1)
|> epoch_op(:effective_balance_updates, &EpochProcessing.process_effective_balance_updates/1)
|> epoch_op(:slashings_reset, &EpochProcessing.process_slashings_reset/1)
|> epoch_op(:randao_mixes_reset, &EpochProcessing.process_randao_mixes_reset/1)
Expand Down
Loading