Part 2: Technical Overview

Deposits and Withdrawals

Withdrawals

  • Consensus layer withdrawals were enabled in the Capella upgrade.
  • A validator must have Eth1 withdrawal credentials to benefit from withdrawals.
  • A one-time update from BLS credentials to Eth1 credentials is possible.
  • Withdrawals are automatic and periodic.
  • Up to 16 withdrawals per block can be processed.
  • A withdrawal might be partial (for active validators) or full (for exited validators).

Background

The ability to make withdrawals from the consensus layer was enabled in the Capella upgrade, the first upgrade after the Merge.

Clearly, a fully-functioning proof of stake system needs ways both to stake and to unstake. However, for the first 29 months of the beacon chain's life, only staking was possible. All stakes, and all rewards earned, were locked within the consensus layer.

To have made withdrawals available pre-Merge would have needed a bridge from the beacon chain to Ethereum's proof of work chain, perhaps via some kind of beacon chain light-client implementation on the Eth1 side. This was deemed to be a complex project that would only have delayed the Merge.

For similar reasons, we didn't enable withdrawals at the time of the Merge, either. The Merge on its own was complex and carried risk. We did what we could to simplify and de-risk it as much as we could, which included postponing withdrawals once again.

Eventually, fulfilling the core devs' soft commitment to the Ethereum community, withdrawals were successfully enabled in the first post-Merge upgrade, Capella, on April the 12th, 2023.

Two approaches were considered for enabling beacon chain Ether to be recovered on the execution chain.

The first design was for pull withdrawals. After a validator had exited, the consensus layer would create a receipt that the staker could manually submit to the execution layer as a normal Ethereum transaction in order to recover the staked Ether and rewards. In a kind of mirroring of deposits, the consensus layer would maintain a Merkle tree of withdrawal receipts, exposing its root to the execution layer so that the withdrawal receipts could be validated when submitted there. Partial withdrawals were not really addressed in that work.

The adopted design, though, was for push withdrawals, as described below. Push withdrawals happen automatically and do not require any action by the staker. This approach provides a better user experience, and required barely any increase to the beacon state size. It takes advantage of the post-Merge Engine API as a bridge between the execution and consensus layers.

Withdrawal Credentials

When the beacon chain was conceived, it was to be only the first phase (Phase 0) of the much larger Ethereum 2.0 project. It wasn't at all clear at that time what would happen to the existing Ethereum 1.0 chain, what kind of accounts would be implemented in Eth2.0, what kind of signature schemes would be used, and so on.

In view of the unknowns, we decided to implement withdrawal credentials as a commitment to being able to withdraw somehow in the future, even though we had little idea what that might look like. The staker would keep a BLS withdrawal key, and, via the BLS withdrawal credentials, would be able to prove ownership of the validator's balance.

The beacon chain launched with only BLS withdrawal credentials, and all the early validators used these. Eth1 withdrawal credentials were committed to in the specs in February, 2021, only three months or so into the beacon chain's life. Since no validation is done on withdrawal credentials when a deposit is made, stakers continue to be free to use whichever they prefer. At the point of the Capella upgrade, 322,491 validators (56.9%) had BLS credentials, and 244,653 (43.1%) had Eth1 credentials1.

BLS withdrawal credentials

BLS withdrawal credentials are often called 0x00 credentials due to their prefix. You can think of them as version zero credentials. A BLS withdrawal credential is the 32-byte hash of a 48-byte BLS public key, with the first byte replaced by 0x00.

Here's validator zero's original BLS withdrawal credential. Note the initial 0x00 byte.

0x00f50428677c60f997aadeab24aabf7fceaef491c96a52b463ae91f95611cf71

The idea is that the staker has a second BLS secret key, a withdrawal key, in addition to their usual signing key. The commitment from the protocol devs was that the withdrawal key could be used in future to sign a credential change message. The BLS withdrawal credential ensures that the claimed public key on that message matches the deposit that was initially made, so only the original depositor can access the stake and the rewards.

Separating the withdrawal key from the normal signing key has a number of benefits. Primarily, it separates ownership of the stake (controlled by the withdrawal key) from management of the stake (controlled by the signing key). This has allowed "non-custodial" staking services to appear, in which the staking service uses the signing key for day-to-day operations, but they have no ownership of the stake or rewards since the individual staker retains the withdrawal key. It also allows the withdrawal key to be kept offline, in cold storage, while the signing key remains online and "hot".

For ease of recovery, the BLS withdrawal key can be generated from the same mnemonic as the signing key by using a slightly different derivation path, as described in ERC-2334. This is what the staking-deposit-cli tool does.

Eth1 withdrawal credentials

Unless you want to keep your Ether locked up on the consensus layer for some reason, everybody should now use Eth1 withdrawal credentials. These are set either when staking2, or by sending a BLS to execution change message.

An Eth1 (execution) withdrawal credential has the prefix 0x01, followed by eleven zero bytes, followed by the 20 bytes of a normal Ethereum address. That address is where all Ether from withdrawals will be sent.

Here's validator zero's current Eth1 withdrawal credential. Note the initial 0x01 byte.

0x0100000000000000000000000d369bb49efa5100fd3b86a9f828c55da04d2d50

The withdrawal address may be a normal Ethereum account (an EOA) or a smart contract. However, when it is a smart contract, no code will be executed on receiving a withdrawal payout. This differs from receiving Ether via a transfer, which can cause a fallback function to be called.

Credential changes

Only validators that have Eth1 withdrawal credentials are eligible for withdrawals. Validators with BLS withdrawal credentials need to send a withdrawal credential change message to update to Eth1 credentials. Until they do this, their stake and rewards remain locked on the consensus layer.

Changing withdrawal credentials is a one-time operation. Once a validator has Eth1 credentials, no further change is possible. The only way to change your withdrawal payout address once it has been set is to exit your validator and re-stake with the new credentials.

Making a credential change

A staker whose validator has BLS withdrawal credentials, who wishes to change to Eth1 credentials, must send a message to the beacon chain, signed with the validator's withdrawal key. The staking-deposit-cli and the ethdo tool are both able to generate this message. It is a straightforward process, requiring the validator's public key, the mnemonic used when staking, and the validator's existing withdrawal credentials as a checksum. It is recommended that generating the message be done offline as the BLS withdrawal key remains highly valuable to hackers until after the credential change has been completed.

Once the credential change message has been generated, it needs to be uploaded to a beacon node to be broadcast to the network. This can be done via any beacon node's REST API, or via the Beaconcha.in explorer's handy signed message submission service.

Some time after the message has been uploaded and broadcast, a block proposer should include the credential change message in a beacon block for processing by the consensus layer. Up to MAX_BLS_TO_EXECUTION_CHANGES (16) such messages can be included per block.

For reference, a BLS to Eth1 credential change message has the following contents.

class BLSToExecutionChange(Container):
    validator_index: ValidatorIndex
    from_bls_pubkey: BLSPubkey
    to_execution_address: ExecutionAddress
Processing a credential change

Credential change messages are handled during block processing by the process_bls_to_execution_change() function.

It checks that,

  1. the validator currently has 0x00 BLS credentials,
  2. the hash of the public withdrawal key (generated from the secret withdrawal key) matches the withdrawal credentials created when the deposit was made, and
  3. the signature on the message verifies against the public withdrawal key provided.

Once satisfied that all is correct, the validator's withdrawal credentials are irrevocably updated to Eth1 withdrawal credentials. The validator is now eligible for automatic push withdrawals that will be made to the to_execution_address provided in the BLSToExecutionChange data.

To reiterate, changing withdrawal credentials is a one-time process. You can only change from BLS to Eth1 credentials. It is not possible to change Eth1 credentials without exiting the validator and re-staking.

Withdrawal processing

As mentioned above, we decided to adopt a "push" mechanism for withdrawals. Push withdrawals happen automatically, with no intervention from the staker. Up to MAX_WITHDRAWALS_PER_PAYLOAD (16) consensus layer withdrawals are made per block.

Validator withdrawals are processed in a round-robin fashion. Starting from validator 0 at the Capella upgrade, with each block, the consensus layer sweeps through the validator set in validator index order until it has found 16 withdrawals to include. The next block proposer will pick up where the previous proposer left off in the validator set and scan for 16 further withdrawals, and so on. If every validator were eligible for a withdrawal, and if the beacon chain is performing perfectly, then a full sweep of 576,000 validators would take 5 days. That is, a validator could expect to receive a partial withdrawal payout every 5 days.

Finding withdrawals

To find the withdrawals it must include, the block proposer calls the get_expected_withdrawals() function. This will return a list of up to MAX_WITHDRAWALS_PER_PAYLOAD Withdrawal objects, each containing the following information.

class Withdrawal(Container):
    index: WithdrawalIndex
    validator_index: ValidatorIndex
    address: ExecutionAddress
    amount: Gwei

The index field is the number of previous withdrawals ever made. It is populated from state.next_withdrawal_index and increases by one for each withdrawal. It is used only for uniquely indexing withdrawals in the execution layer. The validator_index is, of course, the validator whose beacon chain balance will be decreased, and whose Eth1 withdrawal address balance (the address field here) will be increased.

The list of withdrawals is generated deterministically. The block proposer starts from the current value of state.next_withdrawal_validator_index and considers validators in turn. If a validator is eligible for a withdrawal then it is added to the list, otherwise it is skipped. When either MAX_WITHDRAWALS_PER_PAYLOAD withdrawals have been added, or MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP have been considered, the list is returned. If the end of the validator registry is hit, the search wraps around again to validator 0.

To be eligible for a withdrawal, a validator must have Eth1 withdrawal credentials set, and one of the following must also apply:

Both of these may be true at once, in which case the first takes priority and a full withdrawal will be made for the validator.

Usually, a full list of 16 withdrawals will be generated. However, the search is bounded by considering a maximum of MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP validators. If this limit is hit, fewer than 16 withdrawals will be generated.

The reason for limiting the search, rather than making a full sweep through the whole validator set, is to bound the computational load on consensus nodes. Accessing the validator registry can be quite costly; an unbounded sweep could become a performance bottleneck.

There are two scenarios in which the bound might become relevant. The first is if there were long sections of the validator registry in which no validator had upgraded to Eth1 withdrawal credentials. This was more of a concern at the point of the Capella upgrade, when all early validators necessarily had BLS withdrawal credentials. More interesting is the possibility of an inactivity leak, which occurs when the chain stops finalising in a timely way. During an inactivity leak, no validator receives attestation rewards, and many validators receive extra inactivity penalties. Very few balances will be increasing - only block proposers and sync committee members, perhaps. During a prolonged inactivity leak it is possible that large sections of the validator registry would be ineligible for a withdrawal, and the bound on the withdrawals sweep would be enforced.

Performing withdrawals

The consensus layer and the execution layer must coordinate carefully in order to process a withdrawal, which makes the full round-trip a little convoluted. The steps are as follows.

  1. The beacon block proposer assembles a list of withdrawals by calling get_expected_withdrawals() as detailed above.
  2. The beacon block proposer sends the withdrawals list to its attached execution client via the Engine API. See prepare_execution_payload() in the Honest Validator spec. The relevant Engine API data structure is PayloadAttributesV2.
  3. The execution client returns an execution payload that includes the list of withdrawals, along with everything else.
  4. The block proposer incorporates the execution payload into its beacon block and broadcasts it to the network.
  5. On receiving the block, all consensus nodes extract the withdrawals list from the execution payload and call process_withdrawals() to deduct the withdrawal amounts from the validators' balances. Each node calls get_expected_withdrawals() independently, and the beacon block is valid only if its withdrawals list matches.
  6. The consensus node sends the execution payload to its attached execution client. The execution client will process the withdrawals alongside all the other transactions in the payload, incrementing the Eth1 withdrawal addresses as required.

The execution layer mechanics of withdrawal processing are described in EIP-4895. Withdrawal transactions are processed after all the normal transactions in the block.

As mentioned above, withdrawal transactions don't trigger smart contract processing when the Eth1 account balance is incremented. This is primarily to avoid failures (EVM transaction reversions) that would complicate the entire process. It also avoids placing an unknown load on the execution client. The benefit of this is that withdrawals are gasless and therefore free. The receiving account's balance will increase by precisely the same amount of ETH as is deducted from the validator's beacon chain balance.

Partial and full withdrawals

A validator might be eligible for a partial withdrawal or for a full withdrawal. Neither type is prioritised over the other; they both occur alongside each other as validators are considered during the withdrawals sweep. If a validator is eligible for both, a full withdrawal will be performed.

In both cases, there is no minimum withdrawal amount - it could be a single Gwei, the beacon chain's smallest unit of account. Withdrawals are not created for zero amounts.

Partial withdrawals

Partial withdrawals make up the majority of withdrawals processed. Partial withdrawals periodically skim excess balance from validators as they earn rewards.

To be eligible for a partial withdrawal, all of the following must be true. The second and third criteria are checked in the is_partially_withdrawable_validator() predicate.

The amount of the partial withdrawal will be the validator's balance in excess of MAX_EFFECTIVE_BALANCE.

The condition on the validator's effective balance eliminates an edge case where a validator has an effective balance of 31 ETH, but an actual balance of over 32 ETH, which can arise due to hysteresis. If the condition on effective balance were not applied, it might become impossible for a validator ever to regain a full effective balance of 32 ETH (without a top-up deposit), due to its balance being continually skimmed.

Full withdrawals

A full withdrawal takes place after a validator has exited the validator set and subsequently become withdrawable. A validator normally becomes withdrawable about 27 hours after making its way through the exit queue, but a slashed validator will take much longer.

The precise criteria are in the is_fully_withdrawable_validator() predicate. All of the following must apply.

  • The validator has Eth1 withdrawal credentials.
  • The validator is withdrawable (the current epoch is greater than or equal to its withdrawable epoch).
  • The validator has a non-zero balance.

The amount of the full withdrawal will be the validator's entire balance.

Note that there is no flag to indicate that a validator has been withdrawn. This in principle allows a top-up deposit to be made for a validator after a full withdrawal has occurred, in which case another full withdrawal will occur, and the top-up amount will be returned to the execution layer.

See also

The Ethereum.org pages have a section on withdrawals, and there is a separate withdrawals FAQ. They both have plenty of links to further resources.

The relevant spec functions and data structures for withdrawals are as follows.

And for credential changes.


  1. You can check what type of credentials your validator has via its page on the Beaconcha.in explorer. Go to the "Deposits" tab. If your credentials begin 0x00 then they are BLS, if they begin 0x01 then they are Eth1.
  2. Take care when using the staking-deposit-cli to make a deposit. It (silently) defaults to BLS withdrawal credentials unless you specify the --eth1_withdrawal_address command line parameter.

Created by Ben Edgington. Licensed under CC BY-SA 4.0. Published 2023-09-29 14:16 UTC. Commit ebfcf50.