forked from DecentralizedClimateFoundation/DCIPs
174 lines
7.8 KiB
Markdown
174 lines
7.8 KiB
Markdown
|
---
|
||
|
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).
|