Skip to content

Commit

Permalink
editing pass, apply feedback from other sources (#5697)
Browse files Browse the repository at this point in the history
  • Loading branch information
ralexstokes authored Sep 20, 2022
1 parent 26fa717 commit 85f10e6
Showing 1 changed file with 38 additions and 38 deletions.
76 changes: 38 additions & 38 deletions EIPS/eip-4895.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,42 @@ created: 2022-03-10

Introduce a system-level "operation" to support validator withdrawals that are "pushed" from the beacon chain to the EVM.

These operations effect unconditional balance increases to the specified recipients.
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 block as soon as they are dequeued from the beacon chain.
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.

This approach is more involved than "pull"-based alternatives (e.g. [EIP-4788](./eip-4788.md) + user-space withdrawal contract) with respect to the core protocol (by providing a new object type -- operation -- with special semantics) but does provide tighter integration of a critical feature into the protocol itself.

Additionally, the withdrawals themselves are represented as a new type of object in the block -- an "operation" -- that keeps their concern separate from user-level transactions.
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 [EIP-4863](./eip-4863.md) 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 (e.g. [EIP-4788](./eip-4788.md) + user-space withdrawal contract) 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` | TBD |

Beginning with the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST** introduce the following extensions to block validation and processing:
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 block-level object called a `withdrawal` that describes withdrawals that have been validated at the consensus layer.
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 have three key pieces of information supplied from the consensus layer:
1. a monotonically increasing `index` as a `uint64` value
1. a monotonically increasing `index` as a `uint64` value that uniquely identifies each withdrawal
2. a recipient for the withdrawn ether `address` as a 20-byte value
3. an `amount` of ether given in wei as a 256-bit value.

`Withdrawal` objects are serialized as a RLP list according to the schema: `[index, address, amount]`.

### New field in the execution block: withdrawals
### New field in the execution payload: withdrawals

The execution block gains a new field referred to as `withdrawals` which is an RLP list of `Withdrawal` data.
The execution payload gains a new field for the `withdrawals` which is an RLP list of `Withdrawal` data.

For example:

Expand All @@ -59,72 +59,72 @@ withdrawal_1 = [index_1, address_1, amount_1]
withdrawals = [withdrawal_0, withdrawal_1]
```

This new field is encoded after the existing fields in the block structure and is considered part of the block's body.
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
block_rlp = RLP([header, transactions, ommers, withdrawals])
execution_payload_rlp = RLP([header, transactions, [], withdrawals])

block_body_rlp = RLP([transactions, ommers, withdrawals])
execution_payload_body_rlp = RLP([transactions, [], withdrawals])
```

NOTE: due to [EIP-3675](./eip-3675.md) the `ommers` value in this serialization is the RLP encoding of an empty list.
NOTE: the empty list in this schema is due to [EIP-3675](./eip-3675.md) that sets the `ommers` value to a fixed constant.

### Commitment to withdrawals
### New field in the execution payload header: withdrawals root

The execution block header gains a new field committing to the `withdrawals` in the block.
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 block header by inserting each withdrawal into a Merkle-Patricia trie keyed by index in the list of `withdrawals`.
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

block_header.withdrawals_root = compute_trie_root_from_indexed_data(block.withdrawals)
execution_payload_header.withdrawals_root = compute_trie_root_from_indexed_data(execution_payload.withdrawals)
```

### New field in the execution block header: withdrawals root

The block header gains a new field containing the 32 byte root of the trie committing to the list of withdrawals provided in a given block (see [section on commitment](#commitment-to-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.

This new field, the `withdrawals_root`, is appended to the end of the existing list of fields in the block header.
To illustrate:

```python
block_header_rlp = RLP([
execution_payload_header_rlp = RLP([
parent_hash,
ommers_hash,
0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers hash
coinbase,
state_root,
txs_root,
receipts_root,
bloom,
difficulty,
logs_bloom,
0, # difficulty
number,
gas_limit,
gas_used,
time,
timestamp,
extradata,
mix_hash,
nonce,
prev_randao,
0x0000000000000000, # nonce
base_fee_per_gas,
withdrawals_root,
])
```

NOTE: refer to [EIP-3675](./eip-3675.md) as some of the values in the header RLP have fixed values that **MUST*** be used.
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.

### Block validity
### Execution payload validity

Assuming the block is well-formatted, the execution client has an additional block validation to ensure that the `withdrawals_root` matches the expected value given the list present in the block.
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 block_header.withdrawals_root == compute_trie_root_from_indexed_data(block.withdrawals)
assert execution_payload_header.withdrawals_root == compute_trie_root_from_indexed_data(execution_payload.withdrawals)
```

### State transition

The `withdrawals` in a block are processed **after** any user-level transactions are applied.
The `withdrawals` in an execution payload are processed **after** any user-level transactions are applied.

For each `withdrawal` in the list of `block.withdrawals`, the implementation should increase the balance of the `address` specified by the `amount` given.
For each `withdrawal` in the list of `execution_payload.withdrawals`, the implementation increases the balance of the `address` specified by the `amount` given.

This balance change is unconditional and **MUST** not fail.

Expand All @@ -142,10 +142,10 @@ An entirely new type of object firewalls off generic EVM execution from this typ

### Why no (gas) costs for the withdrawal type?

The maximum number of this operation that can reach the execution layer at a given time is bounded (enforced by the consensus layer) and this limit is kept small so that
any execution layer operational costs are negligible in the context of the broader block execution.
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 block footprint is kept small (current parameterizations put the additional overhead at ~1% of current average block size).
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?

Expand Down

0 comments on commit 85f10e6

Please sign in to comment.