本篇文章的做者是 Jan,文章阐述了 Cell 模型中支持的一种很是有趣的 DApp 设计模式:First-class Asset,它让加密资产变成区块链中的「一等公民」。git
喜欢函数式编程的工程师应该很熟悉一个名词:First-class Function,翻译成中文应该叫「头等函数」或者「一等函数」。First-class Function 指的是一类编程语言,在这些语言中函数是一个彻底独立的概念:函数能够被看成值赋给一个变量,能够被看成参数传递给其余函数,也能够被看成返回值从其它函数传出来。在这样的语言中咱们能够像操纵数据同样操纵函数,因此在这些语言中函数和数据同样是「一等公民」(First-class Citizen)。First-class Function 是函数式语言的一个关键特性,不少函数式编程的强大能力来源于此。github
Nervos CKB 使用 Cell 模型来构建整个共同知识库的状态。Cell 模型是一个很是简单可是与现有区块链设计很是不一样的状态模型,咱们在设计 Cell 模型的时候已经意识到,基于 Cell 模型的 DApp 将拥有一些很是不一样的性质,就像函数式编程和面向对象编程会产生风格迥异的设计模式和程序特性同样。在这篇文章中,我想阐述 Cell 模型能够支持的一种很是有趣的 DApp 设计模式,咱们把它叫作 First-class Asset,由于经过它咱们能够将用户自定义的加密资产变成区块链中的「一等公民」。编程
在 Cell 模型以前,各类区块链使用的状态模型基本上就是两种:UTXO 模型和 Account 模型。segmentfault
使用 UTXO 模型的表明是比特币。UTXO 是未被花费的交易输出(Unspent Transaction Output)的缩写,一个 UTXO 能够简单的理解为是一个比特币,然而和通常的硬币不一样,每个 UTXO 的面值都是不同的。每一个 UTXO 中都经过一段锁脚本(Lock Script)记录了这枚硬币的全部者是谁,同时保证只有全部者可以花费这枚硬币。每个比特币全节点都会维护当前全部 UTXO 的集合,这个集合咱们就称为比特币帐本的当前状态(即当前的帐本)。每一次比特币转帐都是一个从 UTXO 集合中删除几个硬币(属于付款方)而后又增长几个新硬币(属于收款方和 / 或付款方)的过程。因为整个帐本状态是基于 UTXO 这个最小单元构建的,咱们把它叫作 UTXO 模型。设计模式
使用 Account 模型的表明是以太坊。Account 就是帐户,和银行帐户相似,表明了资产的全部者,帐户里面最重要的数据是余额(Balance),记录这个帐户持有的以太币的数量。帐户是资产全部者的表明,全部者能够是人(对应外部帐户)或者智能合约(对应合约帐户),外部帐户经过私钥签名来验证资产全部权,合约帐户的全部权经过合约代码来肯定,合约代码和状态都保存合约帐户内部。外部帐户要转帐的时候,用户在交易中指明转帐数量,帐本中的付款方帐户余额和收款方帐户余额就会作相应的减小和增长。因为整个帐本状态是基于帐户(Account)这个最小单元构建的,咱们把它叫作 Account 模型。ruby
UTXO 模型和 Account 模型表明了构建帐本状态的两种思路。帐本是全部者与资产之间关系的集合。UTXO 模型以资产为基础建模,先构建出「硬币」的概念,再给硬币赋予全部者的属性;Account 模型以全部者为基础建模,先构建出「帐户」的概念,再给帐户赋予余额的属性。以哪一种方式做为基础模型决定了系统中的操做的基本对象是资产仍是帐户(全部者)。网络
因此咱们说,硬币(Coin)是 UTXO 模型中的 First-class Citizen,每个 UTXO 都是一个具备独立标识符的对象(Transaction ID + Output Index),Coin 是用户直接操做的对象(用户在构造的交易中包含 UTXO),帐户是基于 Coin 创建的上层概念(只存在于钱包中)。所以 UTXO 是 First-class Coin。编程语言
在 Account 模型中,帐户是 First-class Citizen,聚合在帐户余额中的硬币没有独立的标识符。帐户是用户直接操做的对象,资产的转移是由帐户做为用户的代理实现的,这一点在接受方是合约帐户时体现的最为明显。在这样的模型下,用户定义加密资产(例如 ERC 20)更像是经过第三方记帐的方式,而非点对点的方式转移,这个差别会将第三方(这里的第三方指的是托管加密资产的智能合约)引入资产转移流程,增长智能合约的设计复杂度(咱们能够把智能合约看做在资产转移时会自动执行的逻辑)。为了下降这种复杂度,Account 模型中的交易须要加入特殊的逻辑(Value 字段),可是这样的特殊逻辑只有助于原生资产,同时形成对原生资产和用户自定义资产的不一样代码路径。函数式编程
对于这些问题,Kelvin Fitcher 写过一篇 Looking at ownership in the EVM 进行了很好的分析,在此再也不赘述。函数
有了这些背景,咱们应该更容易理解 CKB 的这一设计理念了:
有了 Cell 模型,咱们可以简化设计,并在 Nervos CKB 上实现做为「一等公民」的用户定义资产(User Defined Assets),简称 First-class Assets.
First-class Assets 与 UTXO 同样,具备独立标识符,能够被用户及脚本直接引用和操做。
如何实现 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 以及 Contract(可选,Contract 能够是一段代码或者指向一个 Code Cell 的 Reference)四个字段。
以下图所示,Cell 的全部者能够直接更新 Cell 中保存的状态,不须要通过任何中间方,而在 Account 模型中用户只能经过合约代码(帐户中的 Code)来操做帐户内的状态,状态其实是托管在合约手中的。
值得指出的是,有了 Cell,CKB 实际上就得到了一种有状态的编程模型。一种广泛的观点是,以太坊编程模型的表达能力来自图灵完备的虚拟机,实际上经过帐户使得智能合约可以保存计算状态是一个大过 EVM的优势(图灵不完备的语言也有很强大的表达能力:https://en.wikipedia.org/wiki...)。
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 中经过 Contract ref 引用 Code Cell 来创建对自身保存的 State 的业务逻辑约束,经过 Lock ref 引用另一个 Code Cell 来表达 State Cell 的全部权。每个 State Cell 能够属于不一样的用户,所以在 Cell 模型下独立的用户状态是很是容易实现的模式(在 Account 模型下,合约状态每每由多个用户状态混合构成,例如在一个 ERC 20 合约中,Alice 和 Bob 的 Token 余额都记录在同一个合约的内部状态里面)。
若是想对 CKB-VM 上的合约编写有更多了解,请看这两篇文章:
An Introduction to Nervos CKB-VM
有了这样一种编程模型,咱们就能构造 First-class Asset 了。
CKB 中的用户定义资产(User Defined Asset)能够这样来构造:
设计资产定义合约(Asset Definition),规定资产的主要约束(例如总数量,发行者,交易先后数量不变等);
保存合约代码到 Asset Definition Cell 中;
在知足发行权限的状况下,发行者发行资产,并将资产状态保存在另外的 State Cell中。State Cell 的 Contract 字段引用保存了资产定义的 Code Cell,保证 State Cell 的变化受到资产定义的约束;
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 的经济模型关注状态存储激励问题:用户在区块链上保存状态不只须要支付写入费用,并且应该承担与存储时间成正比的存储成本。若是用户的资产状态混合在一个地方保存(例如 ERC 20),这些状态的存储成本有谁来支付将是一个问题。(CKB Economics Paper 正在努力写做中…);
只要 Asset Definition Cell 的 Lock 逻辑容许,资产定义能够独立更新。
上面的示意图只是在 CKB 上实现 First-class Asset 的一种方式。除了上面讨论的方面,还有一些有趣的细节,例如,Asset Definition Cell 是否是能够有属于本身的状态?Asset Definition Cell 以及 Asset Cell 的 Capacity 应该由谁来提供?对于这些问题,咱们已经有了一些很是漂亮的想法。这些细节的设计、讨论和实现是咱们如今正在进行的工做。
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 的代码已经彻底开源,这篇文章介绍的内容在代码中都已经实现。欢迎给咱们的代码提出各类意见:
https://github.com/nervosnetw... (CKB 上用 Ruby 脚本编程的示例,理解 CKB 上编程模型的最佳入口)
https://github.com/nervosnetw...
https://github.com/nervosnetw...
感谢 Ian Yang,Xuejie Xiao,Kevin Wang 在 CKB 和 Cell 模型设计中提供的帮助 ~
原文做者:Jan