Solana commitment 级别:决定成交率的那一行配置

读取、发送、对账分别该用哪个 commitment,以及为什么三处共用一个默认值在两个方向上都是错的。

BoltTx Team··14 min read
solanacommitmentrpc交易上链确认交易机器人

大多数 Solana 代码库在 Connection 构造函数里设一次 commitment,之后再也不想它。而这一个选择随后被套用到了读取、发送、确认和对账上 —— 四种情况,四个不同的正确答案。

★选错的代价是不对称的。★ 太弱,你会基于一个之后被回滚的状态行动;太强,你在为自己并不需要的确定性等待若干 slot。

三个级别分别是什么意思

级别 含义 会被回滚吗
processed ★某个验证节点执行了它★
confirmed 超级多数已对该区块投票 极少
finalized 上面已经又建了 31 个以上区块 ★不会★

processed 是你能拿到的最快的东西,也是最不确定的。finalized 是确定性,代价大约是三十个区块的等待。confirmed 夹在中间,也是大多数应用逻辑该待的地方

confirmedfinalized 之间的实际差距,远大于 processedconfirmed 之间的差距。★ 正是这个不对称,让"反正用 finalized 保险"成为一个比大家以为的更贵的默认值。

三种情况,三个答案

读取你将据以行动的状态:confirmed

const connection = new Connection(url, "confirmed");

这个级别的回滚罕见到大多数交易逻辑可以当噪音处理,而延迟是可以接受的。

检测你要抢的事件:processed

connection.onProgramAccountChange(
  PROGRAM_ID,
  (info) => handleLaunch(info),
  "processed",              // ★能拿到的最快★
);

如果你在抢新盘,confirmed 意味着你比那些不等的人更晚知道。★接受一小部分可能被回滚,并在做任何不可逆的事情之前先验证。★

对账与记账:finalized

const tx = await connection.getTransaction(sig, {
  commitment: "finalized",             // ★不会再变★
  maxSupportedTransactionVersion: 0,
});

任何会变成报表里一个数字的东西,都该在 finalized 读。confirmed 的数据算出来的盈亏,是一个仍然可能改变的数字。

如果你的 commitment 都选对了、交易还是落得晚,免费领个 BoltTx key 改一行就能测测提交路径。

Preflight 有它自己的 commitment

这是被完全忽略的那个设置:

await connection.sendRawTransaction(raw, {
  skipPreflight: false,
  preflightCommitment: "processed",     // ★和连接的默认值是分开的★
});

如果你留着 skipPreflight: false、而 preflightCommitment 默认成了 finalized,★你的模拟跑的是大约三十个区块之前的状态。★ 一笔对当前状态完全有效的交易,可能因为它需要的账户是二十个区块前才创建的而没过 preflight

这会产出 Solana 开发里最让人困惑的一类报告:"这笔交易过一分钟重试就好了,没人说得清为什么。"

两个修法,而第二个通常更好:

让 preflight 和你的发送 commitment 对齐。 如果你是对着当前状态发送,就用 processed

干脆跳过 preflight。 对交易场景来说这通常是对的 —— 它省掉一次往返,而且它模拟的本来就是错的那个 slot

确认不该用 finalized

一个常见且昂贵的写法:

// ★为了一件你已经知道的事,等大约 30 个区块。★
await connection.confirmTransaction(sig, "finalized");

对交易机器人来说这基本总是错的。等它返回的时候机会已经没了,而你一直在阻塞等待一个下一步动作并不需要的确定性。

// 按 confirmed 轮询,到过期为止。
const { value } = await connection.getSignatureStatuses([sig]);
const st = value[0];

if (st?.confirmationStatus === "confirmed" || st?.confirmationStatus === "finalized") {
  return st.err ? "reverted" : "success";
}

getSignatureStatuses 会返回它达到的级别,所以你可以逐次调用再决定,而不是提前锁死一个。★ 这比 confirmTransaction 有用 —— 后者替你把决定做了。

processed 真正咬人的地方

回滚风险是真实的,而且它集中在一种情况:基于尚未确认的状态做不可逆的事。

// ★危险的形状。★
const balance = await connection.getBalance(user, "processed");
await creditUserInDatabase(balance);      // 链下,不可逆

如果那个区块被回滚,链会忘记,而你的数据库不会。于是你的记录和现实不一致,而且没有任何东西会自动纠正它。

保证安全的规则是:★用 processed 决定要做什么,用 confirmedfinalized 记录发生了什么。★ 在 processed 上做检测没问题,因为最坏情况是浪费一笔交易;processed 上记账不行,因为最坏情况是一个永久性的不一致。

commitment 也影响你的 blockhash

getLatestBlockhash 接受 commitment 参数,而它改变你的交易能有效多久:

// ★blockhash 越旧,剩下的有效窗口越短。★
const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash("confirmed");

processed 的 blockhash 是最新的,意味着它前面有完整的有效窗口 —— 但它来自一个仍可能被回滚的区块,而针对一个被回滚的 blockhash 签名的交易会被拒绝

finalized 的 blockhash 是安全的,但已经是三十个区块之前的了,所以你在发送之前就烧掉了五分之一的窗口

★这里值得选的默认值是 confirmed:已经有超级多数投过票,回滚罕见到可以按不会发生来规划;代价只是让出窗口里的几个区块。★

一条流程里混用多个 commitment

一个真实的交易流程会用到三个不同级别,而这是正确的,不是随意:

// 1. 快速检测。
subscribeToLaunches("processed");

// 2. 动钱之前先验证。
const pool = await connection.getAccountInfo(poolAddress, "confirmed");
if (!isValid(pool)) return;

// 3. 针对一个留有重试余地的 blockhash 签名。
const { blockhash, lastValidBlockHeight } =
  await connection.getLatestBlockhash("confirmed");

// 4. 不走 preflight 发送。
const sig = await connection.sendRawTransaction(raw, {
  skipPreflight: true,
  maxRetries: 0,
});

// 5. 按 confirmed 收敛,到过期为止。
const outcome = await resolve(sig, lastValidBlockHeight);

// 6. 按 finalized 记账。
if (outcome.status === "success") {
  await recordForAccounting(sig, "finalized");
}

★在 Connection 上设一个 commitment 然后到处继承,正是同时制造出两种失败模式的做法★ —— 检测太慢,记账太弱。

上链应该是什么水平

我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。

★commitment 决定的是你什么时候得知交易落块了,不是它什么时候落块。★ 这是两个不同的问题,把它们混淆,正是有些团队以为"把确认级别调低让机器人变快了"的原因。

BoltTx 在这里的位置

我们负责提交。commitment 是客户端的选择,完全归你。

提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前、传输途中观察不到。你的读取和确认保持你现在的配置就行。

你在本地签名。我们不托管资金、不代签、不修改交易内容。tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。

免费领 API key,没有月费:

const connection = new Connection(
  "https://la.bolttx.io/?api-key=YOUR_KEY",
  "confirmed",
);

常见问题

processed、confirmed、finalized 有什么区别? processed 是某个验证节点执行了它,仍可能被回滚。confirmed 是超级多数对该区块投过票,极少被推翻。finalized 是上面又建了 31 个以上区块,不会再变

Solana 交易机器人该用哪个 commitment? 按用途分别选:检测用 processed,据以行动的读取和 blockhash 用 confirmed,对账用 finalized一个全局设置对三者中至少两个是错的。

用 processed 安全吗? 用来决定做什么,安全 —— 最坏情况是浪费一笔交易。用来记录发生了什么,不安全,因为一次回滚会让你的链下记录和链永久性地对不上。

我的交易为什么 preflight 失败、重试却成功? 多半是 preflightCommitment 默认成了 finalized,于是模拟跑的是大约三十个区块之前的状态。一个刚创建不久的账户,从那个视角看还不存在。

confirmTransaction 该用 finalized 吗? 很少该。它为了大多数交易逻辑并不需要的确定性,要等大约三十个区块。改成按 confirmed 轮询 getSignatureStatuses,并到 blockhash 过期为止。

getLatestBlockhash 该用哪个 commitment? 大多数情况用 confirmedprocessed 给的 blockhash 最新但有被回滚的风险,finalized 已经是三十个区块之前的,在你发送之前就花掉了一部分有效窗口

Solana 上 finalized 要多久? 区块产出之后,大约还需要 31 个区块的额外确认。这正是它属于对账、而不属于热路径的原因。

commitment 级别会影响交易速度吗? 不会。它影响的是你什么时候得知结果,不是交易多快到达出块方。 落块速度来自手续费、路由和重试行为。

confirmed 的交易会被回滚吗? 有可能但极其罕见,因为那需要一个被超级多数投过票的区块被丢弃。对任何会变成永久记录的东西,finalized 才是不带这种风险的级别。

preflightCommitment 是什么,为什么重要? 它是你的 preflight 模拟所依据的 commitment,和连接的默认值是分开的。设得太高,它会对着陈旧状态模拟,并拒绝掉本来有效的交易。

commitment 该设在 Connection 上还是逐次调用设?Connection 上设一个合理的默认值,在重要的地方逐次覆盖。大多数方法都接受 commitment 参数,而关键调用值得显式写出来。

WebSocket 订阅该用哪个 commitment? 抢跑时用 processed,因为等 confirmed 意味着你比不等的人更晚知道。动钱之前再用更高级别验证。

为什么我在 processed 下看到的余额不对? 因为你可能读到了一个之后被回滚的区块。用于展示的余额可以容忍这一点,用于记账或给用户上账的余额不行。

commitment 会影响 getSignatureStatuses 吗? 响应会告诉你这个签名达到了哪个级别,所以你可以逐次调用再决定。这比 confirmTransaction 灵活,后者提前把决定固定死了。

如果我的 blockhash 来自一个被回滚的区块会怎样? 交易会被判为无效并拒绝,因为它引用的 blockhash 不在规范链上。这正是用 processedgetLatestBlockhash 的风险。

交易所充值需要 finalized 吗? 任何在链下给出真实价值的场景都需要。那恰恰是一次回滚会让你的系统和链永久不一致的情况。

读取该怎么选 commitment? 问一句:如果这个值最后是错的,会发生什么。 如果答案是浪费一笔交易,processed 就够;如果答案是一条必须人工纠正的永久记录,就用 finalized

延伸阅读

返回博客列表