Alert Source Discuss
⚠️ Draft Standards Track: Interface

EIP-7756: EOF/EVM 追踪规范

更新 EIP-3155 追踪以添加 EOF 支持

Authors Martin Holst Swende (@holiman), Marius van der Wijden (@MariusVanDerWijden), Danno Ferrin (@shemnon)
Created 2024-08-13
Discussion Link https://ethereum-magicians.org/t/eip-7756-eof-evm-trace-specification/20806
Requires EIP-3155, EIP-4750

摘要

更新 EIP-3155 JSON 追踪规范以支持 EOF 特性。

动机

EIP-3155 为 Legacy EVM 操作定义了一个追踪标准。然而,EVM 对象格式 (EIP-7692) 添加了许多需要在调试追踪中反映的特性。

这些追踪的使用也从状态测试转移出来,包括实时区块追踪和差异模糊测试,增加了保持追踪更新的需要。

该 EIP 有多个目标:

  • 向追踪对象添加成员以支持新的 EOF 特性。
  • 支持追踪包含在 EOF 容器中的合约以及同一追踪中未包含的 “legacy” 合约。
  • 澄清 EIP-3155 规范中任何先前的歧义。

规范

为了提高清晰度并提供统一的规范,整个追踪规范将以内联方式呈现,而不是作为 EIP-3155 之上的一组差异。差异将在向后兼容性部分中突出显示。

本文档中的关键词 “MUST”、”MUST NOT”、”REQUIRED”、”SHALL”、”SHALL NOT”、”SHOULD”、”SHOULD NOT”、”RECOMMENDED”、”NOT RECOMMENDED”、”MAY” 和 “OPTIONAL” 应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

数据类型

类型 说明 JSON 成员示例
Number JSON 数字 "pc":0
Hex-Number 十六进制编码的数字 "gas":"0x2540be400"
String 纯字符串 "opName":"PUSH1"
Hex-String 十六进制编码的字符串 "returnData":"0x60616203"
Array 十六进制字符串数组 "stack":["0x0", "0x0"]
Key-Value 键值结构,键和值编码为十六进制字符串 "storage":{"0x0":"0x1", "0x1":"0x1"}
Boolean Json 布尔值,可以是 true 或 false "pass": true
  • 客户端可以 OPTIONALLY 输出 Number, 在需要 Hex-Number 的地方,反之亦然。这样做的客户端 SHOULD 提供一个 CLI 选项来严格遵守正确的输出类型。
  • 大于 2^53 - 1 的数字必须表示为 Hex-Numbers。这是 JSON 的一个限制。
  • EIP-7756 追踪的消费者 SHOULD 以这样一种方式编写,即类型为 Hex-Number 和 Number 的成员可以分别提供 Number 或 Hex-Number。
  • 请注意,没有字符串格式的十进制数替代方案。十进制表示始终是 JSON 数字,十六进制表示始终是编码十六进制数的字符串。

输出

  • 客户端为执行的每个 EVM 操作输出一个 JSON 对象。
  • 对于相同的操作执行,客户端 MUST NOT 输出多行。
  • 如果发生错误,或者合约耗尽指令,客户端 MUST NOT 为 STOP 操作输出一行。

必需字段

每个追踪行 MUST 具有以下字段。

名字 类型 说明
pc Number 程序计数器
op Hex-Number 操作码
opName String 操作名称
gas Number 执行此操作前剩余的 Gas
stack Array of Hex-Numbers 堆栈上所有值的数组
depth Number 调用堆栈的深度
error Hex-String 错误的描述 (如果支持,SHOULD 包含 revert 原因)
  • 当合约不在 EOF 容器中时,pc 值从合约的开头开始零索引,或者从容器 EOF 容器的开头开始零索引。对于 legacy 合约,第一个执行行是 "pc":0。对于 EOF 合约,零字节对应于 0xEF 魔术字节。第一个执行行将不在 "pc":0,而是在执行的第一个代码部分的第一个字节。
  • 在操作有多个规范名称的情况下,opName SHOULD 是操作的最新名称 (SELFDESTRUCTPREVRANDAO)

  • 如果 stack 为空,则使用空数组 ([]) 而不是 null
  • depth 从 1 开始。
  • 如果当前帧中的先前 CALL 系列或 CREATE 系列操作尚未返回 revert 原因,并且当前操作未触发异常停止,则 error SHOULD 被省略。

推荐字段

在指示的条件下,每个追踪行 SHOULD 具有以下字段。

名字 类型 说明
gasCost Number 此操作的 Gas 成本
memSize Number 内存数组的大小
returnData Hex-String 函数调用返回的数据
refund Hex-Number 全局 Gas 退还量
  • gasCost 是所有 Gas 成本的总和,包括动态成本,例如内存扩展、调用补贴和帐户预热成本。
  • memSize 以 8 位字节为单位计数,而不是 256 位字。
  • 如果 CALL 系列操作未在当前帧中完成,则 returnData SHOULD NOT 存在。

可选字段

在指示的条件下,每个追踪行 MAY 具有以下字段。 如果在追踪中要省略一个字段,它 MUST 始终在同一追踪中省略。

名字 类型 说明
section Number 当前正在执行的 EOF 部分
immediate Hex-String 操作码的立即参数
functionDepth Number EIP-4750 CALLF 返回堆栈的深度
memory Array of Hex-Strings 所有已分配值的数组
storage Key-Value 所有存储值的数组
  • section 成员仅在追踪包含在 EOF 容器中的合约时才必须存在。
  • 对于没有立即数据的操作,immediate 字段 MUST NOT 存在。
    • 对于 PUSH 系列操作,此字段是 OPTIONAL 的,因为立即数据被推送到堆栈上
    • 对于 RJUMPV,这将包括表长度和整个表。客户端 MAY 而是仅存储表长度。
    • 对于所有其他具有立即数据的操作,包括前导零在内的整个立即数据 SHOULD 存在。
  • functionDepth 从 1 开始,如果为 1,则 MAY 被省略。
  • 对于不在 EOF 容器中的代码的追踪行,functionDepth MUST NOT 存在。
  • 如果 memory 为空,则使用空数组 ([]) 而不是 null
  • storage 成员 SHOULD 仅包括通过 SSTORESLOAD 读取或写入的项目,而不包括帐户的整个存储。

例子:

{"pc":0,"op":96,"gas":"0x2540be400","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"depth":1,"error":null,"opName":"PUSH1"}

摘要行

在执行结束时,客户端 SHOULD 打印摘要信息。此摘要 MUST 是单个 JSON 对象。

此信息 SHOULD 具有以下成员。

名字 类型 说明
stateRoot Hex-String 执行事务后状态树的根
output Hex-String 函数的返回值
gasUsed Number 事务使用的所有 Gas
pass Boolean 如果事务成功,或者测试通过
time Number 执行事务所需的纳秒时间
fork String 用于执行的分叉规则的名称
  • 可以提供 timefork 字段 MAY。

例子:

{"stateRoot":"0xd4c577737f5d20207d338c360c42d3af78de54812720e3339f7b27293ef195b7","output":"","gasUsed":"0x3","pass":"true","time":141485}

理论依据

此 EIP 是 EIP-3155 追踪功能的扩展,该功能已使用了多年。信息被添加到现有的追踪中,而不是大幅度地重新启动该功能。

考虑使用 “mini” 追踪来允许将追踪包含在诸如 t8n 之类的工具中,并允许更高效的 RPC 追踪调用,但这似乎与添加功能到现有追踪功能的 EIP 足够不同,因此它将是一个独立的 EIP。

为了确保与现有客户端的最大兼容性,移动到 JSON Schema 的想法被拒绝。

向后兼容性

为未包含的 “legacy” 合约发出追踪 JSON 的客户端将生成兼容的追踪,除非如下所述

EIP-3155 的更改

  • 待测客户端或 CUT 一词已被简单地替换为“客户端”。
  • gasgasCostgasUsed 现在是 Number 类型。
  • opcode 现在是 Hex-Number 类型。

EIP-3155 的新增内容

  • 添加了 immediate 成员以支持包含立即操作的大量指令。如果没有此更改,用户将需要执行合约的字节来合理化追踪。
  • 添加了 sectionfunctionDepth 成员以支持 EIP-4750 EOF 函数。
  • 添加了关于在 EOF 容器中运行时 pc 索引位置的说明。
  • 添加了 Hex-Number/Number 可互换性期望。当指定一种类型时,客户端 MAY 提供任一类型,并且消费者 SHOULD 准备好接受任一类型。客户端 SHOULD 提供一个标志来仅输出具有指定数字类型的成员。

客户端

Besu, evmone, EthereumJS, Geth, Nethermind, 和 Reth 已经在各种工具中生成这些标准追踪。添加新字段将与支持 EIP-7692 中枚举的 EOF EIP 所需的工作保持一致。

测试用例

这是来自以太坊执行规范测试的追踪输出,来自 test_eof_functions_contract_call_succeed 的参数化执行之一。 禁用内存和返回数据。

json lines {"pc":0,"op":"0x60","gas":49979000,"gasCost":3,"memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":2,"op":"0x60","gas":49978997,"gasCost":3,"memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":4,"op":"0x60","gas":49978994,"gasCost":3,"memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":6,"op":"0x60","gas":49978991,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":8,"op":"0x60","gas":49978988,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":10,"op":"0x61","gas":49978985,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH2"} {"pc":13,"op":"0x5a","gas":49978982,"gasCost":2,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000"],"depth":1,"refund":0,"opName":"GAS"} {"pc":14,"op":"0xf1","gas":49978980,"gasCost":49198100,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000","0x2fa9e64"],"depth":1,"refund":0,"opName":"CALL"} {"pc":25,"section":0,"op":"0xe3","immediate":"0x0001","gas":49195500,"gasCost":5,"memSize":0,"stack":[],"depth":2,"refund":0,"opName":"CALLF"} {"pc":29,"section":1,"op":"0xe4","gas":49195495,"gasCost":3,"memSize":0,"stack":[],"depth":2,"functionDepth":2,"refund":0,"opName":"RETF"} {"pc":28,"section":0,"op":"0x00","gas":49195492,"gasCost":0,"memSize":0,"stack":[],"depth":2,"refund":0,"opName":"STOP"} {"pc":15,"op":"0x60","gas":49976372,"gasCost":3,"memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":17,"op":"0x55","gas":49976369,"gasCost":22100,"memSize":0,"stack":["0x1","0x0"],"depth":1,"refund":0,"opName":"SSTORE"} {"pc":18,"op":"0x00","gas":49954269,"gasCost":0,"memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"} {"output":"","gasUsed":45731,"fork":"Osaka","postHash":"0x2c47b4070c1eef501d9548959c3abde2d8dc78ed1d819697c61d2b0861cc78cf","pass":true}

安全考虑

客户端应该意识到,追踪在 CPU 开销和网络带宽方面都可能很昂贵。 默认情况下不应启用跟踪端点,并且在启用它们时应在网络级别上具有访问限制。 否则可能会导致客户端被请求淹没,并且如果作为验证器运行,则会导致客户端无法及时提供执行证明。

差异模糊测试也是一把双刃剑。 虽然它允许客户端团队能够识别共识分裂,但客户端团队需要及时修复发现的任何问题。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Martin Holst Swende (@holiman), Marius van der Wijden (@MariusVanDerWijden), Danno Ferrin (@shemnon), "EIP-7756: EOF/EVM 追踪规范 [DRAFT]," Ethereum Improvement Proposals, no. 7756, August 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7756.