在 CKB 上实现「剪刀、石头、布」小游戏

前情提要

​昨天晚上九点,在 Zoom 频道,成功举办了第三期茶话会,本期茶话会的分享人是来自 CKB 的核心开发者: 蒋金洋 老师。

分享内容主要包括两个部分:第一部分,主要介绍了 CKB 的交易结构,解读了 CKB 交易中各个字段的含义;第二部分,尤为精彩,蒋老师为我们介绍了在 CKB 上实现「剪刀、石头、布」这样一个小游戏的设计思路。

关于这次分享的第一部分,我就不过多介绍了,大家可以观看视频,以及通过 CKB Transaction Structure 了解相关内容。我来和大家着重介绍一下本次分享的第二部分。

要知道不少现场的小伙伴听完蒋老师的分享后,不禁赞叹:

S君:“妙啊,原来还可以这么设计。”

E君:“CKB 居然还可以这么玩!”

所以,你要不要来和我在 CKB 上玩一局「剪刀、石头、布」~

游戏逻辑分析

首先,我们要来分析一下需要实现的游戏逻辑。

现实中,我们是怎么玩「剪刀、石头、布」这个游戏的呢?通常这个游戏是在两个人之间进行的,在确定完两位游戏参与对象后,然后我们会一起喊一个口号,比如“1,2,3,剪刀、石头、布”,然后我们同时伸手做出选择,然后一比较,胜负一目了然。

但是这样的对决,通常会面临一个问题,就是两位玩家实际上是不可能在同一时间做出动作的,这就会导致通常后出手的玩家,会存在一些优势,因为只要手脑并用且速度够快,就可以根据对方的出手情况,在第一时间进行调整,做出必胜的选择。

这其实也是葛优发明,分歧终结机的原因。

640

同样的,我们在区块链上进行「剪刀、石头、布」这样的游戏,也无法保证两位玩家同时做出选择(因为这是不可能的),那么我们在 CKB 上,要如何实现这样一个分歧终结机呢?

先来向大家介绍一下整个游戏设定。本局游戏,我们有两个玩家:Alice 和 Bob,今天他们要来 CKB 上玩一局「剪刀、石头、布」,当然这是一个存在输赢的游戏,为了带一点娱乐的气氛,Alice 和 Bob 都需要拿出一部分 CKB 作为游戏胜利者的奖励,比如两人都拿出 200 CKB,胜利者就可以将这 400 CKB 都拿走。

区块链上的游戏逻辑

让我们思考一下整场游戏在区块链上的实现逻辑:

  1. 第一步,首先 Alice 开始了一局游戏,并提交了一个带有自己选择哈希的 cell,我们这里把 Alice 出的结果记为 [A 选择],注意,而这里提交到链上的是 Hash[A 选择],而不是将 [A 选择] 直接上链。
  2. 第二步,Bob 看到了 Alice 发起了这局游戏,决定加入这局游戏,然后提交了自己的结果,我们记为 [B 选择],注意,这里 Bob 是直接将 [B 选择] 提交到链上的。
  3. 最后一步,Alice 提交自己的 [A 选择] 到链上,然后根据双方出的 [A 选择] 和 [B 选择] 得出最终的胜负平关系,然后分配金额。

接下来我们会一步一步,详细地向大家介绍一下,整个游戏过程是在技术上是如何实现的。

我们先看每一步具体的步骤,最后再来进行一个总结,分析这样的游戏合约需要具体如何去设计。

Alice 创建游戏

首先,第一步,Alice 需要创建一个「剪刀、石头、布」的游戏,她需要发送一笔交易。在这笔交易中:

输入的 Cell,和大家平时持有的 CKB 的状态是完全一致的。

而输出的 Cell,其中 Lock 改成了 Dummy lock,这意味着这个 Cell 总是可以成功解锁,任何人都是可以解开这个 Cell 的。但是这并不是说,谁都可以将其中的 CKB 取走,通过输出 Cell 将 CKB 取出时的逻辑将会受到 Type 的限制。

在 Type 中写入本次「剪刀、石头、布」游戏的合约的脚本,这里可以直接引用已经存在在链上的「剪刀、石头、布」游戏合约的 Cell。

Alice 会在 Data 中存入三个字段,分别是:这局游戏的状态,Hash [A 选择],Alice’s lock hash。

这里游戏状态的标记为:

  • 游戏中只有一个玩家,游戏状态为 0;
  • 游戏中有两个玩家,游戏状态为 1。

注意,这里 Data 中提交的是 Hash [A 选择],因此后手玩家,无法知道 Alice 究竟出了什么。另外这里添加的 Alice’s lock hash,就决定了在当前游戏状态下,输出 Cell 的 Lock 中,只能填写 Alice’s lock hash,此时输出 Cell 的 Lock 只能为创建游戏的玩家,别人没有办法被盗走。

实际上,在这个游戏的逻辑中,最终可以解锁 Cell 拿走 CKB 的只有两个人,一个人游戏的创建者,一个是游戏的参与者。其他人任何人都抢不走这些 CKB。

无人应答,游戏撤销

在上一步中 Alice 创建了一局「剪刀、石头、布」游戏,按照输出 Cell 的 lock 的设计,任何人都可以参与这局游戏。

很遗憾,Alice 创建的游戏,可能很久很久都没有人参与,这时候,我们不可能让 Alice 一直等待在线上。所以我们会设计一个 since 字段,实现这样一个逻辑:从 Alice 创建游戏开始,如果游戏状态一直为 0,超过 1000 个区块,这个时候, Alice 就可以退出游戏,取回自己抵押的 CKB。

关于 since 实现的具体方法,大家可以查看:
https://ckb.dev/topic/27/rfcs-0017-tx-valid-since

我们可以通过 since 对区块号、周期号、区块时间戳进行相对或者绝对的限制,从而保证某笔交易或者某种逻辑必须在某个时间点之后才可以被执行。

Bob 加入了 Alice 创建的游戏

很开心,Bob 看到了 Alice 创建的这局游戏,决定加入这局游戏。注意,任何人都可以看见 Alice 创建的这局游戏,玩家只需要在 CKB 链上搜索所有 Cell 的 Type hash 字段,等于「剪刀、石头、布」游戏合约的哈希值即可。很幸运,这局的另一位玩家恰好叫 Bob 而已。

这个时候 Bob 需要提交一笔交易,其中输入 Cell 有两个,一个是 Alice 创建的,任何人都可以解锁的,包含了游戏合约的 Cell,另一个 Cell 是 Bob 自己的 CKB Cell,其中 Capacity 一项,需要满足 Alice 在游戏合约中要求的玩这局游戏所需的 CKB 的数量。

正如我们在游戏规则中设定的,Alice 和 Bob 都需要出 200 CKB 作为游戏的参与金额,那么在游戏合约中,我们可以去验证 Alice 和 Bob 两次输入的 Cell 的 Capacity 是不是都等于 200 CKB,如果不等,那么这笔交易是无法实现的。

然后我们来看一下输出,其中 Lock 还是 Dummy lock,Type 还是游戏合约,而 Capacity 已经变成了 400 CKB(200 CKB 来自 Alice,200 CKB 来自 Bob)。

在 Data 中,游戏状态已经从 0 变为了 1,这意味着这局游戏已经有两人参加,可以进入最后的结算过程了。Data 中还包含了:Hash [A 选择],Alice’s lock hash,[B 选择],Bob’s lock hash。

这个是时候你想说,Alice 不是知道了 Bob 出了什么吗,那 Alice 是不是可以作弊了?

不,Alice 并不能作弊,熟悉 Hash[] 的小伙伴知道,只有输入两次完全一致的数据或者内容,Hash[] 的值才会一致,一旦有一个字母发生了变化,两次的 Hash 值都是截然不同的。因此 Alice 只有老老实实地提交自己最初做出的选择,游戏合约才可能验证通过。

这里加入的 Alice’s lock hash 和 Bob’s lock hash,保证了最终输出的 Cell 只能归属于 Alice 或者 Bob,不可能被另外一个人抢走。

Alice 长时间不回应


在 Bob 发送完交易之后,Alice 其实已经知道了游戏的结果,但是除了 Alice 之外的其他人都还不知道这局游戏最终的结果,只有等到 Alice 将自己最初的选择提交到链上,大家才知道这局游戏谁才是真正的赢家。

这个时候就可能会发生一种情况。Alice 长时间不提交自己的 [A 选择],可能是由于 Alice 真的掉线了,也可能是因为 Alice 发现自己输了,不想再提交自己的选择。

这个时候我们就又需要用上 since 字段了,游戏合约要求,在第二位玩家(这里就是我们的 Bob),提交完交易之后,如果 1000 个区块后,创建游戏的玩家(也就是我们的 Alice),如果还没有不提交最初的选择,创建第三步结算交易,那么这些 CKB 都归属于第二位玩家。

所以如果 Alice 不在 Bob 发起交易后的 1000 个区块内,提交 [A 选择],作为对 Alice 的惩罚,Bob 将无条件获胜,拿走所有 CKB。

Alice 完成提交,进行游戏结算

上面说到 Alice 在看到 Bob 的交易之后,其实就已经知道比赛的结果了,如果 Alice 获得了胜利,毫无疑问 Alice 非常有动力去提交最终的交易;如果 Alice 输了,因为 since 字段的存在,不管提不提交最终的交易,奖金都归属于 Bob 了(这里请注意,在具体游戏设计中,如果 Alice 按时提交了 [A 选择],发出了最终的交易,哪怕 Alice 输了,也可以考虑返还一小部分 CKB 给 Alice,这样不管是输是赢,Alice 都有动力去创建第三笔交易。(史迪仔认为:这种游戏也可以引入押金模式,创建游戏的玩家需要多存入 100 CKB 作为押金,只有按时提交最终的结果,才能返回押金,不然会被罚没。不管是返还还是押金,其实实现的逻辑是完全一致的))。

这里我们来分析一下最后一笔交易:

输入 Cell 就是之前 Bob 提交完信息的 Cell,在 Witness 内,Alice 需要提交 [A 选择]。

输出 Cell 就是根据游戏规则,Alice 根据游戏结果创建的奖金分配 Cell,其中 Alice 获得了多少 CKB,锁到 Alice 的 lock 中;Bob 获得了多少 CKB,锁到 Bob 的 lock 中。

你是不是会很奇怪,为什么我这里说的是,Alice 创建的奖金分配 Cell,那 Alice 可以随便创建,将奖金都划分给自己么?

当然不可以,Alice 创建的最终结果还是需要通过游戏合约验证的,只有满足游戏合约设定的交易才可能被真正执行。这样设计的原因其实是因为,CKB 做的只是链上的验证,而不做链上的计算,一笔交易被提交到链上,其中的输入 Cell,输出 Cell 都是已经确定了的,只有验证成功的交易才能被执行,验证错误的交易无法被执行,整个过程不像是以太坊,还需要经过一个链上计算的过程。

至此,整个游戏的逻辑已经基本完成了,在这里我再简要地回顾一下这个「剪刀、石头、布」游戏合约需要执行哪些验证内容:

  1. Alice 创建游戏,此时游戏状态为 0,Alice 还提交了 Hash[A 选择],任何人都可以参与此游戏。
  2. 无人加入 Alice 的游戏,1000 个区块后,输出 Cell 的 lock 只能填入 Alice 的 lock hash,游戏金额返还给 Alice,本局游戏消失。
  3. Bob 加入 Alice 的游戏,输入了等于游戏金额的 CKB,并且提交了自己的「剪刀、石头、布」的选择,等待最终开奖。
  4. Alice 在 Bob 完成交易的 1000 个区块后,仍不提交 [A 选择],输出 Cell 的 lock 只能填入 Bob 的 lock hash,游戏金额全部给到 Bob,本局游戏消失。
  5. Alice 在规定时间内,提交了 [A 选择],若两次 Hash[A 选择] 不相等,则交易无法通过验证。若 Hash[A 选择] 通过验证,再依次验证输出 Cell 的 Capacity 和 Lock 是否符合游戏规则。如果全部通过验证,则打包交易,本局游戏消失。

要知道,这是一个任何人都可以创建的「剪刀、石头、布」游戏,任何人都可以加入任何一个已经存在且目前还没有结束的「剪刀、石头、布」游戏。

没有中心化的风险,你不用担心你的 CKB 被人盗走、骗走,你只需要愉快地在这个游戏中玩耍。

更多系列视频,欢迎登陆 bilibili CKB 茶话会 了解更多。

4 Likes

优秀,很棒

不晓得为什么,图片都不能显示,都提示此图片来自微信公众号平台。

53

我自己这边都可以看见图片,就以为没有问题,现在改完了,还有图片显示不正常么?

不太理解这两句的前后关系,Dummy lock是个专有名词/术语吗?

dummy lock 代表的就是一个任何人都可以解锁的 cell。这意味此时这个 cell 里面的 lock script 的 lock hash 是空的,任何人都可以解锁,那是不是意味着就可以把这个 cell 偷走了呢?实则不然,我们会在 type script 里面对解锁的过程和逻辑进行限制,虽然任何人都可以解锁,但是需要按照 type script 内设定的逻辑才能真正解锁。

我最早看到这个 dummy lock,应该是在罗导的这篇文章中,dummy lock 又叫做 always_success_lock_hash

1 Like

谢谢 了解啦
我一开始想确认dummy script 是不是个特殊字段
现在理解了

ckb目前所有基础的字段都在RFC19里面吗?


感恩史迪仔的中译
https://ckb.dev/topic/14/rfcs-0019-data-structures