--- eip: 4895 title: Beacon chain push withdrawals as operations description: Support validator withdrawals from the beacon chain to the EVM via a new "system-level" operation type. author: Alex Stokes (@ralexstokes), Danny Ryan (@djrtwo) discussions-to: https://ethereum-magicians.org/t/eip-4895-beacon-chain-withdrawals-as-system-level-operations/8568 status: Review type: Standards Track category: Core created: 2022-03-10 --- ## Abstract Introduce a system-level "operation" to support validator withdrawals that are "pushed" from the beacon chain to the EVM. These operations create unconditional balance increases to the specified recipients. ## Motivation This EIP provides a way for validator withdrawals made on the beacon chain to enter into the EVM. The architecture is "push"-based, rather than "pull"-based, where withdrawals are required to be processed in the execution layer as soon as they are dequeued from the consensus layer. Withdrawals are represented as a new type of object in the execution payload -- an "operation" -- that separates the withdrawals feature from user-level transactions. This approach is more involved than the prior approach introducing a new transaction type but it cleanly separates this "system-level" operation from regular transactions. The separation simplifies testing (so facilitates security) by reducing interaction effects generated by mixing this system-level concern with user data. Moreover, this approach is more complex than "pull"-based alternatives with respect to the core protocol but does provide tighter integration of a critical feature into the protocol itself. ## Specification | constants | value | units |--- |--- |--- | `FORK_TIMESTAMP` | 1681338455 | Beginning with the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST** introduce the following extensions to payload validation and processing: ### System-level operation: withdrawal Define a new payload-level object called a `withdrawal` that describes withdrawals that have been validated at the consensus layer. `Withdrawal`s are syntactically similar to a user-level transaction but live in a different domain than user-level transactions. `Withdrawal`s provide key information from the consensus layer: 1. a monotonically increasing `index`, starting from 0, as a `uint64` value that increments by 1 per withdrawal to uniquely identify each withdrawal 2. the `validator_index` of the validator, as a `uint64` value, on the consensus layer the withdrawal corresponds to 3. a recipient for the withdrawn ether `address` as a 20-byte value 4. a nonzero `amount` of ether given in Gwei (1e9 wei) as a `uint64` value. *NOTE*: the `index` for each withdrawal is a global counter spanning the entire sequence of withdrawals. `Withdrawal` objects are serialized as a RLP list according to the schema: `[index, validator_index, address, amount]`. ### New field in the execution payload: withdrawals The execution payload gains a new field for the `withdrawals` which is an RLP list of `Withdrawal` data. For example: ```python withdrawal_0 = [index_0, validator_index_0, address_0, amount_0] withdrawal_1 = [index_1, validator_index_1, address_1, amount_1] withdrawals = [withdrawal_0, withdrawal_1] ``` This new field is encoded after the existing fields in the execution payload structure and is considered part of the execution payload's body. ```python execution_payload_rlp = RLP([header, transactions, [], withdrawals]) execution_payload_body_rlp = RLP([transactions, [], withdrawals]) ``` NOTE: the empty list in this schema is due to [EIP-3675](./eip-3675.md) that sets the `ommers` value to a fixed constant. ### New field in the execution payload header: withdrawals root The execution payload header gains a new field committing to the `withdrawals` in the execution payload. This commitment is constructed identically to the transactions root in the existing execution payload header by inserting each withdrawal into a Merkle-Patricia trie keyed by index in the list of `withdrawals`. ```python def compute_trie_root_from_indexed_data(data): trie = Trie.from([(i, obj) for i, obj in enumerate(data)]) return trie.root execution_payload_header.withdrawals_root = compute_trie_root_from_indexed_data(execution_payload.withdrawals) ``` The execution payload header is extended with a new field containing the 32 byte root of the trie committing to the list of withdrawals provided in a given execution payload. To illustrate: ```python execution_payload_header_rlp = RLP([ parent_hash, 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers hash coinbase, state_root, txs_root, receipts_root, logs_bloom, 0, # difficulty number, gas_limit, gas_used, timestamp, extradata, prev_randao, 0x0000000000000000, # nonce base_fee_per_gas, withdrawals_root, ]) ``` NOTE: field names and constant value in this example reflect [EIP-3675](./eip-3675.md) and [EIP-4399](./eip-4399.md). Refer to those EIPs for further information. ### Execution payload validity Assuming the execution payload is well-formatted, the execution client has an additional payload validation to ensure that the `withdrawals_root` matches the expected value given the list in the payload. ```python assert execution_payload_header.withdrawals_root == compute_trie_root_from_indexed_data(execution_payload.withdrawals) ``` ### State transition The `withdrawals` in an execution payload are processed **after** any user-level transactions are applied. For each `withdrawal` in the list of `execution_payload.withdrawals`, the implementation increases the balance of the `address` specified by the `amount` given. Recall that the `amount` is given in units of Gwei so a conversion to units of wei must be performed when working with account balances in the execution state. This balance change is unconditional and **MUST** not fail. This operation has no associated gas costs. ## Rationale ### Why not a new transaction type? This EIP suggests a new type of object -- the "withdrawal operation" -- as it has special semantics different from other existing types of EVM transactions. Operations are initiated by the overall system, rather than originating from end users like typical transactions. An entirely new type of object firewalls off generic EVM execution from this type of processing to simplify testing and security review of withdrawals. ### Why no (gas) costs for the withdrawal type? The maximum number of withdrawals that can reach the execution layer at a given time is bounded (enforced by the consensus layer) and this limit has been chosen so that any execution layer operational costs are negligible in the context of the broader payload execution. This bound applies to both computational cost (only a few balance updates in the state) and storage/networking cost as the additional payload footprint is kept small (current parameterizations put the additional overhead at ~1% of current average payload size). ### Why only balance updates? No general EVM execution? More general processing introduces the risk of failures, which complicates accounting on the beacon chain. This EIP suggests a route for withdrawals that provides most of the benefits for a minimum of the (complexity) cost. ## Backwards Compatibility No issues. ## Security Considerations Consensus-layer validation of withdrawal transactions is critical to ensure that the proper amount of ETH is withdrawn back into the execution layer. This consensus-layer to execution-layer ETH transfer does not have a current analog in the EVM and thus deserves very high security scrutiny. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).