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

Solana getBlockProduction:通过 RPC 获取区块生产与跳过率

使用 Solana 的 getBlockProduction RPC 计算指定 slot 范围或当前 epoch 内每个验证者的区块生产与跳过率,并解读结果用于监控。

TL;DR

Solana 的 getBlockProduction 会针对一个 slot 范围(可选按验证者身份分组)返回一个 range 对象(firstSlot、lastSlot)和一个 byIdentity 映射,其条目为 [leaderSlots, blocksProduced]。某个身份的跳过率为 (leaderSlots - blocksProduced) / leaderSlots,范围整体的跳过率则用同样的方式从总计中计算。不传参数时,该方法报告当前 epoch 按验证者身份聚合的结果,这使其非常适合周期性验证者监控。由于 leaderSlots 统计的是验证者被安排担任 leader 的 slot 数,产出与 leader 之比偏低意味着跳过了区块,而不是缺少调度条目;你可以用 getLeaderSchedule 来确认分母。这些计数是所查询节点的视图,可能落后于链尖,并且在负载均衡器后面的不同节点之间可能不同,因此应将单次观测视为一个样本,并针对持续超过阈值的情况告警,而不是依据单次读数。

getBlockProduction 返回什么,以及为什么这个结构很重要

Solana JSON-RPC 方法 getBlockProduction 返回一个结果对象,包含两个顶层字段:一个包含 firstSlot 和 lastSlot 的 range 对象,以及一个 byIdentity 映射。byIdentity 中的每个条目以验证者身份(一个投票账户地址)为键,并保存一个两元素数组,通常读作 [leaderSlots, blocksProduced]。Solana getBlockProduction 参考文档中的方法参考直接记录了这一结构,并且是这些字段的权威描述。

这个两元素数组就是全部测量结果。leaderSlots 是在所查询范围内该身份被安排担任 leader 的 slot 数量;blocksProduced 是在该节点视图中这些 slot 里实际产出区块的数量。两者之差就是该范围内归因于该身份的跳过 slot 数。由于两个数字共享同一分母,你无需任何额外调用即可计算比率。

这种聚合结构正是 getBlockProduction 与返回枚举数据的方法的区别所在。如果你需要用于检测缺口的实际已产出 slot 列表,那是另一种方法;如果你需要某个时间点的 slot 编号,那又是另一种。getBlockProduction 专门用于在某个 slot 范围内按身份聚合的生产与跳过率测量。

  • range.firstSlot 和 range.lastSlot 界定了节点实际评估的窗口。
  • byIdentity 将投票账户身份映射到 [leaderSlots, blocksProduced]。
  • 每个身份的跳过率 = (leaderSlots - blocksProduced) / leaderSlots。
  • 范围整体跳过率 = (Σ leaderSlots - Σ blocksProduced) / Σ leaderSlots。

使用 range、identity 和默认 epoch 窗口限定范围

getBlockProduction 接受一个可选的配置对象。range 参数接受 { firstSlot, lastSlot },并将测量限制在该闭区间窗口内。identity 参数将 byIdentity 映射限制为单个验证者身份,这在你监控单个验证者且不想解析完整映射时很有用。两者都在Solana getBlockProduction 参考文档的方法参考中有记录。

当你调用该方法且不传参数时,它会报告当前 epoch 按验证者身份聚合的结果。这个默认值是最方便的监控模式:一次调用即可获得到目前为止该 epoch 中每个身份的 leaderSlots 和 blocksProduced。代价是进行中的 epoch 是一个部分窗口,因此你计算出的比率是暂定的,会随着 epoch 完成而变化。

如果你需要界定该窗口的 slot 时间线和 leader 调度,Solana getEpochInfo 与 leader 调度页面介绍了如何通过 RPC 读取 epoch 边界和 leader 调度。将该上下文与 getBlockProduction 结合,可以让你判断某个范围是完整的还是仍然开放的。

  • range:{ firstSlot, lastSlot } 用于有界的历史或近期窗口。
  • identity:将映射限制为单个投票账户地址。
  • 不传参数:当前 epoch,按身份聚合(部分窗口)。
  • 在将比率视为最终值之前,对照 epoch 边界确认窗口。

从 leaderSlots 和 blocksProduced 正确计算跳过率

跳过率是一个比率,而不是计数。对于单个身份,将 leaderSlots 与 blocksProduced 之差除以 leaderSlots。对于整个范围,将所有身份的 leaderSlots 求和,将所有身份的 blocksProduced 求和,然后对总计应用同样的公式。将每个身份的比率相加再取平均是另一种(且通常具有误导性的)统计量,因为它把 leader slot 很少的验证者与 leader slot 很多的验证者赋予相同权重。

要保护分母。如果某个身份的 leaderSlots 为零,则该比率未定义,而不是零;应跳过该身份,或将其报告为窗口内没有安排 slot。对于在短范围内没有 leader slot 的验证者,这种情况自然会发生。

由于 leaderSlots 是安排的数量,产出与 leader 之比偏低意味着该验证者没有在其被安排担任 leader 的 slot 中产出区块。这并不意味着调度有误。你可以用 getLeaderSchedule 独立验证分母,其文档位于 https://solana.com/docs/rpc/http/getleaderschedule,它返回某个 epoch 按身份划分的 leader 调度。

  • 每个身份:(leaderSlots - blocksProduced) / leaderSlots。
  • 范围整体:(Σ leaderSlots - Σ blocksProduced) / Σ leaderSlots。
  • 不要对每个身份的比率取平均;而应按 leaderSlots 加权。
  • 将 leaderSlots = 0 视为未定义,而不是满分。

用 getLeaderSchedule 验证分母

最常见的解读错误是假设 leaderSlots 反映的是已安排领导权之外的东西。事实并非如此:它是该范围内该身份被安排担任 leader 的 slot 计数。要确认这一点,请用 getLeaderSchedule 获取相关 epoch 的 leader 调度,统计分配给每个身份的 slot 数,然后将该计数与 getBlockProduction 针对同一窗口报告的 leaderSlots 值进行比较。

当两者一致时,你的跳过率分母是可信的,blocksProduced 的任何不足都是真正的跳过生产。当两者不一致时,可能的原因是范围与你获取调度的 epoch 不对齐,或者某个节点对该范围的视图与你查询的调度不同。将范围对齐到 epoch 边界可以消除大部分这种歧义。

关于 epoch 边界以及如何读取调度的更深入讨论,请参阅Solana getEpochInfo 与 leader 调度。

  • getLeaderSchedule 返回某个 epoch 按身份划分的已安排领导权。
  • 将其每个身份的 slot 计数与同一窗口的 leaderSlots 进行比较。
  • 不匹配通常表明范围/epoch 未对齐,而不是方法有问题。

可运行的 Node.js 示例:按身份计算当前 epoch 跳过率

以下示例调用 getBlockProduction 且不传参数,以获取按身份聚合的当前 epoch,计算每个身份的跳过率,按比率降序排序列表,并标记超过阈值的身份。它使用现代 Node.js 中可用的全局 fetch 和可配置的 RPC 端点。

将端点设置为你自己的 Solana RPC URL。阈值是你选择的参数;脚本不假设任何特定值是健康的。请定期运行它,并比较连续输出,而不是根据单次运行采取行动。

// measure-skip-rate.mjs
const RPC_URL = process.env.SOLANA_RPC_URL || "https://your-solana-rpc-endpoint";
const SKIP_THRESHOLD = Number(process.env.SKIP_THRESHOLD || 0.05); // 5%

async function rpc(method, params = []) {
  const res = await fetch(RPC_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(JSON.stringify(json.error));
  return json.result;
}

const result = await rpc("getBlockProduction");
const { range, byIdentity } = result;

const rows = Object.entries(byIdentity).map(([identity, pair]) => {
  const [leaderSlots, blocksProduced] = pair;
  const skipRate = leaderSlots > 0 ? (leaderSlots - blocksProduced) / leaderSlots : null;
  return { identity, leaderSlots, blocksProduced, skipRate };
});

rows.sort((a, b) => (b.skipRate ?? -1) - (a.skipRate ?? -1));

console.log(`Range: ${range.firstSlot}..${range.lastSlot}`);
for (const r of rows) {
  const rate = r.skipRate === null ? "n/a" : (r.skipRate * 100).toFixed(2) + "%";
  const flag = r.skipRate !== null && r.skipRate > SKIP_THRESHOLD ? "  <-- above threshold" : "";
  console.log(`${r.identity}  leader=${r.leaderSlots}  produced=${r.blocksProduced}  skip=${rate}${flag}`);
}

const totalLeader = rows.reduce((s, r) => s + r.leaderSlots, 0);
const totalProduced = rows.reduce((s, r) => s + r.blocksProduced, 0);
const rangeSkip = totalLeader > 0 ? (totalLeader - totalProduced) / totalLeader : null;
console.log(`Range-wide skip rate: ${rangeSkip === null ? "n/a" : (rangeSkip * 100).toFixed(2) + "%"}`);

可运行的 curl 示例:有界范围和单个身份

对于有界的历史窗口,请显式传入 range 参数。下面的示例查询特定的 slot 范围并打印原始 JSON,以便你可以同时检查 range.firstSlot 和 range.lastSlot 以及 byIdentity 映射。

要将响应缩小到单个验证者,请添加带有投票账户地址的 identity 参数。当你已经知道自己正在监控哪个身份且不想在客户端过滤大型映射时,这很方便。

# Bounded range
curl -s https://your-solana-rpc-endpoint -X POST -H "content-type: application/json" -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getBlockProduction",
  "params": [
    { "range": { "firstSlot": 250000000, "lastSlot": 250000431 } }
  ]
}'

# Single identity
curl -s https://your-solana-rpc-endpoint -X POST -H "content-type: application/json" -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getBlockProduction",
  "params": [
    { "identity": "YourVoteAccountIdentityHere" }
  ]
}'

在 getBlockProduction、getBlocks、getSlot 和 getEpochInfo 之间选择

这些方法回答不同的问题,不能相互替代。getBlockProduction 给出某个范围内按身份聚合的生产与跳过率测量。getBlocks 返回某个范围内已确认区块的枚举列表,这正是你在检测缺口和索引器对账时想要的;Solana getBlocks 与跳过 slot 缺口检测页面介绍了该工作流。getSlot 和 getEpochInfo 给出时间点的时间线信息,而不是生产计数。

一种实用的分工:使用 getBlockProduction 进行验证者级监控和跳过率告警,当你需要确切知道哪些 slot 对某个消费者缺失时使用 getBlocks,当你需要时间线中的当前位置时使用 getSlot 或 getEpochInfo。如果你正在构建事件驱动监控而不是轮询,Solana slotSubscribe 与 blockSubscribe 机制页面解释了订阅替代方案。

  • getBlockProduction:按身份聚合的生产和跳过率。
  • getBlocks:用于缺口检测的已确认区块枚举。
  • getSlot / getEpochInfo:时间点的时间线位置。
  • 订阅:用于事件驱动监控的推送式替代方案。

跳过率上升的运营含义

跳过率之所以重要,是因为一个在其被安排的 slot 中延迟产出或完全不产出的验证者会降低所有下游的确认延迟。当 leader 跳过时,网络会等待下一个被安排的 leader,这可能会增加交易确认以及任何依赖区块节奏的消费者的延迟。因此,特定身份的跳过率上升是一个值得调查的信号,无论原因在于该验证者本地还是更广泛的网络状况。

针对持续超过阈值的情况告警,而不是单次观测。由于计数是所查询节点的视图,可能落后于链尖,单次读数可能有噪声。一个有用的模式是在滚动窗口上计算比率,将其与你选择的阈值比较,并要求该状况在多个窗口中持续存在后再分页告警。这可以减少来自瞬时链尖延迟或部分完成 epoch 的误报。

关于补充此方法的一般端点监控模式,请参阅监控 RPC 端点。

  • 跳过的 slot 会延迟下游消费者的确认。
  • 在告警前使用滚动窗口和持续性要求。
  • 区分单次有噪声的读数与持续趋势。

为你自己的端点或验证者准备可复现的结果表

你得到的数字取决于你查询的端点和选择的窗口,因此诚实的呈现方式是将其作为你自己填写的表格。针对你的端点运行 Node.js 示例或 curl 调用,记录范围和总计,并在不同端点或不同时间重复。不要将任何单行视为基准;将表格视为你自己的测量记录。

每个端点或每个验证者每个窗口填写一行。保持范围列明确,可以公平地比较各行,并发现差异是由不同窗口而非端点行为解释的。

  • 端点 / 身份:你查询的 RPC URL 或投票账户地址。
  • 范围 firstSlot / lastSlot:节点评估的窗口。
  • Σ leaderSlots 和 Σ blocksProduced:该窗口的总计。
  • 范围整体跳过率:从总计计算,而不是取平均。
  • 每个身份的跳过率:针对你正在跟踪的验证者。
  • 观测时间(时间戳):你运行查询的时间。
  • 备注:epoch 已完成或进行中、端点位于负载均衡器后面等。

限制、权衡与诚实的注意事项

报告的计数是所查询节点的视图。它们可能落后于链尖,并且负载均衡器后面的不同节点可能对同一名义查询返回不同的值。如果你通过负载均衡器采样,请将每个响应视为一个节点的视角,并在一致性重要时将监控查询固定到特定节点。

身份键是投票账户地址。将它们映射到人类可读的验证者名称、运营者或你自己的清单是你必须自己完成的工作;RPC 方法不提供该映射。没有映射,跳过率表就是一串不透明地址。

进行中的 epoch 是一个部分窗口,因此其比率不是最终值,会随着 epoch 完成而变化。如果你需要稳定的数字,请等待 epoch 结束,或使用完全属于过去的有界历史范围。

最后,跳过的 slot 是正常的协议行为,不一定是故障。非零跳过率本身并不是配置错误的证据。请将其与其他信号一起解读,并在没有确凿证据的情况下谨慎地将跳过归因于特定原因。

  • 计数是节点本地的,可能落后于链尖或在节点之间不同。
  • 身份键是投票账户地址;映射是你的责任。
  • 进行中的 epoch 会产生暂定比率。
  • 跳过的 slot 是正常的;非零比率不是故障的证明。

排查常见的 getBlockProduction 问题

如果 byIdentity 映射为空,最可能的原因是范围没有安排领导权,或者窗口与你预期的 epoch 不重叠。扩大范围或去掉 range 参数以回退到当前 epoch。如果映射中缺少单个身份,该身份可能只是在该窗口中没有 leader slot;请对照 getLeaderSchedule 确认。

如果 range 对象的 firstSlot 和 lastSlot 与你请求的不匹配,节点可能已钳制或调整了窗口。始终读取返回的范围,而不是假设你的请求被逐字遵守。如果对同一端点的两次调用结果不同,链尖延迟或部分完成的 epoch 是常见解释;请在 epoch 结束后重新运行。

如果你在比较端点并看到不一致,请记住每个节点都有自己的视图。将查询固定到一个节点,或接受跨端点比较是近似的。关于端点选择和连接上下文,请参阅Solana API 指南(RPC Assistant)和Solana 网络页面。

  • 空映射:扩大范围或使用默认 epoch 窗口。
  • 缺少身份:验证它在该窗口中有 leader slot。
  • 意外范围:读取返回的范围,不要假设你的请求被遵守。
  • 跨端点不一致:节点有独立的视图。

验证者和端点监控的后续步骤

首先针对你的端点运行当前 epoch 示例,并在结果表中记录基线。然后定期调度它,计算滚动跳过率,并仅对持续超过阈值的情况告警。将 getBlockProduction 与 getLeaderSchedule 配对以保持分母诚实,并在需要为消费者枚举缺口时与 getBlocks 配对。

如果你正在为这类监控选择或扩展 RPC 访问,请查看RPC 定价和API 服务选项,并浏览OnFinality Learn 中心了解相邻的 Solana 可靠性主题。目标是拥有一个可重复的测量,而不是一次性的数字。

  • 记录基线,然后安排定期测量。
  • 针对持续的滚动阈值告警,而不是单次读数。
  • 与 getLeaderSchedule 和 getBlocks 配对以实现全面覆盖。

永远不用担心基础设施

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

开始