Alert Source Discuss

EIP-: ```md

Authors

---
eip: 7002
title: 可执行层触发的提款
description: 允许验证者通过其执行层 (0x01) 提款凭证触发退出和部分提款
author: Danny Ryan (@djrtwo), Mikhail Kalinin (@mkalinin), Ansgar Dietrichs (@adietrichs), Hsiao-Wei Wang (@hwwhww), lightclient (@lightclient), Felix Lange (@fjl)
discussions-to: https://ethereum-magicians.org/t/eip-7002-execution-layer-triggerable-exits/14195
status: Final
type: Standards Track
category: Core
created: 2023-05-09
requires: 7685
---

## 摘要

添加一种新机制,允许验证者通过其执行层 (0x01) 提款凭证触发提款和退出。

这些新的执行层退出消息附加到执行层区块,然后由共识层处理。

## 动机

验证者有两个密钥——一个活动密钥和一个提款凭证。活动密钥采用 BLS 密钥的形式,而提款凭证可以是 BLS 密钥 (0x00) 或执行层地址 (0x01)。活动密钥是“热的”,积极签名并执行验证者职责,而提款凭证可以保持“冷的”,仅执行与提取和质押 ETH 所有权相关的有限操作。由于这种安全关系,提款凭证最终是拥有质押 ETH 和任何奖励的密钥。

按照目前的规定,只有活动密钥才能发起验证者退出。这意味着在任何非标准托管关系(即活动密钥与提款凭证是不同的实体)中,资金的最终所有者(提款凭证的持有者)不能独立选择退出并开始提款过程。这会导致信任问题(例如,ETH 可能被活动密钥所有者“挟持”)或不足的解决方法,例如预签名退出。此外,如果活动密钥丢失,用户应该仍然能够通过使用其冷提款凭证来恢复其资金。

为了确保提款凭证(由 EOA 和智能合约拥有)可以以无需信任的方式控制质押 ETH 的命运,本规范允许由 0x01 提款凭证触发的退出。

请注意,可以使用一次性签名消息将 0x00 提款凭证更改为 0x01 提款凭证。因此,为 0x01 凭证启用的任何功能实际上都为 0x00 凭证启用。

## 规范

### 配置

| 名称 | 值 | 注释 |
| - | - | - |
| `WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS` | `0x00000961Ef480Eb55e80D19ad83579A64c007002` | 在哪里调用和存储有关退出/部分提款机制的相关详细信息 |
| `WITHDRAWAL_REQUEST_TYPE` | `0x01` | 提款请求的 [EIP-7685](/docs/eips/EIPS/eip-7685/) 类型前缀 |
| `SYSTEM_ADDRESS` | `0xfffffffffffffffffffffffffffffffffffffffe` | 用于调用合约上的系统操作的地址
| `EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT` | 0 | |
| `WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT` | 1 | |
| `WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT` | 2 | 指向提款请求消息队列的头部的指针 |
| `WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT` | 3 | 指向提款请求消息队列的尾部的指针 |
| `WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET` | 4 | 状态内提款请求消息队列的起始内存槽 |
| `MAX_WITHDRAWAL_REQUESTS_PER_BLOCK` | 16 | 可以出队到区块中的最大提款请求数 |
| `TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK` | 2 | |
| `MIN_WITHDRAWAL_REQUEST_FEE` | 1 | |
| `WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION` | 17 | |
| `EXCESS_INHIBITOR` | `2**256-1` | 在第一次系统调用之前用于计算费用的超额值 |

### 执行层

#### 定义

* **`FORK_BLOCK`** -- 在此 EIP 激活后区块链中的第一个区块。

#### 提款请求操作

新的提款请求操作是一个 [EIP-7685](/docs/eips/EIPS/eip-7685/) 请求
类型为 `0x01`,由以下字段组成:

1. `source_address`: `Bytes20`
2. `validator_pubkey`: `Bytes48`
3. `amount:` `uint64`

提款请求的 [EIP-7685](/docs/eips/EIPS/eip-7685/) 编码计算如下。
请注意,`amount` 由合约以小端序返回,并且必须这样编码。

```python
request_type = WITHDRAWAL_REQUEST_TYPE
request_data = read_withdrawal_requests()

提款请求合约

该合约有三个不同的代码路径,可以概括为以下几个方面:

  1. 添加提款请求 - 需要一个 56 字节的输入,验证者的公钥与一个大端序的 uint64 金额值连接。
  2. 费用获取器 - 如果输入长度为零,则返回添加提款请求所需的当前费用。
  3. 系统进程 - 如果由系统地址调用,则从队列中弹出当前区块的提款请求。
添加提款请求

如果对合约的调用数据输入正好是 56 字节,则执行以下操作:

  • 确保发送了足够的 ETH 来支付当前的提款请求费用 (check_fee())
  • 将当前区块的提款请求计数增加 1 (increment_count())
  • 将提款请求插入到源地址和验证者公钥的队列中 (insert_withdrawal_request_into_queue())

具体来说,该功能在伪代码中定义为函数 add_withdrawal_request()

def add_withdrawal_request(Bytes48: validator_pubkey, uint64: amount):
    """
    添加提款请求将新请求添加到提款请求队列,只要提供了足够的费用。
    """

    # 验证是否提供了足够的费用。
    fee = get_fee()
    require(msg.value >= fee, 'Insufficient value for fee')

    # 增加提款请求计数。
    count = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT)
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, count + 1)

    # 插入到队列中。
    queue_tail_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    queue_storage_slot = WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET + queue_tail_index * 3
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot, msg.sender)
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1, validator_pubkey[0:32])
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2, validator_pubkey[32:48] ++ uint64_to_little_endian(amount))
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1)
费用计算

给定一定数量的超额提款请求,以下伪代码可以计算单个提款请求的成本。

def get_fee() -> int:
    excess = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT)
    require(excess != EXCESS_INHIBITOR, 'Inhibitor still active')
    return fake_exponential(
        MIN_WITHDRAWAL_REQUEST_FEE,
        excess,
        WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION
    )

def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
    i = 1
    output = 0
    numerator_accum = factor * denominator
    while numerator_accum > 0:
        output += numerator_accum
        numerator_accum = (numerator_accum * numerator) // (denominator * i)
        i += 1
    return output // denominator
费用获取器

当合约的输入长度为零时,将其解释为当前费用的获取请求,即合约返回 get_fee() 的结果。

系统调用

FORK_BLOCK 开始处理任何执行区块的结尾(即,在处理所有交易之后以及在执行区块主体提款请求验证之后),以 SYSTEM_ADDRESS 的身份调用 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,不带 calldata。该调用触发以下操作:

  • 合约的队列根据出队的提款请求进行更新,如果队列已清除,则提款请求队列的头部/尾部将重置 (dequeue_withdrawal_requests())
  • 合约的超额提款请求根据当前区块中的使用情况进行更新 (update_excess_withdrawal_requests())
  • 合约的提款请求计数重置为 0 (reset_withdrawal_requests_count())

每个提款请求必须以 dequeue_withdrawal_requests() 返回的完全相同的顺序出现在 EIP-7685 请求列表中。

此外,系统调用和该区块的处理必须符合以下条件:

  • 该调用具有专用的 gas 限制 30_000_000
  • 此调用消耗的 Gas 不计入区块的总 gas 使用量。
  • 分配给此调用的 gas 限制和消耗的 gas 均不包括在对区块 gas 限制的任何检查中。
  • 该调用不遵循 EIP-1559 费用燃烧语义 - 不应将任何值作为此调用的一部分转移。
  • 如果 WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS 上没有代码,则相应的区块必须标记为无效。
  • 如果对合约的调用失败或返回错误,则该区块必须无效。

系统调用触发的功能在伪代码中定义为函数 read_withdrawal_requests()

###################
# 公共函数 #
###################

def read_withdrawal_requests():
    reqs = dequeue_withdrawal_requests()
    update_excess_withdrawal_requests()
    reset_withdrawal_requests_count()
    return ssz.serialize(reqs)

###########
# 助手 #
###########

def little_endian_to_uint64(data: bytes) -> uint64:
    return uint64(int.from_bytes(data, 'little'))

def uint64_to_little_endian(num: uint64) -> bytes:
    return num.to_bytes(8, 'little')

class ValidatorWithdrawalRequest(object):
    source_address: Bytes20
    validator_pubkey: Bytes48
    amount: uint64

def dequeue_withdrawal_requests():
    queue_head_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT)
    queue_tail_index = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT)
    num_in_queue = queue_tail_index - queue_head_index
    num_dequeued = min(num_in_queue, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK)

    reqs = []
    for i in range(num_dequeued):
        queue_storage_slot = WITHDRAWAL_REQUEST_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 3
        source_address = address(sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot)[0:20])
        validator_pubkey = (
            sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 1)[0:32] + sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[0:16]
        )
        amount = little_endian_to_uint64(sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, queue_storage_slot + 2)[16:24])
        req = ValidatorWithdrawalRequest(
            source_address=Bytes20(source_address),
            validator_pubkey=Bytes48(validator_pubkey),
            amount=uint64(amount)
        )
        reqs.append(req)

    new_queue_head_index = queue_head_index + num_dequeued
    if new_queue_head_index == queue_tail_index:
        # 队列为空,重置队列指针
        sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, 0)
        sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_TAIL_STORAGE_SLOT, 0)
    else:
        sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index)

    return reqs

def update_excess_withdrawal_requests():
    previous_excess = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT)
    if previous_excess == EXCESS_INHIBITOR:
        previous_excess = 0

    count = sload(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT)

    new_excess = 0
    if previous_excess + count > TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK:
        new_excess = previous_excess + count - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK

    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, EXCESS_WITHDRAWAL_REQUESTS_STORAGE_SLOT, new_excess)

def reset_withdrawal_requests_count():
    sstore(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_COUNT_STORAGE_SLOT, 0)
字节码
caller
push20 0xfffffffffffffffffffffffffffffffffffffffe
eq
push1 0xcb
jumpi

push1 0x11
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
push2 0x01f4
jumpi

push1 0x01
dup3
mul
push1 0x01
swap1
push0

jumpdest
push0
dup3
gt
iszero
push1 0x68
jumpi

dup2
add
swap1
dup4
mul
dup5
dup4
mul
swap1
div
swap2
push1 0x01
add
swap2
swap1
push1 0x4d
jump

jumpdest
swap1
swap4
swap1
div
swap3
pop
pop
pop
calldatasize
push1 0x38
eq
push1 0x88
jumpi

calldatasize
push2 0x01f4
jumpi

callvalue
push2 0x01f4
jumpi

push0
mstore
push1 0x20
push0
return

jumpdest
callvalue
lt
push2 0x01f4
jumpi

push1 0x01
sload
push1 0x01
add
push1 0x01
sstore
push1 0x03
sload
dup1
push1 0x03
mul
push1 0x04
add
caller
dup2
sstore
push1 0x01
add
push0
calldataload
dup2
sstore
push1 0x01
add
push1 0x20
calldataload
swap1
sstore
caller
push1 0x60
shl
push0
mstore
push1 0x38
push0
push1 0x14
calldatacopy
push1 0x4c
push0
log0
push1 0x01
add
push1 0x03
sstore
stop

jumpdest
push1 0x03
sload
push1 0x02
sload
dup1
dup3
sub
dup1
push1 0x10
gt
push1 0xdf
jumpi

pop
push1 0x10

jumpdest
push0

jumpdest
dup2
dup2
eq
push2 0x0183
jumpi

dup3
dup2
add
push1 0x03
mul
push1 0x04
add
dup2
push1 0x4c
mul
dup2
sload
push1 0x60
shl
dup2
mstore
push1 0x14
add
dup2
push1 0x01
add
sload
dup2
mstore
push1 0x20
add
swap1
push1 0x02
add
sload
dup1
push32 0xffffffffffffffffffffffffffffffff00000000000000000000000000000000
and
dup3
mstore
swap1
push1 0x10
add
swap1
push1 0x40
shr
swap1
dup2
push1 0x38
shr
dup2
push1 0x07
add
mstore8
dup2
push1 0x30
shr
dup2
push1 0x06
add
mstore8
dup2
push1 0x28
shr
dup2
push1 0x05
add
mstore8
dup2
push1 0x20
shr
dup2
push1 0x04
add
mstore8
dup2
push1 0x18
shr
dup2
push1 0x03
add
mstore8
dup2
push1 0x10
shr
dup2
push1 0x02
add
mstore8
dup2
push1 0x08
shr
dup2
push1 0x01
add
mstore8
mstore8
push1 0x01
add
push1 0xe1
jump

jumpdest
swap2
add
dup1
swap3
eq
push2 0x0195
jumpi

swap1
push1 0x02
sstore
push2 0x01a0
jump

jumpdest
swap1
pop
push0
push1 0x02
sstore
push0
push1 0x03
sstore

jumpdest
push0
sload
dup1
push32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
eq
iszero
push2 0x01cd
jumpi

pop
push0

jumpdest
push1 0x01
sload
push1 0x02
dup3
dup3
add
gt
push2 0x01e2
jumpi

pop
pop
push0
push2 0x01e8
jump

jumpdest
add
push1 0x02
swap1
sub

jumpdest
push0
sstore
push0
push1 0x01
sstore
push1 0x4c
mul
push0
return

jumpdest
push0
push0
revert
部署

提款请求合约的部署方式与其他任何智能合约一样。通过从所需的部署交易向后工作来生成一个特殊的合成地址:

{
  "type": "0x0",
  "nonce": "0x0",
  "to": null,
  "gas": "0x3d090",
  "gasPrice": "0xe8d4a51000",
  "maxPriorityFeePerGas": null,
  "maxFeePerGas": null,
  "value": "0x0",
  "input": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5f556101f880602d5f395ff33373fffffffffffffffffffffffffffffffffffffffe1460cb5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff146101f457600182026001905f5b5f82111560685781019083028483029004916001019190604d565b909390049250505036603814608857366101f457346101f4575f5260205ff35b34106101f457600154600101600155600354806003026004013381556001015f35815560010160203590553360601b5f5260385f601437604c5fa0600101600355005b6003546002548082038060101160df575060105b5f5b8181146101835782810160030260040181604c02815460601b8152601401816001015481526020019060020154807fffffffffffffffffffffffffffffffff00000000000000000000000000000000168252906010019060401c908160381c81600701538160301c81600601538160281c81600501538160201c81600401538160181c81600301538160101c81600201538160081c81600101535360010160e1565b910180921461019557906002556101a0565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff14156101cd57505f5b6001546002828201116101e25750505f6101e8565b01600290035b5f555f600155604c025ff35b5f5ffd,
  "v": "0x1b",
  "r": "0x539",
  "s": "0x5feeb084551e4e03a3581e269bc2ea2f8d0008",
  "hash": "0x8ded54be89448d78d4bc97782c0187b099e45380ab681742f9d3754e405c2572"
}
发送者: 0x8646861A7cF453dDD086874d622b0696dE5b9674
地址: 0x00000961Ef480Eb55e80D19ad83579A64c007002

共识层

完整规范

规范草图:

  • 新操作 ExecutionLayerWithdrawalRequest
  • 将在 ExecutionPayload 中显示为 SSZ 列表,其长度受 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 约束
  • 具有与 process_voluntary_exit 类似功能的新函数,但可以使验证失败(例如,验证者已经退出)而不会使区块失败(类似于来自 EL 的存款)
  • 对于在 ExecutionPayload 中找到的每个 ExecutionLayerWithdrawalRequest,此函数在 process_operations 中调用

理由

validator_pubkey 字段

多个验证者可以使用相同的执行层提款凭证,因此使用 validator_pubkey 字段来消除正在退出的验证者的歧义。

请注意,validator_index 也可以消除验证者的歧义。 问题是一些 Staking Pool 的智能合约不知道索引,因为索引只有在验证者在信标链上创建后才知道,而公钥可以提前获得。

消息队列

该合约维护一个提款请求消息的状态内队列,每个区块将这些消息出队到区块中,从而进入执行层。

可以传递到共识层的提款请求的数量受 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 约束,以限制区块大小以及共识层处理的负载。已为 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 选择 16,以便与信标链上类似操作的界限保持一致,例如 VoluntaryExitDeposit

尽管每个区块可以传递到共识层的提款请求的最大数量有限,但执行层 gas 限制可以为每个区块对提款请求预部署合约提供更多的调用。然后,队列允许成功进行这些调用,同时仍保持系统速率限制。

考虑的另一种设计是在单个区块的上下文中成功进行 MAX_WITHDRAWAL_REQUESTS_PER_BLOCK 次调用后,使对合约的调用失败。这将消除对消息队列的需求,但会以在高退出时导致合约调用失败的不良 UX 为代价。缓解这种不良 UX 的复杂性相对较低,并且目前受到青睐。

使用费用进行速率限制

交易自然地在执行层中通过 gas 限制进行速率限制,但愿意支付市场价格 gas 费用(并可能利用构建者市场来支付区块前交易包含)的攻击者可以相对便宜地填满退出操作限制,从而使想要提出提款请求的诚实验证者感到悲伤。

有两种通用方法可以解决这种悲伤——(a)仅允许验证者发送此类消息,并限制每个时间段或(b)利用经济方法来使此类悲伤的成本越来越高。

方法 (a)(未在本 EIP 中使用)将需要 EIP-4788BEACON_ROOT 操作码)来证明与验证者公钥相关的提款凭证,以及一个数据结构来跟踪每单位时间(例如 4 个月)的请求,以确保验证者无法通过提交许多请求来悲伤该机制。此方法的缺点是它需要另一个跨层 EIP,并且具有更高的跨层复杂性(例如,如果将来升级时 BEACON_ROOT 的 merkle 树的形状发生变化,可能需要注意,然后可能需要更新合约和证明结构)。

本 EIP 中使用了方法 (b) 来消除额外的 EIP 要求并降低跨层复杂性,从而使此 EIP 的正确性(现在和将来)更易于分析。EIP-1559 风格的机制,具有动态调整的费用机制,允许用户在正常情况下(平均每个区块少于 2 个)为提款请求支付 MIN_WITHDRAWAL_REQUEST_FEE,但会根据高使用率(即潜在的滥用)以指数方式提高费用。

TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK 配置值

TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK 已选择为 2,这样在退出流失拥塞的极端情况下,信标状态中部分提款队列的增长可以忽略不计。

费用更新规则

费用更新规则旨在近似公式 fee = MIN_WITHDRAWAL_REQUEST_FEE * e**(excess / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION),其中 excess 是链相对于 “目标” 数量(每个区块 TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK)已处理的提款请求的总 “额外” 数量。

与 EIP-1559 类似,它是一个自我修正的公式:随着超额量的增加,fee 会呈指数增长,从而减少使用量并最终迫使超额量下降。

逐个区块的行为大致如下。如果区块 N 处理 X 个请求,则在区块 N 的结尾,excess 增加 X - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK,因此区块 N+1 中的 fee 增加的因子为 e**((X - TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK) / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION)。因此,它具有与现有 EIP-1559 类似的效果,但更 “稳定”,因为它以相同的方式响应相同的总提款请求,而不管它们在一段时间内如何分布。

参数 WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION 控制 blob gas 价格的最大向下变化率。选择它以将最大向下变化率定为每个区块 e(TARGET_WITHDRAWAL_REQUESTS_PER_BLOCK / WITHDRAWAL_REQUEST_FEE_UPDATE_FRACTION) ≈ 1.125

有关费用机制的更详细分析,请参见此处

区块内的提款请求

提款请求被放入信标区块的实际主体中。

共识层和执行层可以彼此独立执行是一个强大的设计要求。这意味着,在这种情况下,共识层不能依赖于对执行层的同步调用来获取当前区块所需的提款请求。相反,请求必须嵌入在信标区块中,这样,如果执行层处于脱机状态,共识层仍然具有完全执行状态转换函数的共识部分所需的必要数据。

通过执行层提交请求

通过 Engine API 验证来自共识层的 secp256k1 签名是所提出的请求机制的替代方法之一,其工程复杂性要低得多。 但是,此方法将通过使拥有验证者提款凭证的智能合约无法从此功能中受益,从而在很大程度上限制提款请求的使用。

向后兼容性

此 EIP 引入了对区块结构和区块验证规则集的不向后兼容的更改。但是,这些更改都不会破坏与当前用户活动和体验相关的任何内容。

安全注意事项

对现有托管关系的影响

可能存在依赖于提款凭证 无法 触发提款请求的假设的现有托管关系和/或产品。我们目前有信心额外的提款凭证功能不会影响现有验证者的安全性,因为:

  1. 提款凭证最终拥有这笔资金,因此允许他们退出 Staking 在所有权方面是自然的。
  2. 我们目前不知道任何依赖于缺少此功能的此类托管关系和/或产品。

如果现有验证者/托管人依赖于此,则可以退出验证者并重新质押,利用指向模拟此行为的智能合约的 0x01 提款凭证。

费用超额支付

对系统合约的调用需要由当前合约状态定义的费用支付。多付的费用不会退还给调用者。通常无法提前计算出确切的所需费用金额。从合约添加提款请求时,合约可以执行读取操作以检查当前费用,然后准确支付所需金额。这是一个 Solidity 中的示例:

function addWithdrawal(bytes memory pubkey, uint64 amount, uint64 requestFeeLimit) private {
    assert(pubkey.length == 48);

    // 从合约中读取当前费用。
    (bool readOK, bytes memory feeData) = WithdrawalsContract.staticcall('');
    if (!readOK) {
        revert('reading fee failed');
    }
    uint256 fee = uint256(bytes32(feeData));

    // 检查费用是否太高。
    if (fee > requestFeeLimit) {
        revert('fee is too high');
    }

    // 添加请求。
    bytes memory callData = abi.encodePacked(pubkey, amount);
    (bool writeOK,) = WithdrawalsContract.call{value: fee}(callData);
    if (!writeOK) {
        revert('adding request failed');
    }
}

注意:系统合约使用 EVM CALLER 操作(Solidity:msg.sender)作为提款的目标地址,即调用系统合约的地址必须与信标状态中记录的 0x01 提款凭证匹配。

注意:如果费用太高,上面的代码会恢复,费用可能在创建提款请求交易和将其包含到区块之间发生显着变化,因此,此检查对于避免超额支付非常重要。

使用 EOA 请求提款将始终

Citation

Please cite this document as:

, "EIP-: ```md," Ethereum Improvement Proposals, no. , . [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-.