大多数 Solana 代码库在 Connection 构造函数里设一次 commitment,之后再也不想它。而这一个选择随后被套用到了读取、发送、确认和对账上 —— 四种情况,四个不同的正确答案。
★选错的代价是不对称的。★ 太弱,你会基于一个之后被回滚的状态行动;太强,你在为自己并不需要的确定性等待若干 slot。
三个级别分别是什么意思
| 级别 | 含义 | 会被回滚吗 |
|---|---|---|
processed |
★某个验证节点执行了它★ | 会 |
confirmed |
超级多数已对该区块投票 | 极少 |
finalized |
上面已经又建了 31 个以上区块 | ★不会★ |
processed 是你能拿到的最快的东西,也是最不确定的。finalized 是确定性,代价大约是三十个区块的等待。confirmed 夹在中间,也是大多数应用逻辑该待的地方。
★confirmed 和 finalized 之间的实际差距,远大于 processed 和 confirmed 之间的差距。★ 正是这个不对称,让"反正用 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 决定要做什么,用 confirmed 或 finalized 记录发生了什么。★ 在 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?
大多数情况用 confirmed。processed 给的 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 不在规范链上。这正是用 processed 取 getLatestBlockhash 的风险。
交易所充值需要 finalized 吗? 任何在链下给出真实价值的场景都需要。那恰恰是一次回滚会让你的系统和链永久不一致的情况。
读取该怎么选 commitment?
问一句:如果这个值最后是错的,会发生什么。 如果答案是浪费一笔交易,processed 就够;如果答案是一条必须人工纠正的永久记录,就用 finalized。