diff --git a/electra-gap.md b/electra-gap.md index cbecad4b1..58696950d 100644 --- a/electra-gap.md +++ b/electra-gap.md @@ -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)) diff --git a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex index 08833bba3..a3024c8a3 100644 --- a/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex +++ b/lib/lambda_ethereum_consensus/state_transition/epoch_processing.ex @@ -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 @@ -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 diff --git a/lib/lambda_ethereum_consensus/state_transition/mutators.ex b/lib/lambda_ethereum_consensus/state_transition/mutators.ex index 53a839996..3112b58cd 100644 --- a/lib/lambda_ethereum_consensus/state_transition/mutators.ex +++ b/lib/lambda_ethereum_consensus/state_transition/mutators.ex @@ -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 """ @@ -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( diff --git a/lib/lambda_ethereum_consensus/state_transition/operations.ex b/lib/lambda_ethereum_consensus/state_transition/operations.ex index 5022663f6..c8f513631 100644 --- a/lib/lambda_ethereum_consensus/state_transition/operations.ex +++ b/lib/lambda_ethereum_consensus/state_transition/operations.ex @@ -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()} @@ -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 @@ -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 @@ -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 diff --git a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex index c20fdf891..9217e15bc 100644 --- a/lib/lambda_ethereum_consensus/state_transition/state_transition.ex +++ b/lib/lambda_ethereum_consensus/state_transition/state_transition.ex @@ -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) diff --git a/lib/types/beacon_chain/deposit.ex b/lib/types/beacon_chain/deposit.ex index 22808ac15..e7842095e 100644 --- a/lib/types/beacon_chain/deposit.ex +++ b/lib/types/beacon_chain/deposit.ex @@ -22,24 +22,29 @@ defmodule Types.Deposit do @spec get_validator_from_deposit(Types.bls_pubkey(), Types.bytes32(), Types.uint64()) :: Types.Validator.t() def get_validator_from_deposit(pubkey, withdrawal_credentials, amount) do - effective_balance = - min( - amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")), - ChainSpec.get("MAX_EFFECTIVE_BALANCE") - ) - far_future_epoch = Constants.far_future_epoch() - %Types.Validator{ + validator = %Types.Validator{ pubkey: pubkey, withdrawal_credentials: withdrawal_credentials, activation_eligibility_epoch: far_future_epoch, activation_epoch: far_future_epoch, exit_epoch: far_future_epoch, withdrawable_epoch: far_future_epoch, - effective_balance: effective_balance, + effective_balance: 0, slashed: false } + + effective_balance = + min( + amount - rem(amount, ChainSpec.get("EFFECTIVE_BALANCE_INCREMENT")), + Types.Validator.get_max_effective_balance(validator) + ) + + %Types.Validator{ + validator + | effective_balance: effective_balance + } end @impl LambdaEthereumConsensus.Container diff --git a/lib/types/beacon_chain/deposit_message.ex b/lib/types/beacon_chain/deposit_message.ex index 3ec9b904d..74bbd621d 100644 --- a/lib/types/beacon_chain/deposit_message.ex +++ b/lib/types/beacon_chain/deposit_message.ex @@ -3,6 +3,7 @@ defmodule Types.DepositMessage do Struct definition for `DepositMessage`. Related definitions in `native/ssz_nif/src/types/`. """ + alias LambdaEthereumConsensus.StateTransition.Misc use LambdaEthereumConsensus.Container fields = [ @@ -28,4 +29,23 @@ defmodule Types.DepositMessage do {:amount, TypeAliases.gwei()} ] end + + @spec valid_deposit_signature?( + Types.bls_pubkey(), + Types.bytes32(), + Types.gwei(), + Types.bls_signature() + ) :: boolean() + def valid_deposit_signature?(pubkey, withdrawal_credentials, amount, signature) do + deposit_message = %__MODULE__{ + pubkey: pubkey, + withdrawal_credentials: withdrawal_credentials, + amount: amount + } + + domain = Misc.compute_domain(Constants.domain_deposit()) + signing_root = Misc.compute_signing_root(deposit_message, domain) + + Bls.valid?(pubkey, signing_root, signature) + end end diff --git a/test/spec/runners/operations.ex b/test/spec/runners/operations.ex index a30290ad6..12aedc666 100644 --- a/test/spec/runners/operations.ex +++ b/test/spec/runners/operations.ex @@ -5,6 +5,9 @@ defmodule OperationsTestRunner do alias LambdaEthereumConsensus.StateTransition.Operations alias LambdaEthereumConsensus.Utils.Diff + alias Types.ConsolidationRequest + alias Types.DepositRequest + alias Types.WithdrawalRequest alias Types.Attestation alias Types.AttesterSlashing @@ -32,7 +35,10 @@ defmodule OperationsTestRunner do "sync_aggregate" => SyncAggregate, "execution_payload" => BeaconBlockBody, "withdrawals" => ExecutionPayload, - "bls_to_execution_change" => SignedBLSToExecutionChange + "bls_to_execution_change" => SignedBLSToExecutionChange, + "consolidation_request" => ConsolidationRequest, + "withdrawal_request" => WithdrawalRequest, + "deposit_request" => DepositRequest # "deposit_receipt" => "DepositReceipt" Not yet implemented } @@ -48,7 +54,10 @@ defmodule OperationsTestRunner do "sync_aggregate" => "sync_aggregate", "execution_payload" => "body", "withdrawals" => "execution_payload", - "bls_to_execution_change" => "address_change" + "bls_to_execution_change" => "address_change", + "consolidation_request" => "consolidation_request", + "withdrawal_request" => "withdrawal_request", + "deposit_request" => "deposit_request" # "deposit_receipt" => "deposit_receipt" Not yet implemented }