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

Solana 承诺级别:processed、confirmed 与 finalized 的区别

Solana 的 processed、confirmed 和 finalized 承诺级别如何改变每次 RPC 读取的返回结果,以及如何有意识地选择级别。

TL;DR

Solana 的 commitment 参数告诉 RPC 节点,在回答你的读取请求之前,某个 slot 必须在分叉选择和投票流程中推进到何种程度。processed 表示节点已在本地生成或看到该区块,它仍可能被回滚;confirmed 表示绝大多数质押已对其投票;finalized 表示该区块已被 root,除非集群重启,否则无法回滚。commitment 是每次调用的参数,因此同一地址或签名在不同级别下可能返回不同结果,尤其是在分叉附近。面向用户的 UX 选择 confirmed,结算和记账选择 finalized,processed 仅用于对延迟敏感的推测性读取。

commitment 参数实际控制什么

每个 Solana JSON-RPC 读取方法都接受一个可选的 commitment 对象,例如 {"commitment":"confirmed"}。它不是全局节点设置,也不是地址或交易的属性:它是每次调用的指令,告诉节点在回答之前你愿意接受的最低不可逆级别。同一个 getBalance 调用在 processed 和 finalized 下可能合法地返回不同的 lamport 值。

Solana 官方 RPC 文档定义了这三个级别及其默认值,交易确认与过期文档描述了区块如何从生成到 root。将这两页视为权威一手来源;下文解释其机制以及如何通过 RPC 应用。

由于 commitment 是按调用指定的,交易机器人、索引器或 dApp 可以在同一秒内以两个级别读取同一数据,并得到两个不同但各自正确的答案。关键在于决定每次读取应使用哪个级别,而不是照搬默认值。

  • processed:节点已在本地生成或观察到该区块;它仍可能被回滚。
  • confirmed:绝大多数质押已对该区块投票;大多数 UI 使用此级别,许多读取的默认值。
  • finalized:该区块已被 root;除非集群重启,否则无法回滚。

各承诺级别之间的 slot 流、投票与 root

Solana 通过历史证明(proof-of-history)产生连续的 slot 流,每个 slot 都有一个预定的 leader。某个 slot 上的区块并非立即不可逆;随着验证者投票,它逐渐变得不可逆。投票是验证者对其在给定 slot 和高度上看到并接受某个区块的签名证明。

当绝大多数质押验证者对一个区块投票后,它达到 confirmed 阈值。随着对该区块及其后代投票的积累,集群将该区块 root,即 finalized 状态。Root 使得回滚需要集群重启,而不是普通的分叉选择。

这就是为什么这些级别是一个阶梯,而不是三个无关的状态。processed 是节点的本地视图,confirmed 是质押加权的集群视图,finalized 是已 root 的历史。在更高级别读取,是对同一条链提出更严格的问题。

承诺级别及其投票/root 机制由 Solana 在交易确认与过期中记录;该页面是下文所述各级别的一手权威来源。

为什么同一读取在不同承诺级别下可能不同

在分叉期间,一笔交易或一个区块可能在一个节点上处于 processed,但在 finalized 下不存在,因为它所在的分叉未被 root。在 processed 下读取的余额可能包含一笔 finalized 读取尚未反映的转账,或者可能反映一笔随后随分叉消失的转账。

这对索引器和记账系统是核心后果:成功的 processed 读取不是最终结果。如果你在 processed 下统计存款,可能会重复计数或统计一笔从未 root 的转账。如果你在 confirmed 下结算,你接受 finalized 可消除的少量残余重组风险。

实用规则是让承诺级别与出错的代价相匹配。推测性的 UI 状态可以容忍 processed;资金移动和账本条目则不应如此。

getLatestBlockhash、过期与重试循环

getLatestBlockhash 需要 commitment,因为它返回的 blockhash 仅在一个有限窗口内有效,通常描述为大约 150 个区块。如果你在 processed 下获取 blockhash 然后等待,该哈希可能在你的交易上链前过期,产生过期错误而非分叉问题。

在你打算确认的同一级别获取 blockhash,或至少使用 confirmed,并在超时后重建交易时重新获取。这直接与重试策略相关:重用已过期 blockhash 的重试无论发送多少次都会失败。关于周围的发送与重试机制,请参阅 Solana RPC 超时、重试与交易发送

健壮的发送者会获取新的 blockhash、签名、发送,然后在选定的承诺级别轮询状态,如果窗口关闭则用新 blockhash 重建。

从每个主要 RPC 接口读取承诺级别

getBalance、getAccountInfo 和 getTransaction 都接受 commitment 并在该级别回答。getSignatureStatuses 是轮询你已发送交易的正确方式,因为它为每个签名返回一个 confirmationStatus 字段,告诉你集群当前将其视为 processed、confirmed 还是 finalized。

WebSocket 订阅在订阅时固定通知级别。你无法在现有订阅上更改 commitment;必须取消订阅并重新订阅。Solana RPC WebSocket 发布订阅与订阅 指南详细介绍了订阅生命周期。

对于历史读取,commitment 仍然适用,但数据已经 root,因此级别主要影响节点如何提供查询,而不是答案是否会改变。关于这一区别,请参阅 通过 RPC 查询 Solana 历史数据

  • getLatestBlockhash:commitment 控制你收到哪个 blockhash 以及它有多新。
  • getBalance / getAccountInfo:commitment 控制是否包含未 root 的状态。
  • getTransaction:commitment 控制是否返回未 root 分叉上的交易。
  • getSignatureStatuses:暴露 confirmationStatus,是已发送交易的正确轮询接口。
  • WebSocket 订阅:通知级别在订阅时固定。

在一个级别发送,在另一个级别确认

发送交易不像读取那样携带 commitment;交易被广播,由集群决定。你控制的是轮询其状态时使用的 commitment。常见的生产模式是发送,然后在 confirmed 下轮询 getSignatureStatuses 用于 UX,并单独要求 finalized 后才记入账户。

粗心地混合级别是常见 bug:机器人发送、在 processed 下轮询、看到成功,然后对一笔随后随分叉消失的交易采取行动。修复方法不是完全避免 processed,而是使确认门控明确并与操作的风险一致。

如果你需要从较旧的确认方法迁移,迁移已弃用的 Solana RPC 方法 解释了 getConfirmed* 的替代方法以及 commitment 如何适应较新的接口。

可运行示例:获取 blockhash 加承诺级别阶梯

下面的脚本在 confirmed 下获取 blockhash,不发送任何内容,并演示用承诺级别阶梯轮询 getSignatureStatuses。将签名替换为你实际发送过的签名。它仅使用标准 HTTPS 上的 JSON-RPC,因此可针对你配置的任何 Solana RPC 端点运行。

在你自己的端点上运行它,并记录观察到的 confirmationStatus 转换和经过时间。不要将任何单次运行视为基准;重点是观察你自己的流量中阶梯从 processed 到 confirmed 再到 finalized 的移动。

// Node.js 18+ (global fetch). Set RPC_URL to your endpoint.
const RPC_URL = process.env.RPC_URL || "https://api.mainnet-beta.solana.com";

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;
}

async function main() {
  // 1. Fresh blockhash at confirmed. Re-fetch if the window closes.
  const bh = await rpc("getLatestBlockhash", [{ commitment: "confirmed" }]);
  console.log("blockhash:", bh.value.blockhash, "lastValidBlockHeight:", bh.value.lastValidBlockHeight);

  // 2. Poll a signature you have sent. Replace with a real signature.
  const signature = process.env.SIGNATURE;
  if (!signature) {
    console.log("Set SIGNATURE to poll a real transaction.");
    return;
  }

  const ladder = ["processed", "confirmed", "finalized"];
  const start = Date.now();
  for (const commitment of ladder) {
    const status = await rpc("getSignatureStatuses", [[signature], { commitment }]);
    const v = status.value[0];
    console.log(
      commitment.padEnd(10),
      "confirmationStatus:", v ? v.confirmationStatus : "null",
      "slot:", v ? v.slot : "-",
      "err:", v ? JSON.stringify(v.err) : "-",
      "t+", Date.now() - start, "ms"
    );
  }
}

main().catch((e) => { console.error(e); process.exit(1); });

结果表:在你自己的端点上测量

提供商行为、节点拓扑和网络条件各不相同,因此唯一可信的数字是你自己测量的。用上述脚本在你自己的端点上重复运行来填写下表,并将样本量和时间窗口与结果一起保留。

记录你在每个承诺级别观察到的 confirmationStatus、从发送到每个级别的经过时间,以及任何 null 或错误响应。对于你在 processed 下看到的交易,在 finalized 下状态为 null 是分叉或过期信号,值得调查,而不是脚本 bug。

  • 要填写的列:承诺级别 | 观察到的 confirmationStatus | 经过毫秒 | slot | err | 备注。
  • 在得出结论前,在不同时段至少运行 20 次发送。
  • 将端点、区域和客户端版本与表格一起保留,以便结果保持可比。
  • 已记录 / 因提供商而异:绝对确认时间和状态延迟不是固定常量。

决策表:将承诺级别与用例匹配

将此作为起始策略,然后用你自己的测量来收紧。目标是每次调用都有意选择,而不是单一全局默认。

对于任何移动资金或写入不可变账本条目的操作,finalized 是安全门控。对于用户可以看到变化的交互式 UI,confirmed 是通常的平衡点。processed 用于可接受后续修正的、对延迟敏感的推测性读取。

  • 实时 UI 余额或投资组合显示:confirmed。
  • 存款入账、提款、记账条目:finalized。
  • 推测性价格或状态预览,非约束性:processed。
  • 为 UX 轮询已发送交易:在 confirmed 下使用 getSignatureStatuses。
  • 结算或不可逆操作:在 finalized 下使用 getSignatureStatuses。
  • 对已 root 数据的历史分析:finalized。

排查常见的承诺级别故障

大多数承诺级别 bug 是级别不匹配,而非协议故障。如果一笔交易看起来成功然后消失,你可能对 processed 读取采取了行动。如果一笔存款被记入两次,你可能在 processed 下计数,又在 finalized 下再次计数而没有去重。

如果一笔交易从未确认,在归咎于集群之前检查 blockhash 窗口:过期的 blockhash 无论承诺级别如何都会失败。如果 WebSocket 通知从未在你预期的级别到达,确认你是在该级别订阅的,因为它无法就地更改。

如果 getTransaction 在 finalized 下返回 null,但你在 processed 下看到了该交易,将其视为分叉或过期事件,并在重试前与已 root 的来源对账。

  • 在发送和确认之间混合级别:将确认门控标准化在一个地方。
  • 假设 confirmed 等于 finalized:它们是不同阈值,具有不同的回滚风险。
  • 忽略 confirmationStatus:显式读取它,而不是从 null 或非 null 结果推断。
  • 在 processed 下读取余额并重复计数:按签名去重并在 finalized 下结算。
  • 将 confirmed 交易视为结算不可逆:资金移动要求 finalized。

限制、权衡以及何时重新审视

更高的承诺级别会带来延迟成本,并且对于存在但尚未 root 的数据可能返回 null。更低的承诺级别更快,但使你面临回滚风险。没有既最快又最安全的设置;这种权衡正是该参数的意义所在。

本文描述的是已记录的协议和 RPC 行为,而非 OnFinality 特定的性能。绝对确认时间、状态延迟和速率行为是已记录 / 因提供商而异的,应在你自己的端点上测量。关于端点选项及如何比较它们,请参阅 Solana RPC 端点(RPC Assistant)RPC 定价

当集群的投票行为、你的提供商拓扑或应用的风险容忍度发生变化时,重新审视你的策略。如果你正在搭建新基础设施,请从 Solana 网络OnFinality Learn 中心 开始。

后续步骤:将你的承诺级别策略投入运营

将你的承诺级别策略写成如上表所示的表格,然后在代码中强制执行,使任何调用点都不会静默使用默认值。集中确认门控,记录 confirmationStatus 转换,并在 finalized 读取与先前的 processed 读取不一致时发出警报。

用结果表测量你自己的端点,将样本量与数字一起保留,并在任何提供商或拓扑变化后重新运行。如果你正在评估托管访问,请查看 API 服务Solana RPC 端点(RPC Assistant) 页面,并将本指南与 Solana RPC 超时、重试与交易发送 配对,以便过期和重试处理与你的承诺级别选择相匹配。

永远不用担心基础设施

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

开始