The first step to becoming a validator is, of course, staking. This is accomplished through the special deposit contract, whose code can be found
here. The contract is deployed at this
address, where we can see deposits of 32 ETH each (Note that these amounts will change after the upcoming
Electra hardfork, allowing effective balances of up to
2048 ETH).
The important step in the process is
passing the validator's public key. There are no checks for the signature provided to the contract; all data is simply added to the Merkle tree of deposits. These checks are deferred to the Beacon layer. Additionally, the deposit contract does not send ETH back because validator deposits are later processed at the Beacon layer, not the execution (Eth1) layer.
The most critical function of this contract (until
EIP-6110) is
emitting the
Deposit (pubkey, withdrawal_credentials, amount, ...) event. The logs of Eth1 blocks are continuously
parsed in the Beacon layer and then
processed, including a BLS signature
validation.
Deposits from potential validators are included in an incremental Merkle tree, which can be reconstructed by parsing events from the deposit contract. Proofs of inclusion are required at the consensus (Beacon) layer to verify the validity of deposits. These mechanics are detailed
here. The first deposit from any validator requires a signature to prove ownership of the corresponding private key. Interestingly, subsequent deposits for the same validator do not require this signature, allowing anyone to top up the validator's balance.
Our target is to reach the
add_validator_to_registry() function, which adds the validator to the
self.validators list. This action is performed in the
apply_deposit() function. Here, the signature is verified, and the deposit is added to a "pending deposits" list (
this branch). This function also
verifies the Merkle proof of deposit inclusion, as mentioned earlier.
It is also worth noting that the current interaction between the execution and consensus layers via log parsing is not very efficient. There is an ongoing proposal (
EIP-6110) to handle all deposit logic directly in the Eth1 blocks, skipping interactions with deposit contract. This change could significantly reduce the current delays in deposit inclusion. Furthermore, currently, any deposit with an invalid signature is ignored and cannot be refunded, as the consensus layer does not directly interact with the execution layer.
Submitting a deposit and its inclusion in the validators list does not activate a validator immediately (recall that the deposit is placed in the "pending" deposits queue). Validators must go through the activation queue. While deposits are processed at every execution block (to ensure no events are missed), validator status updates occur "per-epoch" (as shown in the
processing). At this stage, the
activation_churn_limit parameter determines how many validators can be taken from the queue per epoch. This limit is capped by the 1 / 65536 (
spec.churn_limit_quotient (65536)) fraction of the total validator count. The minimal value is
spec.min_per_epoch_churn_limit (4), which is also used for validator exits, regulating the whole "churn limit" for both validator activations and exits. If the activation queue is full, a validator will wait for the next epochs to become eligible for activation.
We previously encountered
registry_updates here. Now, let's explore this
procedure in greater depth. We see two branches,
pre_electra and
post_electra. Both branches first verify the validator's eligibility by checking its effective balance through
these "pre_electra" and "post_electra" checks. For "pre_electra," the effective balance must be exactly 32 ETH, while for "post_electra," it must be greater or equal to 32 ETH (defined by
spec.min_activation_balance(32 ETH)).
Both functions trigger the automatic exit of validators whose effective balance falls below
spec.ejection_balance(16 ETH).
Next comes the main "activation" process, which assigns the
validator.activation_epoch. In "pre_electra," this is done through the activation queue (
here), while in "post_electra," it is processed without the queue by verifying that eligibility was established in finalized blocks (
here).
Finally, our validator becomes active at the
validator.activation_epoch. This epoch is
determined by incrementing the current epoch and adding the
spec.max_seed_lookahead (4 epochs, ~25.6 minutes).