上一次我们介绍了状态通道应用程序的概念,展示了如何向状态通道转换添加自定义逻辑。在本周的文章中,我们将通过查看一个真实的状态通道应用程序来深入研究这个问题。
我们将要研究的应用程序是最流行的,令人兴奋的剪刀石头布(RPS)游戏。这是一个有趣的例子,因为它很容易理解,同时还涉及一些丰富的状态演化逻辑,以及在许多状态通道应用程序中可能很重要的提交公开模式。为了让您快速了解该应用程序,这里有一个示例的gif:
状态与奖励措施
理解app的第一步是理解底层状态。一轮RPS包含4种不同的状态:
游戏开始时两个玩家都处于Resting状态。然后Alice通过签署一个提议状态来提出一个回合,并将其发送给Bob。该状态包括他们将为之奋斗的赌注,以及对Alice选择出拳(在本例中为“石头”)的预先承诺。然后Bob可以选择是通过返回到Resting状态来拒绝游戏,或者通过签署accept状态来接受游戏。如果他接受了,他会详细说明他准备还击的选择。最后一步是由Alice来Reveal她的选择,并以此作为胜利者,签署展示步骤。正如你在下面的app definition contract部分中看到的,每个阶段的转换规则确保玩家按照规则进行游戏(例如Accept->Reveal步骤只有在显示的移动与预承诺匹配时才有效,依此类推)。
在提交展示阶段,我们必须谨慎对待奖励措施,如果状态通道互动中断了(例如,由于一方停止响应),那么当前结果将在链上最终确定。因此我们必须确保在每个步骤上都能在链上获得的结果不会激励轮到其停止比赛的一方。
RPS中的风险点是步骤展示。当Alice发送Accept状态时,Bob就知道自己是否赢了。我们需要确保即使他输了,他仍然有继续下去的动力。我们的方法是将Accept状态的结果设置为Bob已经输掉了这一轮。这意味着停下来对鲍勃没有好处,所以他最好继续下去。
App definition contract
一旦我们设计了基本的状态和激励机制,下一步就是将智能合约中的逻辑编码成一种可以被状态通道裁决者读取的形式。为此,合约必须符合force-move app interface界面:
interface ForceMoveApp {
struct VariablePart {
bytes outcome;
bytes appData;
}
function validTransition(
VariablePart calldata a,
VariablePart calldata b,
uint256 turnNumB,
uint256 nParticipants
) external pure returns (bool);
}
目光敏锐的你们可能已经注意到,这与我们上周讨论的接口略有不同,当时我们有validTransition(状态s1,states2)。您在上面看到的版本是一个gas优化版本,我们去掉了一些不重要的状态部分,并消除了turnNum和nParticipants的重复。
编写应用程序合约的第一部分是定义状态格式,以及将appData字节反序列化为该格式的函数:
enum PositionType
enum Weapon
struct RPSData {
PositionType positionType;
uint256 stake; // the amount each player contributes to the round
bytes32 preCommit; // keccak(aWeapon, salt)
Weapon aWeapon; // player a's weapon
Weapon bWeapon; // player b's weapon
bytes32 salt;
}
function appData(bytes memory appDataBytes) internal pure returns (RPSData memory) {
return abi.decode(appDataBytes, (RPSData));
}
虽然不是所有的状态都使用所有的属性,但是我们在这里采用了一种简单的方法,即在结构中包含所有属性,并且在未使用时将它们保留为空。我们正在使用solidity的新功能,但不再进行实验性的abi-encoding函数,这使处理状态通道状态比以前更好了!
最后我们定义了validTransition函数本身:
function validTransition(
VariablePart memory fromPart,
VariablePart memory toPart,
uint256, /* turnNumB */
uint256 /* nParticipants */
) public pure returns (bool) {
// ... body omitted ...
}
这里的完整代码相当简单,但是太长了,无法在此处逐行进行。如果您有兴趣,请查看!https://github.com/statechannels/monorepo/blob/master/packages/rps/contracts/RockPaperScissors.sol#L51
使用状态通道钱包
现在已经对应用程序定义合约进行了排序,接下来的任务是制作一个能够允许用户运行状态通道应用程序的Web应用程序。从表面上看,这似乎很复杂。以下是一些需要涵盖的内容:
1. 提供用户界面以允许用户选择他们的动作
2. 根据用户的选择制定适当的状态
3. 签署这些状态并将其发送给对手
4. 存储已发送或已接收的任何状态
5. 运行协议以打开/资助/关闭/拒绝通道
6. 运行协议以发起挑战(如果需要)
7. 监控链并应对挑战
让我们只关注上面的“signing states”部分,以了解这里的挑战。签名数据基本上是每个以太坊应用程序的一个常见部分,并且有一个很好的模式来完成这一点:您将数据/交易发送到您的eth钱包(例如MetaMask),它会提示用户获得许可,然后进行签名。不难看出,这对状态通道没有多大用处。状态通道的全部要点是能够快速进行事务处理,每秒进行多次更新。如果每个签名都必须通过用户单击来批准,则此方法将行不通。此外如果没有定制的工具,用户很难理解数据块对他们签名是否安全。
看起来,为了运行状态通道应用程序,用户需要一个能够解释状态通道状态的钱包。在某种程度上,钱包可以代表用户自动对状态进行签名。将来普通的Eth钱包将具有这些功能,但是暂时我们需要一个特殊的状态通道钱包来处理它们。
除了自动签名交易外,该应用程序还可以将许多特定于状态通道的职责转移到状态通道钱包中。在上面的列表中,状态通道钱包可以使用3-7个,由应用程序负责UI并设计与应用程序相关的状态。
状态通道钱包存储用于签署状态通道更新的密钥,但在需要进行区块链交易时可以到达以太坊钱包。用户授予状态通道钱包权限,以代表他们签署给定应用程序的状态。此处的权限可以是细粒度的,例如允许用户限制总预算/支出率等。
该应用程序负责将消息中继给该通道中的其他参与者。这为应用程序开发人员提供了灵活性,使他们可以选择满足应用程序性能要求的传输层。这也打开了将状态通道状态包括在现有消息中的可能性,例如作为http请求上的自定义标头。
在RPS演示应用中,状态通道钱包被嵌入到iframe的页面中,这意味着用户无需单独安装任何软件。这种方法在安全性和可靠性方面有一些缺点,因为状态通道签名密钥和状态都存储在本地存储中,但是这是一个合理的折衷方案,可以实现良好的入门流程。随着用户开始更频繁地使用状态通道,他们可能会想要安装专用的状态通道钱包应用程序。
应用程序设计模式
即使使用通道钱包管理签名,存储和状态通道协议,我们仍然需要谨慎地在应用程序本身中跟踪状态通道的状态。这是有道理的:通道钱包只能检查状态是否是有效的转换;它不知道应用程序数据的含义,也不知道如何从给定的位置构造新的状态。
一种有用的模式似乎是在应用程序本身内部缓存状态通道的当前状态。然后在很大程度上基于通道的当前状态,以状态机样式设计应用程序是很自然的。对于常见的应用程序(例如付款),合约开发人员可以提供定制的“渠道应用程序客户端”,以免除应用程序开发人员的这一责任,并提供更多的“解决方案”开发体验。
本文中的RPS应用程序基于以下状态机。您会注意到,我们花了相当大的努力来掩盖状态图中存在的玩家之间的潜在差异,以便为玩家提供统一的游戏体验:
该图非常复杂,但希望也很容易解释。您应该了解应用程序开发人员如何与状态通道钱包进行交互,以及如何设计一个处理用户输入和对手触发的状态通道更新的应用程序。
下次
在下一篇文章中,我们将回到核心状态通道协议,研究仲裁器的优化版本,以及如何使用称为TLA +的正式验证工具在协议层中查找和修复安全漏洞。
--------------------------------------------------
原文作者:由Magmo团队负责人@ Consensys R&D的Tom Close撰写
译者:链三丰
译文出处:http://bitoken.world
---------------------------------------------------