拓冰建站拓冰建站
首页 / 资讯中心 / 正文

拆解 geth callTracer 测试用例 inner_throw_outer_revert:嵌套 revert 的传播与追踪

拆解 geth callTracer 测试用例 inner_throw_outer_revert嵌套 revert 的传播与追踪【免费下载链接】go-ethereumGo implementation of the Ethereum protocol项目地址: https://gitcode.com/gh_mirrors/go/go-ethereum这篇技术指南聚焦于 go-ethereumgeth内置追踪器tracers测试数据目录中的inner_throw_outer_revert测试用例一个内层调用 throw、外层调用 revert的 Solidity 部署场景。读完后你将理解该用例如何由.md场景描述与.json预置状态/期望输出构成、callTracer 的callFrame结构如何在源码层面记录error与revertReason以及如何在本仓库中直接运行并验证这些测试。测试用例的落点tracetest 数据目录该文档位于 geth 追踪器测试套件的数据目录中与同名的 JSON 测试用例文件成对出现场景描述用一段 Solidity 源码说明该用例要验证什么行为测试数据完整的创世预置状态genesis alloc、块上下文context、原始交易输入inputRLP 编码的交易以及期望的 trace 输出result。同目录下还有simple.json、revert.json、revert_reason.json、inner_revert_reason.json、oog.json、delegatecall.json、deep_calls.json等一系列 callTracer 用例分别覆盖简单调用、带原因的回滚、嵌套创建、委托调用等场景。驱动这些数据的测试框架是 calltrace_test.go其中三个测试入口分别对应不同版本的追踪器func TestCallTracerLegacy(t *testing.T) { testCallTracer(callTracerLegacy, call_tracer_legacy, t) } func TestCallTracerNative(t *testing.T) { testCallTracer(callTracer, call_tracer, t) } func TestCallTracerNativeWithLog(t *testing.T) { testCallTracer(callTracer, call_tracer_withLog, t) }testCallTracer的执行流程L87-L174是读取testdata/call_tracer目录下的每个.json文件并反序列化为callTracerTest把input字段做二进制解码还原成交易用tests.MakePreState按genesis.alloc构建内存状态库然后经core.TransactionToMessage转为Message在core.ApplyMessage中执行最后把tracer.GetResult()返回的 JSON 与用例中的result字段逐字节比对。值得注意的是测试主循环只加载.json文件见 L93-L96因此.md文件不参与自动化执行它是给维护者看的场景说明书解释该 JSON 用例背后的合约意图。该目录的 README 还说明了新增用例的方式在 Geth 控制台里用makeTest.js脚本抓取某笔交易的 prestate 并执行指定 tracer把输出落成新的测试数据文件。原始场景一个必然失败的合约部署.md文档给出的场景是一对 Solidity 合约引自 inner_throw_outer_revert.mdcontract Revertor { function run() public pure { require(2 3, This called failed); } } contract Contract { constructor() { Revertor r new Revertor(); r.run(); } }调用链可以拆解为三层顶层部署Contract的交易CREATE执行构造函数中层构造函数内new Revertor()部署子合约内层 CREATE底层紧接着对刚部署的Revertor发起r.run()调用内部 CALL/STATICCALL其中require(2 3, ...)必然失败携带字符串原因This called failedrevert。文档对期望行为的结论是一句话trace 应当显示出 revert并且顶层调用top-call与内层调用inner call都要带出 revert reason。这正是嵌套回滚场景下对 callTracer 的核心要求——错误信息必须逐层向上传播且每一层各自的失败原因都要被独立捕获而不是只有最外层看到一句模糊的错误。读懂配套测试数据 inner_throw_outer_revert.json该 JSON 是同一场景较早时期的一条冻结用例一笔来自 RopstenchainId 3主网历史的真实交易部署在块 2295104context.number上执行创世配置为byzantiumBlock: 1700000的 ethash 链见 inner_throw_outer_revert.json 的genesis.config。文件结构与关键字段用例由四部分构成context执行块的元信息——块高2295104、难度、gasLimit5413248、出块者地址等genesis块2295103的完整头信息与alloc预置状态。alloc 中两个关键账户0x33056b5d...af76nonce1带完整合约代码交易的顶层to即包含回滚逻辑的已部署合约0xe819f024...78d4nonce1带完整合约代码内层被调用的合约其字节码中含CallCodeCoin字符串是经典的 CallCode 风格合约inputRLP 编码的 Legacy 交易0xf86d808504e3b292008307dfa69433056b5d...测试框架会将其解码为types.Transactionresult期望的 trace 输出。期望的 trace 输出result记录的正是内层 throw、外层 revert的传播形态{ calls: [ { error: invalid opcode: INVALID, from: 0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76, gas: 0x75fe3, gasUsed: 0x75fe3, input: 0xa9059cbb000000000000000000000000d4fcab9f0a6dc0493af47c864f6f17a8a5e2e82600000000000000000000000000000000000000000000000000000000000002f4, to: 0xe819f024b41358d2c08e3a868a5c5dd0566078d4, type: CALL, value: 0x0 } ], error: execution reverted, from: 0xd4fcab9f0a6dc0493af47c864f6f17a8a5e2e826, gas: 0x7dfa6, gasUsed: 0x7c1c8, input: 0x, to: 0x33056b5dcac09a9b4becad0e1dcf92c19bd0af76, type: CALL, value: 0xe92596fd6290000 }几个值得注意的细节错误逐层不同但都指向失败内层子调用的error是invalid opcode: INVALID该历史用例中内层合约通过跳到INVALID操作码throw属于 Byzantium REVERT 语义之前的经典失败写法顶层调用则记录execution reverted——外层因内层失败而整体回滚gas 语义内层调用分得0x75fe3约 481,411gas 且全部消耗gasUsed与gas相等顶层调用分得0x7dfa6、消耗0x7c1c8gas/gasUsed在源码中对应callFrame的Gas与GasUsed字段序列化时经gencodec转换为十六进制见 callFrameMarshalingtype字段由callFrame.Typevm.OpCode经TypeString()序列化为CALL/CREATE/STATICCALL等字符串。测试框架比对完 JSON 后还会做一道一致性校验顶层gasUsed必须与ApplyMessage返回的真实vmRet.UsedGas相等calltrace_test.go L161-L171保证用例数据与执行引擎始终同步。源码实现native callTracer 如何记录 error 与 revertReasoncallTracer 的 Go 原生实现位于 native/call.go在init()中注册进tracers.DefaultDirectoryL37-L39名称为callTracer。callFrame一次调用帧的完整记录每个调用帧用一个callFrame结构表示L51-L67type callFrame struct { Type vm.OpCode json:- From common.Address json:from Gas uint64 json:gas GasUsed uint64 json:gasUsed To *common.Address json:to,omitempty rlp:optional Input []byte json:input rlp:optional Output []byte json:output,omitempty rlp:optional Error string json:error,omitempty rlp:optional RevertReason string json:revertReason,omitempty Calls []callFrame json:calls,omitempty rlp:optional Logs []callLog json:logs,omitempty rlp:optional Value *big.Int json:value,omitempty rlp:optional revertedSnapshot bool }Error与RevertReason的分工是理解.md场景的关键Error是执行引擎给出的机器级失败状态如execution reverted、invalid opcode: INVALID而RevertReason是把revert(reason)的 ABI 编码输出解码后得到的人类可读原因字符串。revertedSnapshot标记该帧退出时快照是否真的被回滚failed()方法据此判断L73-L75。processOutputrevertReason 的生成条件revertReason不是无条件产出的其生成逻辑集中在processOutputL77-L103func (f *callFrame) processOutput(output []byte, err error, reverted bool) { output common.CopyBytes(output) // Clear error if tx wasnt reverted. ... if err ! nil !reverted { err nil } if err nil { f.Output output return } f.Error err.Error() f.revertedSnapshot reverted if f.Type vm.CREATE || f.Type vm.CREATE2 { f.To nil } if !errors.Is(err, vm.ErrExecutionReverted) || len(output) 0 { return } f.Output output if len(output) 4 { return } if unpacked, err : abi.UnpackRevert(output); err nil { f.RevertReason unpacked } }从源码结构看只有当该帧满足三个条件时才填充RevertReason错误必须是vm.ErrExecutionReverted、退出时携带的output非空且至少 4 字节ABI selector 最小长度、并且abi.UnpackRevert能成功按Error(string)编码解码出原因串。这就解释了为什么旧版inner_throw_outer_revert.json中只有error而没有revertReason内层是INVALID操作码失败输出根本无法按 revert 原因解码而require(2 3, This called failed)这类带原因的 revert 才会走到abi.UnpackRevert分支。另外注意 CREATE/CREATE2 帧失败时会把To置空L90-L92——因为部署失败意味着新地址上没有落任何代码。OnEnter/OnExit调用栈的维护追踪器通过 EVM 钩子维护调用栈callstack []callFrameOnEnterL159-L182在 EVM 进入新作用域CALL/CREATE/DELEGATECALL 等时压入一个帧depth 0的顶层帧把Gas记为交易的 gas limitt.gasLimit由OnTxStart从tx.Gas()得到OnExitL186-L210在作用域退出时弹出栈顶帧写入GasUsed并调用processOutput然后把该帧挂到父帧的Calls数组上形成嵌套结构。OnExit与processOutput在每一层独立执行因此每一层调用帧都各自携带自己的 error/output/revertReason——这正是.md中顶层与内层都要看到 revert reason这一要求的机制基础子帧不会吞掉父帧的失败信息父帧也不会被子帧覆盖。追踪器还支持两项配置callTracerConfigonlyTopCall为 true 时只记录顶层帧不收集子调用与withLog收集事件日志。.md场景属于全量嵌套模式即两项均为默认的 false。对照用例inner_revert_reason.json 中的现代预期行为同目录下的 inner_revert_reason.json 记录了.md场景在带 revert 原因机制下的完整期望形态一个部署交易的 input 字节码中内嵌了This called failed的 32 字节常量搜索7f546869732063616c6c6564206661696c6564即可定位到7f546869...00的 PUSH32 序列与.md中require(2 3, This called failed)的字符串逐字对应。其期望结果同时呈现了两层都有原因L51-L84{ error: execution reverted, revertReason: This called failed, calls: [ { type: CREATE, to: 0xd15abca351f79181dedfb6d019e382db90f3628a, ... }, { input: 0xc0406226, output: 0x08c379a00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012546869732063616c6c6564206661696c656400..., error: execution reverted, revertReason: This called failed, type: STATICCALL } ], type: CREATE }可以看到顶层 CREATE 帧与内层 STATICCALL 帧的revertReason都是This called failed且output保留了未解码的 ABI 编码字节selector0x08c379a0即Error(string)。这与inner_throw_outer_revert.json的只有 error、无 reason形成鲜明对照——两条用例合起来覆盖了嵌套失败的两个历史阶段REVERT 语义之前的 throwINVALID 操作码与之后的带原因 revert。revertReason字段同样存在于 ERC-7562 flat 追踪器的结构中见 native/erc7562.go L60说明错误状态 解码原因的双字段设计是 geth 追踪输出的一贯约定。运行与验证在本仓库中该用例由TestCallTracerNative自动覆盖只读环境下可直接运行验证不修改任何仓库文件# 运行全部 native callTracer 用例inner_throw_outer_revert 在内 go test -run TestCallTracerNative ./eth/tracers/internal/tracetest/ -v # 只跑这一个用例子测试名为文件名的驼峰形式见 util.go 的 camel 函数 go test -run TestCallTracerNative/innerThrowOuterRevert ./eth/tracers/internal/tracetest/ -v # 追踪器基准测试遍历 call_tracer 目录下所有 json go test -bench BenchmarkTracers ./eth/tracers/internal/tracetest/若用例数据与引擎行为不一致测试会输出trace mismatch / have / want的逐字节 JSON 对比calltrace_test.go L158-L160可直接定位是error、revertReason还是 gas 统计出现偏差。实战启示用 callTracer 诊断嵌套 revert把测试用例的机制映射到实际运维场景当一笔部署交易在链上失败时用debug_traceTransaction指定callTracer输出结构就是本文所述的嵌套callFrame。诊断嵌套 revert 时建议按顺序看三处每一层的error区分execution reverted语义回滚与invalid opcode: INVALID/gas 相关错误状态回滚判断失败发生在哪一层每一层的revertReason按.md场景的要求顶层与内层都会给出原因串直接对应 Solidity 中require/revert的文案可据此反查合约源码output原始字节当revertReason因编码不符而缺失时output仍保留了 ABI 编码输出可手工按0x08c379a0前缀解码。inner_throw_outer_revert这条用例的价值正在于它固化了这条诊断链路最棘手的分支——内层失败原因与外层回滚状态并存、且两层信息都必须在 trace 中完整可见——并为processOutput中ErrExecutionReverted、输出长度与abi.UnpackRevert的三重判定提供了可回归验证的基准数据。【免费下载链接】go-ethereumGo implementation of the Ethereum protocol项目地址: https://gitcode.com/gh_mirrors/go/go-ethereum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门