在上一篇文章《一、如何保证 DAS 账户的唯一性》中的最后,我们提到了仍然存在的 Cell 竞争问题。具体问题如下:
假定链上已经注册有 a.bit、b.bit、z.bit 三个账户,现在有两个用户分别想要注册 c.bit,d.bit,且注册时间很靠近。按照规则,注册发生时,他们使用的注册服务会分别构造交易让用户签名,交易的内容为将要注册的账户插入到 b.bit 之后。
问题在于,两笔交易都会试图将 AccountCell(b.bit) 消费掉,而一个 Live Cell 只能被消费一次,那么就会导致必然其中一笔交易会失败。假定注册 c.bit 的交易成功了,而注册 d.bit 的交易失败了。注册 d.bit 的用户不得不被他所使用的注册服务要求重新签名交易。这是由于注册 d.bit 时,原本需要将 AccountCell(b.bit) 消费掉,而现在需要改为消费 AccountCell(c.bit),交易结构内容发生了变化,必须重新签名。
这将导致非常糟糕的用户体验。事实上,当注册的用户数量变多时,大部分用户不得不一次又一次的签名交易,直到他能注册成功。
澄清问题
要解决问题,首要的是澄清问题
上面的问题之所以是个问题,根本之处在于什么呢?是在于引用了相同的 Cell 导致的交易失败吗?如果是这样,我们就会将思考聚焦在如何避免交易引用相同的 Cell。进一步思考下去,我们可能就要推翻有序链表这个设计了。
那如果我们把问题归结为,交易失败并不是问题,用户需要不断签名交易才是问题,会怎么样呢?那我们就会将思考聚焦在如何避免用户不断地签名。而这似乎并不困难。
Keeper
我们引入 Keeper 这个机制来解决这个问题。
Keeper 是:
1) 一个有任何人都可以无需许可运行的链下程序。
2) Keeper 是 dApp 的一部分,不同的 Keeper 服务于不同的 dApp。
3) 它会根据 CKB 链上的状态,发出交易,修改 CKB 的链上状态。
引入 Keeper 之后,多个用户同时注册 DAS 账户的技术过程就变成了:
1) 用户发起一笔交易,释放一个包含注册信息的指令Cell,比如「我要注册 c.bit」,「我要注册 d.bit」。同时,这些 Cell 的 lock 是 always_success,任何人都可以消费它们。这笔交易并不会将用户要注册的账户插入到有序链表中。
2) Keeper 通过监听链上状态,会发出一笔交易。将这两个指令Cell,作为 input,并在 output 中创建对应的 AccountCell(c.bit),AccountCell(d.bit),一起将他们插入到有序链表中合适的位置。
可以看到,通过这种将多个账户注册请求打包一起插入链表的方式,可以有效地避免用户多次签名的问题。那我们对 Keeper 的理解,应仅仅是将注册请求打包处理来避免 Cell 竞争吗?其实不然。
由于 Keeper 是任何人都可以无需许可来运行(他理应被设计为如此)的链下程序,那么当多个人运行 Keeper 时,Keeper 之间又会出现 Cell 竞争问题:每个 Keeper 都在做相同的工作,将用户的指令Cell 变成对应的 AccountCell 插入到链表中合适的位置,它们又要去竞争 Cell 了。
这似乎让人沮丧,Cell 竞争无处不在。但仔细思考,这压根就不再是问题了。Keeper 是程序啊,它们发出的交易失败了 ,有必要的话,它们可以自动签名新的交易。交易失败不会让它们烦躁,也不会让它们有任何损失。而这,正好解决了我们想要解决的问题:如何避免用户不断的签名。
至此,我们便彻底解决了上一篇文章遗留的,由 Cell 竞争所带来的问题。
对 Keeper 的进一步思考:
1) Keeper 更像是一个可执行函数集合,用户的指令Cell 就是对其中某个函数的调用信息。Keeper + 链上验证脚本,构成了完整的以太坊思维框架下的 dApp:与 dApp 交互, 就是发送一笔交易,调用 dApp 暴露出来的函数接口,传入对应的参数。
2) Keeper 模块是 CKB 上 dApp 不可或缺的模块 ——「链下计算」模块。
3) 运行 Keeper 毕竟需要服务器成本,那么谁又会来运行 Keeper 呢?如果没有人运行 Keeper 了,那 dApp 不就无法工作了吗?
- 作为 dApp 开发者有充分的理由和动力去保障 dApp 的正常工作,所以他们会去运行,但也不是绝对的。甚至如果只有 dApp 开发者运行,那开发者的链下服务稳定性,会直接影响 dApp 的可用性。
- 所以,我们更建议从经济激励的角度去思考如何鼓励大家来运行 Keeper。这也正是 DAS 的做法:任何将用户的指令Cell 转变为 AccountCell,从而帮用户完成注册的 Keeper,都可以分享到一定比例的注册费用。事实上,在 DAS 中,大量的逻辑处理都是以这样的方式去激励 Keeper 完成的。
4) 再结合 Keeper 是可执行函数集合这个理解,少量的(也可能为 0) Keeper 奖励 + CKB网络矿工费,两者合并到一起,就是用户与合约交互所需要付出的总体成本。更进一步,Keeper 奖励可以理解为「链下计算」相关的费用,CKB 网络矿工费可理解为「链上验证」相关的费用。 对于以太坊而言,验证和计算都是在链上进行,但我们仍可以从逻辑上将其费用拆分成计算和验证两部分。所以从细节上看,CKB 和 ETH 差别很大,但在某个抽象层级来看,他们又具备一致性。
DAS 创始人 TimYang (杨敏)在 Nervos CKB 上开发了 DAS 去中心化账户服务。借着这次的产品开发,TimYang 通过《从 DAS 开始了解 CKB 应用开发》系列文章,向大家阐述他的设计思路和开发历程,让大家了解如何在世界上第一个基于 UTXO 架构的公链 CKB 上构建产品级应用。
推荐阅读:从 DAS 开始了解 CKB 应用开发(一)—— 如何保证 DAS 账户的唯一性
如若您有更多关于 DAS 产品的使用心得,以及在 CKB 上开发的见解,欢迎前往 Nervos Talk 论坛讨论: