WebSocket 订阅使用指南
通过 BlockVectra WebSocket 端点使用 eth_subscribe 订阅 newHeads 与 logs。掌握连接鉴权、过滤条件限制、退避重连与数据补全。
BlockVectra 提供基于 TLS 的安全 WebSocket(wss://)连接,支持在单一长连接中处理实时以太坊事件订阅与标准 JSON-RPC 2.0 请求。
可用链
WebSocket 支持按链动态开放。客户端可通过公开的 GET /v1/chains 端点读取每个网络的 ws(布尔值)和 subscriptions(支持的订阅类型数组)确认实时状态。
下表列出当前已启用 WebSocket 的网络:
| 链 | {chain} | WebSocket 端点(路径携带 Key) | 支持的订阅类型 |
|---|---|---|---|
| Robinhood Chain | robinhood_mainnet | wss://api.blockvectra.com/v1/robinhood_mainnet/{api_key} | newHeads, logs |
连接与鉴权
客户端通过标准 TLS 建立 WebSocket 连接(wss://)。API key 支持两种传入方式:
- 路径携带 Key:
wss://api.blockvectra.com/v1/{chain}/{api_key} - 请求头携带 Key:
wss://api.blockvectra.com/v1/{chain},在 HTTP 升级(Upgrade)握手阶段传入x-api-key: {api_key}或Authorization: Bearer {api_key}请求头。
握手阶段准入检查
在建立 WebSocket 会话前,服务端会在 HTTP 升级握手阶段进行准入校验:
- 身份认证:未提供 API key 返回 HTTP 401(
missing_api_key);key 未知、已停用或已吊销返回 HTTP 401(invalid_api_key);若服务端 key 表数据同步暂不可用则返回 HTTP 503(auth_unavailable)。 - 账户余额:账户预付余额为 0 或负数返回 HTTP 402(
balance_exhausted);计费状态暂未确认返回 HTTP 503(billing_unavailable)。 - 连接数限制:单 key 连接数超过每实例 20 条,或单账户连接数超过每实例 50 条返回 HTTP 429(
ws_connection_limit)。 - 链可用性:请求未知或未开放的链返回 HTTP 404(
unknown_chain)。 - 实例容量:当服务端实例连接数饱和或排队推送消息超过阈值时,握手返回 HTTP 503(
overloaded)并附带Retry-After头。
连接建立后,客户端可以发送标准 JSON-RPC 2.0 请求(如 eth_blockNumber 或 eth_call)以及 UTF-8 文本帧格式的订阅控制方法。
订阅方法
服务端支持以太坊标准的发布/订阅接口:eth_subscribe 与 eth_unsubscribe。
newHeads
当新区块追加到链头时,实时推送区块头对象。
- 订阅请求:
{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["newHeads"]} - 订阅响应:返回十六进制订阅标识符:
{"jsonrpc":"2.0","id":1,"result":"0x1"} - 推送通知帧:
{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1","result":{"number":"0x1b4","hash":"0x..."}}}
logs
推送与指定过滤条件匹配的事件日志。
-
过滤条件硬性要求:每个
logs订阅过滤器必须指定address(合约地址或地址数组)或topic0(第一位置的主题,非 null)。两者均未指定的过滤请求(例如{}或{"topics":[null,"0x..."]})会被服务端拒绝,返回错误码-32602(logs_filter_required)。 -
过滤规模限制:单次订阅最多指定 100 个地址;主题最多 4 个位置,每个位置最多 16 个候选哈希。
-
实例过滤容量:若服务端实例上活跃的日志过滤器达到上限,订阅返回错误码
-32022(ws_filter_capacity)。 -
链重组:发生区块重组时,被移除区块的日志推送通知中会标记
"removed": true。 -
订阅请求:
{"jsonrpc":"2.0","id":2,"method":"eth_subscribe","params":["logs",{"address":"0x1234567890123456789012345678901234567890"}]}
eth_unsubscribe
通过订阅标识符取消指定订阅。
- 退订请求:
{"jsonrpc":"2.0","id":3,"method":"eth_unsubscribe","params":["0x1"]} - 退订响应:
{"jsonrpc":"2.0","id":3,"result":true}
运行示例
使用 viem v2 的 createPublicClient 与 webSocket 传输层连接。请将 {chain} 替换为目标链标识,{api_key} 替换为你的 API key:
import { createPublicClient, webSocket } from 'viem';
const apiKey = process.env.BLOCKVECTRA_API_KEY || '{api_key}';
const url = `wss://api.blockvectra.com/v1/{chain}/${apiKey}`;
const client = createPublicClient({
transport: webSocket(url),
});
// 1. 订阅新区块头 (newHeads)
const unwatchBlocks = client.watchBlocks({
onBlock: (block) => {
console.log('收到新区块头:', block.number, block.hash);
},
onError: (error) => {
console.error('watchBlocks 错误:', error);
},
});
// 2. 订阅合约事件日志 (logs 过滤必须包含 address 或 topic0)
const unwatchEvents = client.watchEvent({
address: '0x1234567890123456789012345678901234567890',
onLogs: (logs) => {
console.log('收到匹配日志:', logs);
},
onError: (error) => {
console.error('watchEvent 错误:', error);
},
});关闭码与客户端处理
服务端主动终止 WebSocket 连接时会发送带有特定关闭码与简短原因的 Close 帧。下表列出服务端发出的关闭码及建议的处理方式:
| 关闭码 | 原因字符串 | 描述 | 能否重试 | 客户端动作 |
|---|---|---|---|---|
| 1001 | idle | 连接在 3600 秒(1 小时)内无活跃订阅且未收到任何消息 | 是 | 按需重新连接。 |
| 1003 | binary frames are not accepted | 客户端发送了二进制帧;服务端仅接收 UTF-8 文本帧 | 否 | 不要自动重连。修改客户端以发送纯文本帧。 |
| 1009 | message too large | 客户端入站消息超过 1 MiB(1,048,576 字节)限制 | 否 | 不要自动重连。拆分大请求或缩减请求体大小。 |
| 1012 | service restart | 服务端实例维护重启,或单连接已达最长生命周期(24 小时 ± 10% 抖动) | 是 | 使用带随机抖动的退避策略重连,重新建立订阅并补全数据。 |
| 1013 | chain unavailable | 链在此监听端口停止接受 WebSocket 会话,或上游数据源断开 | 是 | 使用带全抖动的指数退避重连,重建订阅并通过 eth_getLogs 补全数据。 |
| 1013 | overloaded | 服务端实例所有连接的排队通知缓冲(256 MiB)达到上限 | 是 | 使用带全抖动的指数退避重连,重建订阅并通过 eth_getLogs 补全数据。 |
| 4402 | insufficient balance | 账户预付余额已耗尽 | 否 | 不要自动重连。充值后再重连。 |
| 4404 | invalid api key | API key 未知、已停用或已吊销 | 否 | 不要自动重连。在控制台检查并更换有效的 API key。 |
| 4408 | slow consumer | 会话推送队列超过 512 KiB(524,288 字节)时服务端以 4408 关闭并丢弃待发通知;4408 关闭帧排在已缓冲推送之后、最多等 2 秒,停止读取或读得远慢于推送速率的客户端往往收不到 4408,看到的是连接直接断开(浏览器报 1006)。 | 是 | 意外断开(没收到关闭帧,浏览器报 1006)按 4408 处理:退避重连,并减少订阅或加快读取;重新建立订阅并通过 eth_getLogs 补全数据。 |
| 4429 | push rate exceeded | 推送频率超过 1,000 次/秒(突发容限 10,000 次) | 是 | 减少订阅数或收窄过滤条件;带退避重连后重建订阅并补全数据。 |
| 4503 | billing unavailable | 计费状态或 key 表数据短时间同步延迟(> 60 秒) | 是 | 此为暂时状态,使用带全抖动的指数退避策略重连。 |
断线重连与指数退避
当服务端重启或网络断开时,为避免大量客户端同时重连导致流量洪峰,客户端必须遵循 API 规格 §15.4 要求的全抖动(full jitter)指数退避算法:
- 退避公式:在第 n 次重试连接前(n = 0, 1, 2, ...),在区间内均匀随机选择等待时长:
delay = random(0, min(20s, 0.5s * 2^n)) - 参数规格(API 规格 §15.4):
- 初始基础退避:
0.5 s(0.5 s * 2^0 = 0.5 s) - 指数倍增乘数:
2^n(0.5 s、1.0 s、2.0 s、4.0 s……) - 最大退避上限:
20 s - 全抖动(Full jitter):在
0到min(20 s, 0.5 s * 2^n)之间均匀选取伪随机值 - 重置计数器:仅在会话持续稳定保持连接至少
60 秒后,才将重试计数 n 重置为 0 - 关闭码 1012:首次重试时引入随机初始延迟,避免集群重启时所有连接同步打散
- 不可重连关闭码:遇到 4402、4404、1003 或 1009 时禁止自动重连。
- 意外断开:意外断开(没收到关闭帧,浏览器报 1006)按 4408 处理:退避重连,并减少订阅或加快读取。
- 初始基础退避:
断线后的数据补全策略
WebSocket 订阅状态不跨连接持久化;断线期间产生的推送通知不会在服务端保留。客户端重连成功后,应按 API 规格 §15.5 实施补全:
- 通过
eth_getLogs补全日志:- 持久化记录已处理并提交的最高区块高度(
last_processed_block)。 - 重连成功后立即调用
eth_subscribe("logs", ...)恢复实时日志接收。 - 调用
eth_getLogs查询断线区间:设置fromBlock为last_processed_block + 1,toBlock为"latest"(或新订阅收到的第一条通知区块),保持地址与主题过滤条件一致。 - 若断线跨度超过 1,000 个区块(受限于
max_logs_block_range: 1000),将查询切分为每个最多 1,000 区块的连续批次。 - 以
(blockHash, transactionHash, logIndex)三元组对补全日志与实时通知进行边界去重。
- 持久化记录已处理并提交的最高区块高度(
- 通过
eth_getBlockByNumber补全区块头:- 记录断线前收到的最新区块高度与哈希。
- 重新订阅
newHeads。 - 调用
eth_getBlockByNumber("latest", false)确认当前高度;若存在缺失高度,按顺序拉取中间区块头并核对parentHash链连续性以排查可能发生的分叉重组。
限制与配额
| 限额 | 数值 | 超出时的结果 |
|---|---|---|
| 单个 API key 并发 WebSocket 连接数 | 每个服务端实例 20 个 | 协议升级握手返回 429(ws_connection_limit) |
| 单个账户并发 WebSocket 连接数 | 每个服务端实例 50 个 | 协议升级握手返回 429(ws_connection_limit) |
| 单个 WebSocket 连接订阅数 | 100 | -32022 subscription_limit |
单个 WebSocket 连接 newHeads 订阅数 | 4 | -32022 subscription_limit |
logs 订阅过滤条件要求 | 必须指定 address 或 topic0(topics 的首个位置) | -32602 logs_filter_required |
| 入站 WebSocket 消息尺寸 | 1 MiB(1048576 字节) | 连接关闭,关闭码 1009 |
| 单连接推送通知队列缓冲 | 512 KiB(524288 字节) | 连接关闭,关闭码 4408(slow consumer) |
| 每个服务端实例推送通知队列缓冲(所有连接合计) | 256 MiB(268435456 字节) | 下一条通知无法容纳的连接以关闭码 1013(overloaded)关闭;超过一半时,新 WebSocket 握手返回 503(overloaded,带 Retry-After),eth_subscribe 返回 -32026 ws_push_overloaded |
| 单连接未读取应答缓冲 | 16 MiB | 在客户端读取足够消息前,服务端不再从该连接读取后续消息 |
| WebSocket 客户端未读取应答 | 单次写入阻塞 30 秒 | 直接断开连接(不发送关闭帧) |
| 单连接推送通知速率 | 1000 次推送/秒(突发 10000) | 连接关闭,关闭码 4429 |
| 空闲 WebSocket 连接 | 3600 秒(1 小时)无订阅且无消息 | 连接关闭,关闭码 1001 |
| WebSocket 连接最长存活时间 | 24 小时(带 ±10% 抖动) | 连接关闭,关闭码 1012 |
下一步
最后更新: