Skip to content

Commit c4bec8f

Browse files
authored
feat: implement electra deposits (#1424)
1 parent b19639b commit c4bec8f

File tree

8 files changed

+311
-40
lines changed

8 files changed

+311
-40
lines changed

electra-gap.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,26 @@ This document outlines the gaps in the current implementation of the Electra. It
6262
- [ ] Modified `process_epoch` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_epoch))
6363
- [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))
6464
- [x] Modified `process_slashings` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_slashings), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1417))
65-
- [ ] New `apply_pending_deposit` ([Spec](docs/specs/electra/beacon-chain.md#new-apply_pending_deposit))
66-
- [ ] New `process_pending_deposits` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_deposits))
65+
- [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))
66+
- [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))
6767
- [ ] New `process_pending_consolidations` ([Spec](docs/specs/electra/beacon-chain.md#new-process_pending_consolidations))
6868
- [ ] Modified `process_effective_balance_updates` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_effective_balance_updates))
69+
- [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))
6970

7071
## Block Processing
7172

7273
- [ ] Modified `process_withdrawals` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_withdrawals))
7374
- [ ] Modified `process_execution_payload` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_execution_payload))
74-
- [ ] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations))
75+
- [x] Modified `process_operations` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_operations), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
7576
- [ ] Modified `process_attestation` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_attestation))
76-
- [ ] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit))
77+
- [x] Modified `process_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
7778
- [ ] Modified `process_voluntary_exit` ([Spec](docs/specs/electra/beacon-chain.md#modified-process_voluntary_exit))
7879
- [ ] New `process_withdrawal_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_withdrawal_request))
79-
- [ ] New `process_deposit_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_deposit_request))
80+
- [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))
8081
- [ ] New `process_consolidation_request` ([Spec](docs/specs/electra/beacon-chain.md#new-process_consolidation_request))
81-
82+
- [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))
83+
- [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))
84+
- [x] Modified `apply_deposit` ([Spec](docs/specs/electra/beacon-chain.md#modified-apply_deposit), [PR](https://github.com/lambdaclass/lambda_ethereum_consensus/pull/1424))
8285
## Execution Engine
8386

8487
- [ ] Modified `is_valid_block_hash` ([Spec](docs/specs/electra/beacon-chain.md#modified-is_valid_block_hash))

lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex

+151
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
1010
alias LambdaEthereumConsensus.Utils.BitVector
1111
alias LambdaEthereumConsensus.Utils.Randao
1212
alias Types.BeaconState
13+
alias Types.DepositMessage
1314
alias Types.HistoricalSummary
1415
alias Types.Validator
1516

@@ -454,4 +455,154 @@ defmodule LambdaEthereumConsensus.StateTransition.EpochProcessing do
454455
max(balance + delta, 0)
455456
end)
456457
end
458+
459+
@spec process_pending_deposits(BeaconState.t()) :: {:ok, BeaconState.t()}
460+
def process_pending_deposits(%BeaconState{} = state) do
461+
available_for_processing =
462+
state.deposit_balance_to_consume + Accessors.get_activation_exit_churn_limit(state)
463+
464+
finalized_slot = Misc.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
465+
466+
{state, churn_limit_reached, processed_amount, deposits_to_postpone, last_processed_index} =
467+
state.pending_deposits
468+
|> Enum.with_index()
469+
|> Enum.reduce_while({state, false, 0, [], 0}, fn {deposit, index},
470+
{state, churn_limit_reached,
471+
processed_amount, deposits_to_postpone,
472+
_last_processed_index} ->
473+
cond do
474+
# Do not process deposit requests if Eth1 bridge deposits are not yet applied.
475+
deposit.slot > Constants.genesis_slot() &&
476+
state.eth1_deposit_index < state.deposit_requests_start_index ->
477+
{:halt,
478+
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
479+
480+
# Check if deposit has been finalized, otherwise, stop processing.
481+
deposit.slot > finalized_slot ->
482+
{:halt,
483+
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
484+
485+
# Check if number of processed deposits has not reached the limit, otherwise, stop processing.
486+
index >= ChainSpec.get("MAX_PENDING_DEPOSITS_PER_EPOCH") ->
487+
{:halt,
488+
{state, churn_limit_reached, processed_amount, deposits_to_postpone, index - 1}}
489+
490+
true ->
491+
handle_pending_deposit(
492+
deposit,
493+
state,
494+
churn_limit_reached,
495+
processed_amount,
496+
deposits_to_postpone,
497+
index,
498+
available_for_processing
499+
)
500+
end
501+
end)
502+
503+
deposit_balance_to_consume =
504+
if churn_limit_reached do
505+
available_for_processing - processed_amount
506+
else
507+
0
508+
end
509+
510+
{:ok,
511+
%BeaconState{
512+
state
513+
| pending_deposits:
514+
Enum.drop(state.pending_deposits, last_processed_index + 1)
515+
|> Enum.concat(deposits_to_postpone),
516+
deposit_balance_to_consume: deposit_balance_to_consume
517+
}}
518+
end
519+
520+
defp handle_pending_deposit(
521+
deposit,
522+
state,
523+
churn_limit_reached,
524+
processed_amount,
525+
deposits_to_postpone,
526+
index,
527+
available_for_processing
528+
) do
529+
far_future_epoch = Constants.far_future_epoch()
530+
next_epoch = Accessors.get_current_epoch(state)
531+
532+
{is_validator_exited, is_validator_withdrawn} =
533+
case Enum.find(state.validators, fn v -> v.pubkey == deposit.pubkey end) do
534+
%Validator{} = validator ->
535+
{validator.exit_epoch < far_future_epoch, validator.withdrawable_epoch < next_epoch}
536+
537+
_ ->
538+
{false, false}
539+
end
540+
541+
cond do
542+
# Deposited balance will never become active. Increase balance but do not consume churn
543+
is_validator_withdrawn ->
544+
{:ok, state} = apply_pending_deposit(state, deposit)
545+
546+
{:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}
547+
548+
# Validator is exiting, postpone the deposit until after withdrawable epoch
549+
is_validator_exited ->
550+
deposits_to_postpone = Enum.concat(deposits_to_postpone, [deposit])
551+
552+
{:cont, {state, churn_limit_reached, processed_amount, deposits_to_postpone, index}}
553+
554+
true ->
555+
# Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
556+
is_churn_limit_reached =
557+
processed_amount + deposit.amount > available_for_processing
558+
559+
if is_churn_limit_reached do
560+
{:halt, {state, true, processed_amount, deposits_to_postpone, index - 1}}
561+
else
562+
# Consume churn and apply deposit.
563+
processed_amount = processed_amount + deposit.amount
564+
{:ok, state} = apply_pending_deposit(state, deposit)
565+
{:cont, {state, false, processed_amount, deposits_to_postpone, index}}
566+
end
567+
end
568+
end
569+
570+
@spec process_pending_consolidations(BeaconState.t()) :: {:ok, BeaconState.t()}
571+
def process_pending_consolidations(%BeaconState{} = state) do
572+
# TODO: Not implemented yet
573+
{:ok, state}
574+
end
575+
576+
defp apply_pending_deposit(state, deposit) do
577+
index =
578+
Enum.find_index(state.validators, fn validator -> validator.pubkey == deposit.pubkey end)
579+
580+
current_validator? = is_number(index)
581+
582+
valid_signature? =
583+
current_validator? ||
584+
DepositMessage.valid_deposit_signature?(
585+
deposit.pubkey,
586+
deposit.withdrawal_credentials,
587+
deposit.amount,
588+
deposit.signature
589+
)
590+
591+
cond do
592+
current_validator? ->
593+
{:ok, BeaconState.increase_balance(state, index, deposit.amount)}
594+
595+
!current_validator? && valid_signature? ->
596+
Mutators.add_validator_to_registry(
597+
state,
598+
deposit.pubkey,
599+
deposit.withdrawal_credentials,
600+
deposit.amount
601+
)
602+
603+
true ->
604+
# Neither a validator nor have a valid signature, we do not apply the deposit
605+
{:ok, state}
606+
end
607+
end
457608
end

lib/lambda_ethereum_consensus/state_transition/mutators.ex

+38-18
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
55
alias LambdaEthereumConsensus.StateTransition.Accessors
66
alias LambdaEthereumConsensus.StateTransition.Misc
77
alias Types.BeaconState
8+
alias Types.DepositMessage
9+
alias Types.PendingDeposit
810
alias Types.Validator
911

1012
@doc """
@@ -122,31 +124,49 @@ defmodule LambdaEthereumConsensus.StateTransition.Mutators do
122124
Types.uint64(),
123125
Types.bls_signature()
124126
) :: {:ok, BeaconState.t()} | {:error, String.t()}
125-
def apply_deposit(state, pubkey, withdrawal_credentials, amount, signature) do
126-
case Enum.find_index(state.validators, fn validator -> validator.pubkey == pubkey end) do
127-
index when is_number(index) ->
128-
{:ok, BeaconState.increase_balance(state, index, amount)}
129-
130-
_ ->
131-
deposit_message = %Types.DepositMessage{
132-
pubkey: pubkey,
133-
withdrawal_credentials: withdrawal_credentials,
134-
amount: amount
135-
}
136-
137-
domain = Misc.compute_domain(Constants.domain_deposit())
127+
def apply_deposit(state, pubkey, withdrawal_credentials, amount, sig) do
128+
current_validator? = Enum.any?(state.validators, &(&1.pubkey == pubkey))
138129

139-
signing_root = Misc.compute_signing_root(deposit_message, domain)
130+
valid_signature? =
131+
current_validator? ||
132+
DepositMessage.valid_deposit_signature?(pubkey, withdrawal_credentials, amount, sig)
140133

141-
if Bls.valid?(pubkey, signing_root, signature) do
142-
apply_initial_deposit(state, pubkey, withdrawal_credentials, amount)
134+
if !current_validator? && !valid_signature? do
135+
# Neither a validator nor have a valid signature, we do not apply the deposit
136+
{:ok, state}
137+
else
138+
updated_state =
139+
if current_validator? do
140+
state
143141
else
144-
{:ok, state}
142+
{:ok, state} = add_validator_to_registry(state, pubkey, withdrawal_credentials, 0)
143+
state
145144
end
145+
146+
deposit = %PendingDeposit{
147+
pubkey: pubkey,
148+
withdrawal_credentials: withdrawal_credentials,
149+
amount: amount,
150+
signature: sig,
151+
slot: Constants.genesis_slot()
152+
}
153+
154+
{:ok,
155+
%BeaconState{
156+
updated_state
157+
| pending_deposits: updated_state.pending_deposits ++ [deposit]
158+
}}
146159
end
147160
end
148161

149-
defp apply_initial_deposit(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
162+
@spec add_validator_to_registry(
163+
BeaconState.t(),
164+
Types.bls_pubkey(),
165+
Types.bytes32(),
166+
Types.gwei()
167+
) ::
168+
{:ok, BeaconState.t()}
169+
def add_validator_to_registry(%BeaconState{} = state, pubkey, withdrawal_credentials, amount) do
150170
Types.Deposit.get_validator_from_deposit(pubkey, withdrawal_credentials, amount)
151171
|> then(&Aja.Vector.append(state.validators, &1))
152172
|> then(

lib/lambda_ethereum_consensus/state_transition/operations.ex

+67-6
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,21 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
1313
alias LambdaEthereumConsensus.Utils.BitList
1414
alias LambdaEthereumConsensus.Utils.BitVector
1515
alias LambdaEthereumConsensus.Utils.Randao
16+
alias Types.PendingDeposit
1617

1718
alias Types.Attestation
1819
alias Types.BeaconBlock
1920
alias Types.BeaconBlockBody
2021
alias Types.BeaconBlockHeader
2122
alias Types.BeaconState
23+
alias Types.ConsolidationRequest
24+
alias Types.DepositRequest
2225
alias Types.ExecutionPayload
2326
alias Types.ExecutionPayloadHeader
2427
alias Types.SyncAggregate
2528
alias Types.Validator
2629
alias Types.Withdrawal
30+
alias Types.WithdrawalRequest
2731

2832
@spec process_block_header(BeaconState.t(), BeaconBlock.t()) ::
2933
{:ok, BeaconState.t()} | {:error, String.t()}
@@ -918,6 +922,43 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
918922
end
919923
end
920924

925+
@spec process_deposit_request(BeaconState.t(), DepositRequest.t()) :: {:ok, BeaconState.t()}
926+
def process_deposit_request(state, deposit_request) do
927+
start_index =
928+
if state.deposit_requests_start_index == Constants.unset_deposit_requests_start_index(),
929+
do: deposit_request.index,
930+
else: state.deposit_requests_start_index
931+
932+
deposit = %PendingDeposit{
933+
pubkey: deposit_request.pubkey,
934+
withdrawal_credentials: deposit_request.withdrawal_credentials,
935+
amount: deposit_request.amount,
936+
signature: deposit_request.signature,
937+
slot: state.slot
938+
}
939+
940+
{:ok,
941+
%BeaconState{
942+
state
943+
| deposit_requests_start_index: start_index,
944+
pending_deposits: state.pending_deposits ++ [deposit]
945+
}}
946+
end
947+
948+
@spec process_withdrawal_request(BeaconState.t(), WithdrawalRequest.t()) ::
949+
{:ok, BeaconState.t()}
950+
def process_withdrawal_request(state, _withdrawal_request) do
951+
# TODO: Not implemented yet
952+
{:ok, state}
953+
end
954+
955+
@spec process_consolidation_request(BeaconState.t(), ConsolidationRequest.t()) ::
956+
{:ok, BeaconState.t()}
957+
def process_consolidation_request(state, _consolidation_request) do
958+
# TODO: Not implemented yet
959+
{:ok, state}
960+
end
961+
921962
@spec process_operations(BeaconState.t(), BeaconBlockBody.t()) ::
922963
{:ok, BeaconState.t()} | {:error, String.t()}
923964
def process_operations(state, body) do
@@ -934,6 +975,17 @@ defmodule LambdaEthereumConsensus.StateTransition.Operations do
934975
body.bls_to_execution_changes,
935976
&process_bls_to_execution_change/2
936977
)
978+
|> for_ops(:deposit_request, body.execution_requests.deposits, &process_deposit_request/2)
979+
|> for_ops(
980+
:withdrawal_request,
981+
body.execution_requests.withdrawals,
982+
&process_withdrawal_request/2
983+
)
984+
|> for_ops(
985+
:consolidation_request,
986+
body.execution_requests.consolidations,
987+
&process_consolidation_request/2
988+
)
937989
end
938990
end
939991

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

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

960-
if length(body.deposits) == deposit_limit do
961-
:ok
962-
else
963-
{:error, "deposits length mismatch"}
1012+
max_deposits = ChainSpec.get("MAX_DEPOSITS")
1013+
1014+
cond do
1015+
state.eth1_deposit_index < eth1_deposit_index_limit and
1016+
length(body.deposits) ==
1017+
min(max_deposits, eth1_deposit_index_limit - state.eth1_deposit_index) ->
1018+
:ok
1019+
1020+
state.eth1_deposit_index >= eth1_deposit_index_limit and Enum.empty?(body.deposits) ->
1021+
:ok
1022+
1023+
true ->
1024+
{:error, "Invalid deposits"}
9641025
end
9651026
end
9661027
end

lib/lambda_ethereum_consensus/state_transition/state_transition.ex

+2
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ defmodule LambdaEthereumConsensus.StateTransition do
156156
|> epoch_op(:registry_updates, &EpochProcessing.process_registry_updates/1)
157157
|> epoch_op(:slashings, &EpochProcessing.process_slashings/1)
158158
|> epoch_op(:eth1_data_reset, &EpochProcessing.process_eth1_data_reset/1)
159+
|> epoch_op(:pending_deposits, &EpochProcessing.process_pending_deposits/1)
160+
|> epoch_op(:pending_consolidations, &EpochProcessing.process_pending_consolidations/1)
159161
|> epoch_op(:effective_balance_updates, &EpochProcessing.process_effective_balance_updates/1)
160162
|> epoch_op(:slashings_reset, &EpochProcessing.process_slashings_reset/1)
161163
|> epoch_op(:randao_mixes_reset, &EpochProcessing.process_randao_mixes_reset/1)

0 commit comments

Comments
 (0)