引介 | FunFair:状态通道合约的参考实现,Part-3
引介 | FunFair:状态通道合约的参考实现,Part-1
其他解决方案
不过,这不是解决争议的唯一方案。以下三点会导致通道参与者遭到惩罚(参见文末的注释)。
超时
如果对手方没有在合理的时间范围内对争议作出响应,发起方(实际上可以是任何人)可以在状态通道上宣告超时。参与者必须要能够在自己的状态通道上做出响应——我们称之为 “保持在线”。该调用本身是非常简单的:
这里,我们只需验证响应时间是否过长 (参见文末注释),并根据发起争议时所用状态的余额来关闭这条通道,然后惩罚未响应的对手方,并将通道关闭。我们确实需要输入状态本身,因为链上只存储了该状态的哈希,而且还是未经验证过的哈希。
使用后续状态发起挑战
状态通道上有一个概念叫作即时确定性——指的是一旦某个状态经过了参与双方的签署,就无法再更改了——无需像在传统区块链上那样等待区块得到确认。
这就意味着回滚到某个状态是违反协议的。假设你处于状态 #10 ,然后对状态 #5 发起争议(可能你处于一个更好的位置), 该操作是不被允许的,而且一下子就会被验证出来。
争议发起方的对手方只需调用
输入任意一个经过双方联合签署的后续状态,就可以证明争议发起方已违反协议。我们只需验证签名的有效性,核实该状态在这条通道中是有效的,以及该状态的 nonce 大于用来发起争议的状态的 nonce 即可。
如果争议发起方真的违反了协议,我们可以关闭通道,并予以惩罚。要注意的是,这可能对争议发起方尤为不利,因为其对手方可能会选择任意一个由双方联合签署的后续状态来发起挑战——甚至可以在惩罚措施还没有施行之前,找到一个自己处于最佳位置的后续状态。因此,不要对任何一个旧状态提出争议。
使用不同的 action 发起挑战
最后这个有点微妙。如果我签署了一个状态转换,并将它连同 Action A 发送给你,然后再使用另一个状态相同的 Action B 在链上发起争议,那么问题就来了。
因为你很可能已经进行了下一步操作,但是我还没将我的签名发送给你,因此你不能以之作为后续争议的解决基础。这就意味着,我可以看到你接下来的 action ,然后利用链上争议来改变我的操作。
这与随机数发生器的运作方式关联很大(参见下文),而且能够让参与者预先知道随机数发生器的结果。
因此,我们认为签署同一个状态下的多个 action 是违反协议的。如果对手方看到了争议,并意识到有另一个被签署的 action ,就可以调用
这里,除了常规检查之外,我们还会检查该 action 的哈希是否与我们用来发起争议的 action 的哈希不同。我们还需要核实 Action 是有效的(例如,轮到他们进行操作了),而且会实现一个有效的状态转换(例如,资金仍有结余)——我们通过运行状态机来推进状态,看是否会出现报错。我们之前已经有了一个单独的验证 Action 函数,不过这是它剩下的唯一一个用例,因此只测试推进状态会更加清楚。
如果这些测试都通过了,我们会关闭通道并惩罚争议发起方。
极端案例
最后,我们需要处理几个极端案例。
无 Action 争议
比起极端案例,这更像是一种优化。如果我已经发起了一个 action ,而你又将你对新状态的签名发送了给我,但是没有再发起一个 action ,我可以用你对该状态的签名发起一个争议,而不一定要在链上实现整个状态转换。
但是,要注意的是,如果你以这种方式发起了一个争议,然后下一个可以发起 action 的参与方只能是你的话,那么麻烦就来了,因为你不能解决自己的争议。
同意并关闭通道最后这一点是必要的。鉴于我们对可终止性(finalisability)的定义,你不能强迫参与者在一个可终止的状态下继续发起 action 。这就好比是在玩家想要停止并关闭通道之时,强迫他们重新再玩一局。
因此,我们有了另一种 resolveDisputeWithAction()
——如果该争议采取的状态具有可终止性,对手方可以直接同意该状态通道,并要求以可确定状态关闭这条通道。争议发起者已经签署了可终止状态,不需要再签署关闭通道请求。对手方要调用:
输入一个已签署过的 “End Channel” 类 action (这里用现成的 action 范例会比较清楚)和状态。这两个输入都要经受常规检查,如果状态确实具有可终止性的话,状态通道就可以在不做出惩罚的情况下正常关闭。
处理资金
为状态通道提供资金
如上文所述,openChannel()
方法是针对内部的,并且认为资金已锁定在合约内。这可以通过很多方法来实现。由于我们的用例是通过 ERC20 代币来实现的,我们现有的实现就利用了这一点。这就意味着我们可以在代币合约中新增多签名功能——我们也已经这么做了。在 FUNTokenController.sol
中,你会看到 multiSigTokenTransferAndContractCall()
函数。这个函数可以让参与双方联合签署信息,从各自的账户中将代币转移到第三方账户中,然后对地址上的合约调用 afterMultiSigTransfer()
方法。
然后,我们从 DisputableStateChannel
中衍生出了一个新的类型,有一个很抓人眼球的名称叫作 TokenMultiSigStateChannel
,使用的是 afterMultiSigTransfer()
方法。该代码假定代币转移是都是通过该方法调用实现的,因此该代码是被严格锁定的,只有白名单内的地址才能调用它。
最后是代码中最复杂的一部分。代币合约中的多签名转移方法需要自己的一套数据,以及开启通道所需的数据包。我们需要手动验证数据的一致性——如果参与者的余额与代币合约所转移的余额不同,我们就无法开启状态通道。虽然有点丑,但是将代码拆成这样就代表只有这个合约需要了解执行多签转账所需的数据结构。
一旦经过验证之后,该函数就可以在其父类上调用 openChannel()
方法,为状态通道注入资金,令其成功开启。
为状态通道提供资金的方法有很多,这不是其中最简单或是最常用的一种。但它有一个很明显的优点,就是可以通过一个交易来开启通道。我们依旧相信,为用户节省成本和时间是一件好事。
将资金从状态通道中释放出来
成功调用 closeChannel()
,或是通过解决争议强制关闭通道之后,我们需要将资金归还给参与者。
基本的 State Channel 类确实知道每个参与方的余额都在发生变化,但是并不知道这些资金的具体情况(代币、以太币等等)。它不一定要知道正在变化中的余额是在进行正确的资金分配。
从中衍生而来的 Disputable State Channel 类可以分辨是否需要惩罚某位参与者,但是不一定要知道具体的惩罚措施是什么。
因此,我们将这个逻辑放到代币多签名状态通道合约中来看。合约知道在这个情况下,资金就是代币,也知道代币合约的地址。然后,我们需要做出决定。我们(FunFair)对如何行使惩罚措施有具体的规则。此外,我们还有一个规定,就是要拿出一些资金奖励给作出贡献的第三方(游戏开发者、成员组织等等)。这种逻辑似乎比较适合放在状态机中,这部分代码对我们来说是最特殊的。
我们定义了一个 distributeFunds()
方法,其原型如下:
位于基础的状态通道合约内,但是并未执行。我们会在代 Multi-Sig State Channel 合约内执行该代码,执行过程如下:
在状态机上调用 getPayouts()
,输入状态通道的余额、状态机的最终状态,以及是否需要对任何人进行惩罚接收付款金额和地址的数组 验证这些付款地址的余额总和是否等于整条通道的余额 付款
FunFair 的状态机——命运机
FateMachine.sol
在我们的系统中是状态机的完整实现——通过提供一个整洁的游戏规则界面,我们还展示了一种可以让该代码支持多种游戏的方法。该命运机执行的是一类基于随机数生成器的游戏,让玩家在预先制定好的规则下进行游戏。轮盘赌、21 点、百家乐等赌场游戏都属于这一类,包括大多数老虎机在内。(玩家)——我赌 6 点,押 100 个代币 (庄家)——好的!(庄家不需要下注) (玩家)——我的提交值是 “0x......” (庄家)——我的提交值是 “0x......” (玩家)——我的显示值是 “0x......” (庄家)——我的显示值是 “0x......”
玩家和庄家预先提交各自列表中最后一个哈希值 (玩家)——我赌 6 点,押 100 个代币 (庄家)——好的 (玩家)——我的显示值是“0x......” (庄家)——我的显示值是“0x......”
(玩家)我赌 6 点,押 100 代币,这是庄家的签名,表示他们愿意接受参与游戏,我的显示值是 “0x……” (庄家)我的显示值是 “0x……”
状态转换和游戏规则
advanceState()
内。是否轮到该玩家发起操作 他们所显示的随机数生成器种子经过哈希运算后是否与列表中的上一个哈希值一致 如果是一轮游戏中发起的第一个 action ,上面是否有跟注的庄家的有效签名 赌注是否违反游戏规则 玩家是否有足够的资金下注 庄家是否有足够的资金跟注
确定最终结果
isEndOfRound
标记(即 isFinalisable
(可终止)),以便确定这条状态通道是否可以彻底终止。getPayouts()
的实现就是其中一种方法。我们展示了两种非对称惩罚函数法,并演示了如何向第三方支付佣金。这个代码实际上会复杂得多。为了保持简单化,我这里没有将完整的实现写出来。你会注意到,命运机状态中还嵌有一个 AdditionalData
结构,这是我还没有提到的。在我们实际的代码中, AdditionalData
里都是状态通道开启过程中积累下来的数据,以便在通道关闭之时进行更复杂的计算(例如,能够使用总的下注金额或下注次数进行计算)。这部分代码针对性很强,因此我不在此公布。游戏规则合约
游戏规则合约示例
(未完)
(文内提供了许多超链接,请点击阅读原文到 EthFans 网站上获取)
原文链接:
https://funfair.io/a-reference-implementation-of-state-channel-contracts/
作者: FunFair
翻译&校对: 闵敏 & 阿剑