Godwoken —— Cell 模型中缺失的那一块

Jinyang Jiang

Jinyang Jiang

Nervos Core Team

对于开发者来说,cell 编程模型无疑是 Nervos CKB 中最有趣的部分。在这里对 cell 模型作一个简短的描述:

  • Cell 是通用的 UTXO
  • 一个 cell 是一个包含任意数据和可定制脚本的 UTXO
  • 当一笔交易尝试销毁或创建一个 cell,cell 中的脚本将被加载并执行,在执行脚本中返回任何错误,都将导致交易失败。

Cell 模型不同于账户模型:

  • 验证而非计算
  • 将数据存储在单独的 cells 中,而不是将数据存储在一个账户中

当你比较两个模型时,你会发现还有许多其他的不同之处,我们今天就不展开讨论无关内容了,你可以在 Nervos Talk 上找到更多关于 cell 模型 VS 账户模型的讨论。

Cell 模型中缺失的那一块

UTXO 模型非常棒,cell 模型也继承了它的灵活性。我们(通过 cell 模型)可以发行 UDT(用户自定义代币,类似于 ERC20),存储链上资产,玩石头剪刀布游戏,或者与比特币实现原子交换。Cell 模型可以实现很多人们最初认为不可能实现的事情。

不幸的是,有一些合约确实很难在 cell 模型上实现:

  • 投票
  • 众筹
  • 去中心化定价的虚拟机
  • ...

这些难题合约有一个共性,那就是需要共享状态。

在一个 UTXO 类的模型内,状态是自然分离的。

在 CKB 中,用户可以在独立的 cells 中进行投票。这样我们就需要一个链下的角色收集这些投票的 cells 并计算结果。

voting

当我们只想“看到”结果时,它实现的非常好。但是我们不能在另一个合约中使用投票结果,举个例子,一个基于投票的 DAO 合约。我们很难在一个链上合约中验证统计后的结果。所以我们必须证明每一个投票 cells 的存在,交易时就需要引用每一个投票的 cell,这可能非常昂贵。

voting-result

再举个例子,我们来看一个众筹合约:

一个 cell 中有所有的众筹代币,用户可以支付 CKB 来获得相应数量的代币。

问题在于,当我们分割这个 cell,众筹 cell 的 outpoint 就被改变了;其他用户必须等到下一个区块才能看到新的 outpoint。所以在一个区块时间内,只有一个用户可以参与众筹,这是不可接受的。

与投票的例子类似,一个典型的解决方案就是引入一个链下角色,用户在个人的 cells 中发出众筹请求;然后链下角色需要收集这些 cells 并将结果放在一个 cell 中。

我们可以看到,由于在 cell 模型中,状态是自然分离的,因此我们必须依赖某个链下角色来收集状态。

这样当方法是可行的,但是仍然会有一些问题:

  • 如何有效地证明收集的结果
  • 如何保证在引入了链下角色后的去中心化
  • 用户如何与链下角色进行交互

当然,解决这些问题并不难;我们可以通过支付链下角色一些费用来激励他们;使用某种挑战机或者零知识证明(zk proof)来验证收集的结果;定义一些协议来规定和链下角色的交互。我们总能解决这些问题。

等等,我想要的只是一个投票合约。我为什么需要整这么多(复杂的)东西?

一个合约管理一切

one-contract

可不是嘛!我们可不想为每一个合约都整这么多东西,我们只需要构建一次:

Godwoken 是一个建立在 CKB 上的基于账户模型的编程层,目标就是管理一切状态共享的合约。

Godwoken 由以下几个部分组成:

  • 主合约 —— 一个维护所有账户、所有区块的全局状态的类型脚本(layer 1.5 层)
  • 挑战合约 —— 一个处理挑战需求的类型脚本
  • 聚合器 —— 一个收集 layer 1.5 层交易的链下程序,并定期向主合约提交 layer 1.5 层的区块。
  • 验证器 —— 一个持续监控两个合约的链下程序。验证器在一个无效区块被提交时会发送一个挑战请求,在一个错误的挑战请求被提交时会发送一个无效的挑战请求。

godwoken-components

你可能发现,这听起来像是最近非常流行的 rollup 的解决方法,是的,没错。但是我们关注的是聚合的问题,而非可扩展性的问题。Godwoken 提供了 CKB 基于账户模型的编程能力来解决聚合问题。

一些人将 Rollup 称之为 layer 1.5 层;也有一些人认为它是 layer 2 层;还有人认为它是 layer 1 层(根据信任级别)。本文将 Godwoken 成为 layer 1.5 层,以便将其与 layer 1 层的 CKB 区分开。

Godwoken 使用和原生 CKB 合约相同的技术栈。唯一的区别在于 Godwoken 合约是基于账户模型的;它将验证账户的状态而不是 cells 的状态。账户状态和 layer 1 层的 cells 之间的映射关系是由 Godwoken 的主合约处理的,对于 layer 1.5 层的合约来说,是透明的。

对于需要创建一个投票合约的开发者来说,只需要使用脚本简单地创建一个账户,这个脚本就会验证输入的数据和账户状态。

// pseudo code
fn verify_voting(i, votes) -> bool {
state[i] += votes;
merkle_root(state) == output_account_root
}

从上面的伪代码中,我们可以看到验证模型类似于 layer 1 层。

Godwoken 主合约使用了一个稀疏 merkle tree来存储所有的账户和账户状态。

因此,如果我们想要在 layer 1.5 层合约之间使用一个状态,我们可以简单得为这个状态生成一个 merkle proof,并在合约中验证 merkle proof。

如果我们想在 layer 1 层的合约中使用一个 layer 1.5 层的状态,我们可以在交易的 cell_deps 字段中引用 Godwoken 主合约 cell,并从 cell 中读取 Godwoken 中的全局状态来获得 merkle root,这样就可以验证状态和 merkle proof 了。

通过创建一个抽象的账户层,我们可以在 CKB 上通过最小的成本创建一个状态共享的合约。

在后续的文章中,我们将会讨论 Godwoken 中的详细信息,我们如何维护 layer 1.5 层的账户和合约,以及基于账户模型的合约是如何工作的。

Godwoken 设计文档

稀疏 merkle tree

原文链接