Ksana
EN

概念

Ksana 的工作原理

Ksana 在自己的快速区块里几秒内就执行你的操作,把每个区块的数据写进比特币交易,并且只凭比特币上的数据判定哪些区块已经最终。

本页目录

Ksana 是一套智能合约系统,它的记录保存在比特币上。出块者每隔几秒挖出一个区块,并立即执行其中的操作;随后,每个区块的数据都写进普通的比特币交易,等这些数据有了六个比特币确认,区块便已最终。任何人只要有一个比特币节点,就能只凭比特币重建并核对全部状态。主网尚未上线:本页描述的一切,都已在公开 signet 上运行。

两条链,一份记录

快轨约 5 秒确认

比特币约 10 分钟一块

  • 快轨确认≈ 5 秒
  • 已写入比特币下一个比特币区块
  • BTC 最终6 个确认
  • 结算

上方是快轨区块,下方是比特币区块:每次结算都把一段快轨落进一个比特币区块,其中的操作随之从快轨确认,到已写入比特币,再到 BTC 最终。

Ksana 把工作分给两条链。快轨是 Ksana 自己的链,由工作量证明区块组成,几秒内就为操作排好顺序并执行。比特币保存每个快轨区块的数据,并决定最终性:这些数据一旦有了六个确认,要推翻它就得重组比特币。

因此,每笔操作都要经过三道印记:有快轨区块执行了它,就是快轨确认(约 5 秒);区块的数据进入一个比特币区块,就是已写入比特币;这些数据有了六个比特币确认,就是 BTC 最终。确认与最终性一页讲每道印记保证什么,以及要等多久。

比特币从不进入 Ksana。合约可以核实一笔比特币付款确实发生过,办法是对照比特币区块头验证 Merkle 证明,或者读取携带这次操作的那笔交易;但 Ksana 的任何规则都花不了比特币。BTC 只在你亲自签名的比特币交易里转手,充值就是一例。

快轨

出块者与工作量证明

人人都能出块:用一个出块者账户启动 ksana-node,它就会挖矿。出块者收集操作并执行,把得到的状态根写进区块头,再求解工作量证明。工作量证明用的是 Argon2id,占用 2 MiB 内存,在一个 CPU 核心上每次尝试约 1 毫秒;这样选,是为了让普通电脑也能挖矿,让浏览器也能验证区块头。

难度逐块调整:有操作要收录时,区块约每 5 秒一个;没有可收录的,出块者就挖空块,空块更难挖,几分钟才出一个。出块者靠自己的区块赚取 KSA。挖矿一页讲算法、难度和成本,KSA 与奖励一页讲奖励。

区块 DAG,而不是一条链

每 5 秒出一个区块,两个出块者常常几乎同时挖到区块。Ksana 不会丢掉其中一个,而是两个都保留:一个区块最多可以指明 10 个父块,于是区块构成一个有向无环图(DAG),再由 GHOSTDAG(Kaspa 使用的协议)给整张图排出唯一的顺序。

每个区块都有一个选定父块,即背后工作量最多的那个父块;从最优的顶端沿选定父块一路往回走,得到的就是主链。主链区块会合并它的祖先中还没有被任何区块排定顺序的那些区块,即它的合并集:先按固定顺序执行这些区块的条目,再执行自己的。被合并区块中的操作,在合并它的主链区块的高度生效;与排在它前面的条目冲突的条目(同一操作出现两次、nonce 已被使用、余额已被花掉)会被跳过,也不付手续费。

GHOSTDAG 还会把区块染成蓝色或红色。蓝块是及时让网络其余部分看到的区块;红块则在视线之外待了太久,可能出自攻击者,它的条目照样执行,但得不到出块奖励。

锚定块

每个区块都指明一个比特币区块,作为它的锚定块。出块者锚定在自己的比特币链顶之下 ANCHOR_DEPTH 个区块处(signet 上为 1,主网为 2,devnet 为 0),而且绝不低于该区块各父块的锚定块。执行时读取比特币,只读到锚定块为止:区块必须收录的路径 C 交易、合约可以读取的比特币区块头,以及哪些区块的数据已经上链。因此,无论各节点自己的比特币链顶在哪里,算出的结果都相同。

这个深度能吸收浅的比特币重组:不超过 ANCHOR_DEPTH 个区块的重组不会让任何锚定块变成孤块,主链及其状态保持原样。更深的重组会把锚定在这些成为孤块的比特币区块上的区块移出主链,其中的路径 B 操作回到条目池,以同样的操作 ID 再次被收录。

结算进比特币

提交交易出块者付费

输入

  • in出块者的资金Taproot 密钥路径

输出

  • 0找零
  • 1承诺Taproot,一个脚本叶子:信封
揭示交易携带区块

输入

  • in提交交易的输出 1

见证

  1. 签名
  2. 信封叶子KSN1紧凑区块头与条目
  3. 控制块

输出

  • 0P2A 锚点输出,240 聪任何人都能用 CPFP 加速这对交易
  • 1空的 OP_RETURN让交易不低于最小体积

一次结算是先提交、后揭示的一对交易:出块者的资金支付提交交易,揭示交易的 Taproot 见证里装着带有区块数据的 KSN1 信封。

区块的数据通过由两笔交易组成的结算写进比特币。提交交易花费出块者自己的比特币,创建一个 Taproot 输出,这个输出承诺了一段装有数据的脚本。揭示交易花费这个输出,于是数据出现在它的见证里,装在一个 KSN1 信封中。揭示交易还向一个 P2A 锚点输出支付 240 聪,任何人都能借此用一笔子交易提高这对交易的手续费(CPFP);它还有一个空的 OP_RETURN 输出,使交易高于 Bitcoin Core 的最小交易体积。

之所以要两笔交易,是因为只用一笔时,数据只能放在不受任何签名覆盖的见证元素里,任何转发者都能把它换掉而交易 ID 不变;先在一个输出里承诺这段脚本,就把数据固定住了。放在见证里,每字节数据只计四分之一个虚拟字节。

信封里的载荷装着本区块的区块头和条目、更早的区块中还没上比特币的区块头,以及更早的、没有自带结算的区块的条目(区块体)。区块头和条目都采用紧凑编码:一串连续的空块中,第一个之后的每个区块头只占 14 字节;一笔路径 B 的 KSA 转账占 79 到 143 字节。在 regtest 上实测,一次普通结算的提交交易为 154 vB,揭示交易为 209 vB。没有条目、或者只有路径 C 条目的区块,只需把区块头写上比特币,因为任何人都能从比特币推导出它的条目。

携带结算的区块在主链上达到 2 个区块深(SETTLEMENT_DEPTH)之后,出块者才广播这次结算,以免快轨的短暂分叉把两笔相互冲突的结算送上比特币。签好名的交易就在区块里随区块传播,所以任何节点都能广播它们。

批量结算与谁来付费

并不是每个区块都带结算。默认情况下,区块在两个条件都满足时才带一次结算:有区块体要上链(自己的或更早区块的),而且没有更早的结算还在等待确认;否则它把自己的区块体留给下一次结算,除非等待中的区块体或它自己的区块体超过 10 KiB(载荷上限的八分之一)。所以繁忙的链大约每个比特币区块发出一次结算,携带上一次以来的全部内容。

区块带结算的出块者,用自己的出资钱包支付这次结算的比特币手续费。作为回报,凡是数据由这次结算最先送上链的区块,它都成为这些区块的携带者,得到这些区块奖励的 5% 和路径 B 手续费的一半。走路径 B,你只付 KSA;走路径 C,你只付自己那笔比特币手续费。

数据没能上链时

快轨依赖它的数据写进比特币,所以规则确保总有人把数据送上链:

  • 救援:一次结算无法确认时(它的资金被花掉了,或者手续费一直太低),其他节点用自己的资金重建同样的载荷,按主链顺序轮流进行。
  • 补发:任何人都可以把缺失的区块头和区块体放进一个指明收款账户的载荷里发出去。对于由这个载荷最先送上链的区块,只要数据上链的位置比它们的锚定块高出超过 ANCHOR_DEPTH + 2 个比特币区块(signet 上即超过 3 个),收款账户就成为它们的携带者;这给出块者自己的结算留出了先上链的时间。
  • 解锁:在一个区块及其之前所有区块的数据都上了比特币之前,谁也拿不到这个区块的奖励和手续费,所以之后的每个出块者都希望缺失的数据上链。
  • 积压上限:如果一个区块体在其区块的锚定块之后过了 6 个比特币区块(SETTLEMENT_TIMEOUT)仍未上链,那么在它上链之前,新区块只能包含路径 C 条目。路径 B 暂停;路径 C 照常工作。

由比特币决定最终性

每个节点和每个索引器都用同一个基于比特币数据的函数判定最终性,所以它们无需彼此信任,也会得出相同的答案。在比特币上的载荷里出现过的区块头,自成一个 DAG。在每个比特币高度上,这个函数从这些区块头中取最重的快轨链,从最后一个已最终的区块开始沿链往下走:一个区块的数据、连同它合并的区块的数据,都有了 6 个比特币确认(BTC_FINALITY_CONFS),并且执行它得到有效的结果,该区块即为最终。

当有人造出一条竞争的快轨链时,这意味着:

  • 区块在最终之前,可能被一条更重的链取代。这正是你接受快轨确认时要权衡的风险。
  • 一条更重、却不把数据写上比特币的链,成不了最终。及时把数据送上链的较轻的链会先被判定为最终,较重的那条则被“最终掉”。
  • 当一条较轻的竞争链出现在比特币上,而一个节点自己的链过了一个比特币区块仍未出现在那里时,节点会以能进入下一个区块的费率补发自己的区块头,让诚实的链在对方的数据有六个确认之前上链。
  • 只靠快轨算力推翻不了已最终的区块:那需要重组比特币,而且区块成为最终之后,每挖出一个比特币区块,所需的重组就更深一层。比特币重组见安全模型。

只有有效的区块才会被判定为最终。锚定块(或它合并的某个区块的锚定块)不在比特币最优链上的区块,首先被排除;执行无效的区块,连同建在它之上的所有区块一起被排除;随后,函数改取次优的链。每个节点的主链都必须经过已最终的区块,缺少其中某个区块的节点,会用比特币上的数据把它重建出来。ksana_getChainInfo 以 finalized 报告最新的已最终区块。

提交操作

你可以用两种方式发送操作:

  • 路径 B(代付):签名操作时附上以 KSA 计的手续费,发给任意节点。出块者几秒内就会收录它,并支付把它送上链的比特币手续费。
  • 路径 C(原生):把操作放进你自己的一笔比特币交易,写在 KSN1 数据输出里,并支付比特币手续费。这笔交易在 signet 上有了 2 个确认(主网上为 5 个)之后,快轨必须收录它,而且排在所有其他条目之前:这就是强制收录。

出块者可以拒绝路径 B 操作,但漏掉路径 C 操作的区块是无效的。路径 C 保证了任何出块者都无法把你挡在门外,也是只凭比特币就能行动的途径:在充值中,一笔比特币交易付款给卖方,换取存放在兑换合约里的 KSA。操作与手续费一页讲解这两条路径、手续费和充值。

索引器

钱包与应用signet 钱包、TypeScript SDK、ksana-cli
Ksana 节点检查每个区块,并出新块 节点P2P节点P2P节点
比特币每个节点和索引器都通过 bitcoind 读取
索引器只凭比特币重建整条链状态根一致

钱包和应用调用节点的 JSON-RPC,节点锚定比特币并把结算送上比特币,索引器只凭 bitcoind 重建整条链并核对状态根。

索引器只连接 bitcoind,从不连接 Ksana 节点。它读取比特币上的载荷,重建区块 DAG,按与节点相同的规则选出主链,用同样的代码执行每个区块,并拿每个区块的状态根与它的区块头核对。它运行同一个最终性函数,报告同一个 finalized 区块。

所以这份记录不依赖任何 Ksana 节点保持在线或诚实:即使所有节点都消失了,任何人也能从比特币重建全部状态。遇到区块体无效或状态根不对的区块,索引器会取消它进入主链的资格,并重新选择主链。运行索引器一页介绍怎样运行索引器,以及怎样把它与节点对照。

执行

合约是 WebAssembly 模块,通常用 Rust 配合 ksana-contract-sdk 编写,由 wasmtime 49.0.1 运行。规则排除了一切可能因机器而异的东西(浮点运算、不设上限的内存和栈、会变的 wasmtime 版本),所以同样的状态加上同样的条目,在每台计算机上都得出同样的状态根。账户、KSA 和其他内置资产都是原生模块:转账不需要合约。

每笔操作都带一个 gas 上限 max_gas。普通 WebAssembly 指令每条 1 gas,宿主函数和内置动作有固定价格(一笔 KSA 转账为 10,000),用量会超过上限的操作则失败。运行中失败的操作仍要付手续费,也会用掉它的 nonce;它做过的其他一切都会回滚。

状态存放在一棵 Jellyfish Merkle 树里,每个区块头都带着区块执行后这棵树的根:这就是节点和索引器拿来比对的状态根。一个区块自己没有条目、也没有合并任何条目时,不写入任何状态,所以它的状态根与选定父块的相同。合约这一侧,从编写合约开始。

完整规则

规范 docs/SPEC.md 收录了本页概述的每一条规则: