Provides validator deposits as a list of deposit operations added to the Execution Layer block
|Authors||Mikhail Kalinin (@mkalinin), Danny Ryan (@djrtwo), Peter Davies (@petertdavies)|
Table of Contents
- Backwards Compatibility
- Security Considerations
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.
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).
- Reduction of design and engineering complexity of Consensus Layer client software on a component that has proven to be brittle.
DEPOSIT_CONTRACT_ADDRESS parameter MUST be included into client software binary distribution.
FORK_BLOCK– the first block in a blockchain with the
timestampgreater or equal to
The structure denoting the new deposit operation consists of the following fields:
RLP encoding of deposit structure MUST be computed in the following way:
rlp_encoded_deposit = RLP([ RLP(pubkey), RLP(withdrawal_credentials), RLP(amount), RLP(signature), RLP(index) ])
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:
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:
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)
Beginning with the
FORK_BLOCK, client software MUST extend block validity rule set with the following conditions:
- Value of
deposits_rootblock header field equals to the trie root committing to the list of deposit operations contained in the block. To illustrate:
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)
- 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:
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), withdrawal_credentials=Bytes32(deposit_data), amount=little_endian_to_uint64(deposit_data), signature=Bytes96(deposit_data), index=little_endian_to_uint64(deposit_data) ) # 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.
Consensus layer changes can be summarized into the following list:
ExecutionPayloadis extended with a new
deposit_receiptsfield to accommodate deposit operations list.
BeaconStateis appended with
deposit_receipts_start_indexused to switch from the former deposit mechanism to the new one.
- As a part of transition logic a new beacon block validity condition is added to constrain the usage of
- A new
process_deposit_receiptfunction is added to the block processing routine to handle
Detailed consensus layer specification can be found in following documents:
eip6110/beacon-chain.md– state transition.
eip6110/validator.md– validator guide.
eip6110/fork.md– EIP activation.
Due to the large follow distance of
Eth1Data poll an index of a new validator assigned during deposit processing remains the same across different branches of a block tree, i.e. with existing mechanism
(pubkey, index) cache utilized by consensus layer clients is re-org resilient. The new deposit machinery breaks this invariant and consensus layer clients will have to deal with a fact that a validator index becomes fork dependent, i.e. a validator with the same
pubkey can have different indexes in different block tree branches.
Consensus layer clients will be able to remove
Eth1Data poll mechanism in an uncoordinated fashion once transition period is finished. The transition period is considered as finished when a network reaches the point where
state.eth1_deposit_index == state.deposit_receipts_start_index.
index field may appear unnecessary but it is important information for deposit processing flow on the Consensus Layer.
The list is unbounded because of negligible data complexity and absence of potential DoS vectors. See Security Considerations for more details.
Deposit contract does not emit any events except for
DepositEvent, thus additional filtering is unnecessary.
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.
At the time of the latest update of this document, the total number of submitted deposits is 824,598 which is 164MB of deposit data. Assuming frequency of deposit transactions remains the same, historic chain data complexity induced by this EIP can be estimated as 60MB per year which is negligible in comparison to other historical data.
After the beacon chain launch in December 2020, the biggest observed spike in a number of submitted deposits was on June 1, 2023. More than 12,000 deposit transactions were submitted during 24 hours which on average is less than 2 deposit, or 384 bytes of data, per block.
Considering the above, we conclude that data complexity introduced by this proposal is negligible.
The code in the deposit contract costs 15,650 gas to run in the cheapest case (when all storage slots are hot and only a single leaf has to be modified). Some deposits in a batch deposit are more expensive, but those costs, when amortized over a large number of deposits, are small at around ~1,000 gas per deposit. Under current gas pricing rules an extra 6,900 gas is charged to make a
CALL that transfers ETH, this is a case of inefficient gas pricing and may be reduced in the future. For future robustness the beacon chain needs to be able to withstand 1,916 deposits in a 30M gas block (15,650 gas per deposit). The limit under current rules is less than 1,271 deposits in a 30M gas block.
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 DoS attack surface of the execution layer.
The most consuming computation of deposit processing is signature verification. Its complexity is bounded by a maximum number of deposits per block which is around 1,271 with 30M gas block at the moment. So, it is ~1,271 signature verifications which is roughly ~1.2 seconds of processing (without optimisations like batched signatures verification). An attacker would need to spend 1,000 ETH to slow down block processing by a second which isn’t sustainable and viable attack long term.
An optimistically syncing node may be susceptible to a more severe attack scenario. Such a node can’t validate a list of deposits provided in a payload which makes it possible for attacker to include as many deposits as the limitation allows to. Currently, it is 8,192 deposits (1.5MB of data) with rough processing time of 8s. Considering an attacker would need to sign off on this block with its crypto economically viable signature (which requires building an alternative chain and feeding it to a syncing node), this attack vector is not considered as viable as it can’t result in a significant slow down of a sync process.
An optimistically syncing node have to rely on the honest majority assumption. That is, if adversary is powerful enough to finalize a deposit sequence, a syncing node will have to apply these deposits disregarding the validity of deposit receipts with respect to the execution of a given block. Thus, an adversary that can finalize an invalid chain can also convince an honest node to accept fake deposits. The same is applicable to the validity of execution layer world state today and a new deposit processing design is within boundaries of the existing security model in that regard.
Online nodes can’t be tricked into this situation because their execution layer validates supplied deposits with respect to the block execution.
This EIP removes a hard limit on a number of deposits per epoch and makes a block gas limit the only limitation on this number. That is, the limit on deposits per epoch shifts from
MAX_DEPOSITS * SLOTS_PER_EPOCH = 512 to
max_deposits_per_30m_gas_block * SLOTS_PER_EPOCH ~ 32,768 at 30M gas block (we consider
max_deposits_per_30m_gas_block = 1,024 for simplicity).
This change affects a number of top ups per epoch which is one of the inputs to the weak subjectivity period computation. One can top up own validators to instantly increase a portion of stake it owns with respect to those validators that are leaking. The analysis does not demonstrate significant reduction of a weak subjectivity period sizes. Moreover, such an attack is not considered as viable because it requires a decent portion of stake to be burned as one of preliminaries.
Copyright and related rights waived via CC0.
Please cite this document as:
Mikhail Kalinin (@mkalinin), Danny Ryan (@djrtwo), Peter Davies (@petertdavies), "EIP-6110: Supply validator deposits on chain [DRAFT]," Ethereum Improvement Proposals, no. 6110, December 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6110.