引介 | FunFair:状态通道合约的参考实现,Part-1
引言
过去的两年半以来,FunFair 一直在开发一款完整的端到端区块链产品,以状态通道为核心。该产品的首个原型实现于 2017 年 6 月部署完成。2018 年 8 月,我们所发布的基于该技术的一款(有可能是全网第一款)商业应用掀起了热潮。
我们打算从本文开始披露我们的核心技术的具体运作方式,将更多技术细节公诸于众,最后实现开源。
该项目最核心的部分是,包含了我们独有的状态通道实现的以太坊 Solidity 合约。我们把这个状态通道实现称为命运合约(Fate Channel),因为它内嵌了具有确定性但是无法预测的随机数生成器(RNG)。就当前部署的版本而言,我们编写的代码既复杂,又混乱难懂;我们已经花一些时间重写了代码,以可读性为首要考量对其进行了重构,而且还有一个额外的好处,就是能更加清晰地展现平台的扩展方式——我们会在本文中提到这段代码。
首先,我们要说清楚的是,目前部署的是整个状态通道概念实现的 1.0 版本。该实现可以为参与双方开设双向的状态通道,具有完备的功能,而且允许参与方提出争议,不过我们还没有开始涉足虚拟通道和中心模型等活跃的研究领域。我们的首要考量是对外发布具有实用性的独立实现;不过,在重构代码的过程中我们会尽可能将其一般化。这样一来,如何在该实现中集成 Guardians/ Watchtowers(如 PISA )之类的平行研究就变得一目了然。
我们还发现,其他状态通道的开发者已经开始相互合作,尝试将该技术的一些方面标准化。本文有两个目的——一方面,与任意标准进行交叉对照,看看我们是否应该采用它们;另一方面,看看本文所述概念是否应该纳入这些标准之内。
最后要强调的是,文中涉及的代码是一个参考实现,尚未经历开放测试——我们会在适当的时候这么做,并将该代码应用到现有产品上。目前先不要将该代码投入实际应用!我们之后还会会更新并改进该代码,等准备就绪之时再发布出来。
状态通道概述
在阅读本文之前,最好对状态通道的概念有所了解。有很多非常优秀的论文和讨论值得一读。我们对状态通道代码的定义是下面这个函数的实现:
advanceState(state, action) => newState
这个函数的意思是,取一个状态,对这个状态进行某项操作,然后返回一个新的状态。整个过程不会产生副作用(side-effect)。
状态通道是一个允许两方乃至多方通过状态机来更新共享状态的协议;该协议可以确保状态更新通过协同的方式发生在链下,同时又因为得到相同的安全属性支持而仿佛是上链交易一般。
在传统的设置中,多个参与方将资金(意思包含 “一切有价值的东西”)提交至链上的状态通道合约。在链下交互的过程中会不断发生状态更新,等到将来的某个时间点,就最终状态达成共识之后,该最终状态会被提交到链上,将链上的资金释放回参与方手中。
在状态通道实际实现的开发过程中,我们的主要精力还是放在如何解决故障上面。状态通道需要参与者在线合作——如果出现掉线或有争议的情况该怎么办?我们在本文中深入探讨了 Dispute(争议)和 Dispute Resolution(争议解决)。要注意的是,“Dispute” 一词在研究者社区中本身就存在争议——在适当的时候会用更合适的词来替代它!
目前,很多关于虚拟通道(通道内的通道)等技术的研究正在进行之中,这意味着可以通过更加通用和长远的方式一次性将更新后的状态提交到链上,利用这些来自链下的状态更新可以开启或关闭单条状态通道。本文不会讨论具体方法,仅以此作为对基本概念的延伸。
其它研究还包括如何解除状态通道对——再强调一遍,本文对此不做讨论,不过我会谈一谈如何将状态通道技术引入我们的系统。
代码
本文中提到的代码可以在这条链接中找到。本文的结尾处放了关于代码的一些说明——还有另外一些是关于本文剩余内容的说明。
开启状态通道(第一部分)
假设有两个参与者想要开启一个状态通道。然后他们要怎么做呢?满足两个条件的状态通道才可被视为开启:
相关资金已被锁定,以及 双方都签署了开启状态通道的承诺(commitment)
openChannel()
函数来处理第二点,并假定第一点已经实现了,为资金锁定实现带来更多的可能性——这就是这个函数 internal
的原因。下文将讲述如何为状态通道提供资金的实现。StateChannel.sol
中,我们的 openChannel()
函数如下所示:function openStateChannel(bytes memory packedOpenChannelData) internal returns (bool)
OpenChannelData
包含定义和开启状态通道所必需的一切信息,而开启通道调用的首要功能是验证这部分信息是否有意义,然后在链上存储一个开启通道的永久记录。要注意的是,双方必须预先签署过该数据。channelID
是用来鉴别状态通道的唯一标识。状态通道合约支持多条并行通道——另一种方法是每开启一个通道都部署一个新的合约,但是部署合约所需消耗的 gas 成本很高。为了增强普适性,双方可以随机选择一个从未用过的 ID ,而不是尝试在合约上做文章。状态(第一部分)
开启通道(第二部分)
生成初始状态
OpenChannelData
并返回初始状态的函数。该函数:会根据 OpenChannelData
中提供的数据来设置通道 ID 和初始余额会设置 address(this)
的通道地址会将 nonce 设置成零 调用状态机去获取由 ABI Encoder 打包的初始状态
OpenChannelData
中也会包含上述信息,并将它们传递给状态机——这些都被状态机用作初始化数据,用来生成初始状态。我们后面详细讲解状态机之时,会再进行深入的介绍。开启通道(第三部分)
OpenChannelData
中提供的哈希值进行比较,并验证对该哈希值的签名。OpenChannelData
的哈希值。这是一个很有趣的优化。在早期开发阶段,我们会将整个 OpenChannelData
数据都存储在链上——我们需要几乎一切数据(除了时间戳和状态机初始化数据)。然而, SSTORE
成本很高,结构又很庞大。最终发现,可以只存储 OpenChannelData
的哈希值,既可以节约成本,又可以降低复杂程度。对于后续需要(几乎全部)数据的合约调用来说,完整的 OpenChannelData
会作为一个参数传入该调用。然后,我们所要做的就是核实该数据的哈希值是否与已存储的哈希值相匹配,确保可以一切照常。你会在每一个外部可调用函数的开头看到执行该操作的代码。(未完)
(文内提供了许多超链接,请点击阅读原文到 EthFans 网站上获取)
原文链接:
https://funfair.io/a-reference-implementation-of-state-channel-contracts/
作者: FunFair
翻译&校对: 闵敏 & 阿剑
你可能还喜欢:
科普 | 菜鸟学习状态通道,Part-4:账本通道和虚拟通道
引介 | Counterfactual 项目:广义的以太坊状态通道