尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

BSC 基于 go-ethereum 的 Tracing 接口演进全解:从 EVMLogger 到 Hooks 与 State Journaling

BSC 基于 go-ethereum 的 Tracing 接口演进全解:从 EVMLogger 到 Hooks 与 State Journaling BSC 基于 go-ethereum 的 Tracing 接口演进全解从 EVMLogger 到 Hooks 与 State Journaling【免费下载链接】bscA BNB Smart Chain client based on the go-ethereum fork项目地址: https://gitcode.com/GitHub_Trending/bs/bsc导读本文以 BNB Smart Chain 客户端仓库基于 go-ethereum 的 fork中的 core/tracing/CHANGELOG.md 为主线系统梳理了以太坊系客户端 EVM 追踪tracing接口从 v1.14.0 到最新 Unreleased 版本的完整演进历程。你将掌握tracing.Hooks结构体的全部事件监听器及其签名、四大状态变化原因类型Balance/Gas/Nonce/Code ChangeReason的取值语义、状态日志State Journaling回滚机制的原理与用法以及 BSC 在 Parlia 共识下对系统交易system transaction追踪的扩展实现从而具备编写自定义 live tracer 与解读链上状态变化事件流的完整能力。一、为什么要有一份 Tracing 接口的 ChangelogTracing 是 EVM 可观测性的核心从 RPC 层的debug.traceTransaction到节点内部的 live tracing再到供给量统计、MEV 分析都依赖 EVM 在状态转换过程中向外抛出的事件。这类接口一旦变更所有基于其上的 tracer 都要跟着调整因此官方以 CHANGELOG 形式逐版本记录每一处增删改保证自定义 tracer 开发者可以对照迁移。core/tracing/CHANGELOG.md 记录的是core/tracing包即 live tracing 的低层钩子库的变更覆盖 v1.14.0 至今的所有版本。它与 hooks.go、journal.go 等源码一一对应是理解该接口最权威的文档入口。二、v1.14.0破坏性重构EVMLogger 谢幕、Hooks 登场2.1 从接口到结构体为什么必须改v1.14.0 是 tracing 接口历史上最大的一次破坏性变更其背景是新的 live tracing 特性对应 go-ethereum PR #29189。核心变化一句话概括EVMLogger接口被彻底移除取而代之的是新的tracing.Hooks结构体。Hooks内部保存的是指向事件监听函数的指针function pointers。EVM 在内部通过这些函数指针派发事件如果 tracer 没有实现某个钩子EVM 可以直接跳过该事件而不必调用一个空实现。这正是本次重构的初衷——旧接口要求 tracer 实现所有方法哪怕空实现而新方案事件派发可跳过nil 检查性能与代码整洁度双赢未来新增钩子无需破坏既有 tracer事件接收者可以动态指派。2.2 4byte tracer 的前后对比原文档示例CHANGELOG 以 4byte tracer 的构造函数为例展示了迁移方式。旧写法返回一个满足接口的实例func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) { t : fourByteTracer{ ids: make(map[string]int), } return t, nil }新写法返回指向tracers.Tracer结构体的指针并显式指定事件监听器func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (*tracers.Tracer, error) { t : fourByteTracer{ ids: make(map[string]int), } return tracers.Tracer{ Hooks: tracing.Hooks{ OnTxStart: t.onTxStart, OnEnter: t.onEnter, }, GetResult: t.getResult, Stop: t.stop, }, nil }在当前仓库的 eth/tracers/native/4byte.go 中可以看到该 tracer 的最终形态它注册在tracers.DefaultDirectoryRPC 调用的 tracer 目录构造函数为newFourByteTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error)只挂载了OnTxStart与OnEnter两个钩子并额外实现了GetResult返回收集到的 4byte 标识符集合与Stop中断信号。注意tracers.Tracer现在也是结构体而非接口。2.3 事件监听器命名与签名的全面调整改名总览Capture* → On*CHANGELOG 明确指出所有方法名从Capture*模式改为On*模式且签名也有多处修改旧方法新方法关键变化CaptureStartOnEnter顶层帧由 depth0 区分已删除见下文CaptureEndOnExit顶层帧由 depth0 区分已删除见下文CaptureTxStartOnTxStart(vm *VMContext, tx *types.Transaction, from common.Address)传入完整交易对象与from*VMContext替代旧的*vm.EVMCaptureTxEndOnTxEnd(receipt *types.Receipt, err error)返回完整 receiptCaptureEnterOnEnter(depth int, typ byte, from, to common.Address, input []byte, gas uint64, value *big.Int)新增depth表示调用栈深度顶层为 0OnEnter的触发时机提前到调用开始因此部分原先不触发 Enter/Exit 的错误场景现在也会触发个别交易会多出一层调用记录CaptureExitOnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool)新增depth与reverted指示该调用帧是否被回滚CaptureStateOnOpcode(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error)op为 byte 类型可转型为vm.OpCode不再传*vm.ScopeContext改为tracing.OpContextCaptureFaultOnFault(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error)同上删除的方法CaptureStart与CaptureEnd被删除——这两个钩子原本标记交易的顶层调用帧。现在顶层帧信息由OnEnter/OnExit的depth参数区分depth 0 即顶层CaptureStart中的create bool参数可以从OnEnter的typ byte推断即vm.OpCode(typ) vm.CREATE。新增的状态观察方法live tracing 的一半价值在于增强对区块链状态的观测能力。CHANGELOG 列举了可通过 API 调用的自定义 tracer 受益的新方法完整方法列表见 hooks.go 中的 Hooks 结构体OnGasChange(old, new uint64, reason GasChangeReason)跟踪一笔交易及其子调用内 gas 的完整生命周期——先是购买 gas然后逐笔消耗与退款最后退回剩余部分OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason)跟踪账户余额变化尽可能给出原因转账、购买 gas、提款入账等OnNonceChange(addr common.Address, prev, new uint64)跟踪账户 nonce 变化OnCodeChange(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte)跟踪账户代码变化OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash)跟踪账户存储变化OnLogChange(log *types.Log)跟踪 EVM 发出的日志。需要说明的是在当前仓库的 hooks.go 中日志钩子实际命名为OnLogLogHook func(log *types.Log)CHANGELOG 中写作OnLogChange属于早期命名以源码为准。2.4 支撑类型VMContext 与 OpContext钩子签名中反复出现的两个接口在 hooks.go 中定义OpContext提供执行某条 opcode 时的上下文MemoryData()内存、StackData()栈、Caller()/Address()、CallValue()/CallInput()以及ContractCode()当前合约字节码v1.14.10 新增对应 PR #30466VMContext提供 EVM 执行环境Coinbase、BlockNumber、Time、Random、BaseFee以及StateDB让 tracer 可以直接读取全量状态见下文。三、四大 ChangeReason 类型状态变化的原因标注为了让 tracer 理解状态变化背后的业务语义tracing包定义了四个枚举类型。它们在 hooks.go 中均有完整定义并为调试与日志输出生成了 String() 方法对应仓库中的 gen_balance_change_reason_stringer.go 等四个生成文件v1.15.4 中GasChangeReason与NonceChangeReason补齐了自动生成的 String()对应 PR #31234。3.1 BalanceChangeReason用于标注余额变化原因取值包括发行类BalanceIncreaseRewardMineUncle叔块奖励、BalanceIncreaseRewardMineBlock出块奖励、BalanceIncreaseWithdrawal信标链提款、BalanceIncreaseGenesisBalance创世分配交易费用类BalanceIncreaseRewardTransactionFee小费归矿工/构建者、BalanceDecreaseGasBuy购买 gas部分按 EIP-1559 销毁、BalanceIncreaseGasReturn未用 gas 退还DAO 分叉类BalanceIncreaseDaoContract、BalanceDecreaseDaoAccount常规转账类BalanceChangeTransfer调用转账发送方为减、接收方为增、BalanceChangeTouchAccount零值触碰创建账户自毁类BalanceIncreaseSelfdestruct、BalanceDecreaseSelfdestruct、BalanceDecreaseSelfdestructBurn回滚类BalanceChangeRevert调用失败后余额回滚仅当 tracer 使用 journaling 包装层时才发出v1.15.0 引入BSC 特有当前仓库扩展BalanceDecreaseBSCDistributeReward 210系统地址向验证者分发奖励时的扣减、BalanceIncreaseBSCDistributeReward 211验证者收到奖励。3.2 GasChangeReasonCHANGELOG 特别提示这类原因分两种——以GasChangeTx开头的每笔交易只发一次以GasChangeCall开头的按调用帧可能多次发出。交易级GasChangeTxInitialBalance、GasChangeTxIntrinsicGas、GasChangeTxRefunds、GasChangeTxLeftOverReturned以及 v1.15.0 新增的GasChangeTxDataFloor交易数据为达到最低 gas 要求而额外支付的 gas恒为负向变化调用级GasChangeCallInitialBalance、GasChangeCallLeftOverReturned、GasChangeCallLeftOverRefunded、GasChangeCallContractCreation/ContractCreation2、GasChangeCallCodeStorage、GasChangeCallOpCode、GasChangeCallPrecompiledContract、GasChangeCallStorageColdAccessEIP-2929 冷访问、GasChangeCallFailedExecution未 revert 的执行失败烧掉剩余 gasVerkle 见证相关仅 post-Verkle 生效分叉前不应出现GasChangeWitnessContractInitv1.14.4合约创建初始化阶段加入 witness、GasChangeWitnessContractCreationv1.14.4合约创建收尾阶段、GasChangeWitnessCodeChunkv1.14.4合约代码块加入 witness、GasChangeWitnessContractCollisionCheckv1.14.9合约地址碰撞检查时加入 witness特殊值GasChangeIgnored 0xFF表示该 gas 变化由事件直发手动跟踪应被忽略。3.3 NonceChangeReasonv1.15.0 引入取值NonceChangeGenesis创世 nonce、NonceChangeEoACallEOA 调用、NonceChangeContractCreator创建合约的账户 nonce 增加、NonceChangeNewContract新合约自身 nonce、NonceChangeAuthorizationEIP-7702 授权导致的 nonce 变化、NonceChangeRevert调用失败回滚 nonce由 journaling 层发出。3.4 CodeChangeReasonUnreleased 新增当前 Unreleased 版本引入取值CodeChangeContractCreationCREATE/CREATE2 部署新合约、CodeChangeGenesis创世或初始设置写入代码、CodeChangeAuthorizationEIP-7702 Set Code Authorization 设置代码、CodeChangeAuthorizationClearEIP-7702 委托被清空为零地址、CodeChangeSelfDestruct自毁清除代码、CodeChangeRevert调用失败回滚代码由 journaling 层发出、CodeChangeSystemContractUpgradeBSC 特有扩展硬分叉期间系统合约代码升级。四、状态日志State Journaling调用回滚的自动补偿4.1 问题背景在 v1.15.0 之前tracer 收到状态变化事件后必须自己跟踪被修改的账户与存储槽并在某个调用帧失败时手工回滚这些变化。这既繁琐又容易出错。v1.15.0 引入了一个状态日志库当调用帧失败revert时自动向 tracer 发出反向变化reverse change事件。4.2 使用方式原文档示例使用前需要先把钩子包装起来再注册 tracerfunc init() { tracers.LiveDirectory.Register(test, func (cfg json.RawMessage) (*tracing.Hooks, error) { hooks, err : newTestTracer(cfg) if err ! nil { return nil, err } return tracing.WrapWithJournal(hooks) }) }4.3 覆盖的状态变化journaling 库覆盖的状态变化包括OnBalanceChange——注意回滚时携带BalanceChangeRevert原因OnNonceChange、OnNonceChangeV2OnCodeChangeOnStorageChange。4.4 底层实现原理core/tracing/journal.go 给出了完整实现核心机制是快照snapshot与修订revision栈WrapWithJournal(hooks)做合法性检查hooks不能为 nil不能同时设置OnNonceChange与OnNonceChangeV2不能同时设置OnCodeChange与OnCodeChangeV2。随后复制原 Hooks用 journal 自己的实现覆盖OnTxEnd、OnEnter、OnExit及各类状态变化钩子OnEnter每次进入调用帧时调用snapshot()记录修订点状态变化事件余额、nonce、代码、存储被追加到entries日志同时转发给原 tracerOnExit时若reverted true则调用revert()从日志尾部向前回放entry.revert(hooks)把状态反向回调如余额从 new 回到 prev并附带BalanceChangeRevert原因若未回滚则仅popRevision()丢弃修订OnTxEnd重置日志每个交易有自己的 EVM 调用栈。其中revert事件的方向是反向广播如余额balanceChange.revert会以(addr, new, prev, BalanceChangeRevert)调用原钩子存储则以(addr, slot, new, prev)反向调用代码则以新旧 codeHash/code 对调后回放。4.5 测试用例印证core/tracing/journal_test.go 中的测试用例完整验证了这套语义TestJournalIntegration模拟两层调用帧内层帧全部变更后 revert外层成功验证余额回到 100、nonce 回到 0、代码保留、存储只保留外层变更TestJournalTopRevert顶层也 revert 时余额回到 0、nonce 回到 0TestJournalNestedCalls复杂嵌套调用下只有第 4 个子帧 revert验证其变更被撤销而其余变更保留TestNonceIncOnCreate与TestNonceIncOnCreateParentReverts验证 CREATE 帧自身失败时创建者 nonce 增加不被该帧 revert因为 nonce 自增发生在 EVM 快照之前但父帧 revert 时会一并回滚——journal.go 通过将当前帧的修订点推进到该条目之后实现这一精确语义TestOnNonceChangeV2/TestOnCodeChangeV2验证 V2 钩子回滚后 nonce 与代码正确复原TestAllHooksCalled用反射遍历Hooks全部字段确保包装层不会吞掉任何钩子。五、系统调用与 BSC 的系统交易钩子5.1 系统调用钩子v1.14.3 起为了显式标记系统合约的执行v1.14.3 新增OnSystemCallStart()EVM 开始处理系统调用时触发。系统调用发生在交易范围之外之后会跟随正常的 EVM 执行事件OnSystemCallEnd()系统调用结束时触发。当时唯一的系统调用是按 EIP-4788 更新父信标区块根beacon block root更多系统调用计划在未来硬分叉中加入。v1.15.0 中OnSystemCallStart被弃用升级为OnSystemCallStartV2(vm *VMContext)使 tracer 在系统调用期间也能访问 EVM 上下文见 hooks.go 中OnSystemCallStartHook与OnSystemCallStartHookV2的定义。5.2 BSC 特有系统交易钩子当前仓库扩展在 hooks.go 中可以看到 go-ethereum 上游没有的扩展OnSystemTxStart与OnSystemTxEndOnSystemTxStartHook/OnSystemTxEndHook用于标记Parlia 共识引擎内执行的系统交易——例如系统合约升级、奖励分配等链上交易。其事件流为OnSystemTxStart OnTxStart OnTxEnd OnSystemTxEnd即系统交易钩子与普通OnTxStart/OnTxEnd叠加触发仅实现 OnTxStart/End 的 tracer 也能正确追踪这类交易而实现了 OnSystemTxStart/End 的 tracer 则可以对系统交易做特殊路由。另有OnSystemTxFixIntrinsicGas(uint64)钩子用于系统交易执行时不计算内在 gas在结束时从总 gas 中扣除内在 gas 以修正统计。在 consensus/parlia/parlia.go 的applyTransaction中可以看到实际调用链构造 EVM 后先tracer.OnSystemTxStart()再tracer.OnTxStart(evm.GetVMContext(), expectedTx, msg.From)随后以 defer 方式注册OnTxEnd与OnSystemTxEnddefer 后进先出保证OnTxEnd先于OnSystemTxEnd执行最终通过applyMessage完成状态转换。5.3 BlockHash 与状态读取扩展OnBlockHashRead(blockNum uint64, hash common.Hash)v1.15.0 新增EVM 读取某个区块的 blockhash 时触发VMContext.StateDB.GetCodeHash(addr) common.Hashv1.15.0获取账户的代码哈希VMContext.StateDB.GetTransientState(addr, slot) common.Hashv1.14.12对应 PR #30531访问合约的 transient storageBlockEvent.TDTotal Difficulty字段在 v1.15.0 被移除对应 PR #30744区块事件不再携带总难度。六、小版本中的细节行为变更v1.14.12OnCodeChange钩子现在会在合约因selfdestruct移除代码时被调用此前这类场景不发出任何 code change 事件——如果你在统计代码变更需要注意自毁事件现在也会出现在事件流中v1.15.0BalanceChangeReason新增BalanceChangeRevertGasChangeReason新增GasChangeTxDataFloorNonceChangeReason新增并包含NonceChangeRevertUnreleasedOnCodeChange(addr, prevCodeHash, prevCode, codeHash, code)被弃用替代为带原因参数的OnCodeChangeV2(addr, prevCodeHash, prevCode, codeHash, code, reason CodeChangeReason)对应 go-ethereum PR #32525原因覆盖合约创建、创世初始化、EIP-7702 授权、自毁与回滚。七、实战如何编写并注册一个 Live Tracer结合上述接口一个完整的 live tracer 由三部分组成注册tracers.LiveDirectory.Register、钩子实现tracing.Hooks、以及可选的 journaling 包装。仓库中 eth/tracers/live/supply.go 是现成的供给量统计 tracer 范例func init() { tracers.LiveDirectory.Register(supply, newSupplyTracer) } func newSupplyTracer(cfg json.RawMessage) (*tracing.Hooks, error) { var config supplyTracerConfig if err : json.Unmarshal(cfg, config); err ! nil { return nil, fmt.Errorf(failed to parse config: %v, err) } // ... return tracing.Hooks{ OnBlockchainInit: t.onBlockchainInit, OnBlockStart: t.onBlockStart, OnBlockEnd: t.onBlockEnd, OnGenesisBlock: t.onGenesisBlock, OnTxStart: t.onTxStart, OnBalanceChange: t.onBalanceChange, OnEnter: t.onEnter, OnExit: t.onExit, OnClose: t.onClose, }, nil }该 tracer 的统计逻辑完全建立在本章所述的钩子语义之上OnBalanceChange根据BalanceChangeReason分类累加BalanceIncreaseRewardMineBlock/MineUncle计入发行奖励、BalanceIncreaseWithdrawal计入提款、BalanceDecreaseSelfdestructBurn计入杂项销毁其余原因忽略创世余额在OnGenesisBlock中处理因为BalanceIncreaseGenesisBalance在每个交易中并不出现OnEnter/OnExit用调用栈跟踪内部调用在OnExit(depth 0)时统计子调用中的销毁量如 SELFDESTRUCT 且 from to 的 burn并在reverted时丢弃OnBlockStart/OnBlockEnd按块统计 EIP-1559 与 blob 的销毁gasUsed × baseFeeblob 则通过eip4844.CalcBlobFee计算输出采用 lumberjack 轮转日志以 JSONL 逐块写入supply.jsonl。另一份 eth/tracers/live/noop.go 注册了一个空 tracertracers.LiveDirectory.Register(noop, newNoopTracer)是最小的 live tracer 骨架适合作为新 tracer 的起点。需要区分两个注册目录RPC 可调用的自定义 tracer 注册在tracers.DefaultDirectory如 4byte而 live tracer 注册在tracers.LiveDirectory。如果你要追踪的状态变更需要回滚补偿记得在注册前用tracing.WrapWithJournal(hooks)包装如 CHANGELOG 中的示例所示。八、结语接口演进的规律与迁移要点纵观 core/tracing/CHANGELOG.md 与 core/tracing/hooks.go可以总结出该接口的三条演进主线从实现全部方法到按需挂载EVMLogger接口 →Hooks结构体事件派发从必须实现变为nil 即跳过这是后续一切新增钩子能够零破坏推进的前提从知道变了到知道为什么变Balance/Gas/Nonce/Code 四大 reason 枚举不断完善配合 v1.15.0 的 state journaling让 tracer 不仅能观测状态变化还能准确理解变化的原因与回滚语义从链上交易到系统范围OnSystemCallStart/EndEIP-4788与 BSC 的 OnSystemTxStart/EndParlia 系统交易将追踪范围扩展到交易之外的共识层与系统合约执行。对于计划编写自定义 tracer 的开发者迁移要点可归纳为以On*命名挂载你关心的钩子用 reason 枚举区分状态变化语义调用帧级回滚交给WrapWithJournal涉及 BSC 系统交易时补上OnSystemTxStart/End注意OnCodeChange已弃用新代码一律使用带 reason 的OnCodeChangeV2。【免费下载链接】bscA BNB Smart Chain client based on the go-ethereum fork项目地址: https://gitcode.com/GitHub_Trending/bs/bsc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表