注:本篇文章是上一篇《构建一个通用的Plasma》的后续。
我们近期发表了一篇文章,描述我们构建的一个全新的通用plasma架构。本篇我们将讨论这个架构的细节,并会用python实例来演示这些细节。
简介
我们在上一篇文章中讲了以下几个要点:
要掌握plasma的每一项最新研究成果是相当有挑战性的。但凡对Plasma有过一定了解的人都知道对plasma,不同的团队有不同的方案。每种方案都有自己不同的取舍。要掌握这些层出不穷的最新成果不全职投入几乎不可能。
绝大多数DAPP开发者只是希望能便捷快速地开发DAPP。开发者需要好的开发工具,带详细开发文档的程序库。没人愿意为了开发一个简单的APP而从无到有地再开发一遍区块链系统。
因此,我们开始着手开发,希望让用户不需要精通plasma就能开发区块链应用程序,经过努力,我们最终开发出了一个通用的plasma链。
第二层:关于状态的声明
所谓“第二层”,其核心思想是用户要能用区块链主链之外的数据(也就是链下数据)来保证链上资产。比如,你可以通过一系列链下数据的操作从以太坊主链上的一个智能合约中提取一笔数字资产,而整个过程无需经过以太坊就能完成。
但是,链下数据要发挥作用最终还是要使交易在链上完成。当用户“声明”要把链下数据提交到区块链上(的智能合约)时这个过程被触发。在上面提取资产的案例中,这个链上智能合约就包含了客户要提取的那笔数字资产。这个“声明”的内容类似“我签名承认这些信息,我被允许提取资产X”。
但如果用户在提交这个“声明”后,又对另一个“声明”进行签名,声称要把资产X也发给另外一个用户,那就会使前一个提取资产的声明失效。为了防止这类问题,我们对每个声明都赋予了一段“争议期”(dispute period)。在争议期内,任何人都可以对此声明发起异议。争议期过后则该声明被视作生效。
再回到Plasma。Plasma链由区块组成,每个区块都包含若干交易信息。当一个新的区块生成时,一个对该区块的“提交”(commitment)就被加密,并发布到主链上。这些经过加密的提交是用来证明区块中交易信息的。每个提交是一个merkle树的根,这个merkle树的根由区块中所有的状态更新生成。
以太坊上有一个plasma智能合约,它记录了plasma区块被“提交”的顺序,并防止这些提交被改写。因此这些区块的提交信息也具备时间意义。当用户想引用plasma链上已经发生的交易时,他必须引用对应的区块号。比如,用户可以作这样的声明:“区块Y中有一笔交易X,它给了我资产Z”。提交信息所包含的时间意义非常重要,尤其在发生争端时,你需要知道某件事发生在哪件事之前,哪件事之后。
抽象链下数据主义
通过前面的描述,我们看到第二层主要是处理链下数据,用链下数据最终决定链上交易。在现有的应用中,链下数据主要被用来确定资产的归属---谁拥有什么,归属权是否发生了变更。
爱丽丝会将一笔资产存进以太坊上的一个智能合约,她是这笔资产的主人。她会对一个链下信息签名,该信息声明把这笔资产转让给鲍勃。之后,鲍勃便可以用这个签名信息从以太坊上取走这笔资产。
我们也可以做比归属权转让更复杂的事情。比如爱丽丝往钱包里存了一个加密猫(CryptoKitty)并对一系列声明进行签名。这些声明的目的是要改变这只猫的毛色。在她对这一系列声明中的最后一个签完名之后,必须把这个毛色改变的声明提交到以太坊。
对爱丽丝提交的这个声明,以太坊上的智能合约必须要有一种方法理解它。要理解这一系列声明中的每个声明,每个状态变化,这个plasma合约在逻辑上都得进行相应的变化。在旧式的Plasma架构中,每当要增加一个新的功能时,用户都不得不重新开发部署一个新的plasma合约,然后把老合约中的所有资产全部转移到新合约。这个过程有风险而且麻烦,使得合约升级很困难。
Predicate合约:让Plasma派上用场
只要用户想给现有的合约增加新功能就会碰到上面我们提到的合约升级问题。这是Plasma链的设计必须要考虑的问题。经过一系列脑力风暴,我们找到了一个新的途径,无需改变Plasma链上的合约就能给合约增加新的功能。
到目前为止,我们所讨论的这些问题都有个共性---链下数据在发生争议(比如用户向以太坊提交了一个虚假的声明)时会被采用。所谓的争议有这些情况:对声明产生争议,这意味着声明所描述的某个事件状态已经过时;对归属权产生争议,这意味着用户发表归属权转移的声明后又把归属权再次转给了其他人;对上例中提到的改变猫毛色出现了争议,这意味着猫的毛色已经发生了改变。
我们主要的突破在于当争议发生时,对争议的裁决不需要在原合约中处理,而可以另外编写智能合约来判定该争议是否有效。当用户想增加新的功能时,也可以另外再写一个合约来实现这个新功能,而让原合约引用这个新功能就好。我们把这些额外写的合约统称为“predicate”。
这样一来,用户就很容易添加新的功能了。不过,当用户为新功能编写了predicate合约后需要明确自己编写的predicate合约所对应的功能。比如在上例中,如果有一个predicate合约是判定关于猫毛色改变的声明是否存在争议的,则这个predicate合约就不能处理关于猫眼睛颜色改变的争议。因此用户要准确知道每个predicate合约是具体干什么的。
怎么办?很简单,我们只需要增加一个状态,表明哪个predicate是用来做什么的就行。用在上面那个猫毛色改变的例子中,就可以表述为“我要把这只猫的毛色变为蓝色,如果有争议,则该争议由位于地址(0x123)的predicate合约来处理”。
当用户想在plasma链上部署新功能时,可以把predicate合约部署在以太坊上,部署的地址可以任意。实际上predicate合约的功能不仅是处理争议(我们后面会进一步论述)。这里要指出的是,当用户要编写一个Predicate合约时,他实际上要实现一套标准接口定义的函数。
Predicate实操
状态对象(State Object)
现在我们就来一步步深入了解Predicate的实操细节。“状态对象”(state object)是我们Plasma链的构建模块。一个状态对象包含两个属性:
predicateAddress:链上地址。它被用来控制状态对象。
parameters:数据块。它用来描述状态对象。
Plasma状态对象的结构
状态对象实际上是一种数字资产,它是一种广义的Plasma Cash上不可互换(non-fungible)的代币。它和Plasma Cash上的代币类似:每一个Plasma Cash代币有一个coinID,每一个状态对象也有一个stateID。
stateID根据该状态对象被存入plasma链的先后顺序进行分配。对parameters和predicate怎么定义就没有特定的规则。每一个plasma区块都包含若干“状态更新”(state update)。每一个状态更新都根据一个stateID定义一个状态对象。
在区块0提交状态更新:在区块0,爱丽丝拥有ID为0的状态对象
如果我们使用的Cash变量取值为一个范围,那么状态对象的ID取值也将是一个范围,基于其ID定义的状态更新取值同样也是一个范围。下例为一个状态更新的定义。
stateUpdate = {
start: uint,
end: uint,
plasmaBlockNumber: uint,
stateObject: stateObject
}
Plasma链在以太坊上有个智能合约,当plasma链的用户要向以太坊上提交区块的哈希值时,就会向这个智能合约提交。这个智能合约有个函数verifyUpdate(update: stateUpdate, updateWitness:bytes[]) -> bool
这个函数会检查一个状态更新所对应的Merkle树证明(updateWitness)。
Predicate接口
要编写一个Predicate合约,用户需要定义一套标准的接口函数。
Plasma合约所做的最重要的事情就是判定状态更新是否有效。尤其要防止对区块有完全控制权的操作者偷偷对一个状态更新做手脚:比如把某个状态对象的所有者像下面这样定义为他自己,这是盗窃:
stateObject.parameters.owner == operator
为了实现这个功能,我们引入“状态否决”(state deprecation)的概念。对一个stateID,我们说它所对应的有效状态是它最早那个未被“否决”的状态。状态否决的过程和比特币中UTXO(Unspent Transaction Output)转变为STXO(Spent Transaction Output)的过程很类似。
这样,即便这个操作者偷偷改变了一个后期的状态更新,将资产的所有者定义为自己(stateObject.parameters.owner == operator),但稍早的一个状态更新(stateObject.parameters.owner == Alice)也已经生效。这时早前的状态更新所定义的该资产所有者(Alice)将会“否决”操作者偷偷改变的这个状态更新。
因此,Predicate最重要的功能是定义了状态可被否决的机制,其接口定义如下:
verifyDeprecation(stateID: uint, update: stateUpdate, deprecationWitness: bytes)
函数verifyDeprecation会检查参数stateID所对应的stateUpdate是否被否决,其返回值为true或false。参数deprecationWitness是predicate合约用来判定状态更新是否被否决而使用的数据,它可为任意数据,比如“update.stateObject.parameters.owner”的签名。这里必须保证只有资产真正的所有者(owner)才能否决一个状态更新。
注意:这个函数并不真正否决一个状态更新。当plasma合约要裁决一个争议时,需要知道一个状态对象是否被否决,这时它就会调用这个函数。
Predicate合约的接口还有另外三个函数,按其重要性由高到低排列如下:
finalizeExit(exit: bytes)
当“exit”得以兑现时,plasma合约就会把与这个“exit”相关的声明所涉及的资产发到predicate合约的地址,然后调用这个函数。
canInitiateExit(stateUpdate: bytes, initiationWitness: bytes) -> bool
这个函数让predicate合约限制只有什么人才有权对一个提交的状态发起声明。比如一个和资产归属权有关的predicate合约就只希望资产的所有人(owner)才有权发起声明。
getAdditionalDisputePeriod(stateUpdate: bytes) -> uint
这个函数让predicate合约增加处理争议的时间。我们建议只在必须增加争议处理时间的情况下(比如处理原子互换问题)才调用这个函数。函数通常返回值为0。
Predicate合约实例:资产归属权Predicate(Ownership Predicate)
我们来看一个实例。最简单的Predicate合约就是资产归属权predicate。这种predicate合约可以变更资产的所有者。
第一步要定义状态对象。这很简单,这个对象的参数只有一个:资产所有者的地址。当一个状态对象调用这个归属权predicate合约时,用法如下:
OwnedByAlice = {
parameters: {
owner: '0xAliceAddress...',
},
predicate: '0xOwnershipPredicateAddress...'
}
这里最重要的是要实现函数“verifyDeprecated”的功能。它的参数为“deprecationWitness”。这个参数包含以下两个信息:
1. state.parameters.owner的签名:这个签名表明资产所有者同意状态更新stateUpdate。
2. 一份表明新的状态更新stateUpdate已被提交到某个后续plasma区块中的证明。
函数verifyDeprecated要检查签名以及相关的Merkle证明,以确保所有这些信息都有效。
否决归属权状态更新需要满足的条件
归纳起来,当一个资产所有者(owner)要否决现有的状态而同意新的状态时他要这么做:
上图展示了一个归属权Predicate合约的工作流程:爱丽丝把stateID 0发给鲍勃,否决区块0的状态。
剩下的函数就非常简单了。函数“canInitiateExit”检查发起声明者是不是资产真正的所有者,函数“finalizeExit”把资产转交给所有者(owner),函数“getAdditionalDisputePeriod”返回0。
下面是一段用Python写的实例代码。我们用Python写是为了简便起见,但如果用Solidity或Vyper写会很容易。
完整的代码实现可参看:https://gist.githubusercontent.com/ben-chain/44dbb78841db2a96cf7145114e81ee7b/raw/5573b3ee8ee37c151dd2942c7b7af9cf18333601/ownership-predicate.py
这个predicate合约描述的是资产的归属权是否可以被转移。我们在这里描述的逻辑在绝大部分都和实际在plasma合约中的实现是一样的。我们还在ETH Dever大会上演示了原型。
未来
我们提出的这个架构是我们对plasma理解的一大进步。这一大步直接从支付通道跳到了广义状态通道。它让用户在无需升级plasma协议的情况下就能向plasma架构中添加新的特性和功能。
未来,我们认为关于predicate的设计会成为plama中一个新的研究领域。现在predicate的研究还处于早期阶段,但未来可能发展出的领域有以下这些:
状态通道(State channels)
基于predicate合约的去中心化交易所(DEX predicates)
反碎片化(Defragmentation)的predicate合约
嵌套式(Nested)plasma (一个predicate合约本身就是一个plasma合约)
P2P和CDP合约
要注意的是,predicate合约不是万能药。它的性能受到plasma本身的限制。我们还要在通用性方面做更多的工作。尽管如此,现在predicate已经很强大了,几乎所有的plasma应用都能用predicate实现,包括不是基于Plasma Cash的应用也可以。
我们认为有希望以此为契机把整个plasma的生态系统都标准化,让任何使用这种状态否决架构的应用都能用predicate合约来处理。
第二层扩展的各种方案说到底就是如何使用链下数据来确认未来的链上状态。无论旧的状态是否会被签名(状态通道),提交(plasma)或其它方式否决,它们最终都能达到同样的效果。我们最终希望开发出一个统一,共享的语言,这个语言能用于处理所有的第二层方案。在我们的憧憬中,未来所有的钱包都能通过一套标准接口与第二层的各种应用进行交互,用户不用再对每个应用单独写一套不同的接口。我们所有的努力都是为了实现互通互操作,所有的应用都能做到互通互操作。
资源连接:
Python模拟:
https://github.com/plasma-group/research/tree/master/gen-plasma
技术标准:
https://pigi.readthedocs.io/en/latest/src/specs/generalized-plasma-state.html
欢迎加入Plasma Contributors电报群:
https://t.me/plasmacontributors
参考链接:https://medium.com/plasma-group/plapps-and-predicates-understanding-the-generalized-plasma-architecture-fc171b25741
网友评论