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 是操作的最新名称 (SELFDESTRUCT
和PREVRANDAO
) - 如果
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 仅包括通过SSTORE
或SLOAD
读取或写入的项目,而不包括帐户的整个存储。
例子:
{"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 | 用于执行的分叉规则的名称 |
- 可以提供
time
和fork
字段 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 一词已被简单地替换为“客户端”。
gas
、gasCost
和gasUsed
现在是 Number 类型。opcode
现在是 Hex-Number 类型。
EIP-3155 的新增内容
- 添加了
immediate
成员以支持包含立即操作的大量指令。如果没有此更改,用户将需要执行合约的字节来合理化追踪。 - 添加了
section
和functionDepth
成员以支持 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.