Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南12 分钟阅读

Bittensor WebSocket RPC:Substrate 订阅与可靠流

了解如何使用 Bittensor 的 WebSocket RPC 进行实时 Substrate 订阅:链头、存储和外部交易,并附有 Node.js 示例和故障排除。

TL;DR

本指南介绍 Bittensor 基于 Substrate 的 WebSocket RPC 订阅,涵盖 JSON-RPC 格式、连接生命周期,以及使用 @polkadot/api 的可运行 Node.js 示例,并针对常见断开连接提供故障排除。

什么是 Bittensor WebSocket RPC?

Bittensor 的 WebSocket RPC 是 Finney 网络的实时接口,Finney 网络是一个基于 Substrate(Polkadot SDK)的区块链。它允许您订阅实时更新,如新区块、最终确定的区块头、存储更改和外部交易状态。与 HTTP RPC 不同,WebSocket 保持持久连接,使节点能够异步推送通知。本指南向您展示如何可靠地使用它,重点介绍 Substrate JSON-RPC 订阅 API。

主要端点是 wss://finney-rpc.onfinality.io(或您提供商的 URL)。公共端点通常有速率限制和连接上限,因此了解生命周期是构建健壮应用程序的关键。

Substrate 的 RPC 系统由 JSON-RPC 2.0 规范定义,订阅方法遵循一致的模式:您发送一个方法名以 _subscribe 结尾的请求,收到一个订阅 ID,然后接收通知,并使用相应的 _unsubscribe 方法取消订阅。此模式在 Substrate RPC 文档 中有记录。

具体到 Bittensor,Finney 网络运行基于 Substrate 的链,并带有用于激励机制的定制 pallet。核心 RPC 方法是标准的 Substrate 方法,但您也可能遇到 Bittensor 特定功能的自定义方法。请始终查看 Bittensor 文档 以获取最新支持的 RPC 方法列表。

  • Bittensor 基于 Substrate 构建,因此支持标准的 Substrate RPC 方法。
  • WebSocket 订阅对于需要实时数据的矿工、验证者和 dApp 至关重要。
  • 本指南涵盖读取链状态,不包括挖矿/注册(使用专门的 Subtensor 流程)。

Substrate JSON-RPC 订阅 API

Substrate JSON-RPC API 提供了几种订阅方法。最常见的有:chain_subscribeNewHeadschain_subscribeFinalizedHeadsstate_subscribeStorageauthor_submitAndWatchExtrinsic。每个方法返回一个订阅 ID,并以 JSON-RPC 消息的形式发送通知。

请求格式是标准的 JSON-RPC 2.0:{"jsonrpc":"2.0","id":1,"method":"chain_subscribeNewHeads","params":[]}。响应包含一个带有订阅 ID 的 result。通知以 {"jsonrpc":"2.0","method":"chain_newHead","params":{"subscription":"sub_id","result":{...}}} 的形式到达。

例如,订阅新区块头的原始 WebSocket 请求如下:

{"jsonrpc":"2.0","id":1,"method":"chain_subscribeNewHeads","params":[]}
响应可能是:
{"jsonrpc":"2.0","result":"0x1234","id":1}
然后您会收到如下通知:
{"jsonrpc":"2.0","method":"chain_newHead","params":{"subscription":"0x1234","result":{"number":"0x1a2b","hash":"0x...","parentHash":"0x...","stateRoot":"0x...","extrinsicsRoot":"0x...","digest":{...}}}}

@polkadot/api 库抽象了这些原始消息,但理解底层格式有助于调试和使用其他客户端。

  • chain_subscribeNewHeads – 最佳区块头(可能被重组)。
  • chain_subscribeFinalizedHeads – 最终确定的区块头(共识安全)。
  • state_subscribeStorage – 特定键的存储更改。
  • author_submitAndWatchExtrinsic – 跟踪外部交易状态(例如,readyinBlockfinalized)。

连接生命周期以及公共 WSS 端点为何断开

公共 Bittensor WSS 端点是共享资源。它们通常强制空闲超时(例如,60 秒无消息)、每个 IP 的连接上限,以及可能终止连接的负载均衡。当连接断开时,您必须重新连接并重新订阅所有活动订阅。

如果您使用带有 provider 选项的 ApiPromise@polkadot/api 库会自动处理重连。但是,您必须确保您的代码以幂等方式重新订阅——这意味着它可以安全地重新运行订阅逻辑而不会重复处理程序。

WebSocket 协议包含内置的 ping/pong 机制,但并非所有客户端都实现它。为了保持连接活跃,您可以定期发送 JSON-RPC 请求,如 {"jsonrpc":"2.0","method":"system_health","params":[],"id":1}。这是一个轻量级调用,返回节点健康状况并重置空闲计时器。

负载均衡器也可能在维护或扩展事件期间关闭连接。实现带有抖动的指数退避是避免惊群问题的良好实践。Polkadot.js 文档 提供了重连策略的指导。

  • 空闲超时:发送 ping 或 keepalive 消息以防止断开。
  • 连接上限:限制每个 IP 的并发连接;使用单个连接进行多个订阅。
  • 负载均衡:节点可能重定向或关闭连接;实现指数退避。

使用 @polkadot/api 的可运行 Node.js 示例

下面是一个完整的 Node.js 脚本,它连接到 Bittensor 的 WebSocket RPC,订阅新区块头和最终确定的区块头,并记录区块号。它还演示了使用 on('connected')on('disconnected') 事件进行重连处理。

要运行它,请安装 @polkadot/apiws(如果需要)。该脚本使用 ApiPromise.create 和 WSS 提供程序。它订阅 subscribeNewHeadssubscribeFinalizedHeads,打印区块号和哈希。预期输出显示区块头流。

该脚本将 autoConnect 设置为 false 以手动控制连接,这对于实现自定义重连逻辑很有用。provider.on('connected')provider.on('disconnected') 事件允许您记录连接状态并在需要时触发重新订阅。

在生产环境中,您会将订阅逻辑包装在一个函数中,以便在重连时再次调用,确保不会创建重复订阅。订阅调用返回的 unsub 函数可用于在重连前进行清理。

  • 使用 api.rpc.chain.subscribeNewHeads() 获取最佳区块头。
  • 使用 api.rpc.chain.subscribeFinalizedHeads() 获取最终确定的区块头。
  • 处理 disconnected 事件以触发重连逻辑。
// 安装:npm install @polkadot/api
const { ApiPromise, WsProvider } = require('@polkadot/api');

const WS_URL = 'wss://finney-rpc.onfinality.io';

async function main() {
  const provider = new WsProvider(WS_URL, false); // autoConnect false 用于手动控制
  const api = await ApiPromise.create({ provider });

  provider.on('connected', () => console.log('已连接'));
  provider.on('disconnected', () => console.log('已断开'));

  // 订阅新区块头(最佳区块)
  const unsubNew = await api.rpc.chain.subscribeNewHeads((header) => {
    console.log(`新区块头: #${header.number} hash=${header.hash}`);
  });

  // 订阅最终确定的区块头
  const unsubFinal = await api.rpc.chain.subscribeFinalizedHeads((header) => {
    console.log(`最终确定: #${header.number} hash=${header.hash}`);
  });

  // 保持运行;要停止,调用 unsubNew() 和 unsubFinal()
}

main().catch(console.error);

// 预期输出(示例):
// 已连接
// 新区块头: #123456 hash=0x...
// 最终确定: #123455 hash=0x...
// ...

使用键过滤订阅存储更改

要监视特定的存储项,请使用带有存储键列表的 state_subscribeStorage。您必须提供哈希键(twox_64 concat blake2_256)或使用 @polkadot/api 生成它。例如,要监视特定账户的余额,您可以使用 api.query.system.account 并传递键。

通知包括键和新值。这很高效,因为您只收到您关心的键的更改,减少了带宽和处理。

存储键生成遵循 Substrate 的存储哈希方案。对于像 System.Account 这样的映射,键是 pallet 名称和项目名称的 twox_64 哈希与账户地址的 blake2_256 哈希连接。@polkadot/api 库在您对查询对象调用 .key() 时在内部处理此操作。

您还可以通过传递数组一次订阅多个键。这对于在单个订阅中监视一组账户或特定存储项很有用,减少了 WebSocket 消息的数量。

  • 使用 api.query.system.account(address).key() 获取存储键。
  • 将键数组传递给 state_subscribeStorage
  • 在节点级别进行过滤可减少数据传输。
// 示例:订阅特定账户的余额更改
const { ApiPromise, WsProvider } = require('@polkadot/api');

async function main() {
  const provider = new WsProvider('wss://finney-rpc.onfinality.io');
  const api = await ApiPromise.create({ provider });

  const address = '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'; // 示例
  const key = api.query.system.account.key(address);

  const unsub = await api.rpc.state.subscribeStorage([key], (items) => {
    items.forEach(({ key, value }) => {
      console.log(`存储更改: ${key} -> ${value}`);
    });
  });

  // 预期输出:存储更改: 0x... -> 0x...
}

main().catch(console.error);

最佳区块头与最终确定的区块头:使用哪个?

对于矿工和验证者来说,最佳区块头(新区块头)通常足以监视区块生产。然而,对于需要最终性的应用程序(例如,跨链桥、支付确认),您应该使用最终确定的区块头以避免重组。

Bittensor 的共识使用主观权重机制,但对于读取链状态,区别是标准的 Substrate 行为。使用 subscribeNewHeads 获取实时但可能重组的的数据,使用 subscribeFinalizedHeads 获取不可逆的数据。

最终确定的区块头由 GRANDPA 最终性小工具确定,它在一定数量的区块后提供确定性最终性。chain_subscribeFinalizedHeads 订阅仅发出已被 GRANDPA 最终确定的区块,使其对于不可逆操作是安全的。

相比之下,chain_subscribeNewHeads 发出节点认为最佳的区块,如果出现更长的链,可能会被重组。对于监视区块生产,这通常没问题,但对于结算逻辑,始终使用最终确定的区块头。

  • 最佳区块头:延迟最低,可能被重组。
  • 最终确定的区块头:对于不可逆操作安全。
  • 根据您的用例选择:监视与结算。

常见故障和故障排除清单

即使使用健壮的客户端,您也可能遇到问题。以下是常见故障和修复方法:

如果您看到 429 Too Many Requests,说明您遇到了速率限制。减少订阅频率或使用专用端点。更多信息,请参阅我们的 Bittensor RPC 速率限制和 429

另一个常见问题是收到来自 WebSocket 服务器的 1011 Internal Error,这通常表示服务器由于内部错误或策略违规而关闭连接。检查您的订阅数量和消息频率。

如果您看到 1008 Policy Violation,可能是由于超出连接限制或发送无效数据。确保您的客户端发送正确的 JSON-RPC 请求并遵守服务器的限制。

有关 WebSocket 关闭代码的完整列表,请参阅 IANA WebSocket 关闭代码注册表

  • 连接断开:使用指数退避实现重连并重新订阅。
  • 订阅 ID 不匹配:确保您使用正确的订阅 ID 处理通知。
  • 存储键错误:仔细检查键生成;使用 api.query 获取正确的键。
  • 超时:每 30 秒发送一次 ping 以保持连接活跃。
  • 对于一般性修复,请参阅 通用 WebSocket RPC 断开连接修复

权衡与限制

公共 WebSocket 端点很方便,但有局限性:速率限制、连接上限和潜在的不稳定性。对于生产环境,请考虑使用专用端点或您自己的节点。另外,请注意 Bittensor 的挖矿/注册使用单独的 Subtensor 流程,而不是标准的 RPC 订阅。

提供商的限制各不相同;请始终查看提供商的文档。对于 OnFinality 的服务,请参阅 API 服务RPC 定价

公共端点在许多用户之间共享,因此它们可能会遇到更高的延迟和偶尔的限流。专用端点提供有保证的资源,推荐用于需要一致性能的应用程序。

此外,WebSocket 连接消耗服务器资源,因此提供商通常限制每个 IP 的并发连接数。使用单个连接进行多个订阅比打开多个连接更有效。

  • 公共端点不适合重型生产使用。
  • 专用端点提供更高的可靠性和更低的延迟。
  • 了解读取链状态与挖矿操作之间的区别。

后续步骤和进一步阅读

既然您了解了 Bittensor WebSocket RPC,您就可以构建实时应用程序了。有关更多详细信息,请探索官方 Bittensor 文档Polkadot.js 文档

查看我们的 Bittensor RPC 指南(RPC 助手) 以获取快速解答,以及 Bittensor Finney 网络页面 以获取端点详细信息。要更广泛地学习,请访问 OnFinality Learn 中心

要更深入地了解 Substrate 的 RPC 方法,Substrate RPC 文档 是权威参考。有关 WebSocket 特定的注意事项,MDN WebSocket 文档 提供了协议的良好概述。

永远不用担心基础设施

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

开始