本指南介绍 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_subscribeNewHeads、chain_subscribeFinalizedHeads、state_subscribeStorage 和 author_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– 跟踪外部交易状态(例如,ready、inBlock、finalized)。
连接生命周期以及公共 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/api 和 ws(如果需要)。该脚本使用 ApiPromise.create 和 WSS 提供程序。它订阅 subscribeNewHeads 和 subscribeFinalizedHeads,打印区块号和哈希。预期输出显示区块头流。
该脚本将 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 文档 提供了协议的良好概述。
- 探索 Bittensor Finney 网络页面 以获取端点详细信息。
- 使用 RPC 助手 进行快速故障排除。
- 查看 RPC 定价 以了解专用端点。