EIP-7867: 流程控制钱包调用能力
一种提供原子性和流程控制配置的 EIP-5792 能力。
Authors | Sam Wilson (@SamWilsn) <sam@binarycake.ca> |
---|---|
Created | 2025-01-17 |
Discussion Link | https://ethereum-magicians.org/t/wallet-sendcalls-capability-flow-control/22624 |
Requires | EIP-5792 |
摘要
本提案扩展了 EIP-5792,允许 dapps 降低其所需的原子性保证,并控制失败/回滚调用后的行为。它引入了批处理范围的 strict
与 loose
原子性概念,其中 strict
批处理在面对链重组时保持原子性,而 loose
批处理则不然;以及每次调用失败/回滚后继续处理 (continue
) 或停止处理 (halt
) 的能力。
动机
虽然基本的 EIP-5792 规范对于智能合约钱包来说非常有效,但它不允许表达钱包可以实现的全部流程控制选项。例如,dapp 可能只是为了节省 gas 而提交一个批处理,并不关心是否所有调用都在失败时回滚。钱包可能只能通过区块构建器后通道提供有限形式的原子性,但这对于交易平台来说可能就足够了。
规范
本文档中的关键词 “MUST”(必须)、”MUST NOT”(禁止)、”REQUIRED”(需要)、”SHALL”(应)、”SHALL NOT”(不得)、”SHOULD”(应该)、”SHOULD NOT”(不应该)、”RECOMMENDED”(推荐)、”NOT RECOMMENDED”(不推荐)、”MAY”(可以)和 “OPTIONAL”(可选)应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
RPC 接口
以下小节是对 EIP-5792 中 API 端点的修改。
如果请求与下面定义的模式不匹配,钱包 MUST 使用错误代码 INVALID_SCHEMA
拒绝该请求。
wallet_sendCalls
以下 JSON Schema 应作为 flowControl
的键,插入到请求对象中,作为批处理范围或调用范围的 capabilities
对象的值(视情况而定)。
批处理范围
Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"optional": {
"type": "boolean"
},
"atomicity": {
"enum": ["strict", "loose", "none"]
}
}
}
示例请求
[
{
"version": "1.0",
"from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"chainId": "0x01",
"calls": [],
"capabilities": {
"flowControl": {
"atomicity": "loose"
}
}
}
]
调用范围
Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"optional": {
"type": "boolean"
},
"onFailure": {
"enum": ["rollback", "halt", "continue"]
}
}
}
示例请求
[
{
"version": "1.0",
"from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"chainId": "0x01",
"calls": [
{
"to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
"value": "0x182183",
"data": "0xfbadbaf01",
"capabilities": {
"flowControl": {
"onFailure": "continue"
}
}
}
]
}
]
wallet_getCapabilities
以下 JSON Schema 被插入到从 wallet_getCapabilities
返回的每个链的对象中,键为 flowControl
。
Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"minProperties": 1,
"properties": {
"none": { "$ref": "#/$defs/onFailure" },
"loose": { "$ref": "#/$defs/onFailure" },
"strict": { "$ref": "#/$defs/onFailure" }
},
"$defs": {
"onFailure": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"enum": ["rollback", "halt", "continue"]
}
}
}
}
示例响应
{
"0x1": {
"flowControl": {
"loose": ["halt", "continue"],
"strict": ["continue"]
}
}
}
概念
调用失败
回滚
回滚的非正式定义是“不引起链上的任何有意义的更改”。回滚的批处理仅进行 gas 统计和簿记(例如 nonce)更改。换句话说,回滚是 EIP-5792 在调用失败时的默认行为。
关键调用
关键调用是一种会导致整个批处理在失败时回滚的调用,相应地,非关键调用则不会。具体来说,关键调用的调用范围 onFailure
为 rollback
(或不存在 onFailure
),而非关键调用则为 halt
或 continue
。
原子性级别
本提案引入了三个原子性级别:strict、loose 和 none;分别通过将批处理范围的 atomicity
设置为 strict
、loose
或 none
来启用。也可以通过完全省略 atomicity
来启用 strict。
Strict 原子性只是命名了 EIP-5792 的默认行为:单个批处理中的调用 MUST 是连续的并以原子方式应用(或批处理回滚)。
另一方面,Loose 原子性是一种较弱的保证。在发生区块重组的情况下,批处理中的任何数量的调用 MAY 出现在链上(可能与其他交易穿插在一起)。如果没有发生区块重组,Loose 原子性 MUST 提供与 strict 相同的保证。
None 原子性级别仅提供调用按批处理中的顺序出现在链上的保证。批处理中的任何数量的调用 MAY 出现在链上(可能与其他交易穿插在一起)。
行为
wallet_sendCalls
如果以下两个条件都满足,钱包 MUST 使用错误代码 MISSING_CAP
拒绝 wallet_sendCalls
请求:
- 不存在 批处理范围的
flowControl
能力;并且 - 存在 调用范围的
flowControl
能力。
请注意,如果调用范围的 flowControl
能力被标记为可选,则上述要求仍然适用。
当批处理范围的 capabilities
中存在 flowControl
时,以下更改将覆盖 EIP-5792 中指定的行为。
删除的要求
删除了 EIP-5792 中定义的以下要求:
钱包:
- MUST NOT 等待任何调用完成最终确定批处理
- MUST 在单个交易中以原子方式提交多个调用
- 如果任何调用失败,MAY 回滚所有调用
- MUST 在失败的调用后不执行任何进一步的调用
- 如果批处理中的一个或多个调用预计会失败,则 MAY 拒绝该请求(当按顺序模拟时)
添加的要求
钱包:
批处理原子性
- MAY 将批处理拆分为多个交易。
- MUST 将缺少批处理范围的
atomicity
级别视为等同于strict
。 - 当批处理范围的
atomicity
为strict
时,MUST 提供严格保证(如上定义)。 - 当批处理范围的
atomicity
为loose
时,MUST 提供 至少 宽松保证(如上定义)。 - 当批处理范围的
atomicity
为none
时,MUST 提供 _至少_按顺序调用包含保证(如上定义)。 - 当批处理范围的
atomicity
为none
时,MAY 提供宽松保证(如上定义)。 - 当批处理范围的
atomicity
为loose
或none
时,MAY 提供严格保证(如上定义)。 - 如果一个或多个关键调用(如上定义)失败,MUST 回滚批处理。
- 如果零个关键调用(如上定义)失败,MUST NOT 回滚批处理。
- 换句话说,如果唯一的失败是非关键的,则成功的调用必须出现在链上。
- MUST NOT 执行一个调用超过一次(或永远不允许一个调用被执行)。
流程控制
- MUST 将缺少调用范围的
flowControl
能力视为等同于将onFailure
设置为rollback
。 - MUST 将缺少调用范围的
onFailure
模式视为等同于rollback
。 - 对于
onFailure
设置为halt
的失败调用,MUST NOT 执行任何后续调用。 - 对于
onFailure
设置为continue
的失败调用,MUST 继续正常执行调用。
错误
- 如果批处理包含至少一个关键调用,并且批处理请求钱包可以提供但用户拒绝的原子性级别(例如,可能发生在 EIP-7702 设置代码交易中),则 MUST 拒绝(错误代码为
REJECTED_LEVEL
)。- 请注意,这仅适用于专门因为原子性而被用户拒绝的情况。它不会更改因其他原因而被拒绝的批处理的行为。此错误代码 MUST NOT 用于其他拒绝原因。
- 如果批处理包含至少一个关键调用,并且批处理请求钱包因用户拒绝以外的任何原因无法提供的原子性级别,则 MUST 拒绝(错误代码为
UNSUPPORTED_LEVEL
)。- 支持
strict
但不支持loose
的钱包 SHOULD NOT 拒绝loose
批处理,而 SHOULD 将请求升级为 strict 原子性。 - 请注意,恰好具有一个调用的批处理 始终 满足 strict 原子性的要求。
- 支持
- 对于包含不支持的
onFailure
模式的批处理,MUST 拒绝(错误代码为UNSUPPORTED_ON_FAIL
)。 - 对于包含不支持的调用范围
onFailure
模式的组合/排序的批处理,MUST 拒绝(错误代码为UNSUPPORTED_FLOW
)。- 当在
none
批处理中使用时,即使批处理已升级为loose
或strict
原子性,钱包 MUST 拒绝rollback
。这也适用于未指定显式onFailure
模式的调用。
- 当在
- 如果预计批处理将被回滚,则 MAY 拒绝该请求(错误代码为
ROLLBACK_EXPECTED
)。 - 如果批处理中的任何调用预计会失败,则 SHOULD 在执行任何调用之前通知用户。
wallet_getCallsStatus
当使用与已启用批处理范围的 flowControl
能力提交的批处理相对应的批处理标识符调用 wallet_getCallsStatus
时,以下更改将覆盖 EIP-5792 中定义的行为。请注意:
- 当使用未启用此能力的批处理调用时,没有变化;并且
- 即使批处理的行为与默认行为没有改变(例如,将
atomicity
设置为strict
并省略所有调用的flowControl
能力),以下更改仍然适用。
删除的要求
删除了 EIP-5792 中定义的以下要求:
- 如果钱包在单个交易中以原子方式执行多个调用,则
wallet_getCallsStatus
MUST 返回一个带有receipts
字段的对象,该字段包含单个交易收据,对应于包含调用的交易。- 如果钱包通过其他地方定义的某些
capability
以非原子方式执行多个调用,则wallet_getCallsStatus
MUST 返回一个带有receipts
字段的对象,该字段包含 一个收据数组,用于包含在链上的所有包含批处理调用的交易。这包括包含在链上但最终回滚的批处理调用。
添加的要求
能力
返回的能力对象:
- MUST 包含一个
flowControl
键,该键设置为布尔值true
。- 包含有关此处各个调用状态的更多详细信息可能很诱人,但请不要这样做。而是使用在其他地方定义的多状态能力。
收据
返回的 receipts
数组:
- 对于同一事务,MUST NOT 包含多个收据。
- SHOULD NOT 包含没有来自所请求批处理的调用的事务的收据。
- MUST 包含恰好一个收据,用于捕获每个成功的调用。
- 多个调用 MAY 在一个收据中捕获,但是一个调用的成功执行 MUST NOT 被多个收据捕获。
- 给定批处理中的两个调用(A 和 B),以下是每个收据调用的非详尽示例组合。每个
(...)
都是来自单个交易的收据。- 有效示例:
[(successful A, successful B)]
[(successful A), (successful B)]
[(successful A, unsuccessful B), (successful B)]
[(unsuccessful A), (successful A), (successful B)]
- 无效示例:
[(successful A, unsuccessful B), (successful A, successful B)]
[(successful A, successful A), (successful B)]
- 有效示例:
- MAY 包含一个或多个收据,用于捕获每个失败的调用。
- 例如,钱包可以使用更高的 gas 限制重试交易。可以包括失败的交易收据和成功的交易收据,尽管只需要成功的收据。
- SHOULD 在多个
wallet_getCallsStatus
请求中保持稳定,仅附加新的收据。- 例如:
[(unsuccessful A)]
后跟[(unsuccessful A), (successful A)]
是有效的;但是- 应避免
[(unsuccessful A)]
后跟[(successful A)]
。
- 例如:
状态码
本提案修改了一些状态码,以与 EIP-5792 的 GetCallsResult.status
字段一起使用,并引入了以下新代码:
代码 | 描述 |
---|---|
102 |
部分执行 |
207 |
部分成功 |
在本节中,“已包含”的调用定义为已成功或未成功执行的调用。已记录在链上但尚未执行的调用不符合条件。包含在可能仍被回滚的批次中的已执行调用也不符合条件。
当批处理中的所有调用(包括最高到和包括 onFailure
模式为 halt
的失败调用(如果存在))都已包括在内,并且钱包不会重新提交失败的调用时,批处理是“完整的”。
100
待定
如果批处理中的任何调用都已包含在链上,则 MUST NOT 返回状态 100
。
102
部分执行
仅当以下所有条件都为真时,才应返回状态 102
:
- 批处理中的至少一个调用已包含在链上;并且
- 批处理不完整。
状态为 102
的响应 MUST 包含至少一个收据,并且 SHOULD 包含所有包含调用的事务的收据。
请注意,捕获失败的调用的收据并不意味着该调用最终将失败。钱包可能会重新提交一个调用(例如,具有更高的 gas 限制),并且最终该调用可能会成功执行。
200
已确认
如果批处理中的任何调用失败(包括批处理回滚,以及 onFailure
模式 halt
/ continue
),则 MUST NOT 返回状态 200
。
207
部分成功
仅当以下所有条件都为真时,才应返回状态 207
:
- 批处理中的至少一个调用已包含并成功;
- 批处理中至少一个
onFailure
模式为continue
的调用已包含并失败; - 没有
onFailure
模式为rollback
的调用已包含并失败; - 没有
onFailure
模式为halt
的调用已包含并失败; 并且 - 批处理已完成。
500
链规则失败
为了清楚起见,当批处理已回滚 或 当所有调用都是非关键的并且全部失败时,状态 500
是正确的代码。
如果包含任何调用并且成功,则应改为返回 200
、207
或 600
之一。
600
部分链规则失败
仅当以下所有条件都为真时,才应返回状态 600
:
- 批处理中的至少一个调用已包含并成功;
- 批处理中至少一个
onFailure
模式为halt
的调用已包含并失败; - 没有
onFailure
模式为rollback
的调用已包含并失败; 并且 - 批处理已完成。
wallet_getCapabilities
对 wallet_getCapabilities
的响应指示对于具有两个或更多调用的批处理的每个支持的批处理范围 atomicity
级别,支持哪些调用范围的 onFailure
模式。此处的支持表示“本地支持”。提供 strict
原子性但不提供 loose
原子性的钱包 MUST NOT 宣传对 loose
的支持(即使钱包会将 loose
升级到 strict
而不会出错。)
钱包:
- MAY 响应一个、两个或三个
atomicity
级别。 - MAY 在每个
atomicity
级别中响应一个、两个或三个onFailure
模式。这些级别不需要支持相同的模式。 - 如果 完全 支持特定的原子性 / onFailure 组合,则 MUST 包括该组合。例如,如果特定的排序是不可能的——比如
rollback
在halt
之前是可以的,但是halt
在rollback
之前是不可以的——那么rollback
和halt
都必须包含在数组中。
例子
普通外部拥有帐户(EOA)
普通的 EOA 可能会通过每个块提交一个事务来提供 halt
功能,并通过一次提交所有调用来提供 continue
功能。
{
"0x1": {
"flowControl": {
"none": [ "halt", "continue" ]
}
}
}
屏蔽内存池外部拥有帐户(EOA)
与普通的 EOA 不同,屏蔽的内存池可以提供有关交易原子性的其他保证。在此示例中,钱包仅在使用 none
原子性时提供 onFailure
模式 continue
,但在使用 loose
时提供所有三个级别。
{
"0x1": {
"flowControl": {
"none": [ "continue" ]
"loose": [ "rollback", "halt", "continue" ]
}
}
}
智能合约钱包
在此示例中,钱包将处理指定 none
和 loose
的批处理,就好像它们请求了 strict
一样。即使批处理可以使用,wallet_getCapabilities
响应也不会列出 none
或 loose
。
{
"0x1": {
"flowControl": {
"strict": [ "rollback" ]
}
}
}
错误代码
名称 | 值 |
---|---|
INVALID_SCHEMA |
|
MISSING_CAP |
|
REJECTED_LEVEL |
|
UNSUPPORTED_LEVEL |
|
UNSUPPORTED_ON_FAIL |
|
UNSUPPORTED_FLOW |
|
ROLLBACK_EXPECTED |
理由
待定
向后兼容性
未发现向后兼容性问题。
安全考虑
除非原子性级别为 strict,否则应用程序开发人员不能将批处理中的每个调用都视为独立的事务。换句话说,在批处理中的任何调用之间可能存在其他不受信任的事务。失败的调用最终可能会翻转为成功,反之亦然。即使是严格的原子批处理,在面对区块重组时也可以在成功/失败之间翻转。松散原子批处理中的调用可以包含在单独的不连续块中。对于包含批处理中的所有调用需要多长时间没有约束。应用程序应在智能合约调用中编码截止日期和超时行为,就像他们今天对事务一样,包括以其他方式捆绑的事务。
版权
版权和相关权利已通过 CC0 放弃。
Citation
Please cite this document as:
Sam Wilson (@SamWilsn) <sam@binarycake.ca>, "EIP-7867: 流程控制钱包调用能力 [DRAFT]," Ethereum Improvement Proposals, no. 7867, January 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7867.