摘要:交易回执是以太坊交易执行后的结果凭证。本文用 2026-10-08 主网一笔成功的 USDT 转账和一笔失败交易,对照 ethereum.org 中文 JSON-RPC 文档逐个讲清 eth_getTransactionReceipt 的 status、gasUsed、cumulativeGasUsed、effectiveGasPrice、logs、logsBloom 字段,在 Etherscan 里看 Gas 与 Logs,并用 curl 和 Python 实测复算 Transfer 事件签名、累计 Gas 差值与失败交易照扣的手续费。

一笔以太坊交易发出去以后,节点返回的「交易回执」(Transaction Receipt)才是它执行结果的凭证:成没成功、实际烧了多少 Gas、合约吐出了哪些事件日志,都写在回执里。它和「交易本身」最容易混:交易(eth_getTransactionByHash)是你签名提交的请求,里面是 gas 上限、nonce、input;回执(eth_getTransactionReceipt)是打包执行之后才有的结果,待处理的交易拿不到回执。本文只讲回执里每个字段是什么、在 Etherscan 哪里看、怎么用 JSON-RPC 自己核对一遍。

文中用的是 2026-10-08 以太坊主网两笔真实交易:一笔成功的 USDT 转账 0xbcfff2ac…97ef1(区块 26,145,106),一笔失败的合约调用 0x59e42e81…ef34a(区块 26,145,103)。查询全部是只读的,用的是公开 RPC 节点,不需要钱包和私钥。

一、回执有哪些字段:先对照官方文档

以太坊官网的 JSON-RPC API(中文) 列出了 eth_getTransactionReceipt 的返回值。除了哈希、区块号这类定位字段,真正需要读懂的是下面几个:

以太坊交易回执怎么读:Status、Logs 与 Cumulative Gas 字段核对-程序旅途
图1:ethereum.org/zh JSON-RPC API「eth_getTransactionReceipt」:返回交易收据对象,字段包括 transactionIndex、cumulativeGasUsed、effectiveGasPrice、gasUsed、contractAddress、logs、logsBloom、type,以及 root(拜占庭升级前)或 status(1 成功 / 0 失败);2026-10-08 截图
字段 含义 核对时注意
status 0x1 成功,0x0 失败(回滚) 拜占庭升级(EIP-658)之后才有;更早的回执是 root
gasUsed 这一笔实际消耗的 Gas 手续费 = gasUsed × effectiveGasPrice
cumulativeGasUsed 区块里从第 1 笔到这一笔为止的 Gas 累计 不是你的手续费,减去上一笔的累计才是本笔用量
effectiveGasPrice 每单位 Gas 实付价格(Base Fee + 实际小费) EIP-1559 交易里它通常低于你设的 maxFeePerGas
logs 合约执行时发出的事件日志数组 代币到账要看这里,不看 Value
logsBloom 256 字节布隆过滤器 用来快速筛「这一块里可能有某事件」,会有误判,不能当证据
contractAddress 部署合约时为新合约地址 普通转账和调用为 null
type 交易类型:0x0 传统、0x1 访问列表、0x2 动态费用 Blob 交易(0x3)的回执还会多出 Blob Gas 相关字段

有一个容易被忽略的设计:真正写进区块、被区块头里 receiptsRoot 承诺的回执内容只有交易类型、status、cumulativeGasUsed、logsBloom 和 logs。gasUsed、effectiveGasPrice、from/to 这些是节点在你查询时结合交易和区块信息补算出来的。这也解释了为什么协议层存的是「累计值」——相邻两笔一减就是单笔用量,下文会实际验证。

二、在 Etherscan 里看成功交易:Status 与 Gas 两块

打开成功那笔交易,顶部 Status 显示 Success,Transaction Action 写着从 0x5c03…AFB5B 转出 3,750 USDT。往下拉,点「More Details: Click to show more」展开,Gas 相关字段都在这里:

以太坊交易回执怎么读:Status、Logs 与 Cumulative Gas 字段核对-程序旅途
图2:etherscan.io/tx/0xbcfff2ac…:ERC-20 Tokens Transferred 为 3,750 USDT,Value 为 0 ETH;Transaction Fee 0.000051860446506484 ETH;Gas Price 1.124735876 Gwei;Gas Limit & Usage by Txn 80,000 | 46,109(57.64%);Gas Fees Base 0.124735876 Gwei、Max 1.163964527 Gwei、Max Priority 1 Gwei;Other Attributes 为 Txn Type 2(EIP-1559)、Nonce 214、Position In Block 22;界面为英文,美元估值随行情变化
  1. Gas Limit & Usage by Txn = 80,000 | 46,109:前者是交易里设的上限,后者就是回执的 gasUsed。没用完的部分不收费。
  2. Gas Price 1.124735876 Gwei 对应回执的 effectiveGasPrice。它正好等于 Base 0.124735876 Gwei + Max Priority 1 Gwei,没碰到 Max 1.163964527 Gwei 的上限。规则细节见 以太坊 Gas 费怎么算:EIP-1559 字段对照。
  3. Transaction Fee = 46,109 × 1.124735876 Gwei = 0.000051860446506484 ETH,和截图一致。其中 Base Fee 部分被销毁(Burnt),小费归出块者。
  4. Position In Block: 22 就是回执的 transactionIndex(从 0 开始数)。累计 Gas 就是按这个顺序往下加的。
  5. Value 是 0 ETH:转代币时 ETH 金额为 0,代币数量不在交易字段里,而在日志里。

三、Logs:代币到账真正看的地方

点顶部的「Logs (1)」标签,Etherscan 把回执里的 logs 解码成了「Transaction Receipt Event Logs」:

以太坊交易回执怎么读:Status、Logs 与 Cumulative Gas 字段核对-程序旅途
图3:etherscan.io/tx/0xbcfff2ac…#eventlog:日志序号 14,Address 为 0xdac17f958d2ee523a2206206994597c13d831ec7(Tether: USDT Stablecoin),Name 为 Transfer(index_topic_1 address from, index_topic_2 address to, uint256 value);Topics 0 为 0xddf252ad…,1: from、2: to 为两个地址;Data 为 value(uint256): 3750000000;界面为英文

一条日志由三部分组成,按顺序读:

address:谁发的事件
这里是 USDT 合约 0xdAC1…1ec7。先核对这个地址:任何人都能部署一个也叫 USDT、也发 Transfer 事件的假币合约,只有合约地址对得上官方,才说明是真 USDT。
topics:事件签名 + 带 indexed 的参数
topics[0] 是事件签名的 keccak256 哈希,Transfer(address,address,uint256) 固定是 0xddf252ad…b3ef;topics[1]、topics[2] 是左侧补零到 32 字节的 from 和 to 地址。最多 4 个 topic,正因为被索引,eth_getLogs 才能按地址筛选。
data:没有 indexed 的参数
这里只有 value = 3,750,000,000。USDT 的 decimals 是 6,所以是 3,750 USDT。小数位怎么读见 web3.py 读 ERC-20:balanceOf 与 decimals。

注意左边的序号 14:这是回执里的 logIndex,它在整个区块内编号,不是这笔交易内从 0 开始。前面 22 笔交易已经发出了 14 条日志,这一条排第 15 个。

四、用 JSON-RPC 自己核对:累计 Gas 与事件签名

浏览器帮你解码过了,想确认它没算错,可以直接问节点。先用 curl 拿原始回执(为了好读,logsBloom 截断了,本机真实返回如下):

curl -s https://ethereum-rpc.publicnode.com -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["0xbcfff2ac1a49eb4071148c34ce4ee3de03558cdb2d762fd90ee829b85ec97ef1"]}' \
  | jq '.result | .logsBloom = (.logsBloom[0:18] + "…")'

{
  "type": "0x2",
  "status": "0x1",
  "cumulativeGasUsed": "0xd5ab2",
  "logs": [
    {
      "address": "0xdac17f958d2ee523a2206206994597c13d831ec7",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x0000000000000000000000005c038625536d5672c141b0b191f54751bcaafb5b",
        "0x000000000000000000000000abfcc17bb765688b1224c71ad9290be8c5ef53fb"
      ],
      "data": "0x00000000000000000000000000000000000000000000000000000000df847580",
      "blockNumber": "0x18ef152",
      "transactionIndex": "0x16",
      "logIndex": "0xe",
      "removed": false
      …
    }
  ],
  "logsBloom": "0x0000000000000000…",
  "gasUsed": "0xb41d",
  "effectiveGasPrice": "0x430a1b84",
  "from": "0x5c038625536d5672c141b0b191f54751bcaafb5b",
  "to": "0xdac17f958d2ee523a2206206994597c13d831ec7",
  "contractAddress": null
  …
}

所有数字都是十六进制字符串(0xb41d = 46,109)。返回的前几个键恰好是 type、status、cumulativeGasUsed、logs、logsBloom,也就是上面说的「共识部分」。接着用一段 Python(标准库 + eth_utils 算 keccak)做三项核对:解码字段、复算事件签名、用上一笔交易的累计值验证 gasUsed,再查那笔失败交易:

以太坊交易回执怎么读:Status、Logs 与 Cumulative Gas 字段核对-程序旅途
图4:本机 Python 3.13 真实输出:成功交易 status 0x1、gasUsed 46,109、cumulativeGasUsed 875,186、effectiveGasPrice 1,124,735,876 wei;keccak("Transfer(address,address,uint256)") 与 topics[0] 一致 True;data 3750000000 / 1e6 = 3750 USDT;手续费 0.000051860446506484 ETH;同区块第 21 笔累计 829,077,差值 46,109 等于 gasUsed(True);最后一笔累计 31,112,589 等于区块 gasUsed(True);失败交易 status 0x0、gas 上限 400,000 用掉 45,642、logs 0、仍付手续费 0.000005583312239454 ETH;2026-10-08 执行

脚本核心只有几行,读法和输出一一对应:

r    = call('eth_getTransactionReceipt', [OK])
blk  = call('eth_getBlockByNumber', [r['blockNumber'], False])
prev = call('eth_getTransactionReceipt', [blk['transactions'][idx - 1]])

# 1) 事件签名
'0x' + keccak(text='Transfer(address,address,uint256)').hex() == r['logs'][0]['topics'][0]
# 2) 本笔用量 = 本笔累计 - 上一笔累计
H(r['cumulativeGasUsed']) - H(prev['cumulativeGasUsed']) == H(r['gasUsed'])
# 3) 区块最后一笔的累计 = 区块头 gasUsed
H(last['cumulativeGasUsed']) == H(blk['gasUsed'])
  1. 累计值减出来就是单笔用量:875,186 − 829,077 = 46,109,正好是 gasUsed。
  2. 最后一笔的累计值等于整个区块的 gasUsed(31,112,589),所以 cumulativeGasUsed 也是检查「区块用了多满」的入口。
  3. 事件签名可以本地复算:自己对事件原型做 keccak256,和 topics[0] 比对,就知道浏览器把事件名解码对了。用 eth_getLogs 按这个哈希筛选,就能拿到某合约所有转账。

五、失败交易的回执:status 0、没有日志,但照样扣费

再看失败的那笔。Etherscan 的 Status 是红色的 Fail with error 'SPL',To 一栏下面提示「execution reverted」:

以太坊交易回执怎么读:Status、Logs 与 Cumulative Gas 字段核对-程序旅途
图5:etherscan.io/tx/0x59e42e81…:Status 为 Fail with error 'SPL',To 下方提示 Warning: Error encountered during contract execution [execution reverted];Value 0 ETH;Transaction Fee 0.000005583312239454 ETH;Gas Limit & Usage by Txn 400,000 | 45,642(11.41%);没有 Logs 标签;界面为英文
  • status = 0x0、logs 为空:回滚会撤销这笔交易在状态上的所有改动,执行中发出的事件也一并作废,所以页面上连 Logs 标签都没有。
  • 手续费照扣:节点确实执行到了出错的那一步,45,642 Gas 按实付价计费。失败不等于免费。
  • 没把 Gas 上限用光:revert 会把剩余 Gas 退还,只按已执行部分收费;只有 Gas 耗尽(out of gas)这类失败才会把上限全部用掉。
  • 错误原因不在回执里:回执只有 0/1。'SPL' 这类原因字符串是浏览器重放交易得到的 revert 数据,回执本身不存。

常见误读

  • 「status 是 1,币就到我这了」:status 只说明交易没回滚。代币有没有转给你,要看 logs 里是不是官方合约发出的 Transfer,to 是不是你的地址。
  • 「cumulativeGasUsed 是我付的 Gas」:那是区块内的累计值,单笔用量看 gasUsed。
  • 「拿到回执就等于最终确认」:回执只说明交易被某个区块收录。区块被重组时,订阅到的日志会带 removed: true,交易可能换块重新执行。多少确认才稳妥见 以太坊确认数与最终性。
  • 「查不到回执就是交易丢了」:Pending 交易本来就没有回执,返回 null。先去内存池看它是否还在、nonce 是否被卡,见 以太坊 Nonce 与卡住交易核对。
  • 用 logsBloom 判断「有没有这事件」:布隆过滤器只能确定「一定没有」,「可能有」会误判,结论必须以 logs 为准。

想用自己的交易练手

把上面的 curl 命令里的哈希换成你自己钱包里的一笔交易,就能看到自己的回执。还没有自托管钱包的话,可以用 欧易 Web3 钱包(创建时可填推荐码 CHENGXULVTU)。在主网发交易需要少量 ETH 付 Gas,可以在 币安 或 欧易 买入后提到钱包,第一次先小额试;只想练习的话,用 Sepolia 测试网完全够。

相关阅读

说明:文中交易、区块号、Gas 与手续费数字均来自 2026-10-08 的 Etherscan 截图和公开 RPC 节点实际返回;Gas 价格、确认数和美元估值随时变化。字段定义以 ethereum.org JSON-RPC 文档与以太坊执行层规范为准。本文只做技术科普,不构成任何投资建议。