Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
可靠性与一致性阅读约 13 分钟

为你在通信的 RPC 节点做指纹识别:chainId、net_version、clientVersion

面向任何以太坊 RPC 端点的预检身份核验:在将生产流量交给它之前,先验证链 ID、网络 ID、客户端版本和创世哈希。

TL;DR

每个以太坊 RPC 端点都可以在接收生产流量之前被指纹识别,方法是调用一小组只读身份方法:eth_chainId 以十六进制数量返回 EIP-155 链 ID,net_version 返回遗留的十进制网络 ID 字符串,web3_clientVersion 返回自由格式的客户端字符串,eth_getBlockByNumber('0x0', false) 返回创世区块,其哈希唯一标识该链。链 ID 与网络 ID 不是同一个值,在交易签名中不可互换使用;链 ID 是绑定到重放保护签名中的值,而网络 ID 是遗留标识符,其语义因客户端而异。被错误配置为提供主网数据的测试网端点仍会响应 eth_chainId,因此可靠的检测方法是将返回的链 ID 和创世哈希都与预期常量进行比较。指纹识别成本低、只读、可按端点缓存,但它无法确认归档能力、速率限制策略,也无法确认 web3_clientVersion 是否真实,因为该字符串是自报的,可能被代理掩盖。

为什么端点身份是预检要求

在你要求其背后的节点表明身份之前,RPC URL 只是一个不透明的字符串。在将用户交易、索引器读取或故障转移流量路由到某个端点之前,你需要知道它服务于哪条链,以及是哪个客户端软件在应答。以太坊 JSON-RPC 规范定义了这些身份方法,而 execution-apis schema 是其请求和响应格式的权威来源。

身份检查成本很低:它们是只读调用,返回小载荷,不触碰状态。这使它们适合作为启动探针、周期性健康检查或负载均衡器中的准入关卡。它们也是抵御一类静默错误配置的第一道防线,例如测试网端点提供主网数据,或故障转移路径指向完全不同的链。

本文区分三件常被混为一谈的事:已记录的协议行为(规范说明这些方法返回什么)、提供商特定行为(给定主机实际返回什么,这会有差异),以及你可以针对自己的端点自行运行的测量方法。凡是本应给出基准数值的地方,本文描述如何测量,而不是断言某个数字。

  • 已记录:eth_chainId 以十六进制数量返回 EIP-155 链 ID。
  • 已记录:net_version 以字符串返回十进制网络 ID。
  • 因客户端而异:net_version 是否与链 ID 一致,以及 eth_protocolVersion 是否被实现。
  • 因提供商而异:web3_clientVersion 是被透传、改写还是掩盖。

身份方法及各自实际证明什么

eth_chainId 以十六进制数量返回链 ID,例如以太坊主网为 0x1。这是出现在 EIP-155 重放保护交易签名中的值,因此它是对签名正确性最重要的身份字段。如果钱包针对错误的链 ID 签名,交易要么被拒绝,要么更糟——在另一条链上有效。

net_version 返回十进制字符串,例如 "1"。它早于 EIP-155,是遗留网络标识符。在许多链上它恰好等于链 ID,但这只是惯例,不是保证。有些客户端对它的代理不一致,有些链则有意让两个值不同。应将 net_version 视为次要信号,绝不要作为签名的权威依据。

web3_clientVersion 返回自由格式字符串,例如 Geth/v1.14.x/linux-amd64/go1.22。它标识客户端家族、版本、操作系统和语言运行时。由于是自由格式,你应防御性地解析它,绝不要假设固定格式。eth_protocolVersion 在许多客户端中已弃用,可能返回错误或过时值;不要基于它构建逻辑。eth_syncing 报告同步进度,可用于确认端点已追上链头,这在检测落后于链头的 RPC 节点中有更深入的介绍。

  • eth_chainId:十六进制数量,EIP-155 签名的权威依据。
  • net_version:十进制字符串,遗留,语义因客户端而异。
  • web3_clientVersion:自由格式字符串,自报,需防御性解析。
  • eth_protocolVersion:在许多客户端中已弃用,避免依赖。
  • eth_syncing:同步进度,不是身份字段,但属于健康检查关卡的一部分。

交易签名中的链 ID 与网络 ID

这一区别很重要,因为交易签名使用的是链 ID,而不是网络 ID。EIP-155 将链 ID 绑定到签名中,因此为一条链签名的交易无法在另一条链上重放。如果你的签名路径读取 net_version 并将其用作链 ID,你就引入了一个正确性缺陷,它可能只在两个值不同的链上才暴露出来。

安全模式是读取 eth_chainId,将其与你打算使用的网络的硬编码预期常量比较,如果不匹配则拒绝签名。网络 ID 可以记录用于诊断,但不应作为签名的关卡。在 OnFinality 上,以太坊网络页面列出了支持的网络及其链 ID,因此你可以在配置中固定预期值。

一种常见故障模式是配置文件存储单个 "network" 字段,同时用于显示和签名。将其拆分为显式的 chainId 字段和单独的诊断用 networkId 字段,可以消除歧义。

  • 用链 ID 签名,绝不用网络 ID。
  • 按环境将预期链 ID 固定为常量。
  • net_version 仅用于诊断记录。
  • 当 eth_chainId 与固定常量不匹配时拒绝签名。

检测静默提供主网数据的测试网端点

被错误配置的端点可以针对它实际服务的链正确应答 eth_chainId,而你的应用却以为它指向测试网。如果你的预期常量有误,或者该端点是会改写响应的代理,仅靠链 ID 无法发现这一点。稳健的检查是比较两个独立的值:链 ID 和创世区块哈希。

eth_getBlockByNumber('0x0', false) 返回创世区块。其哈希是链创世配置的确定性函数,实际上是一个唯一指纹。将返回的创世哈希与目标网络的已知良好常量比较,可以检测出提供主网数据的测试网端点,因为创世哈希不同。这与多端点 RPC 一致性检查中使用的原理相同,那里用链头和创世的一致性来检测分歧。

将两项检查结合:链 ID 必须等于预期常量,创世哈希必须等于预期常量。如果任一失败,该端点就不是你以为的那个。

  • 链 ID 检查可发现错误网络路由。
  • 创世哈希检查可发现改写链 ID 的代理。
  • 两项检查结合比任何单项都更强。
  • 在配置中按网络存储预期创世哈希。

Node.js 中的预检指纹识别例程

下面的例程调用 eth_chainId、net_version、web3_clientVersion、eth_blockNumber 和 eth_getBlockByNumber('0x0', false),然后将链 ID 和创世哈希与预期常量比较。它是只读的,可以安全地在启动时或按计划运行。对你的池中每个端点运行它并记录输出。

该函数返回结构化的指纹对象。在负载均衡器或故障转移控制器中,你会在将端点加入活动池之前调用它,并拒绝任何 chainId 或 genesisHash 不匹配的端点。多提供商负载均衡与故障转移指南介绍了如何将其接入池中。

const EXPECTED = {
  chainId: '0x1',
  genesisHash: '0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3'
};

async function rpc(url, method, params = []) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
  });
  const json = await res.json();
  if (json.error) throw new Error(method + ': ' + JSON.stringify(json.error));
  return json.result;
}

async function fingerprint(url) {
  const [chainId, netVersion, clientVersion, blockNumber, genesis] = await Promise.all([
    rpc(url, 'eth_chainId'),
    rpc(url, 'net_version'),
    rpc(url, 'web3_clientVersion'),
    rpc(url, 'eth_blockNumber'),
    rpc(url, 'eth_getBlockByNumber', ['0x0', false])
  ]);
  const genesisHash = genesis && genesis.hash;
  return {
    url,
    chainId,
    netVersion,
    clientVersion,
    blockNumber,
    genesisHash,
    chainIdOk: chainId === EXPECTED.chainId,
    genesisOk: genesisHash === EXPECTED.genesisHash
  };
}

fingerprint('https://your-endpoint.example')
  .then(fp => console.log(JSON.stringify(fp, null, 2)))
  .catch(err => console.error('fingerprint failed:', err.message));

Geth、Nethermind、Erigon、Besu 和 Reth 的客户端版本字符串

web3_clientVersion 因客户端家族而异。Geth 通常返回以 Geth/ 开头的字符串,Nethermind 以 Nethermind/ 开头,Erigon 以 erigon/ 开头,Besu 以 besu/ 开头,Reth 以 reth/ 开头。确切格式没有标准化,因此解析开头的标记,并将其余部分视为不透明元数据。

次要版本差异会影响方法支持。trace 和 debug 命名空间并非在所有客户端或版本中都一致可用,提供商也可能完全禁用它们。如果你的应用依赖 trace_* 或 debug_* 方法,指纹识别客户端版本是必要但不充分的检查:你还必须探测你打算调用的具体方法。以太坊 RPC 节点指南更详细地介绍了方法可用性。

由于该字符串是自报的,代理可以改写或掩盖它。在一个 URL 后面前置多个客户端版本的提供商可能返回通用字符串。应将客户端版本视为诊断和能力规划的提示,而不是安全边界。

  • Geth、Nethermind、Erigon、Besu 和 Reth 各自使用不同的开头标记。
  • 次要版本可能改变启用了哪些命名空间。
  • 探测你需要的具体方法;不要仅凭版本字符串推断。
  • 代理可能掩盖或改写该字符串。

缓存指纹并在变化时重新检查

对每个请求都做指纹识别是浪费。给定端点的链 ID 和创世哈希实际上不可变,因此按端点缓存它们,仅在端点变化或健康检查失败时重新验证。客户端版本和区块高度更易变,可以按较慢的节奏刷新。

一个实用的策略是:在端点注册时指纹识别一次,无限期缓存 chainId 和 genesisHash,每隔几分钟刷新 clientVersion 和 blockNumber,如果任何健康检查失败或端点 URL 变化,则重新运行完整指纹识别。这样成本几乎为零,同时仍能发现提供商在 URL 背后静默更换后端。

监控 RPC 端点指南介绍了如何将这些检查纳入更广泛的健康检查循环,而 OnFinality Learn 中心汇集了相关的可靠性主题。

  • 按端点缓存 chainId 和 genesisHash;它们不可变。
  • 按较慢的节奏刷新 clientVersion 和 blockNumber。
  • 在健康检查失败或 URL 变化时重新运行完整指纹识别。
  • 当缓存的 chainId 或 genesisHash 意外变化时告警。

你可以针对自己的端点运行的测量方法

下表是记录端点池指纹的模板。通过针对每个 URL 运行上面的 Node.js 例程来填写它。不要依赖本文中的数字;测量你自己的端点并记录结果。这就是由读者验证的方法:数值是你的,不是我们的。

记录端点 URL、返回的链 ID、返回的网络 ID、客户端版本字符串、最新区块高度、创世哈希,以及链 ID 和创世哈希是否与你的预期常量匹配。在任何提供商变更或事件后重新运行该表。

  • 端点 URL:你正在测试的确切 RPC URL。
  • chainId:eth_chainId 返回的十六进制值。
  • netVersion:net_version 返回的十进制字符串。
  • clientVersion:web3_clientVersion 返回的自由格式字符串。
  • blockNumber:eth_blockNumber 返回的十六进制值。
  • genesisHash:eth_getBlockByNumber('0x0', false) 返回的 hash 字段。
  • chainIdOk / genesisOk:与预期常量匹配的布尔值。

排查不匹配和意外响应

当指纹检查失败时,第一步是确定哪个字段不匹配。链 ID 不匹配通常意味着端点指向的网络与预期不同,或者你配置中的预期常量有误。链 ID 正确但创世哈希不匹配,则提示存在代理或非标准链配置。

如果 net_version 与 eth_chainId 不一致,不要假设端点已损坏。在某些链上两个值合法地不同,在某些客户端上 net_version 的代理不一致。记录该差异,并继续信任 eth_chainId 用于签名。

如果 web3_clientVersion 返回意外或通用的字符串,提供商可能正在掩盖它。这未必是故障,但这意味着你不能依赖版本字符串进行能力规划。改为探测你需要的具体方法。如果 eth_protocolVersion 返回错误,这在许多现代客户端上是预期的,不应视为失败。

  • 链 ID 不匹配:检查网络路由和你的预期常量。
  • 链 ID 正确但创世哈希不匹配:怀疑代理或非标准链。
  • net_version 不一致:记录下来,签名时信任 eth_chainId。
  • 通用客户端版本:探测你需要的具体方法。
  • eth_protocolVersion 错误:在许多客户端上是预期的,不是失败。

局限性、权衡与隐私考量

指纹识别成本低且只读,但它有实际局限。web3_clientVersion 是自报的,可能被代理欺骗或掩盖,因此它不是安全边界。net_version 的语义因客户端而异,且文档说明会因客户端而不同,因此它不应作为签名的关卡。这些方法都无法确认端点具备归档能力、遵守特定速率限制,或在负载下保持稳定。

还有一个隐私考量:指纹识别会枚举你的客户端组合和端点拓扑。如果你运行许多端点,身份调用的模式可能暴露你依赖哪些客户端和提供商。对大多数团队来说这是次要问题,但对有严格运营安全要求的团队值得注意。

最后,身份检查是必要但不充分的。它们应与链头滞后检查、跨端点一致性检查以及方法级探测结合使用。API 服务和 RPC 定价页面介绍了 OnFinality 如何组织端点访问,而以太坊 RPC 节点指南涵盖了更广泛的运营图景。

  • web3_clientVersion 是自报的,可能被欺骗或掩盖。
  • net_version 语义因客户端而异;不要用它签名。
  • 身份检查无法确认归档能力或速率限制策略。
  • 指纹识别会枚举你的客户端组合和端点拓扑。
  • 将身份检查与链头滞后和一致性检查结合。

生产端点治理的后续步骤

将指纹识别例程变成关卡:在链 ID 和创世哈希与预期常量匹配之前,任何端点都不得进入活动池。缓存不可变字段,刷新易变字段,并对意外变化告警。这是任何签署交易或路由用户资金的应用的最低可行身份治理。

在此基础上,加入链头滞后检测、多端点一致性检查,以及针对你所依赖的特定命名空间的方法级探测。OnFinality Learn 中心汇集了这些主题,而以太坊网络页面列出了支持的网络和链 ID,因此你可以在配置中固定预期值。

如果你正在评估提供商,请对每个候选端点运行指纹表并比较结果。这些数值由你测量,它们是你了解 URL 背后实际情况的最可靠信号。

  • 以链 ID 和创世哈希匹配作为端点准入关卡。
  • 缓存不可变字段;按计划刷新易变字段。
  • 加入链头滞后、一致性和方法级探测。
  • 在承诺之前对候选提供商运行指纹表。

永远不用担心基础设施

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

开始