在上一篇文章《一、如何保证 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 是:
- 一个有任何人都可以无需许可的运行的链下程序。
- Keeper 是 Dapp 的一部分,不同的 Keeper 服务于不同的 Dapp。
- 它会根据 CKB 链上的状态,发出交易,修改 CKB 的链上状态。
引入 Keeper 之后,多个用户同时注册 DAS 账户的技术过程就变成了:
- 用户发起一笔交易,释放一个包含注册信息的
指令Cell
,比如「我要注册c.bit
」,「我要注册d.bit
」。同时,这些 Cell 的 lock 是always_success
,任何人都可以消费它们。 这笔交易并不会将用户要注册的账户插入到有序链表中。 - Keeper 通过监听链上状态,会发出一笔交易。将这两个
指令Cell
,作为 inputs,并在 outputs 中创建对应的AccountsCell(c.bit)
,AccountsCell(d.bit)
,一起将他们插入到有序链表中合适的位置。
可以看到,通过这种将多个账户注册请求打包一起插入链表的方式,可以有效的避免用户多次签名。那我们对 Keeper 的理解,应仅仅是将注册请求打包处理来避免 Cell 竞争吗?其实不然。
由于 Keeper 是任何人都可以无需许可的运行(他理应被设计为如此)的链下程序,那么当多个人运行 Keeper 时,Keeper 之间又会出现 Cell 竞争问题:每个 Keeper 都在做相同工作,将用户的 指令Cell
变成对应的 AccountCell
插入到链表中合适的位置,它们又要去竞争 Cell 了。
这似乎让人沮丧 ,Cell 竞争无处不在。但仔细思考,这压根就不再是问题了。Keeper 是程序啊,它们发出的交易失败了 ,有必要的话,它们可以自动签名新的交易。交易失败不会让它们烦躁,也不会让它们有任何损失。而这,正好解决了我们想要解决的问题:如何避免用户不断的签名。
至此,我们便彻底解决了上一篇文章遗留的,由 Cell 竞争所带来的问题。
对 Keeper 的进一步思考
- Keeper 更像是一个可执行函数集合,用户的
指令Cell
就是对其中某个函数的调用信息。Keeper + 链上验证脚本,构成了完整的以太坊思维框架下的 Dapp:与 Dapp 交互, 就是发送一笔交易,调用 Dapp 暴露出来的函数接口,传入对应的参数。 - Keeper 模块是 CKB 上 Dapp 不可或缺的模块 – 「链下计算」模块。
- 运行 Keeper 毕竟需要服务器成本,那么谁又会来运行 Keeper 呢?如果没有人运行 Keeper 了,那 Dapp 不就无法工作了吗?
- 作为 Dapp 开发者有充分的理由和动力去保障 Dapp 的正常工作,所以他们会去运行,但也不是绝对的。甚至如果只有 Dapp 开发者运行,那开发者的链下服务稳定性,会直接影响 Dapp 的可用性。
- 所以,我们更建议从经济激励的角度去思考如何鼓励大家来运行 Keeper。这也正是 DAS 的做法:任何将用户的
指令Cell
转变为AccountCell
,从而帮用户完成注册的 Keeper,都可以分享到一定比例的注册费用。事实上,在 DAS 中,大量的逻辑处理都是以这样的方式去激励 Keeper 完成的。
- 再结合 Keeper 是可执行函数集合这个理解,少量的(也可能为 0) Keeper 奖励 + CKB网络矿工费,两者合并到一起,就是用户与合约交互所需要付出的总体成本。更进一步,Keeper 奖励可以理解为「链下计算」相关的费用,CKB 网络矿工费可理解为「链上验证」相关的费用。对于以太坊而言,验证和计算都是在链上进行,但我们仍可以从逻辑上将其费用拆分成计算和验证两部分。所以从细节上看, CKB 和 ETH 差别很大,但在某个抽象层级来看,他们又具备一致性。