本文深入探讨了以太坊节点的不同类型(全节点、存档节点、轻节点)及其对数据访问和调试的影响。重点介绍了eth_call
和debug_traceCall
这两个重要的RPC方法,分析了它们的功能、使用场景、常见问题以及如何根据实际需求选择合适的工具。此外,还讨论了不同以太坊客户端的差异以及运行自有节点的考虑因素和成本。
在过去的几篇文章中,我们深入探讨了以太坊的细节,从解码原始交易到处理 EIP 规范和构建真实交易。如果 EVM 有一个外科住院医师项目,我们现在都应该穿上白大褂了。这篇文章有点不同。把它想象成我们的“咖啡休息”剧集:仍然有用,仍然是实践性的,但更多的是理解我们实际用来运行和检查所有区块链魔法的工具,而不是字节码的体操。
要实际运行这些交易,甚至模拟它们,你需要一个节点。而且并非所有节点都是相同的。你连接到的节点类型决定了你可以访问哪些历史数据、你可以多么准确地调试故障,甚至某些开发者工具是否可以工作。
在这篇博文中,你将学习到:
1. 节点类型
2. 不同的节点客户端,不同的行为
3.
eth_call
深度剖析4.
debug_traceCall
深度剖析5. 主要区别和注意事项
6. 选择正确的工具
在我们讨论 eth_call
和 debug_traceCall
之前,我们需要解决下一个问题:你连接的是哪种节点?
你使用的以太坊节点类型决定了哪些数据可用以及在模拟或跟踪交易时你可以追溯到多久以前。选择错误的节点可能导致令人困惑的“缺少状态”错误或不完整的跟踪。
完整节点存储所有区块头和区块体以及足够的近期状态数据来验证区块链并为链的最新部分提供查询服务。
它不存储每个区块的完整历史状态,只存储一小部分最近的状态。通常是最近的 128 个区块左右。较早的中间状态会被修剪以节省资源。
完整节点一次验证一个区块区块链,下载并检查区块的交易及其相应的状态更改。
它们的同步方式存在差异:一些从创世区块开始,验证历史中的每个区块,而另一些(如 Geth 的 snap sync)从一个较新的、受信任的检查点开始,并从此向前推进。
无论同步方法如何,完整节点仅保留最新状态的本地副本。任何较旧的数据都会被删除以节省磁盘空间。这并不意味着数据永远消失。如果需要,节点可以通过重放早期区块的交易来重建较旧的状态,尽管这比直接从存储中读取要慢。
历史访问:
典型用例:
注意事项:
如果你尝试对几个月或几年前的区块进行 eth_call
或 debug_traceCall
,完整节点很可能会失败,因为该状态很久以前就被修剪掉了。
存档节点包含完整节点拥有的所有内容,加上自创世区块以来每个区块的完整历史状态记录。这意味着它保留每个帐户余额、合约存储值和状态树,就像在任何时间点一样,而无需修剪。
当你查询存档节点在区块 5,000,000 的状态时,它可以立即返回数据,而无需从早期区块重建它。这使其成为需要精确历史数据的开发者和服务的理想选择。
典型用途:
debug_traceCall
),而无需额外的计算。权衡:
运行存档节点比完整节点需要更多的存储和资源。因此,许多团队依赖 RPC 提供商,他们提供付费的存档访问,而不是托管自己的存档节点。
轻节点仅存储区块头,并依赖完整节点或存档节点来获取其需要的任何其他数据。它使用区块头中包含的加密证明来验证该数据,从而允许它在不保存完整链的情况下确认正确性。
这使得轻节点非常节省资源,它们可以在存储或带宽有限的设备上运行,但也意味着它们在大多数查询中都依赖其他节点。
典型用途:
局限性:
debug_traceCall
,因为它不包含执行数据。eth_call
只有在连接的完整/存档节点支持请求的区块时才有可能。当你运行像 eth_
或 debug_
这样的 RPC 调用时,你不是在与作为单个系统的“以太坊”对话,而是在与 以太坊协议的特定实施(称为 客户端)对话。
以太坊有多个执行层客户端,全部由不同的团队使用不同的编程语言构建,并且具有自己的设计选择。它们都遵循相同的共识规则,但它们可能在以下方面有所不同:
debug_
或 trace_
RPC 方法;其他客户端根本不实现它们。这些客户端在共识规则方面是可互换的,但它们的 RPC 功能和性能有所不同,尤其是在高级调试/跟踪方面。
eth_call
深度剖析eth_call
在节点本地运行一个交易,而不广播它或更改状态。把它想象成一个精确的排练:EVM 根据所选区块的状态执行你的 calldata,返回结果(或回滚数据),然后丢弃所有内容。
block.number
、timestamp
、basefee
来自指定的区块标签/编号。gas
太低,调用可能会“out of gas”。如果你省略它,节点通常会应用一个软上限(取决于实现,有些节点不需要显式设置它)。方法签名:
eth_call(params, blockNumber)
其中 params 是:
{
"to": "0xTargetContract",
"from": "0xOptionalCaller",
"data": "0xCalldata",
"value": "0x...", // 可选; 影响模拟期间的 opcodes/balances
"gas": "0x...", // 有时可选; 模拟的上限
"maxFeePerGas": "0x...", // 有时可选; 影响 BASEFEE/GASPRICE 读取
"maxPriorityFeePerGas": "0x...", // 可选
"gasPrice": "0x..." // 遗留; 如果 EIP-1559 字段不存在,则影响 GASPRICE
}
第二个参数是区块选择器:
"latest"
、"pending"
、"safe"
、"finalized"
或像 "0xABCDEF"
这样的十六进制区块编号。提示: 调试时使用 特定的区块编号 ,以使结果稳定。
完整的 JSON-RPC 示例
{
"method": "eth_call",
"params": [\
{\
"from": "0xAAAaaaaAAAAaaaaAAAaaaaaAAAAAaaaaaAAAAaaA",\
"to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",\
"value": "0x..", // 表示价值的十六进制整数\
"data": "0x..."\
},\
"0x12D687F" // 区块编号\
],
"id": 1,
"jsonrpc": "2.0"
}
Curl 示例:
curl -X POST <你的节点 URL> \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [\
{\
"from": "0x...",\
"to": "0x...",\
"value": "0x1",\
"data": "0x..."\
},\
"latest"\
],
latest
上调用可能会在调试时给你不相关的输出。gas
设置为未设置仍然会达到内部上限;在诊断故障时设置一个慷慨的 gas
(不用担心,你不会失去它)。GASPRICE
/ BASEFEE
的合约将看到你在调用对象中设置的(或默认值)。msg.sender
或 msg.value
,请相应地设置 from
/ value
;否则,你正在测试错误的路径。eth_call
不会“模拟”先前的批准或转账,在调用之前先设置状态(或者使用像 anvil
这样的 fork,例如我们在之前的博客文章中学到的)。eth_call
debug_traceCall
深度剖析debug_traceCall
运行模拟交易,如 eth_call
,但也返回完整的执行跟踪,因此你可以看到 EVM 如何获得结果:内部调用、opcode、gas 使用情况,以及它回滚的确切步骤(就像我们在之前的一篇博客文章中学到的那样)。
eth_call
相比)CALL/DELEGATECALL/STATICCALL
都有输入/输出。pc
、op
、gas
、gasCost
、depth
、stack
、memory
、storage
diff。注意: 需要启用跟踪的节点。
历史区块:需要 archive 来跟踪任意旧的区块; full 节点可能只能跟踪最近的历史。
方法签名:
debug_traceCall(params, blockNumber, config:(optional))
Params + blockNumber 与 eth_call
中的相同
config:
可选的跟踪选项对象,具有以下字段:
tracer
: (string) [可选] tracer 的模式/类型(如下所述)。tracerConfig
: (object) [可选] tracer 的配置选项(不同的节点可能允许不同的配置,因此对于实际参考,请查看节点文档,其中之一是 timeout
以终止长时间的跟踪,这应该在每个节点上都可用)你应该知道的 Tracer 模式 (Geth)
callTracer
(默认的好选择): 结构化的调用帧 (from
, to
, input
, output
, value
, gasUsed
, children)。4byteTracer
: 使用 4byte DB 尽力解码到名称的函数选择器。prestateTracer
: 捕获重现调用所需的最小状态。vmTrace
/ structLogs
: 按 opcode 跟踪 (非常冗长, 较慢)。注意: 不同的节点/客户端允许不同的配置
{
"method": "debug_traceCall",
"params": [\
{ "to": "0x...", "data": "0x..." },\
"latest",\
{ "tracer": "callTracer", "timeout": "30s" }\
],
"id": 2, "jsonrpc": "2.0"
}
curl <你的节点-rpc-url> \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"debug_traceCall","params":[{"from":"0x...","to":"0x...","data":"0x..."}, "latest", {"tracer": "callTracer", "timeout": "30s"}],"id":1,"jsonrpc":"2.0"}'
debug
。使用你控制的节点或具有跟踪支持的提供商。callTracer
用于日常调试,并设置 timeout
。trace_call
/ trace_replayTransaction
)。不要在没有适配器的情况下硬编码为一种格式。eth_call
一样,如果你的合约分支于 msg.sender
、msg.value
、GASPRICE
或 BASEFEE
,则设置 from
、value
、费用字段。latest
处进行跟踪可能会在你脚下随着状态的发展而变化。debug_traceCall
在 eth_call
和 debug_traceCall
之间进行选择不是关于哪个“更好”。而是关于你试图回答什么问题。
eth_call
:view
/ pure
)或模拟交易的结果。debug_traceCall
:我们已经介绍了从 完整、存档 和 轻 节点之间的差异到各种 以太坊客户端 如何以不同方式实现 RPC 方法,再到对 eth_call
和 debug_traceCall
的深入研究以及如何选择正确的工具。
要点:
eth_call
用于快速回答, debug_traceCall
用于完整的故事,它们是互补的,而不是竞争对手。如果出现以下情况,运行你自己的节点可能是正确的选择:
debug_traceCall
或存档历史记录等功能,而无需依赖提供商的限制。成本说明:
- 原文链接: medium.com/@andrey_obruc...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!