《从 DAS 开始了解 CKB 应用开发》二、善用 Keeper

在上一篇文章《一、如何保证 DAS 账户的唯一性》中的最后,我们提到了仍然存在的 Cell 竞争问题。具体问题如下:

假定链上已经注册有 a.bitb.bitz.bit 三个账户,现在有两个用户分别想要注册 c.bitd.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,作为 inputs,并在 outputs 中创建对应的 AccountsCell(c.bit)AccountsCell(d.bit),一起将他们插入到有序链表中合适的位置。

可以看到,通过这种将多个账户注册请求打包一起插入链表的方式,可以有效的避免用户多次签名。那我们对 Keeper 的理解,应仅仅是将注册请求打包处理来避免 Cell 竞争吗?其实不然。

由于 Keeper 是任何人都可以无需许可的运行(他理应被设计为如此)的链下程序,那么当多个人运行 Keeper 时,Keeper 之间又会出现 Cell 竞争问题:每个 Keeper 都在做相同工作,将用户的 指令Cell 变成对应的 AccountCell 插入到链表中合适的位置,它们又要去竞争 Cell 了。

这似乎让人沮丧 :fearful:,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 差别很大,但在某个抽象层级来看,他们又具备一致性。

14 Likes

Keeper蛮有意思的,希望下次可以讲的更细一点

谢谢分享。假设两个用户同时发起注册 c.bit, 两个不同的 keeper 分别打包了他们的注册申请,那总归有一个会失败。作为用户来说,是不是要等上链失败之后才知道 c.bit 被人抢注了,这样会不会有用户体验上的问题。

要知道上链是否失败,需要的时间并不长,所以体验影响不大。

抢注问题,其实比你在上面列举的这个场景要复杂一些。比如,我通过观察内存池中的交易,看到你在注册 hoomjac.bit ,那我就发一笔交易去注册 hoomjac.bit ,同时给更高的矿工费,抢注下来,然后找你回卖给你。

解决办法参考了 ENS 的做法,用户注册账户需要分两步:

  1. 用户释放一个 Cell,里面包含用户的公钥拼接账户名之后 hash 值
  2. 用户引用第一步中的 Cell,开始真正的注册。此时,我们会要求第一步中的 Cell 达到一定的确认数,并且用户要公开自己想要注册的账户名。

这样下来,既可以防止科学家恶意抢跑,也可以很大程度上避免两个用户真的同时注册同一个账户。

2 Likes

学到了,谢谢。期待后续的更新

期待 :smiling_face_with_three_hearts:

提一个关于 frontrunning 的问题:

由于交易是由 Keeper 完成组装然后进行上链的,那么如果一个 Keeper 看到用户注册了一个还不错的域名,比如说 “apple.bit”,那么 Keeper 是可以自己再发起一笔注册 “apple.bit” 的交易放在用户交易前面的。网络中存在很多个 Keeper 的话,可能会缓解这样的问题,但似乎并不能解决问题。

当然如果矿工也过来做 Keeper 的话,他们才是终极 Keeper。

不知道 DAS 这边是如何思考以及解决这一问题的。

2 Likes

两笔交易需要一定的间隔,第二笔交易需要引入第一笔交易创造的 Cell。并且,用户在第二步中才会公开自己要注册的账户的明文。当恶意 Keeper 或者矿工看到明文时,其实已经来不及了。他必须先从第一笔交易开始,等他准备好的时候,其他 Keeper 已经帮用户完成注册了。

4 Likes

DAS 什么时候公测啊

预计六月中旬,到时会提前公布时间

1 Like

畅想: 每一个ID或域名都是一个NFT,如ok.bit,每一个主ID都能无限分发子ID-mNFT。如001.ok.bit。如果主域名ok.bit.host是一个加密聊天室,是否能识别该子ID的归属,对应进入聊天室的白名单的权益。 如有作恶的子域名,能否发起投票,对作恶的子域名进行禁言,警告,留群观察,销毁剔除等可扩展NFT的功能。 不是很懂,随便想的,不知道有没有用

可以的,发挥想象,也可以不用子域名

DAS似乎并不是一个账户模型,而更像是个人主页。把DAS当作nft或公钥私钥都不对。