【野蛮人的读书笔记】对 CKB 的一点点理解:原来 CKB 上的状态生成是这么一回事

对我来说,在理解 NervosCKB 的过程一直是痛苦并快乐的,因为每当我在学习的过程中产生不解时,我往往需要多花一点时间才能知其所以,但每当知道为什么这样设计的时候,有会感受到 NervosCKB 的绝妙。虽然我现在依旧是个菜鸡。
我想刚开始了解 CKB 时,一定会遇到一个大坑,就是怎么在上面编程。
尤其曾经在以太坊上有编程经验的小伙伴们,一定会遇到的问题是把 Account 模型的思维用在 CKB 上,这时就是蛋疼的开始啦。
以下我会简单说一下我看到的 CKB 交易是怎么构造,以及 CKB上 的交易结构长的是什么样,一方面整理自己所学,一方面也帮助大家绕开一些坑。
一、状态是链下生成的
『 状态是链下生成的 』这句话可能大家在看 Nervos 的 RFC002 时,就已经看过了,但当下的感觉不一定会特别强烈。这句话如果白话一点说,就是对于开发者而言,交易是要自己在来链下构造出来的,CKB-VM 只管验证。举例来说,如果 Bob 今天要用 Neuron 钱包,把自己的 100 CKB 送给 Alice,那么其实这笔交易就是在 Neuron 中被构造好的,而不是在 CKB 的链上产生的。

                   如果在 Neuron 中要生成状态是怎么样的

所以你可以看到,其实在 CKB 的交易中,这些 Input 和 Output 都是在链下被生成的,然后 CKB-VM 负责去验证他的生成。所以在 RFC 002 中的这张图中的 Generation Layer 就是泛指各种可以生成交易状态的方法(包括但不限),他可能是像 Neuron,imToken 这样的桌面或者移动端的钱包 ,也可能是像一个游戏的 Dapp,甚至还可以是 Layer2 、联盟链等服务。
CKB-VM 会验证逻辑,正确无误之后,链上就会同步这些 Cell 中的状态。

                            RFC 002 中阐述状态生成的示意图

因此他和以太坊的编程思路是不一样的,在以太坊上,你要做的是发送一笔交易,然后以太坊的虚拟机去帮你生成结果,但在 CKB 上你必须自己构造好交易的初始状态和最终状态,因此结果是你已经可以预想到的,这样做的好处在交易广播前就可以在链下知道结果,也就确保了产品怎么运转,状态怎么生成这件事是可以在开发者的设想之中的。

二、CKB 的交易和数据结构
这也是我在理解的时候花了很久才懂一点的的地方。我认为对于整体的 CKB 的数据结构和交易结构,如果有基本的认识,那么就可以更清楚什么东西在做什么用的,从 RFC019 我学习到整体的数据结构会是这样:区块 》 交易 》 Cell 》 Script 。

Cell 和 Script :
首先,我们先从 Cell 开始说起,Cell 就是存放状态的最小单元。 如果有了解比特币的 UTXO 的朋友就知道,和以太坊的账户模型不一样的地方是,每个 UTXO 都只能被使用一次,以太坊就有点像你在银行的账户一样,可以不断的在一本账本里多增加一笔或者减少一笔,但 UTXO 模型有点像一个又一个的小猪铺满。你要花到里面的钱,你就必须把这个猪铺满打破,然后把花了多少钱存到某个新铺满中,剩下多少钱也存到另一个铺满中。CKB 中也一样只是这个铺满的名字叫做 Cell ,而且 Cell 铺满里除了能够存钱,还能够存更多种东西。

那么这个铺满中有什么呢?Cell 由这四种字段组成:
容量( Capacity):Capacity 不仅代表着这个 Cell 里面的总字节数限制,也代表着该用户有多少的原生代币 CKBytes。 一个 CKB 等于 1 Byte 的存储空间。
数据(data):在 Cell 中存储的状态数据。
类型脚本(type script):type script 验证状态怎么生成的脚本。类型脚本典型的应用场景就是智能合约的使用,
锁定脚本(lock script ): lock script 代表 Cell 的所有权的脚本,只允许 Cell 的所有者才能转移 Cell,有就是所有者才有打破这个 Cell 铺满的权力。
从这样的结构我们可以发现,Cell 是个能够装下状态,以及装下各种逻辑的 UTXO,相较起来更加的泛化,并且能够将能够将所有权以及智能合约分别放在 lock 和 type 这两个不同的位置存放。

Transcation:
在了解完 Cell 之后,接下来就是怎么交易的结构长什么样子了,但我这里并没有要细讲这些细节,而是要让大家有个大概的印象,知道在 CKB 中,一笔 Transaction 究竟是什么样子。
基本上来说,一笔 cell 会包含的,主要是 Cell Input 和 Cell Output ,Cell Input 告诉你 Cell 在进行交易前长什么样子,然后 Cell Output 则是告诉你 Cell 交易后长什么样子,另外,有一个具有强大功能的字段是 cell_dep,他可以让你去引用先前链上还是处于 live 状态的 cell ,你可以引用它上面的逻辑,有点像调用其他的合约一样,去和你的 input cell 一起作用,产生新的 output cell ,除此之外,一笔交易还会包含像是 version, header_deps, outouts_data, 以及witness 等等字段,让交易能够更容易的被验证。
如下方的代码就是一笔完整交易的内容,你可以看到这些交易的内容,都是在上链之前必须被开发者写好的。

{
  "version": "0x0", // verison 是代表当前转账系统是什么版本
  "cell_deps": [    // 一个 output 的数组,告诉你这笔交易引用了哪一笔交易
    {
      "out_point": {
        "tx_hash": "0xbd864a269201d7052d4eb3f753f49f7c68b8edc386afc8bb6ef3e15a05facca2",
        "index": "0x0"
      },
      "dep_type": "dep_group"
    }
  ],
  "header_deps": [ // 这笔交易以来在哪个区块
    "0xaa1124da6a230435298d83a12dd6c13f7d58caf7853f39cea8aad992ef88a422"
  ],
  "inputs": [    // Cell input
    {
      "previous_output": {
        "tx_hash": "0x8389eba3ae414fb6a3019aa47583e9be36d096c55ab2e00ec49bdb012c24844d",
        "index": "0x1"
      },
      "since": "0x0"
    }
  ],
  "outputs": [ // Cell output
    {
      "capacity": "0x746a528800",
      "lock": {
        "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
        "args": "0x56008385085341a6ed68decfabb3ba1f3eea7b68",
        "hash_type": "type"
      },
      "type": null
    },
    {
      "capacity": "0x1561d9307e88",
      "lock": {
        "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
        "args": "0x886d23a7858f12ebf924baaacd774a5e2cf81132",
        "hash_type": "type"
      },
      "type": null
    }
  ],
  "outputs_data": [
    "0x",
    "0x"
  ],
  "witnesses": // 使相应的 lock script 可以被打开
["0x55000000100000005500000055000000410000004a975e08ff99fa0001
    42ff3b86a836b43884b5b46f91b149f7cc5300e8607e633b7a29c94dc01c6616a12f62e74a1
    415f57fcc5a00e41ac2d7034e90edf4fdf800"
  ]
}

以上这只是一个范例,但当然还有很多细节是没有讲清楚的,可以再参看 RFC019 数据结构和 RFC022 交易结构。但总体而言你可以看到一笔交易就是这样被构造的,如果你想更简略的看CKB 的区块和交易的话,他大致会长的像下图这样的结构:

以上是最近我在学习 CKB 上发现的一些心得,希望下次我可以更清楚的告诉大家,一笔合约在 CKB 上怎么写。

3 Likes