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

发布时间:2026/9/18 19:52:58

拆解 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),仅供参考
延伸阅读

更多相关文章

2026/9/18 19:52:58

Markdown内嵌HTML实战:字体颜色、渲染器兼容与避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 19:52:58

FineReport V11.0安装激活部署全流程详解与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 19:47:58

uniapp多端样式适配实战:rpx、条件编译与穿透避坑指南

做了这么多年 uniapp 项目,我一直觉得“样式”这件事是最能体现细节功力的地方。功能逻辑写得再顺,样式一端崩溃、一端变样,用户第一时间就会觉得产品不靠谱。uniapp 的样式体系本质上还是 CSS,但因为它要同时跑小程序、H5、App 三…

2026/9/18 20:58:03

BLE广播格式实战:31字节、AD Structure与扩展广播

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 20:58:03

教育数字化转型下教师核心能力培养与实践

1. 教育数字化转型背景下的教师角色重塑去年参加一场基础教育研讨会时,有位资深教师的发言让我印象深刻:"教了二十年书,教案写了十几本,突然发现不会上课了。"这句话折射出当前教育变革中的典型困境。随着智能教育设备的…

2026/9/18 20:58:03

智能垃圾桶技术拆解:从红外感应、MCU控制到商业落地

简介:一份智能垃圾桶项目商业计划书,源于海口某智能感应垃圾桶生产企业的真实创业方案,适合创业大赛参赛者、产品经理、智能家居从业者及相关专业学生学习,用于理解环保家居类项目的商业论证方法和计划书写作结构。资源包仅含1个d…

2026/9/18 20:58:03

信用证MT700审证与交单实战:字段拆解到单据制作全指南

简介:这是一份由迪拜外资银行开给中国银行南京分行的不可撤销信用证完整样本,涵盖国际贸易结算中最典型的跟单信用证格式。全证清晰呈现了开证行、申请人、受益人、编号、开证日期、有效期、金额、付款方式、装运港与目的港等核心要素,并完整…

2026/9/18 20:53:02

从单店到连锁:超市信息系统架构设计与进销存实战

简介:面向大型超市管理者与信息化规划人员的一份系统策划方案建议书,旨在解决超市多环节运营效率低、数据分散的问题。文档以需求背景分析为起点,对比传统零售、自助购物、线上线下结合等经营模式,进而提出系统总体设计目标与设计…

2026/9/18 14:13:01

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/18 14:13:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/18 14:13:02

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/18 14:13:02

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码