193 lines
8.0 KiB
Markdown
193 lines
8.0 KiB
Markdown
|
---
|
||
|
eip: 6110
|
||
|
title: Supply validator deposits on chain
|
||
|
description: Provides validator deposits as a list of deposit operations added to the Execution Layer block
|
||
|
author: Mikhail Kalinin (@mkalinin), Danny Ryan (@djrtwo)
|
||
|
discussions-to: https://ethereum-magicians.org/t/eip-6110-supply-validator-deposits-on-chain/12072
|
||
|
status: Draft
|
||
|
type: Standards Track
|
||
|
category: Core
|
||
|
created: 2022-12-09
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
Appends validator deposits to the Execution Layer block structure. This shifts responsibility of deposit inclusion and validation to the Execution Layer and removes the need for deposit (or `eth1data`) voting from the Consensus Layer.
|
||
|
|
||
|
Validator deposits list supplied in a block is obtained by parsing deposit contract log events emitted by each deposit transaction included in a given block.
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
Validator deposits are a core component of the proof-of-stake consensus mechanism. This EIP allows for an in-protocol mechanism of deposit processing on the Consensus Layer and eliminates the proposer voting mechanism utilized currently. This proposed mechanism relaxes safety assumptions and reduces complexity of client software design, contributing to the security of the deposits flow. It also improves validator UX.
|
||
|
|
||
|
Advantages of in-protocol deposit processing consist of but are not limit to the following:
|
||
|
|
||
|
* Significant increase of deposits security by supplanting proposer voting. With the proposed in-protocol mechanism, an honest online node can't be convinced to process fake deposits even when more than 2/3 portion of stake is adversarial.
|
||
|
* Decrease of delay between submitting deposit transaction on Execution Layer and its processing on Consensus Layer. That is, ~13 minutes with in-protocol deposit processing compared to ~12 hours with the existing mechanism.
|
||
|
* Eliminate beacon block proposal dependency on JSON-RPC API data polling that suffers from failures caused by inconsistencies between JSON-RPC API implementations and dependency of API calls processing on the inner state (e.g. syncing) of client software.
|
||
|
* Eliminate requirement to maintain and distribute deposit contract snapshots ([EIP-4881](./eip-4881.md)).
|
||
|
* Reduction of design and engineering complexity of Consensus Layer client software on a component that has proven to be brittle.
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
### Constants
|
||
|
|
||
|
| Name | Value | Comment |
|
||
|
| - | - | - |
|
||
|
|`FORK_TIMESTAMP` | *TBD* | Mainnet |
|
||
|
|
||
|
### Configuration
|
||
|
|
||
|
| Name | Value | Comment |
|
||
|
| - | - | - |
|
||
|
|`DEPOSIT_CONTRACT_ADDRESS` | `0x00000000219ab540356cbb839cbe05303d7705fa` | Mainnet |
|
||
|
|
||
|
`DEPOSIT_CONTRACT_ADDRESS` parameter **MUST** be included into client software binary distribution.
|
||
|
|
||
|
### Definitions
|
||
|
|
||
|
* **`FORK_BLOCK`** -- the first block in a blockchain with the `timestamp` greater or equal to `FORK_TIMESTAMP`.
|
||
|
|
||
|
### Deposit
|
||
|
|
||
|
The structure denoting the new deposit operation consists of the following fields:
|
||
|
|
||
|
1. `pubkey: Bytes48`
|
||
|
2. `withdrawal_credentials: Bytes32`
|
||
|
3. `amount: uint64`
|
||
|
4. `signature: Bytes96`
|
||
|
5. `index: uint64`
|
||
|
|
||
|
RLP encoding of deposit structure **MUST** be computed in the following way:
|
||
|
|
||
|
```python
|
||
|
rlp_encoded_deposit = RLP([
|
||
|
RLP(pubkey),
|
||
|
RLP(withdrawal_credentials),
|
||
|
RLP(amount),
|
||
|
RLP(signature),
|
||
|
RLP(index)
|
||
|
])
|
||
|
```
|
||
|
|
||
|
### Block structure
|
||
|
|
||
|
Beginning with the `FORK_BLOCK`, the block body **MUST** be appended with a list of deposit operations. RLP encoding of an extended block body structure **MUST** be computed as follows:
|
||
|
|
||
|
```python
|
||
|
block_body_rlp = RLP([
|
||
|
rlp_encoded_field_0,
|
||
|
...,
|
||
|
# Latest block body field before `deposits`
|
||
|
rlp_encoded_field_n,
|
||
|
|
||
|
RLP([rlp_encoded_deposit_0, ..., rlp_encoded_deposit_k])
|
||
|
])
|
||
|
```
|
||
|
|
||
|
Beginning with the `FORK_BLOCK`, the block header **MUST** be appended with the new **`deposits_root`** field. The value of this field is the trie root committing to the list of deposits in the block body. **`deposits_root`** field value **MUST** be computed as follows:
|
||
|
|
||
|
```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.deposits_root = compute_trie_root_from_indexed_data(block.body.deposits)
|
||
|
```
|
||
|
|
||
|
### Block validity
|
||
|
|
||
|
Beginning with the `FORK_BLOCK`, client software **MUST** extend block validity rule set with the following conditions:
|
||
|
|
||
|
1. Value of **`deposits_root`** block header field equals to the trie root committing to the list of deposit operations contained in the block. To illustrate:
|
||
|
|
||
|
```python
|
||
|
def compute_trie_root_from_indexed_data(data):
|
||
|
trie = Trie.from([(i, obj) for i, obj in enumerate(data)])
|
||
|
return trie.root
|
||
|
|
||
|
assert block.header.deposits_root == compute_trie_root_from_indexed_data(block.body.deposits)
|
||
|
```
|
||
|
|
||
|
2. The list of deposit operations contained in the block **MUST** be equivalent to the list of log events emitted by each deposit transaction of the given block, respecting the order of transaction inclusion. To illustrate:
|
||
|
|
||
|
```python
|
||
|
def parse_deposit_data(deposit_event_data): bytes[]
|
||
|
"""
|
||
|
Parses Deposit operation from DepositContract.DepositEvent data
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
def little_endian_to_uint64(data: bytes): uint64
|
||
|
return uint64(int.from_bytes(data, 'little'))
|
||
|
|
||
|
class Deposit(object):
|
||
|
pubkey: Bytes48
|
||
|
withdrawal_credentials: Bytes32
|
||
|
amount: uint64
|
||
|
signature: Bytes96
|
||
|
index: uint64
|
||
|
|
||
|
def event_data_to_deposit(deposit_event_data): Deposit
|
||
|
deposit_data = parse_deposit_data(deposit_event_data)
|
||
|
return Deposit(
|
||
|
pubkey=Bytes48(deposit_data[0]),
|
||
|
withdrawal_credentials=Bytes32(deposit_data[1]),
|
||
|
amount=little_endian_to_uint64(deposit_data[2]),
|
||
|
signature=Bytes96(deposit_data[3]),
|
||
|
index=little_endian_to_uint64(deposit_data[4])
|
||
|
)
|
||
|
|
||
|
# Obtain receipts from block execution result
|
||
|
receipts = block.execution_result.receipts
|
||
|
|
||
|
# Retrieve all deposits made in the block
|
||
|
expected_deposits = []
|
||
|
for receipt in receipts:
|
||
|
for log in receipt.logs:
|
||
|
if log.address == DEPOSIT_CONTRACT_ADDRESS:
|
||
|
deposit = event_data_to_deposit(log.data)
|
||
|
expected_deposits.append(deposit)
|
||
|
|
||
|
# Compare retrieved deposits to the list in the block body
|
||
|
assert block.body.deposits == expected_deposits
|
||
|
```
|
||
|
|
||
|
A block that does not satisfy the above conditions **MUST** be deemed invalid.
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
### `index` field
|
||
|
|
||
|
The `index` field may appear unnecessary but it is important information for deposit processing flow on the Consensus Layer.
|
||
|
|
||
|
### Not limiting the size of deposit operations list
|
||
|
|
||
|
The list is unbounded because of negligible data complexity and absence of potential DoS vectors. See [Security Considerations](#security-considerations) for more details.
|
||
|
|
||
|
### Filtering events only by `DEPOSIT_CONTRACT_ADDRESS`
|
||
|
|
||
|
Deposit contract does not emit any events except for `DepositEvent`, thus additional filtering is unnecessary.
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
This EIP introduces backwards incompatible changes to the block structure and block validation rule set. But neither of these changes break anything related to user activity and experience.
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
### Data complexity
|
||
|
|
||
|
At the time of writing this document, the total number of submitted deposits is 478,402 which is 88MB of deposit data. Assuming frequency of deposit transactions remains the same, historic chain data complexity induced by this EIP can be estimated as 50MB per year which is negligible in comparison to other historic data.
|
||
|
|
||
|
After the beacon chain launch in December 2020, the biggest observed spike in a number of submitted deposits was on March 15, 2022. More than 6000 deposit transactions were submitted during 24 hours which on average is less than 1 deposit, or 192 bytes of data, per block.
|
||
|
|
||
|
Considering the above, we conclude that data complexity introduced by this proposal is negligible.
|
||
|
|
||
|
### DoS vectors
|
||
|
|
||
|
With 1 ETH as a minimum deposit amount, the lowest cost of a byte of deposit data is 1 ETH/192 ~ 5,208,333 Gwei. This is several orders of magnitude higher than the cost of a byte of transaction's calldata, thus adding deposit operations to a block does not increase Execution Layer DoS attack surface.
|
||
|
|
||
|
## Copyright
|
||
|
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|