From 85f10e690d10087adfa6817a9b4958256ec10e23 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Tue, 20 Sep 2022 15:24:27 +0200 Subject: [PATCH] editing pass, apply feedback from other sources (#5697) --- EIPS/eip-4895.md | 76 ++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/EIPS/eip-4895.md b/EIPS/eip-4895.md index d766ebd8e4e839..d0f199df2de987 100644 --- a/EIPS/eip-4895.md +++ b/EIPS/eip-4895.md @@ -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: @@ -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. @@ -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?