结论先说
交易回执(receipt)是”这笔交易执行完的官方记录”:状态(成功/回滚)、gas 总用量、gas 分解、事件日志(logs)、部署合约地址(部署交易时)、以及所在块的状态根。它是 DApp/钱包/索引器判断”交易到底发生了什么”的第一手数据——比”浏览器说成功了”更底层(浏览器读的就是回执 + 链上状态)。理解回执字段,就理解了”失败交易为什么也付 gas”、“日志怎么被订阅”、“部署后怎么拿合约地址”这些高频问题的标准答案。
字段逐项拆解
一,status(0/1):1 = 执行成功(状态转换保留),0 = 执行失败(revert,状态转换全部回滚)。注意:status=0 的交易”在块里、有回执、gas 照付”——失败不是”没发生”,是”发生了但状态没变”。二,cumulativeGasUsed(区块内累计 gas)+ gasUsed(本交易 gas = 本块累计 - 上一笔累计):本交易实际消耗的 gas;配合区块的 base fee 与 tip 算出实际费用。三,logs(事件数组):执行中 EVM LOG 操作产生的记录(地址、topics、data)——即使交易最终 revert,revert 前发出的日志也不保留(revert = 全部回滚,含日志);成功的交易日志永久留在链上(索引器/钱包订阅的基础)。四,contractAddress:仅部署交易有值(新合约地址,可从”部署者 + nonce”推导);调用交易为 null。五,日志的 topic0:事件签名的哈希(前端按 topic0 过滤”我要的事件类型”)。
“失败也付 gas”在回执里的体现
执行到 revert(require 失败/异常/显式 revert):状态转换回滚(余额/存储/日志全退),但”执行消耗的 gas”不回滚——因为 gas 是”对计算资源的补偿”,计算真实发生了(验证者/节点跑了这些操作)。回执体现:status=0 + gasUsed=实际消耗值(> 0)→ 费用 = gasUsed × gas 价照付。两个易混的”免费”场景:一,交易”没进块”(gas 价太低/网络故障)——没执行,没付 gas(交易只是没被打包);二,交易”执行失败进块”——执行了(付 gas),状态没变。区分标准看回执存不存在:有回执 = 执行过 = gas 已付;没回执(pending 消失)= 没执行 = 没付钱。钱包显示”失败”时先查回执的 status 与 gasUsed,比看界面文案准确。
日志(logs):回执里最有”用途”的字段
日志是合约”对外广播”的机制(emit 事件):topics(最多 3 个,常放”事件签名哈希 + 索引化参数”)+ data(非索引参数)。用途:一,前端订阅(eth_getLogs 按地址/topic/块范围过滤——“这个合约的 Transfer 事件”就是一类 topic0 过滤);二,索引器构建应用状态(链下数据库从日志流重建”应用视图”——很多 DApp 的”你的仓位”读的是索引器对日志的聚合,不是直接读存储槽);三,跨合约/跨链的信号(“事件即 API”:日志是链上系统间通信的松耦合通道)。日志的代价:每条日志占 gas(data 部分按 calldata 类规则计价)——“事件密集”的合约执行更贵,这是”日志多 = 交易贵”的结构性原因。注意:日志在区块里永久可查(收据树的一部分),但”读它”走 RPC(历史日志查询依赖节点/服务的保留范围——全节点修剪后历史日志查询走归档/索引服务)。
收据与收据树:可验证性
每个区块的收据组成一棵 Merkle 树(键 = 交易索引,见 Patricia Trie 专题),树根(receiptsRoot)在区块头里。含义:一,“某交易的回执”可以用一条 Merkle 证明验证(轻客户端/跨链验证”这笔交易在这个块里且结果是 X”);二,回执里的 stateRoot(L1 交易回执的状态根字段)= 该交易执行后的世界状态根——“链上验证’某步执行后状态‘“的原语(跨链桥/验证合约用它)。普通用户/开发者日常读回执走 RPC(eth_getTransactionReceipt),不需要自己构造证明——但”回执可被独立验证”这个性质,是大量跨链/轻客户端设计的构件。
实操:常见问题的标准答案
“我的交易成功了为什么 DApp 没更新”——查回执 status(=1 则状态已变,DApp 展示滞后/索引器延迟;=0 则 revert,查 revert 原因(trace 工具/reason 字符串))。“部署后合约地址是多少”——回执 contractAddress(或按部署者 + nonce 推导)。“这笔交易花了多少 gas”——gasUsed × 实际 gas 价(base fee + tip,块内数据可查);“估算和实际差很多”——执行路径比估算复杂(循环/外部调用),估算只是”gasLimit 建议”。“日志为什么查不到”——块范围/索引器延迟/节点历史保留(修剪节点查老日志要换端点)——三个原因按概率排查。
常见误读
“revert = 交易没发生”——它发生了(在块里、有回执、付了 gas),只是状态没变;“没发生”是”没进块”。“日志 = 交易备注”——日志是结构化的链上事件(topic/data 编码,可被程序过滤),不是自由文本备注(data 可以塞任意字节,但没人会当备注系统用——gas 不便宜)。“回执里的状态根 = 最终状态”——是”该交易执行后的中间状态根”(块内下一笔交易还会改),块的”最终状态根”在区块头(该块最后一笔后的状态)。
风险提示
RPC 查询历史回执/日志的可用范围依赖节点模式(全节点修剪后历史走归档/索引服务,见归档专题);“回执字段语义”是执行规范的一部分(稳定,但 gas 规则/日志计费的参数细节随升级微调),引用以规范当前版本为准。本文为结构解释,不构成对任何 RPC 服务/DApp 的评价;排查交易问题时”回执 + trace 工具”是标准动作,单一来源的”交易失败原因”(界面文案)按”提示”理解,以链上数据为准。
小结
一句话记忆:回执 = status(成功/回滚)+ gas 实际用量 + 日志 + (部署时)合约地址 + 状态根;“失败也付 gas”看 status=0 且 gasUsed>0,“没付 gas”是没进块;日志是”事件即 API”的通道(订阅/索引/跨合约信号的底层)。排查任何交易问题,第一步永远是 eth_getTransactionReceipt——链上数据优先于界面文案。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。