摘要:交易回执是以太坊交易执行后的结果凭证。本文用 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 |
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 相关字段都在这里:

- Gas Limit & Usage by Txn = 80,000 | 46,109:前者是交易里设的上限,后者就是回执的
gasUsed。没用完的部分不收费。 - Gas Price 1.124735876 Gwei 对应回执的
effectiveGasPrice。它正好等于 Base 0.124735876 Gwei + Max Priority 1 Gwei,没碰到 Max 1.163964527 Gwei 的上限。规则细节见 以太坊 Gas 费怎么算:EIP-1559 字段对照。 - Transaction Fee = 46,109 × 1.124735876 Gwei = 0.000051860446506484 ETH,和截图一致。其中 Base Fee 部分被销毁(Burnt),小费归出块者。
- Position In Block: 22 就是回执的
transactionIndex(从 0 开始数)。累计 Gas 就是按这个顺序往下加的。 - Value 是 0 ETH:转代币时 ETH 金额为 0,代币数量不在交易字段里,而在日志里。
三、Logs:代币到账真正看的地方
点顶部的「Logs (1)」标签,Etherscan 把回执里的 logs 解码成了「Transaction Receipt Event Logs」:

一条日志由三部分组成,按顺序读:
这里是 USDT 合约 0xdAC1…1ec7。先核对这个地址:任何人都能部署一个也叫 USDT、也发 Transfer 事件的假币合约,只有合约地址对得上官方,才说明是真 USDT。
topics[0] 是事件签名的 keccak256 哈希,Transfer(address,address,uint256) 固定是 0xddf252ad…b3ef;topics[1]、topics[2] 是左侧补零到 32 字节的 from 和 to 地址。最多 4 个 topic,正因为被索引,eth_getLogs 才能按地址筛选。这里只有 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,再查那笔失败交易:

脚本核心只有几行,读法和输出一一对应:
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'])
- 累计值减出来就是单笔用量:875,186 − 829,077 = 46,109,正好是 gasUsed。
- 最后一笔的累计值等于整个区块的 gasUsed(31,112,589),所以
cumulativeGasUsed也是检查「区块用了多满」的入口。 - 事件签名可以本地复算:自己对事件原型做 keccak256,和
topics[0]比对,就知道浏览器把事件名解码对了。用eth_getLogs按这个哈希筛选,就能拿到某合约所有转账。
五、失败交易的回执:status 0、没有日志,但照样扣费
再看失败的那笔。Etherscan 的 Status 是红色的 Fail with error 'SPL',To 一栏下面提示「execution reverted」:

- 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 费怎么算:Base Fee、Priority Fee 与 EIP-1559 字段对照
- 以太坊内存池是什么:Pending 交易在浏览器里怎么看(还没有回执的阶段)
- web3.py 读 ERC-20:balanceOf、decimals 换算与裸 eth_call 核对(eth_call 只读调用不产生回执)
说明:文中交易、区块号、Gas 与手续费数字均来自 2026-10-08 的 Etherscan 截图和公开 RPC 节点实际返回;Gas 价格、确认数和美元估值随时变化。字段定义以 ethereum.org JSON-RPC 文档与以太坊执行层规范为准。本文只做技术科普,不构成任何投资建议。
