Ksana
EN

概念

确认与最终性

一笔操作要经过三道印记:几秒内得到快轨确认;所在区块的数据进入一个比特币区块,就是已写入比特币;这些数据有了六个比特币确认,就是 BTC 最终。本页说明每道印记能防住什么,以及该等多久。

本页目录

你发出的每笔操作都要经过三道印记,等到哪一道,由你按事情的轻重来定。快轨确认只受快轨自身工作量证明的保护;已最终的区块,只有重组比特币才能推翻。主网尚未上线;而如今可以观察这些印记的公开 signet,算力太小,它的快轨确认起不了多少保护作用(见下文)。

三道印记

  1. ≈ 5 秒 快轨确认 已在快轨上执行:余额已经变动,合约看得到新状态。
  2. 下一个比特币区块 已写入比特币 区块的数据已在一个比特币区块里,任何人都能读到。
  3. 比特币 6 个确认 BTC 最终 由比特币判定:任何快轨链都取代不了它,要推翻它只能重组比特币。

一笔操作的三道印记:约 5 秒得到快轨确认,结算之后的下一个比特币区块里已写入比特币,6 个比特币确认后 BTC 最终。

印记 已经发生了什么 典型耗时
快轨确认 节点的主链接受了这笔操作,并且执行成功。 约 5 秒
已写入比特币 接受它的区块及之前所有区块的数据,都已在比特币区块里。 结算发出后的下一个比特币区块
BTC 最终 这些数据有了 6 个比特币确认,所在的链是每个节点和索引器都判定为最终的那条。 约一小时起

RPC 中的状态

ksana_getOpStatus(ksana-cli op 和 SDK 的 getOpStatus 调用的就是它)返回以下五种状态之一:

状态 含义
pending 在本节点的条目池里,尚未进入区块。
fast_confirmed 已收录并执行成功:即快轨确认这道印记。
failed 已收录,但失败了,它的改动已回滚。手续费照付,nonce 也已用掉;只有没通过检查(版本、链 ID、长度、手续费模式、签名或 nonce)的路径 C 操作例外。
skipped 只被无法执行它的被合并区块收录过(重复、nonce 已被使用、余额已被花掉)。不收手续费,状态也还可能改变。
btc_final 执行成功,而且接受它的主链区块已经最终。

读这些状态时要注意:

  • 已写入比特币不是一种操作状态。要用 ksana_getSettlement 查询操作所在区块的结算:接受该区块的主链区块及其之前一切的数据都上了比特币,settled 就变为 true;这个主链区块最终后,btc_final 也变为 true。没有自己的结算的区块,即空块或只含路径 C 操作的区块,会返回 -32004。主链上的一个区块一旦 settled 或 btc_final,它下面的每个区块也都如此,所以要从它往上第一个有结算的区块读取这两道印记。
  • 失败的操作一直是 failed,即使它的区块已经最终。要知道这次失败本身已经最终,请看所在区块结算的 btc_final。
  • skipped 不是终点。被跳过但仍然有效的操作会回到条目池,可以再次被收录,所以它可能先显示 pending,再变成 fast_confirmed。
  • 未知的操作会返回错误,错误码为 -32004。路径 C 操作在被某个区块收录之前,节点都不知道它;已经离开条目池的操作(被手续费更高的操作替换,或从已满的池中被挤出)也返回同样的错误。
  • block 给出收录这笔操作的区块的哈希,并以 height 给出接受它的主链区块的高度:对于被合并的区块,就是其中操作生效的高度。

每道印记保证什么

快轨确认

你的操作已在节点的主链上执行:余额和合约状态都已包含它,之后的操作也建立在它之上。保护它的只有快轨的工作量证明。在它的区块最终之前,一条更重的快轨链可以取代这个区块;付款方如果把一笔冲突的操作(nonce 相同的另一笔)放进那条链,这笔付款就被撤销了。

比特币重组也可能撤下一个已快轨确认的区块。不超过 ANCHOR_DEPTH 个区块(signet 上为 1,主网为 2)的重组不会让任何锚定块成为孤块,主链保持原样;更深的重组则会把锚定在这些成为孤块的比特币区块上的区块移出主链。这些区块中的路径 B 操作会回到条目池,以同样的操作 ID 再次被收录,所以诚实的付款会自己回来;只有付款方抢先让 nonce 相同的另一笔操作进入链中,这样的重组才会变成一次撤销。

已写入比特币

接受你的操作的区块及其之前所有区块的数据,都已在比特币区块里。从此,任何拥有比特币节点的人都能读到这笔操作,不借助任何 Ksana 节点就能重算它的效果;区块在快轨上有 2 个区块深或已经最终后,索引器就会执行它。这个区块的奖励和手续费现在可以发放了。

这道印记关乎可用性,而不是最终性。在区块最终之前,更重的快轨链仍可能取代它;比特币重组也可能把携带数据的交易移出它所在的区块,直到这笔交易再次得到确认。

BTC 最终

接受你的操作的主链区块位于已最终的链上:它的数据及其合并的区块的数据都有了 6 个比特币确认,它的执行经过了检查,而且每个节点和索引器只凭比特币数据就会得出同样的判定。无论背后有多少算力,任何快轨链都取代不了它:一条竞争链要算数,它的区块头最晚得在让你的区块数据得到第六个确认的那个比特币区块里上链。要推翻它,就得重组比特币;而且已最终的区块和比特币交易一样,之后每多一个比特币区块,就更安全一分(见安全模型)。

每道印记要多久

  • 快轨确认:路径 B 约 5 秒,signet 和主网都一样。路径 C 操作要等它的比特币交易有了 NATIVE_CONFIRMATIONS + ANCHOR_DEPTH 个确认,才会得到快轨确认:signet 上为 2 个,主网为 5 个。
  • 已写入比特币:区块有了 2 个快轨区块的深度后,结算就会发出,它的数据落进下一个接纳它的比特币区块,在两个网络上平均都约 10 分钟。若更早的一次结算仍在等待确认,你的区块的数据就要等下一次结算,这会多出大约一个比特币区块。
  • BTC 最终:数据上链后再过五个比特币区块,所以最快也要在你发出操作约一小时之后。

在冷清的链上,你的区块之后的 2 个区块通常是空块,每个都要几分钟(挖矿一页解释了原因),所以结算会比在繁忙的链上晚几分钟发出。下面是 2026 年 10 月 14 日 signet 上的一笔转账:

时间(UTC) 发生了什么 印记
03:29:04 区块 127 收录一笔 KSA 转账,此时区块 124 的结算还在等一个 signet 区块。 快轨确认
03:40:29 signet 区块 326,271 确认了区块 124 的结算。
03:41:56 区块 130 是个空块,它带的结算里装着区块 127 的区块体。
04:01:27 这次结算发出后的下一个 signet 区块 326,273 确认了它。 已写入比特币,用时 32 分钟
04:31:34 signet 区块 326,278 让它有了 6 个确认。 BTC 最终,用时 62 分钟

该等多久

快轨确认有多安全,取决于攻击者占快轨算力的份额,以及链是否繁忙。在规范 §10.4 的模型中,算力份额为 α 的攻击者从装着你这笔付款的区块的父块开始,私下挖一条分支,一旦领先就把它公布出来。下面两张表给出:你等待给定的时间后接受的付款,被撤销的概率(取三种攻击策略中对攻击者最有利的一种,每格模拟 50,000 场竞赛)。

繁忙的链,区块里装着其他用户的操作:

等待 α = 5% α = 10% α = 20%
30 秒 2.0% 7.5% 27%
1 分钟 1.5% 6.5% 25%
5 分钟 0.06% 0.91% 9.3%
10 分钟 0.03% 0.37% 5.7%
30 分钟 0.002% 0.05% 2.3%

空闲的链,你的区块之后只有空块:

等待 α = 5% α = 10% α = 20%
30 秒 26% 43% 69%
1 分钟 20% 33% 58%
5 分钟 4.3% 13% 37%
10 分钟 1.8% 7.7% 28%
30 分钟 0.38% 2.6% 13%

在空闲的链上,诚实的工作量以权重 12 到 120 的空块形式到来,每个都要几分钟,所以在这几分钟里,保护你这笔付款的几乎只有它自己所在的区块。在繁忙的链上,攻击者运气好挖到的一个空块,分量相当于 12 个诚实区块,而且一挖到就算数,所以即使在繁忙的链上,短时间的等待也有一定风险。

所以,按事情的轻重来选:

  • 小额:快轨确认就够了。
  • 繁忙链上的中等金额:等约 5 分钟。
  • 大额,或在空闲链上任何要紧的事:等到 BTC 最终。

能重组比特币的矿工是另一种威胁:面对这样的矿工,快轨确认的分量只等于其锚定块的比特币确认数。要撤销 m 个比特币区块之前产出的区块,至少需要 ANCHOR_DEPTH + 1 + m 个区块的重组,主网上为 3 + m 个;这样的矿工同样也能双花比特币。推翻已最终的区块同样需要重组比特币,BTC 最终一节对此有说明。

在 signet 上

signet 的难度是按它的两个种子节点设定的,它们各用一个 CPU 核心挖矿,所以一台有几个核心的电脑就能超过它们。请用 signet 观察这些印记如何表现,而不是判断它们有多安全;何况 signet 上的币本来就没有价值。

查询印记

先向节点查询操作的状态,再查询收录它的区块的结算:

import { KsanaRpcClient, RpcError, SIGNET } from '@ksana/sdk';

const rpc = new KsanaRpcClient('https://signet.ksana.network/rpc');
await rpc.getParams(SIGNET); // 节点若在别的网络上,抛出 ChainMismatchError

const opId = '7796031150fd930f07cc99dac446343106456583197d3acc5a15c1be3dba2114';
const status = await rpc.getOpStatus(opId); // 操作未知时抛出 RpcError -32004
let mark = 'none yet';
if (status.block !== null && (status.status === 'fast_confirmed' || status.status === 'btc_final')) {
  mark = 'fast-confirmed';
  // 从操作所在高度往上,第一个有自己结算的主链区块
  const tip = (await rpc.getChainInfo()).tip.height;
  for (let height = status.block.height; height <= tip; height++) {
    try {
      const settlement = await rpc.getSettlement(height);
      if (settlement.settled) mark = 'on Bitcoin';
      if (settlement.btc_final) mark = 'final';
      break;
    } catch (e) {
      if (!(e instanceof RpcError && e.code === -32004)) throw e; // 它没有自己的结算
    }
  }
}
console.log(status.status, mark);

上面时间线里那笔转账的操作状态,ksana-cli 打印出来是这样的:

输出
{
  "block": {
    "hash": "7586db3476895fa540484c578469b78fb1ce99f4ad38968aac905c71682ee5c3",
    "height": "127"
  },
  "index": 0,
  "receipt": {
    "error": null,
    "events": [],
    "gas_used": "10000",
    "op_id": "7796031150fd930f07cc99dac446343106456583197d3acc5a15c1be3dba2114",
    "output": "",
    "success": true
  },
  "status": "btc_final"
}

以及区块 127 的结算,它由后面的一个区块携带:

输出
{
  "status": "confirmed",
  "settled": true,
  "btc_final": true
}

这里的 status 是结算自身的进度:广播之前为 pending(区块还不到 2 个区块深,或者还没有区块携带它的数据),等待比特币区块期间为 broadcast,数据进入比特币区块后为 confirmed。就整条链而言,ksana_getChainInfo 以 finalized 报告最新的已最终区块:在节点的主链上,于 finalized.height 或更低高度被接受的操作都已最终。索引器的 indexer_status 只凭比特币报告 finalized,所以对比两者就能检验节点。signet 钱包也会为它发出的每笔操作显示同样的印记。