Alert Source Discuss
⚠️ Draft Standards Track: Interface

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 降低其所需的原子性保证,并控制失败/回滚调用后的行为。它引入了批处理范围的 strictloose 原子性概念,其中 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 在调用失败时的默认行为。

关键调用

关键调用是一种会导致整个批处理在失败时回滚的调用,相应地,非关键调用则不会。具体来说,关键调用的调用范围 onFailurerollback(或不存在 onFailure),而非关键调用则为 haltcontinue

原子性级别

本提案引入了三个原子性级别:strict、loose 和 none;分别通过将批处理范围的 atomicity 设置为 strictloosenone 来启用。也可以通过完全省略 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
  • 当批处理范围的 atomicitystrict 时,MUST 提供严格保证(如上定义)。
  • 当批处理范围的 atomicityloose 时,MUST 提供 至少 宽松保证(如上定义)。
  • 当批处理范围的 atomicitynone 时,MUST 提供 _至少_按顺序调用包含保证(如上定义)。
  • 当批处理范围的 atomicitynone 时,MAY 提供宽松保证(如上定义)。
  • 当批处理范围的 atomicityloosenone 时,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 批处理中使用时,即使批处理已升级为 loosestrict 原子性,钱包 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 被多个收据捕获。
    • 给定批处理中的两个调用(AB),以下是每个收据调用的非详尽示例组合。每个 (...) 都是来自单个交易的收据。
      • 有效示例:
        • [(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 是正确的代码。

如果包含任何调用并且成功,则应改为返回 200207600 之一。

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 包括该组合。例如,如果特定的排序是不可能的——比如 rollbackhalt 之前是可以的,但是 haltrollback 之前是不可以的——那么 rollbackhalt 都必须包含在数组中。
例子
普通外部拥有帐户(EOA)

普通的 EOA 可能会通过每个块提交一个事务来提供 halt 功能,并通过一次提交所有调用来提供 continue 功能。

{
    "0x1": {
        "flowControl": {
            "none": [ "halt", "continue" ]
        }
    }
}
屏蔽内存池外部拥有帐户(EOA)

与普通的 EOA 不同,屏蔽的内存池可以提供有关交易原子性的其他保证。在此示例中,钱包仅在使用 none 原子性时提供 onFailure 模式 continue,但在使用 loose 时提供所有三个级别。

{
    "0x1": {
        "flowControl": {
            "none": [ "continue" ]
            "loose": [ "rollback", "halt", "continue" ]
        }
    }
}
智能合约钱包

在此示例中,钱包将处理指定 noneloose 的批处理,就好像它们请求了 strict 一样。即使批处理可以使用,wallet_getCapabilities 响应也不会列出 noneloose

{
    "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.