Part 2: Technical Overview
Deposits and 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).
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.
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
Here's validator zero's original BLS withdrawal credential. Note the initial
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
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.
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
It checks that,
- the validator currently has
- the hash of the public withdrawal key (generated from the secret withdrawal key) matches the withdrawal credentials created when the deposit was made, and
- 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
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.
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.
To find the withdrawals it must include, the block proposer calls the
get_expected_withdrawals() function. This will return a list of up to
Withdrawal objects, each containing the following information.
class Withdrawal(Container): index: WithdrawalIndex validator_index: ValidatorIndex address: ExecutionAddress amount: Gwei
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:
- The validator has exited, has become withdrawable, and has a non-zero balance. Such a validator is eligible for a full withdrawal.
- The validator has an effective balance of
MAX_EFFECTIVE_BALANCE(32 ETH), and an actual balance higher than that. Such a validator is eligible for a partial withdrawal.
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.
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.
- The beacon block proposer assembles a list of withdrawals by calling
get_expected_withdrawals()as detailed above.
- 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
- The execution client returns an execution payload that includes the list of withdrawals, along with everything else.
- The block proposer incorporates the execution payload into its beacon block and broadcasts it to the network.
- 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.
- 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 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
- The validator has Eth1 withdrawal credentials.
- The validator's effective balance is
- The validator's actual balance exceeds
The amount of the partial withdrawal will be the validator's balance in excess of
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.
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.
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.
- The constants
next_withdrawal_validator_indexin the beacon state.
Withdrawalcontainer, and the
withdrawalslist in the
process_withdrawals()in block processing.
- Preparing the
ExecutionPayloadin the Capella honest validator guide, and
PayloadAttributesV2in the Execution API.
- EIP-4895, "Beacon chain push withdrawals as operations", covers the execution layer side.
And for credential changes.
- Withdrawal prefixes in constants.
process_bls_to_execution_change()in block processing.
submitPoolBLSToExecutionChangemethod of the Beacon API.
- 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
0x00then they are BLS, if they begin
0x01then they are Eth1.↩
- Take care when using the
staking-deposit-clito make a deposit. It (silently) defaults to BLS withdrawal credentials unless you specify the
--eth1_withdrawal_addresscommand line parameter.↩