Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7804: Withdrawal Credential Update Request

Allow validators to update their withdrawal credentials via execution requests

Authors Lucas Saldanha (@lucassaldanha), Mikhail Kalinin (@mkalinin)
Created 2024-10-31
Discussion Link https://ethereum-magicians.org/t/eip-7804-withdrawal-credential-update-request/21514

Abstract

This proposal defines a mechanism to allow validators to update their withdrawal credentials using a new execution request type (0x03). The request allows for changing the execution address and the withdrawal credential prefix (0x01 or 0x02).

Motivation

When the ability to update a validator BLS withdrawal credentials to execution address was introduced in Capella, one of the most common questions was about allowing the withdrawal credential to be changed in the future. Either for security (e.g. credential rotation) or to allow for alternative ways of handling withdrawals (e.g. having a contract address as credentials). The main reason for not adding this options was because implementing this communication channel between the Execution Layer and the Consensus Layer is complex (based on the experience with the Eth1 bridge).

In Electra, the protocol was upgraded with Execution Requests (deposits, withdrawals and consolidations), and a mechanism for general purpose execution requests EIP-7685, decreasing the complexity of adding a new request from execution to consensus layer.

The introduction of execution requests that are created on the execution layer, opened up possibilities on how validators can be managed. Execution request can be created via smart contracts, allowing for decentralized and on-chain mechanisms to be explored. This also means validators will be able to move between execution and compounding withdrawal credentials (follwoing the correct churn on the total staked amount).

For an execution request to be authorized in the consensus layer, the withdrawal credential of the validator must be the same of the msg.caller when processing the transaction on the EVM. So the validator’s withdrawal credential must be the same address of the smart contract creating the request (not the credential of the transaction sender), or the contract needs to use DELEGATECALL to ensure the caller address matches the address in the validator’s withdrawal credential. Validators that already have a contract address as their withdrawal credentials should be able to update their contracts to meet this demand (assuming they have an updatable contract).

There problems for existing validators that have their withdrawal credentials set to an EOA account is they will never be able to use use smart contracts for creating execution requests, because the current design does not allow for these credentials to ever be changed.

Allowing for validators to update their withdrawal credentials mean they can opt-in and out of different schemes and strategies on managing their validators, favouring experimentation and innovation. Today, the only alternative to this is exitting the validator and creating a new validator with different withdrawal credentials.

Specification

Constants

Name Value Comment
FORK_TIMESTAMP TBD Mainnet

Configuration

Name Value Comment
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_PREDEPLOY_ADDRESS 0x09Fc772D0857550724b07B850a4323f39112aAaA Where to call and store relevant details about the withdrawal credentials update mechanism
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_TYPE 0x03 The EIP-7685 type prefix for withdrawal credential update request
SYSTEM_ADDRESS 0xfffffffffffffffffffffffffffffffffffffffe Address used to invoke system operation on contract
EXCESS_WITHDRAWAL_CREDENTIALS_UPDATE_REQUESTS_STORAGE_SLOT 0  
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_COUNT_STORAGE_SLOT 1  
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_QUEUE_HEAD_STORAGE_SLOT 2 Pointer to head of the withdrawal credential update request message queue
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_QUEUE_TAIL_STORAGE_SLOT 3 Pointer to the tail of the withdrawal credential update request message queue
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_QUEUE_STORAGE_OFFSET 4 The start memory slot of the in-state withdrawal credential update request message queue
MAX_WITHDRAWAL_CREDENTIALS_UPDATE_REQUESTS_PER_BLOCK 4 Maximum number of withdrawal credential update requests that can be dequeued into a block
TARGET_WITHDRAWAL_CREDENTIALS_UPDATE_REQUESTS_PER_BLOCK 1  
MIN_WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_FEE 1  
WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_FEE_UPDATE_FRACTION 17  
EXCESS_INHIBITOR 2**256-1 Excess value used to compute the fee before the first system call

Execution Layer

  • FORK_BLOCK – the first block in a blockchain with the timestamp greater or equal to FORK_TIMESTAMP.

Withdrawal Credentials Update request operation

The new withdrawal credential update request operation is an EIP-7685 request with type 0x03 and consists of the following fields:

  1. pubkey: Bytes48
  2. old_address: Bytes20
  3. new_address: Bytes20

The EIP-7685 encoding of a withdrawal credential update request is computed as follows.

request_type = WITHDRAWAL_CREDENTIALS_UPDATE_REQUEST_TYPE
request_data = read_withdrawal_credential_update_requests()

Withdrawal Credentials Update Request Contract

The contract is similar to other execution requests contracts for Deposits, Withdrawals and Consolidation. The contract has three different code paths, which can be summarized at a high level as follows:

  1. Add request - requires a 68 byte input, the validator’s public key concatenated with the new address for withdrawal credentials.
  2. Excess requests getter - if the input length is zero, return the current excess requests count.
  3. System process - if called by system address, pop off the withdrawal credential update requests for the current block from the queue.

Block processing

At the end of processing any execution block where block.timestamp >= FORK_TIMESTAMP (i.e. after processing all transactions and after performing the block body requests validations) client software MUST include a call the contract as SYSTEM_ADDRESS and empty input data to trigger the system subroutine execute. The resopnse should be treated as a new request type (0x03) according to EIP-7685.

Block Validation

EL must check that the commitment hash in the execution block header matches the hash of the list of execution requests the CL sends when validating the execution block (including any requests of the the new defined type 0x03).

Consensus Layer

Summary of changes:

  • New container WithdrawalCredentialUpdateRequest
  • New method in Block Processing: process_withdrawal_credential_update_request
  • New Beacon State mutator method: update_withdrawal_credentials
class WithdrawalCredentialUpdateRequest(Container):
    validator_pubkey: BLSPubkey
    old_address: ExecutionAddress  # request contract will set this to msg.caller
    new_address: ExecutionAddress
def process_withdrawal_credential_update_request(state: BeaconState, withdrawal_credentials_update_requests: WithdrawalCredentialUpdateRequest) -> None:
    validator_pubkeys = [v.pubkey for v in state.validators]
    # Verify pubkey exists
    request_pubkey = withdrawal_credentials_update_requests.validator_pubkey
    if request_pubkey not in validator_pubkeys:
        return
    index = ValidatorIndex(validator_pubkeys.index(request_pubkey))
    validator = state.validators[index]

    # Verify withdrawal credentials
    has_correct_credential = has_execution_withdrawal_credential(validator)
    is_correct_old_address = (
        validator.withdrawal_credentials[12:] == withdrawal_credentials_update_requests.old_address
    )
    if not (has_correct_credential and is_correct_old_address):
        return

    credential_type = withdrawal_credentials_update_requests.type
    is_eth1_type = credential_type == ETH1_ADDRESS_WITHDRAWAL_PREFIX
    is_compounding_type = credential_type == COMPOUNDING_WITHDRAWAL_PREFIX
    # Verify valid type
    if not (is_eth1_type or is_compounding_type):
        return;

    update_withdrawal_credentials(state, index, credential_type, withdrawal_credentials_update_requests.new_address)
def update_withdrawal_credentials(state: BeaconState, index: ValidatorIndex, new_credential_type: Bytes1, new_withdrawal_credentials: ExecutionAddress) -> None:
    old_credential_type = validator.withdrawal_credentials[:1]

    validator = state.validators[index]
    validator.withdrawal_credentials = (
        new_credential_type
        + b'\x00' * 11
        + new_withdrawal_credentials
    )

    # If moving from 0x01 to 0x02
    if (old_credential_type == ETH1_ADDRESS_WITHDRAWAL_PREFIX and new_credential_type == COMPOUNDING_WITHDRAWAL_PREFIX) {
      #TODO: in this case we need to ensure we put them through the churn/likely re-using some of the exiting rules
      switch_to_compounding_validator(state, index)
    }

Rationale

Backwards Compatibility

Test Cases

Security Considerations

Ownership is defined based on control of the withdrawal credential account, either as a private key (for EOA accounts) or controlling the smart contract at the address set as withdrawal credential. Therefore allowing an update should not bring any security implications.

Futher discussion needed.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Lucas Saldanha (@lucassaldanha), Mikhail Kalinin (@mkalinin), "EIP-7804: Withdrawal Credential Update Request [DRAFT]," Ethereum Improvement Proposals, no. 7804, October 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7804.