Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
故障排查阅读约 9 分钟

Top Trace:使用 RPC 追踪检查失败和待处理交易

如何使用 trace 和 debug RPC 方法跟踪交易从提交到失败的全过程,包括可复现的诊断流程和可供填写的结表。

TL;DR

“top trace”是交易执行追踪中最外层的调用帧——所有内部调用、回滚和日志都从该入口派生。先读取 top trace 可以告诉你失败发生在交易自身逻辑中、嵌套合约调用中,还是在执行开始之前。本文解释了 trace 和 debug RPC 方法背后的思维模型,提供了可运行的流程来捕获和比较不同提供商的追踪,并展示了如何将原始追踪转化为诊断结论。它将有文档记录的节点行为与你应针对自己的 RPC 端点自行运行的测量区分开来。

top trace 实际代表什么

当交易在 EVM 链上执行时,节点会记录一棵调用帧树:交易的入口调用、它发起的每个合约调用、每个 delegatecall、staticcall 和 create,以及每个调用的返回或回滚状态。top trace 是该树的根——对应交易本身的帧,包含 to 地址、输入数据、提供的 gas 以及整个执行的最终状态。

这一点很重要,因为大多数交易失败只会以单个不透明的字符串报告给你。钱包或 dapp 看到的是没有原因的 execution reverted,或者一笔永远不会被打包的交易,而顶层错误不会告诉你哪个嵌套调用失败了。追踪是从该症状向下走到实际回滚帧的唯一可靠方式。

该术语也常被宽泛地用于指代查询地址或区块时 trace API 返回的第一个追踪。在本文中,它指单笔交易执行的根帧,这种解释使诊断流程具有可复现性。

  • Top trace = 单笔交易执行的根调用帧。
  • 子帧 = 内部调用、delegatecall、create 及其结果。
  • 回滚原因(如果有)通常位于最深的失败帧中,而不是根帧。

假设与前提条件

你需要一个暴露 trace 或 debug 命名空间的 RPC 端点。并非所有提供商都启用它们:debug_* 和 trace_* 方法开销大,可能默认被禁用,并且通常受到速率限制或仅限归档节点使用。在围绕追踪构建工作流之前,请确认你的端点实际提供哪些方法。

你还需要一笔你已经在链上看到的交易的交易哈希,或者一笔你正试图诊断的待处理交易。对于待处理交易,追踪取决于节点的内存池可见性,这因提供商而异,且不保证可用。

最后,将追踪视为只读诊断。它不会重新提交或修改任何内容,但可能缓慢且繁重,因此避免针对共享端点进行紧密循环调用。如果你需要持续的追踪访问,专用节点是运行它的合适场所——有关如何配置,请参阅专用节点选项。

trace 和 debug 方法有何不同

追踪 RPC 方法有两个系列,它们不可互换。debug_* 系列是 Geth 风格:debug_traceTransaction 返回嵌套调用树(或根据追踪器配置返回扁平操作码日志)。trace_* 系列是 Parity/OpenEthereum 风格:trace_transaction 返回带有 traceAddress 路径的扁平追踪列表,你需要将其重新组装成树。

两者描述相同的执行,但形状不同。使用默认调用追踪器的 debug_traceTransaction 响应将子调用嵌套在 calls 内。trace_transaction 响应给你一个数组,其中每个条目都有一个 traceAddress,如 [0, 1],表示“根的第一个子调用的第二个子调用”。根本身的 traceAddress 为空。

你得到哪一种取决于端点背后的客户端。Geth 和 Erigon 暴露 debug_*;Erigon 和 Nethermind 可以暴露 trace_*;一些提供商两者都暴露,一些都不暴露。在针对端点编写代码之前,请检查其方法列表。

curl -s -X POST RPC_URL \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"debug_traceTransaction","params":["0xYOUR_TX_HASH",{"tracer":"callTracer"}]}'

使用 debug_traceTransaction 捕获 top trace

callTracer 变体是最可读的起点,因为它返回可直接检查的嵌套结构。根对象就是你的 top trace:它具有 type、from、to、value、gas、gasUsed、input、output,以及 calls 或 error 字段。

如果根对象有 error 且没有 calls,则失败发生在顶层帧本身——例如,入口函数在任何外部调用之前回滚。如果根对象有 calls 且其中一个带有 error,则失败是嵌套的,你需要向下深入。

以下 Python 代码片段获取追踪并打印根帧以及第一个失败的子调用,这通常足以识别罪魁祸首:

import json, urllib.request

RPC_URL = "RPC_URL"
TX_HASH = "0xYOUR_TX_HASH"

payload = json.dumps({
    "jsonrpc": "2.0", "id": 1,
    "method": "debug_traceTransaction",
    "params": [TX_HASH, {"tracer": "callTracer"}],
}).encode()

req = urllib.request.Request(RPC_URL, data=payload,
                             headers={"Content-Type": "application/json"})
root = json.load(urllib.request.urlopen(req))["result"]

print("root:", root.get("type"), root.get("to"), root.get("error"))

def walk(frame, depth=0):
    for child in frame.get("calls", []):
        if child.get("error"):
            print("  " * depth, "fail:", child.get("type"),
                  child.get("to"), child.get("error"))
        walk(child, depth + 1)

walk(root)

从 trace_transaction 重新组装 top trace

如果你的端点暴露的是 trace_transaction,你会得到一个扁平数组。top trace 是 traceAddress 为 [] 的条目。其他每个条目都是后代,traceAddress 数组是从根开始的路径。

要重建树,按 traceAddress 长度排序,并将每个条目附加到其父路径。这是机械性的,但值得在辅助函数中做一次,因为当一笔交易发起数十次调用时,用肉眼读取扁平列表容易出错。

同样的诊断规则适用:找到带有 error 字段的最深条目,然后读取其 revertReason(如果存在)。根条目的 error 通常只显示 Reverted,而子条目携带实际的原因字符串。

curl -s -X POST RPC_URL \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"trace_transaction","params":["0xYOUR_TX_HASH"]}'

可复现的比较流程

由于追踪支持和追踪保真度因端点而异,选择端点的诚实方法是根据你自己的负载进行测量。将同一组交易哈希通过每个候选端点运行,并将结果记录在如下表中。不要填写本文中的数字——单元格是供你测量用的。

端点标签测试的方法返回的根帧找到的最深失败帧延迟(毫秒)错误/备注
RPC_URL_Adebug_traceTransaction
RPC_URL_Bdebug_traceTransaction
RPC_URL_Ctrace_transaction

选择固定的交易样本:一些成功的、一些在顶层帧回滚的、一些在嵌套调用中回滚的,以及一些仍在待处理的。在大致相同的时间对同一样本运行每个端点,并记录是否返回了追踪、失败帧是否存在以及调用耗时。重复运行几次,以免单个慢响应主导你的结论。

只有当端点服务于同一条链和同一个区块高度时,比较才有意义。如果一个端点落后,它对近期交易的追踪可能缺失或不一致,这本身就是一个发现。

解读结果:健康的追踪是什么样子

成功的追踪有一个没有 error 的根帧,gasUsed 小于或等于 gas,以及一棵所有子帧都无错误返回的树。根的 output 是交易入口调用的返回数据。

在常见情况下,回滚的追踪恰好有一个失败帧,其上方的帧被标记为回滚,因为失败向上传播。如果你看到多个失败的兄弟帧,交易可能使用了 try/catch 模式或容忍个别失败的多调用——在得出交易失败的结论之前,请阅读父帧的逻辑。

返回空或格式错误结果的追踪通常意味着端点不支持该方法、交易对于非归档节点来说太旧,或者节点修剪了重新执行所需的状态。这些是端点问题,不是合约问题。

排查常见的追踪失败

最常见的失败是 method not found 或 method not supported。这是提供商配置问题,不是你的代码中的错误。确认你的端点的方法列表,如果追踪不可用,要么切换端点,要么将工作负载转移到你控制的节点。

第二常见的是 missing trie node 或状态修剪错误。追踪会针对历史状态重新执行交易,因此对于修剪窗口之外的任何内容,它都需要归档节点。如果你正在追踪近期交易并且仍然看到此错误,端点可能在具有不同保留策略的节点之间进行负载均衡。

第三是超时。追踪调用比 eth_call 更重,在大交易上可能超过默认 HTTP 超时。增加客户端超时,并避免针对共享端点并行化追踪请求。对于高容量追踪,专用节点完全消除了共享速率限制;有关容量如何计费,请参阅 RPC 定价。

  • 方法未找到 → 端点未暴露 trace/debug 命名空间。
  • 缺少 trie 节点 → 非归档节点或状态已修剪。
  • 超时 → 大交易或共享速率限制;使用退避重试或专用容量。
  • 空结果 → 交易尚未索引或节点落后于链头。

限制与权衡

追踪不能替代阅读合约源代码。追踪告诉你哪个帧回滚以及原因是什么,但不告诉你合约逻辑为什么选择该路径。将追踪与已验证的源代码和输入 calldata 配对使用。

追踪输出也依赖于客户端。Geth 的 callTracer 和 Erigon 的 trace_transaction 描述相同的执行,但在字段名、嵌套方式以及如何表示 delegatecall 和 create 方面有所不同。你构建的任何工具都应规范化形状,而不是假设某个客户端的格式。

最后,追踪待处理交易是尽力而为的。内存池可见性因提供商而异,你在一个节点内存池中看到的交易可能对另一个节点不可见。除非你控制节点,否则不要构建依赖于追踪未确认交易的生产工作流。

后续步骤

首先确认你当前端点提供哪些追踪方法,然后针对一个小的固定样本运行上述比较流程。如果追踪不可用或不可靠,将该工作负载转移到专用节点,并保留共享端点用于普通读取。

有关 RPC 端点如何构建以及每个命名空间成本的更广泛了解,请参阅 RPC 端点指南 和 多链 RPC 端点指南。在做出决定之前比较网络,请浏览支持的网络。如果你更愿意让别人替你选择端点,RPC Assistant 可以为你的追踪工作负载推荐具体的提供商和网络配置。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始