First-class Asset

First-class Asset

喜欢函数式编程的工程师应该很熟悉一个名词:First-class Function,翻译成中文应该叫“头等函数”或者“一等函数”。First-class Function指的是一类编程语言,在这些语言中函数是一个完全独立的概念:函数可以被当作值赋给一个变量,可以被当作参数传递给其他函数,也可以被当作返回值从其它函数传出来。在这样的语言中我们可以像操纵数据一样操纵函数,所以在这些语言中函数和数据一样是“一等公民”(First-class citizen)。First-class Function是函数式语言的一个关键特性,很多函数式编程的强大能力来源于此。

Nervos CKB使用Cell模型来构建整个共同知识库的状态。Cell模型是一个非常简单但是与现有区块链设计非常不同的状态模型,我们在设计Cell模型的时候已经意识到,基于Cell模型的DApp将拥有一些非常不同的性质,就像函数式编程和面向对象编程会产生风格迥异的设计模式和程序特性一样。在这篇文章中,我想阐述Cell模型可以支持的一种非常有趣的DApp设计模式,我们把它叫做First-class Asset,因为通过它我们可以将用户自定义的加密资产变成区块链中的“一等公民”。

状态模型的快速入门

在Cell模型之前,各种区块链使用的状态模型基本上就是两种:UTXO模型和Account模型。

使用UTXO模型的代表是比特币。UTXO是未被花费的交易输出(Unspent Transaction Output)的缩写,一个UTXO可以简单的理解为是一个比特币,然而和一般的硬币不同,每一个UTXO的面值都是不一样的。每个UTXO中都通过一段锁脚本(lock script)记录了这枚硬币的所有者是谁,同时保证只有所有者能够花费这枚硬币。每一个比特币全节点都会维护当前所有UTXO的集合,这个集合我们就称为比特币账本的当前状态(即当前的账本)。每一次比特币转账都是一个从UTXO集合中删除几个硬币(属于付款方)然后又增加几个新硬币(属于收款方和/或付款方)的过程。由于整个账本状态是基于UTXO这个最小单元构建的,我们把它叫做UTXO模型。

使用Account模型的代表是以太坊。Account就是账户,和银行账户类似,代表了资产的所有者,账户里面最重要的数据是余额(Balance),记录这个账户持有的以太币的数量。账户是资产所有者的代表,所有者可以是人(对应外部账户)或者智能合约(对应合约账户),外部账户通过私钥签名来验证资产所有权,合约账户的所有权通过合约代码来确定,合约代码和状态都保存合约账户内部。外部账户要转账的时候,用户在交易中指明转账数量,账本中的付款方账户余额和收款方账户余额就会做相应的减少和增加。由于整个账本状态是基于账户(Account)这个最小单元构建的,我们把它叫做Account模型。

First-class Coin

UTXO模型和Account模型代表了构建账本状态的两种思路。账本是所有者与资产之间关系的集合。UTXO模型以资产为基础建模,先构建出“硬币”的概念,再给硬币赋予所有者的属性;Account模型以所有者为基础建模,先构建出“账户”的概念,再给账户赋予余额的属性。以哪种方式作为基础模型决定了系统中的操作的基本对象是资产还是账户(所有者)。

所以我们说,硬币(Coin)是UTXO模型中的First-class citizen,每一个UTXO都是一个具有独立标识符的对象(Transaction ID + Output Index),coin是用户直接操作的对象(用户在构造的交易中包含utxo),账户是基于coin建立的上层概念(只存在于钱包中)。因此UTXO是First-class Coin。

First-class%20Asset%20_%20UTXO%20Transaction

在Account模型中,账户是First-class citizen,聚合在账户余额中的硬币没有独立的标识符。账户是用户直接操作的对象,资产的转移是由账户作为用户的代理实现的,这一点在接受方是合约账户时体现的最为明显。在这样的模型下,用户定义加密资产(例如ERC20)更像是通过第三方记账的方式,而非点对点的方式转移,这个差异会将第三方(这里的第三方指的是托管加密资产的智能合约)引入资产转移流程,增加智能合约的设计复杂度(我们可以把智能合约看作在资产转移时会自动执行的逻辑)。为了降低这种复杂度,Account模型中的交易需要加入特殊的逻辑(value字段),但是这样的特殊逻辑只有助于原生资产,同时造成对原生资产和用户自定义资产的不同代码路径。对于这些问题,Kelvin Fitcher写过一篇Looking at ownership in the EVM进行了很好的分析,在此不再赘述。

First-class%20Asset%20_%20Account%20Transaction

有了这些背景,我们应该更容易理解CKB的这一设计理念了:

有了Cell模型,我们能够简化设计,并在Nervos CKB上实现作为“一等公民”的用户定义资产(User Defined Assets),简称 First-class Assets.

First-class Assets与UTXO一样,具有独立标识符,可以被用户及脚本直接引用和操作。

First-class State

如何实现First-class Assets呢?

无论用何种方式,我们都需要记录所有者和资产之间的关系。这些关系记录,本质上是经过共识的状态。要有First-class Assets, 必须先有First-class State,而这正是Cell模型的出发点。

Nervos CKB的名字来自于Common Knowledge Base(共同知识库)的缩写。我们之所以把Nervos网络中的区块链称为“共同知识库“,是因为它的责任是持续不断的对网络的共同状态形成全球共识,换句话说,CKB是一个由全球共识维护的状态库。一个状态库的基本模型,很自然的是将整个状态划分为更小的状态单元组织起来。这些更小的状态单元,就是Cell。

由于Cell是一种状态单元,有独立的标识符(Transaction ID + Cell Output Index),可以被直接引用,作为参数传递给脚本,它是CKB中的“一等公民”,也就是说状态是CKB中的“一等公民”。Cell不仅仅是一种First-class State,而且是最简单的一种First-class State:一个Cell中只有Capacity,Data,Lock以及Type(可选,Type可以是一段代码或者指向一个Code Cell的Reference)四个字段。如下图所示,Cell的所有者可以直接更新Cell中保存的状态,不需要经过任何中间方,而在Account模型中用户只能通过合约代码(账户中的code)来操作账户内的状态,状态实际上是托管在合约手中的。

值得指出的是,有了Cell,CKB实际上就获得了一种有状态的编程模型。一种普遍的观点是,以太坊编程模型的表达能力来自图灵完备的虚拟机,实际上通过账户使得智能合约能够保存计算状态是一个大过EVM的优点(图灵不完备的语言也有很强大的表达能力)。CKB通过Cell和CKB-VM(Simple Yet Powerful! 这得另外写一篇文章了)的组合实现了一种新的有状态的智能合约编程模型。这个编程模型更加适合Layer 2,因为通过分析Layer 2协议的共同模式我们可以看到,协议层之间的交互对象应该是状态对象(State Transaction)而不是事件对象(Event Transaction),Layer 1应该是一个状态层而不是计算层。

CKB编程模型的另一个特点是,不区分数据(状态)和代码。这句话的意思是,与Account模型不同,合约的状态和代码都可以储存在Cell的data字段中,保存代码的Cell可以被其它Cell引用(因为它们是First-class State!),合约的状态和代码不需要绑定在一起,存放在一个地方。开发者可以通过一条简单的指令把代码Cell或者数据Cell的内容载入运行时内存,然后根据需要自行将其解释为代码执行或者数据来读写。

有了这些底层支持,我们就可以将一个合约的代码和状态分开保存在不同的地方:Code Cell的code (data)字段存放代码,而State Cell的state (data)的字段则保存状态;在State Cell中通过type ref引用Code Cell来建立对自身保存的state的业务逻辑约束,通过lock ref引用另外一个Code Cell来表达State Cell的所有权。每一个State Cell可以属于不同的用户,因此在Cell模型下独立的用户状态是非常容易实现的模式(在Account模型下,合约状态往往由多个用户状态混合构成,例如在一个ERC20合约中,Alice和Bob的Token余额都记录在同一个合约的内部状态里面)。

如果想对CKB-VM上的合约编写有更多了解,请看这篇文章这篇文章

有了这样一种编程模型,我们就能构造First-class Asset了。

First-class Asset

CKB中的用户定义资产(User Defined Asset)可以这样来构造:

  1. 设计资产定义合约(Asset Definition),规定资产的主要约束(例如总数量,发行者,交易前后数量不变等)。
  2. 保存合约代码到Asset Definition Cell中。
  3. 在满足发行权限的情况下,发行者发行资产,并将资产状态保存在另外的State Cell中。State Cell的Type字段引用保存了资产定义的Code Cell,保证State Cell的变化受到资产定义的约束。
  4. Asset Cell的持有者可以通过更新Lock来改变Asset Cell的所有者。

可以看到,在这样的设计中,用户定义的资产是作为独立对象存在于系统中的,每一份资产都是一个Cell,每一份资产都拥有自己的标识符。我们完全可以认为Asset Cell是UTXO的通用化版本。这样的First-class Asset有如下优点:

  • Asset Cell可以被引用,可以直接作为其它合约的参数传入。只要引用Asset Cell的input有正确的用户授权,合约就可以正常的使用用户的Asset Cell。
  • 资产定义与资产状态分离。Asset Definition Cell的所有者是资产的发行者,而Asset Cell是属于每个用户的。Asset Cell的授权逻辑和业务逻辑分离,所有权完全由自己的lock决定,与Asset Definition的逻辑无关,这意味着First-class Asset不是托管在资产发行者、开发者或是资产定义合约的手中,而是真正完全属于用户的
  • 用户的资产相互隔离,用户资产状态独立。CKB的经济模型关注状态存储激励问题:用户在区块链上保存状态不仅需要支付写入费用,而且应该承担与存储时间成正比的存储成本。如果用户的资产状态混合在一个地方保存(例如ERC20),这些状态的存储成本有谁来支付将是一个问题。(CKB Economics Paper正在努力写作中…)
  • 只要Asset Definition Cell的lock逻辑允许,资产定义可以独立更新。

上面的示意图只是在CKB上实现 First-class Asset的一种方式。除了上面讨论的方面,还有一些有趣的细节,例如,Asset Definition Cell是不是可以有属于自己的状态?Asset Definition Cell以及Asset Cell的capacity应该由谁来提供?对于这些问题,我们已经有了一些非常漂亮的想法。这些细节的设计、讨论和实现是我们现在正在进行的工作。

Summary

Cell模型是一个高度抽象的模型,事实上,你不仅可以在Cell上实现First-class Asset,也可以在Cell上模拟Account. 通过这篇文章的介绍我们可以看出,Cell模型是一个不同于UTXO模型和Account模型的新设计。除了状态模型的不同,CKB还将计算(也就是状态生成)转移到了链外,在链上只需要对状态进行验证的逻辑。独特的状态模型和计算验证分离这两点决定了CKB的编程模型上必然会出现新的DApp范式和设计模式。

从CKB白皮书完成到现在将近一年的时间中,我们看到越来越多的人开始关注和讨论First-class State和First-class Asset这两种新的思路(虽然大家用的名词各自都不一样),这些进展让我们非常兴奋。如果你有兴趣对First-class State和First-class Asset进行更多的探讨,或是在CKB的编程模型上有什么有趣的想法,欢迎联系我们讨论~

CKB的代码已经完全开源,这篇文章介绍的内容在代码中都已经实现。欢迎给我们的代码提出各种意见:

感谢Ian Yang, Xuejie Xiao,Kevin Wang在CKB和Cell模型设计中提供的帮助~

14 Likes

最后那张图里面的 Alice’s Asset Cell 控制权是在Alice自己的手中吗?
Alice能不能直接把这个cell里面的内容改掉,把这笔资产销毁?甚至是把contract ref改掉,摇身一变成另外一种Asset?

Alice的Asset Cell完全是属于alice的,她可以把资产销毁,把内容改成任意的东西。

Asset Definition Cell有可能会被发行者销毁吗?

2 Likes

取决于Asset Definition Cell的lock是怎么写。

发行者可以构造一个永远无法解锁的逻辑,这样的cell不能销毁;发行者也可以构造一个, e.g., 在100天后可以解锁的逻辑,这个Asset Definition Cell就可以在100天后被销毁,这也意味这相关Asset Cell的有效期只有100天。

2 Likes

刚开始深入学习 nervos哈,问些比较小白的问题

  1. Layer 1 Nervos UDA 一个资产/Token 和 Layer2 Appchain使用 cita 定义一个资产/Token , 会有什么差异哇?彼此之间是什么关系哇
  2. nervos 测试网预计什么时候上线哇
  3. CKB Economics Paper 预计什么时候出哇

cita使用的是evm和account模型,差异在文章中已经讨论了。彼此之间的关系由layer2的开发者定义,例如appchain上的token可能和ckb上某个UDA是1:1的映射,也可能和任何ckb上的UDA都没有转换关系。

测试网 Q 1 会出,经济模型很快就出啦!

关于First Class Asset的若干问题:

在CKB下的UDA中,合约代码保存在了Asset Definition Cell内,持币者通过State Cell内的Type字段引用合约使该Cell成为该合约的Asset Cell,UDA的定义和Asset本身是完全可以分离开的。

Q1:一个UDA的Capacity总量和什么有关?

  • 一个UDA有100个持币用户而另一个UDA有1000个持币用户,所有引用这两个UDA的Asset Definition Cell的Asset Cell的Capacity总和是和用户数基本上成正比的吗?
  • 一个Asset Cell里面存10000个Asset而另一个Asset Cell存100个同样的Asset,所对应的Capacity是完全一样的么?(还是按照字节数来讲略有差别?)

Q2:在以太坊上,正常进行ERC20之类转账时,我还是需要购买eth当作转账燃料,(当然目前也有一些项目方正在进行,转账时直接将ERC20作为燃料的项目开发)。这一部分CKB上是如何设计的?

  • CKB内转账需要转账费么?
  • Capacity一定意义上是CKB的使用权,当A用户转Asset到一个新账户B的时候,新形成的Asset Cell的Capacity对应的CKB的使用权由谁来出?(就比如像一个项目方进行一个空投,被空投账户里面需要有CKB或者有租用CKB的使用权,这样才能获得空投,还是完全不需要就可以接受空投?或者是项目方先承担Capacity,但是这个Asset Cell被空投用户通过公钥lock(这种情况下,capacity和lock的所有权将会分离)用户通过转账unlock这个Asset Cell,然后解锁掉项目方预先承担的这一部分Capacity,如果是这样的话,被空投用户如果不解锁,这部分Capacity就会被永久占用。)
  • 我是否可以不持有CKB就能够实现UDA的转账?

Q3:在Cell模型中,真正表示拥有该Asset资产的数据存储在那一部分中?是Data中保存的State,表示该Cell拥有什么UDA,拥有了多少UDA等等;然后在Type中进行了一个对Asset Definition Cell的Reference以及一个校验,校验Data中数据的真实性?

  • 我拥有一个Asset Cell,我可以通过指令改变Type中对于Asset Definition Cell的Reference,那么当我真的这么做的时候,该Asset就永久的消失了么?还是说我之后可以通过再加入对Asset Definition Cell的Reference恢复该部分资产。这样是不是就可以实现转账的时候隐匿转账内容了?
3 Likes

回答一些我的理解,如果有出入还请 @doitian 补充
Q1:
1、一个 Asset Cell 会需要一个最小的 Capacity,只要大于这个 Capacity 即可,所以 Asset Definition Cell 引用的 Asset Cell 每一个 Capacity 都可以不一样。
2、第二点,按照前面的逻辑,这里我理解只能说“需要的最小 Capacity”,你指的是 Asset Cell 里面某一个种 token 持有 1000 个和 100 个 token 所需要的 Capacity,这样的话是一样的,因为两个 Cell 中写入的字段差距只是在 amount 这里是 1000 还是 100,这个 Asset Cell 至少需要的 Capacity 是该 UDA 在单个 Asset Cell 中所需要的 Capacity 的最大值。

Q2:
在 CKB 中在协议层实现了 Atom swap,用户可以直接使用 UDA(典型场景比如稳定币)支付 CKB 手续费,也就是构造一个交易,矿工进行验证和撮合,完成原子交换。(没错,这可以直接成为 DEX)
1、需要转账手续费,这一点和比特币的设计是类似的。有点类似 Ethereum 但是我们的手续费设计会很简单,因为 CKB-VM 每运行一段代码 Cycle 数都是很清楚的,可以直接拿来计算需要的手续费,而不是 Ethereum 那样复杂的 Gas 设计。
2、“Capacity 一定意义上是 CKB 的使用权?”这句话可能有点偏差,Capacity 是这个 Cell 在 CKB 占空间的大小。空投在 CKB 上做会相对复杂,比如项目方 A 做空投,A 就需要建立很多个很小的 Cell,然后 lockscript 就是用户的公钥。(估算一下一个 Asset Cell 20 字节,其实也不贵吧 Lol)。也就是说,项目方 A 发 UDT 的同时还给用户们每个人发了一点点 CKB。
3、不持有 CKB 你就没有 UDA,别人转给你就要给你一小块 Cell。
我知道这里的问题是对于新用户一定要有 Cell 才能进行操作体验是非常不友好的,但是前面也说了可以使用 UDA 支付手续费,同样的逻辑你可以从交易所买了 UDA 然后把一部分 UDA 换成 CKB 存储这些资产,用户体验上可以做到没有用 CKB。

Q3:是的 Data 中的 state。注意 Cell 中有四个部分,Capacity, Data, Lock, Type,Lock 是所有权,Type 是修改权,Lock用于转移Cell的归属权(对应Owner);Type则用于解锁 Cell 里的data值的修改。

每一次转账都是旧的 Cell 销毁生成新的 Cell,用户解锁 lockscript,type ref 到 Asset Definition Cell 修改生成新的 Cell,每一次生成新的 Cell Type 都会经过验证,所以你所说的先去掉 Type 再加上可能不能够成立,无法经过验证。

2 Likes

感谢巨美无比的Ryan做出的解答,追问若干:

Q1:
1.一个Asset Cell的Capacity是否存在上限?我是否可以使用大量的Capacity而仅仅用来存放一个Asset,你可以说从经济角度来讲这是非常不合理的,因为你完全可以把这一部分租赁出去。但是在激励层有这样的架构,每年增发的CKB中除去N Dao的部分,CKB处于使用状态所占比例的增发给到矿工,未使用状态的比例的增发给到“生态委员会”,(因为经济白皮书还未发布,这一部分还未确定,仅个人认知的一些内容而言),这样的设定下在租赁收益较低的情况下,是否存在大户和矿工行为上合谋(这应该也不能算作是作恶),或者仅矿工而言,在没有卖出挖到的CKB时,缺乏意义的使用CKB是否更有利于其获得更高收益。

Q2:
1.对于第二小问,对于项目方空投UDT时是否需要赠送一点CKB存疑。这里面关键应该就是capacity和lock是否可以来自不同用户,如果是来自不同用户,那么项目方可以不用送出去CKB,仅仅在lockscript用用户的公钥,用户可以通过转入CKB或者租用CKB或者消耗UDT本身完成把空投转出去。
2.20字节的Asset Cell贵不贵是一个相对问题,在以太坊上的问题就是,一次UDA的转账的价值本身如果都不如消耗的gas贵的话,这样的转账缺乏支撑基础。而在CKB上空投的衡量因素就变成了,空投部分的价值和转账手续费+20字节的CKB(按照你说的别人转给你就要给你一小块Cell)之间的对比,因为目前对CKB总量未知,所以无法判断具体情况。(如果CKB持价格可观,薅一份羊毛等于赚了两份资产)

Q3:
明白了,不要轻易改Type,是在作死。
在想一个好玩的事情,通过区块链浏览器应该可以完全知道每个Asset Cell对应的是什么UDA吧,不然的话,我不进行转账,然后把每个Asset Cell的Type去掉,那这样的话,哪怕黑客或者坏蛋获得了我的私钥,也没有办法获得这个Asset Cell里面的UDA。

3 Likes

要持有UDA不一定需要持有capacity, 用户可以”借来“capacity存放自己的UDA。一个有些接近这个思路的是demo中的这个例子

User Defined Token which uses only one cell per wallet

这个例子展示了如何按照预定义的规则修改别人cell中的UDA数量 :slight_smile:

1 Like

没有太看懂,只能试着去理解代码,在这个User Defined Token which uses only one cell per wallet例子中Alice没有向Bob转移一定的CKB Capacities,而只创建了一个3010 CKB Capacities 用于存放token2 合约信息的wallet cell,所以之后Bob转给Alice的12345个token2 的CKB Capacities是Alice向Bob借用过来的?

3010 CKB Capacities 用来开户创建一个新的 UDT Cell 的,后面的转账只是更新这个 Cell 里的数量。

这个 Cell 的 lock script 保证了任何人都可以把它用作 input,但是交易必须产生一个 lock 相同,但是 UDT balance 更高的 output,而只有账户所有人才可以减少 balance 或者销户。

可以说是用 Cell 去模拟一个 Account

第一个问题:没上限,你有钱开心就行。如果我够任性为什么不买一栋四层海景带大花园和泳池的别墅养两只柴犬呢。

系统新增发的币,一部分给将资产放在 DAO 里面的持有者,其他给矿工。如果全网 10% token 放在 DAO,那么一个块 10% 出块奖励给 DAO 这里。这样在 DAO 中能够抵抗(相对于币的数量)的通胀。

系统出块奖励是固定的(这是关键),实际上就是两个利益方:放 DAO 的持有人和矿工,虽然他们说不上竞争,每个人都是处于自己利益最大化的选择,但也没有合作的动机(也就是你说的用缺乏意义的方式使用 CKB,比如我这种如果有钱了就买别墅只养:dog:的人)。

Q2:我也有点疑惑。如果是持有一定 CKB 的用户是可以用 Cell 模拟 account 的就是 Jan 给的那个 Demo 展示,只是这个 account 不是无限大(Capacity 是 upper cap)。

我还没有找 CKB team 确认过,但是是不是这样的情况:Ian 提到

而你在 airdrop 的时候需要找用户的公钥,有些用户是有自己的 Cell 并且 Cell 有一定空余空间的。作为项目方我可以直接 airdrop 到这些 Cell 里面,仅「增加」UDT(如果用户有这个 UDT 那么 UDT balance 增加,没有就增加这个字段)。

但是 Jan 的

我也没太理解,后面可以在确认一下

Q3:可以,查询用户公钥对应的 Cell 里的 type ref 就能知道这个 UDA 的 Definition Cell。

你 Type 去掉你自己也丢了这笔钱诶老哥,你自己操作资产靠的也就是你自己的私钥…if you lose it,you lose all

在这种转到用户有一定Cell空余空间的情况下,是默认创建一个最小CKB Capacities的Cell,还是只要找到一个放得下这些UDT的Cell即可?(这是不是也和UTXO模型有关?比如这个用户其实是有Cell的,但是每个都不足20字节(也是够惨的),或者都不足放下这个UDT的CKB Capacities,那么其实也是无法完成这次空投的)

我想是找一个放得下的 Cell 。 input 是一个有多余空间的 Cell,lock 不变, 增加新的 UDT 或者修改这个 UDT 的 balance,然后通过节点的验证。

Cell 的确很像 UTXO(应该说是 generalized UTXO)。可以考虑很多中方案,比如仅选择有空间的 Cell 复制他们的 lockscript(公钥)去 airdrop。如果是一个新用户那么需要给一点点 CKB。

1 Like

非常棒的想法!这个模型真的很漂亮,支持并行,支持数据和逻辑分离。

不过有两个问题想请教:
1)是否支持合约升级,即直接升级合约代码,新的合约可以操作旧的数据?
2)Libra的MOVE是账户模型下做数据和逻辑分离,有没有相关的关于MOVE vs First-Class State的文章或讨论?想进一步学习一下。

这个可以参见 type id,也就是在 CKB 中提供一个独一无二的 type script。这个独一无二的 type script 和 hash type ,为 CKB 提供了一种可以升级已部署的智能合约的方法。
详细可以参见:CKB 脚本编程:Type ID
Introduction to CKB Script Programming 6: Type ID

其实在去年 Libra 刚发布的时候我们也有过一些讨论,这个欢迎参考: