等离子集团的等离子规格

TLDR:我们为Plasma Cash变体创建了一个规范,并在Node.js和Vyper中实现了它。 本文档涵盖了设计规范,并一路为实现提供参考。 我们的代码支持将新链部署到测试网,其他血浆链及其区块浏览器的链上注册表以及通过命令行钱包进行交易。

介绍

区块链网络作为可扩展性解决方案的愿景已迅速传播。 采用多链交易并行化方法是提高吞吐量的一种有前途的方法……不幸的是,它还带来了重大挑战:

  • 我们不想划分安全性,例如100条链,每条链占总安全性的1%。
  • 分片等高级解决方案很有前途,但尚未准备就绪。

我们需要一种可扩展性的解决方案,该解决方案应:

  • 提供与以太坊主网类似的安全级别,而无需支付数百万美元的采矿费。
  • 可以在现有的以太坊上实现。

我们认为,满足这些标准的最强候选者是链的网络,每个链都通过等离子框架固定到主网。

Plasma是一系列协议,允许个人轻松部署高吞吐量,安全的区块链。 以太坊主链上的智能合约可以确保用户的资金安全,即使“等离子链”行为完全恶意。 这消除了对像侧链这样的可信任钉住机制的需求。 等离子链是非托管的,可以在不牺牲安全性的情况下优先考虑可伸缩性。

我们设想了许多等离子链的未来,使用户可以选择交易的地点。 因此,除了发布我们的等离子链实现之外,我们还创建了PlasmaRegistry.vy 。 该注册表允许新链通过列出其IP / DNS地址,自定义“名称”字符串以及它们的合同地址来加入网络。 注册表合同会验证受信任的部署,因此可以向用户保证,该注册表上的任何合同都可以安全地存入- 即使其操作员是恶意的

等离子链实施的属性

这篇文章详细说明了Plasma Group当前的协议和实现,该协议和实现来自研究领域的最新发展。

我们的规范具有以下属性:

  • 通过大范围硬币的单笔交易,解决了等离子现金中的“固定面额”问题。
  • 块大小随交易数量而不是存款数量而定。
  • 轻量级客户证明以块大小的对数比例缩放,并且自存款以来以块为线性,这使操作员成为系统的唯一(计算)瓶颈。
  • 一种简化的,乐观的退出过程,该过程允许退出仅指定最近的事务,而不是事务及其父级。
  • 链间原子交换,这为去中心化交换协议奠定了基础。
  • 无限的存款能力。

我们的实现遵循上述规范,并提供以下内容:

  • 用Java语言编写的命令行等离子链运算符。
  • 用Javascript和命令行钱包编写的等离子客户端实现。
  • 支持用Vyper编写的ETH和ERC20令牌的智能合约。
  • 集成的JSON RPC,允许客户端下载和验证轻量级客户端证明并进行交易。
  • 等离子操作员主持的区块浏览器。
  • 一个模拟的客户群,生成交易以进行负载测试。
  • 等离子“注册”合同,其中列出了一组经过验证的安全合同和运营商IP地址,供用户浏览。

如果您有兴趣检查协议和代码实现,那么您来对地方了!

但是,在深入探讨之前,有一些免责声明:

  • 我们的血浆实施方案是目前仅适用于testnet的Beta版软件。 目前肯定有严重的错误。
  • 该协议与其他Plasma实现之间的主要区别(在下面解释!)是块结构:Merkle sum树。 这具有显着的好处,但是增加了复杂性。 与侧链相比,等离子体已经很复杂。
  • 代码尚未审核或正式验证,并且未进行任何优化。
  • 尽管运营商是唯一的计算瓶颈,但当今的主要性能限制仍然是带宽。 监管证明要求下载的块数量成线性关系。 我们的代码是每个块的改进,但仍然是线性的。 这个活跃的研究领域尚未准备好实施。
  • 尽管我们的安全机制和退出游戏均已实施和测试,但我们尚未建立自动警卫服务,这意味着必须手动构建挑战和应对措施。

有了它,让我们进入吧! 这篇文章的其余部分将深入探讨我们的规范,代码的用途以及其作用。

目录

  1. 通用定义和数据结构
    一种。 硬币ID分配
    b。 面额
  2. 硬币范围内的交易
    一种。 转账
    b。 键入和未键入的边界
    C。 多发送和传输/事务原子性
    d。 序列化
  3. 块结构规格
    一种。 求和树节点规范
    b。 家长计算
    C。 计算分支范围
    d。 将传输解析为叶子
    e。 分支有效性和隐式NoTx
    F。 原子多发送
  4. 证明结构和检查
    一种。 证明格式
    b。 交易证明
    C。 转移证明
    d。 证明步骤
    e。 快照对象
    F。 存款记录
    G。 交易证明有效性
  5. 合约和退出游戏
    一种。 跟踪存款和退出
    b。 退出游戏与香草血浆现金的关系
    C。 指定区块编号的交易
    d。 每硬币交易的有效性
    e。 合同如何处理交易检查
    F。 挑战立即取消出口
    G。 乐观的退出和包容性挑战
    H。 无效的历史挑战
  6. 未来
    一种。 实施中缺少的部分
    b。 规格中缺少的零件

仓库与架构

我们的Github在MIT许可下提供了我们所有的实现:

  • plasma-chain-operator :旋转您自己的等离子链并部署到testnet。
  • plasma-core :等离子客户端的核心功能-逻辑的轻巧。
  • plasma-node :用于实现CLI的plasma-core Node.js包装器
  • plasma-js-lib :JS帮助程序,用于构建集成了等离子体事务的Web应用程序。
  • plasma-contractsPlasmaChain.vyPlasmaRegistry.vy Vyper合同。
  • plasma-explorer :由操作员托管的块浏览器。
  • plasma-utils :用于建立我们的等离子规格的共享实用程序。
  • plasma :上述组件的集成测试。

这是plasma-core实现的架构:

这是plasma-chain-operator实现的体系结构:

1.一般定义和数据结构

本节将涵盖协议组件的术语和直觉。 这些数据结构通过plasma-utils的库serialization进行编码和解码。 可以在模式中找到每个结构的所有数据结构的逐字节精确二进制表示形式。

硬币ID分配

等离子资产的基本单位表示为硬币。 与标准等离子现金一样,这些硬币也是不可替代的,我们将硬币的索引称为coinID ,即16个字节。 它们是按每个资产的存款顺序(ERC 20 / ETH)分配的。 值得注意的是,链中的所有资产都共享相同的ID空间,即使它们是不同的ERC20或ETH。 这意味着所有资产类别(我们称为tokenTypetoken )中的tokenType共享同一棵树,从而提供最大的压缩率。

我们通过使前4个字节引用硬币的tokenType来实现这一点,接下来的12个字节表示该特定tokenType所有可能的硬币。

例如:第0个tokenType始终是ETH ,因此第一个ETH存款将把硬币0x00000000000000000000000000000000消费权授予存款者。

每次存款收到的硬币总数恰好是(amount of token deposited)/(minimum token denomination)

例如:假设tokenType 1是DAI ,硬币面额是0.1 DAI ,并且第一个存款人发送0.5 DAI 。 这意味着其tokenType == 1 ,因此第一个存款者将接收到从0x00000001000000000000000000000000到包括硬币0x00000001000000000000000000000004coinID

硬币都共享相同的ID空间

面额

实际上,面额将远低于0.1 。 代替直接在合同中存储面额,它为每个tokenType存储一个decimalOffset映射,该映射表示在已存入的ERC20 (或ETH的wei )与已接收的等离子硬币数量之间的小数位偏移。 这些计算可以在智能合约的depositERC20depositETHfinalizeExit函数中找到

decimalOfset 注意: 在此版本中, decimalOfset s硬编码为0,因为我们缺乏对客户端/操作员代码的支持。

2.硬币范围内的交易

转账

事务由指定的block号和Transfer对象的数组组成,这些对象描述事务的每个范围的详细信息。 从plasma-utils的模式中( length s以字节为单位):

我们可以看到, Transaction中的每个Transfer指定一个tokenTypestartendsenderrecipient

键入和未键入的边界

上面要注意的一件事是, start值和end值不是16个字节,就像coinID一样,而是12个字节。这在上面有关存款的部分的上下文中应该是有意义的。 为了获得由传输描述的实际coinID ,我们将token字段的4个字节连接到startend的左侧。 我们通常将12字节版本称为transferuntypedStartuntypedEnd ,并将串联的版本称为typedStarttypedEnd 。 这些值也由串行器公开。

另一个注意事项:在任何传输中,对应的coinID都以start包含和end排除定义。 也就是说,传输的确切coinID[typedStart, typedEnd) 。 例如,可以使用Transfer transfer.token = 0transfer.start = 0transfer.end = 100Transfer发送前100个ETH硬币。 第二个100将具有transfer.start = 100transfer.end = 200

多发送和传输/事务原子性

Transaction模式由一个4字节的block号(仅在该特定等离子块中包含的事务才有效)和一个Transfer对象数组组成。 这意味着一个事务可以描述多个转移,这些转移要么全部自动执行,要么不执行,具体取决于整个交易的包含性和有效性。 这将成为后续发行中分散交换和碎片整理的基础。

序列化

如上所述, plasma-utils为数据结构实现了自定义序列化库。 JSON RPC和智能合约都使用由序列化程序编码的字节数组。

编码非常简单,每个值的固定为模式定义的字节数。

对于涉及可变大小数组的编码,例如包含1个或多个TransferTransaction对象,在元素数量之前加一个字节。 序列化库的测试可以在这里找到。

当前,我们具有以下对象的架构:

  • Transfer
  • UnsignedTransaction
  • Signature
  • SignedTransaction
  • TransferProof
  • TransactionProof

3.块结构规范

Plasma Cash引入的最重要的改进之一是“防光”。以前,等离子构造要求用户下载整个等离子链以确保资金安全。 使用Plasma Cash,他们只需下载与自己资金相关的Merkle树的分支即可。

这是通过引入新的交易有效条件来实现的:特定coinID交易仅在Merkle树中的coinID第一个叶子处有效。 因此,仅下载该分支就足以确信该硬币不存在有效交易。 这种方案的问题在于,交易以这种面额“卡住了”:如果要交易多个硬币,则需要多个交易,每个叶子上有一个。

不幸的是,如果我们将基于范围的交易放到常规Merkle树的分支中,则光线证明将变得不安全。 这是因为拥有一个分支并不能保证其他分支不会相交:

树叶4和6都描述了(3,4)范围内的交易。 拥有一个分支并不能保证另一个分支不存在。

对于常规的Merkle树, 唯一保证没有其他分支相交的方法是全部下载并检查。 但是,这不再是一个可靠的证明!

等离子实现的核心是一个新的块结构 ,以及一个伴随的新交易有效性条件 ,它使我们能够获得基于范围的交易的证明。 块结构称为Merkle 总和树,其中每个哈希旁边是sum值。

新的有效性条件使用特定分支的sum值来计算startend范围。 此计算经过特殊设计,因此两个分支的计算范围 不可能 重叠。 仅当transfer范围在此范围内时, transfer才有效,因此这使我们回到了轻客上!

本节将指定和树的确切规格,范围计算的实际含义以及如何实际构建满足范围计算的树。 有关导致我们制定此规范的研究的更详细的背景和动机,请随时查看此帖子。

我们已经编写了血浆Merkle总和树的两种实现:一种是在数据库中为操作员完成的,另一种是在内存中用于plasma-utils测试的。

求和树节点规范

Merkle总和树中的每个节点为48个字节,如下所示:
[32 byte hash][16 byte sum]
sum的16个字节的长度与coinID相同不是coinID

我们有两个帮助器属性, .hash.sum ,将这两个部分拉出。 例如,对于某些node = 0x1b2e79791f28c27ed669f257397e1deb3e522cf1f27024c161b619d276a25315ffffffffffffffffffffffffffffffff
node.hash == 0x1b2e79791f28c27ed669f257397e1deb3e522cf1f27024c161b619d276a25315node.sum == 0xffffffffffffffffffffffffffffffff

家长计算

在常规的Merkle树中,我们构造了一个散列节点的二叉树,直到单个根节点。 指定求和树格式是定义parent(left, right)计算函数的简单问题,该函数接受两个同级作为参数。 例如,常规的Merkle总和树具有:
parent = function (left, right) { return Sha3(left.concat(right)) }其中Sha3是哈希函数, concat将两个值附加在一起。

要创建Merkle 总和树, parent函数还必须在其子代的.sum值上连接加法运算的结果:

  parent =函数(左,右){ 
返回Sha3(left.concat(right))。concat(left.sum + right.sum)
}

例如,我们可能有

  parent(0xabc ... 0001,0xdef ... 0002)=== 
哈希(0xabc ... 0001.concat(0xdef ... 0002))。concat(0001 + 0002)===
0x123…0003

请注意, parent.hash是对每个sibling.sum和哈希的承诺:我们对二者的全部96个字节进行哈希处理。

计算分支范围

我们使用Merkle总和树的原因是它使我们能够计算分支描述的特定范围,并100%确信不存在其他有效的重叠分支。

我们通过将leftSumrightSum分支上来计算此范围。 将其初始化为0,在每次父计算时,如果包含证明在右侧指定同级,则取rightSum += right.sum ;如果在左侧,则将leftSum += left.sum

然后,分支描述的范围是[leftSum, root.sum — rightSum) 。 请参见以下示例:

分支的Merkle总和计算。

在此示例中,分支6的有效范围是[21+3, 36–5) == [24, 31) 。 注意31–24=7 ,这是叶子6的总和! 类似地,分支5的有效范围是[21, 36-(7+5)) == [21, 24) 。 请注意,它的结束与分支6的开始相同!

如果您使用它,您将发现不可能构造一个Merkle总和树,其中的两个不同分支覆盖相同的范围。 在树的某个级别,总和必须被打破! 继续,尝试通过制作另一个与范围(4.5,6)相交的分支来“欺骗”叶子5或6。 仅填写? s在灰色框中:

您会在树的某个级别看到它总是不可能的:

这就是我们吸引轻客的方式。 我们将分支范围边界称为implicitStartimplicitEnd ,因为它们是根据包含证明“隐式”计算的。 我们有一个plasma-utils检查工具中的分支检查器,它通过calculateRootAndBounds()进行了测试和客户端证明检查:

PG Plasma客户端总和树分支检查器

以及在Vyper中通过

请注意, 键入的范围是开始和结束,即完整的16个字节。

将传输解析为叶子

在常规的Merkle树中,我们通过对“叶子”进行散列来构造节点的底层:

在我们的例子中,我们希望叶子成为交易。 因此,散列很简单,但是对于树的底层,我们仍然需要一个.sum值。

给定一些txA和一个transferA ,总和应该是多少? 事实证明, 不仅transferA.end — transferA.start 。 这样做的原因是,如果传输不接触,则会破坏分支的范围。 我们需要“填充”总和值以弥补此差距,否则root.sum会太小。

有趣的是,这是一个不确定的选择,因为您可以在间隙的右边或左边填充节点。 我们选择了以下“左对齐”方案将叶子解析为块:

传输和解析

我们将最底端的.sumparsedSum该分支的parsedSum ,而TransferProof模式包括一个.parsedSum值,该值用于重建底部节点。

分支有效性和隐式NoTx

因此,由智能合约检查的分支的有效性条件如下: implicitStart <= transfer.typedStart < transfer.typedEnd <= implicitEnd 。 请注意,在“等离子现金流量”中求和树的原始设计中,某些叶子填充有特殊的“ NoTx”事务,以表示未进行交易。 使用这种格式,不进行交易的硬币就是[implicitStart, transfer.typedStart)[transfer.typedEnd, implicitEnd)范围内的那些硬币。 智能合约保证在任何挑战或对出口的响应中都不能使用这些范围内的硬币。

原子多发送

通常(为了支付交易费用和交换费用),交易需要多次转移才能有效(原子地)。 结果是,每个.transfers都需要包含一次有效交易-每个交易都具有与该特定transfer.typedStart.typedEnd有关的有效金额。 但是,对于这些包含项中的每一个,解析到底部.hash的仍然是完整UnsignedTransaction的哈希(而不是单个Transfer

5.证明结构和检查

与传统的区块链系统不同,完整的等离子节点不会存储每笔交易,它们只需要存储与其拥有的资产相关的信息。 这意味着sender必须向recipient 证明发送者实际上拥有给定范围。 完整的证明包含足以保证以太坊链本身不分叉的令牌可以在主链上赎回的所有信息。

证明主要包括交易的包含和不包含,这会更新这些硬币的监管链。 必须根据操作员向主链上的智能合约提交的大宗哈希检查包含根。 通过追踪证明方案中验证的产销监管链,从代币的初始存款到合同直到现在,都可以保证赎回的能力。

plasma-core遵循相对简单的方法来验证传入的交易证明。 本节介绍该方法。

证明格式

历史证明包括一组存款记录和一长串相关Transaction以及相应的TransctionProof

plasma-utils公开了一个static checkTransactionProof(transaction, transactionProof, root)方法,在这里, plasma-core通过调用ProofService来使用该ProofService

交易证明

TransactionProof对象包含检查给定Transaction有效性的所有必要信息。 即,它只是一个TransferProof对象的数组。 根据以上有关原子多重发送的部分,给定的TransactionProof仅在其所有TransferProofs有效时才有效。

转移证明

TransferProofs包含以正确的块号恢复与Transaction中给定Transfer对应的有效分支的包含所需的所有必要信息。 构成:

  • Merkle总和树的实际节点代表分支的完整“ inclusionProof”
  • 叶子的索引,以计算分支跟踪的二进制路径
  • 解析的底部.sum ,如上述求和树规范中所述
  • 该特定发件人的signature

直接来自plasma-utils模式:

请注意, inclusionProof是一个可变长度的数组,其大小取决于树的深度。

证明步骤

验证过程的核心涉及从存入开始将每个证明元素应用于当前的“已验证”状态。 如果任何证明元素未导致有效的状态转换,则我们必须拒绝该证明。

应用每个证明元素的过程很直观; 我们只需按照合同的托管规则在每个区块中应用交易即可。

快照对象

跟踪历史上拥有的范围的方法称为snapshot
很简单,它代表一个区域的已验证所有者:

 { 
typedStart: Number,
typedEnd: Number,
block: Number,
owner: address
}

存款记录

每个收到的范围必须来自相应的存款。
存款记录由其tokenstartenddepositer人和blockNumber

对于每笔存款记录,验证者都必须与以太坊进行仔细核对,以验证所声称的存款确实确实存在,并且在此期间没有退出。

如果是这样,则以每个snapshot.owner为存储者的形式将一个verifiedSnapshots数组初始化为这些存储。

接下来,我们应用所有给定的TransactionProof ,相应地更新verifiedSnapshots 。 对于每个transaction和相应的transactionProof ,验证程序将执行以下步骤:

  1. 验证给定的证明元素有效。 如果不是,则抛出错误。
  2. 对于transaction每次transfer ,请执行以下操作:
    一种。 “拆分”上面在transfer.typedStarttransfer.typedEndimplicitStartimplicitEnd更新的所有快照
    b。 为所有得到的具有等于transaction.blockNumber — 1block verifiedSnapshots.block增加.block编号。blockNumber transaction.blockNumber — 1
    C。 对于介于transfer.starttransfer.end之间的每个拆分snapshot
    一世。 验证snapshot.owner === transfer.from 。 如果不是,则抛出错误。
    ii。 设置snapshot.owner = transfer.sender

TransactionProofs必须以blockNumber递增。

一旦将此操作递归地应用于所有TransactionProof ,客户端可以通过在blockNumber搜索具有等于当前等离子区块的blockNumberowner等于她的地址的所有blockNumber来检查自己现在拥有的新硬币。

交易证明有效性

上面步骤1中的交易有效性检查等同于检查智能合约的有效性条件。 基于上述求和树规范的基本有效性检查如下:

1.检查事务编码格式是否正确。
2.对于每个transfer和相应的transferProof
一种。 检查signature解析为其transfer.sender地址
b。 验证leafIndex的根是否等于该等离子块的根哈希,并且其二进制路径由leafIndex定义
C。 计算分支的implicitStartimplicitEnd ,并验证implicitStart <= transfer.start < transfer.end <= implicitEnd

4.合约和退出游戏

当然,监管链的证明是没有用的,除非它也可以传递给主链以确保资金安全。 接受链上证明的机制是等离子安全模型的核心,被称为“退出游戏”。

当用户希望将其资金从等离子链中移出时,他们会进行“退出”,从而打开了争议期。 在争议期结束时,如果没有未解决的争议,则将钱从主链上的等离子合同发送到出口方。 在争议期内,用户可能会提出“挑战”,声称退出的钱不归退出者合法所有。 上述证明保证了对这些挑战的“响应”始终是可以计算的。

退出游戏的目的是即使在最大对抗性操作者的情况下也能确保资金安全。 特别是,我们必须减轻三种主要攻击:

  • 数据预扣:操作员可以向合同发布根哈希,但不告诉任何人区块的内容。
  • 包括伪造/无效交易 :操作员可以将交易包含在sender不是监管链中的先前recipient的区块中。
  • 审查:某人存入钱后,操作员可以拒绝发布任何汇款的交易。

在所有这些情况下,退出游戏的质询/响应协议可确保这些行为不允许盗窃,最多可进行1个质询,然后进行1个响应。

跟踪存款和退出

存款映射
每次放置新的一组硬币时,合同都会更新一个映射,每个映射都包含一个deposit结构。 从合同中:

请注意,此结构既不包含存放的untypedEnd也不包含tokenType 。 这是因为合同将这些值用作映射映射中的键。 例如,访问访问给定存款的存款人如下: someDepositer: address = self.deposits[tokenType][untypedEnd].depositer

这种选择节省了一些时间,并且还使一些代码更简洁,因为我们不需要存储任何种类的存款ID来引用存款。

可退出范围映射
除了在每次存入self.deposits时添加self.deposits条目外,合同还需要以某种方式跟踪历史出口,以防止在同一范围内出现多个出口。 这有点棘手,因为出口不会像存款那样按顺序发生,并且搜索出口清单会很昂贵。

我们的合同实施了一个固定大小的解决方案,该解决方案存储一个可退出范围的列表,并在出现新退出时更新该列表。 从智能合约:

再次,我们使用带有键tokenTypeuntypedEnd的双嵌套映射,以便我们可以调用self.exitable[tokenType][untpyedEnd].untypedStart来访问范围的开始。 请注意,Vyper对于所有未设置的映射键都返回0,因此我们需要一个isSet bool,以便用户不会通过传递未设置的exitableRange “欺骗”合同。

合同的self.exitable范围会根据通过调用removeFromExitable的帮助函数对finalizeExit成功调用进行拆分和删除。 请注意,甚至无需挑战先前已退出范围内的退出; 他们将永远不会通过checkRangeExitable调用的checkRangeExitable测试。 您可以在此处找到该代码。

退出游戏与香草血浆现金的关系

从本质上讲,我们规范中的退出游戏与原始等离子现金设计非常相似。 通过调用该函数来启动退出

 beginExit(tokenType: uint256, blockNumber: uint256, untypedStart: uint256, untypedEnd: uint256) -> uint256: 

为了对退出提出异议,所有挑战均指定一个特定的coinID ,该coinID被质疑,然后对该特定硬币进行等离子现金风格的挑战游戏。 只需要证明一个硬币就无效即可取消整个出口。

分别给出口和两种类型的可响应质询都提供一个exitIDchallengeID ,它们是通过递增的challengeNonceexitNonce顺序分配的。

指定区块编号的交易

在原始的等离子现金规范中,要求退出者同时指定已退出的交易及其先前的“父”交易,以防止“飞行中”攻击,在这种攻击中,操作员会延迟包括有效交易,并在无效交易之间插入无效交易。

这给我们基于范围的方案带来了一个问题,因为一个事务可能有多个父对象。 例如,如果Alice向Carol发送(50, 100] 0,50 (0, 50] ,而Bob向Carol发送(50, 100] ,则Carol现在可以向Dave发送(0, 100] 。但是,如果Dave想要退出,则(0, 50](50, 100]是父母。

尽管指定多个父代绝对是可行的,但此规范将耗费大量汽油,并且实施起来似乎更加复杂。 因此,我们选择了更简单的替代方案,其中每个交易都指定了其发送者打算进入的“区块”,并且如果包含在另一个区块中则无效。 这解决了飞行中的攻击,这意味着合同不需要交易的父母。 对于那些对该方案的正式文章和安全证明感兴趣的人,值得一看这篇出色的文章。

每硬币交易的有效性

值得一提的是,退出游戏的一个不直观的特性是,某笔交易可能对其范围内的某些硬币“有效”,但对另一些硬币则“无效”。

例如,假设Alice将(0, 100]发送给Bob,Bob又将(50, 100]发送给Carol。Carol不需要验证Alice是全部(0, 100] 0,100 (0, 100]的合法所有者。需要保证爱丽丝拥有(50, 100] ,这是适用于她收据的保管链的一部分。尽管在某种意义上,如果爱丽丝不拥有(0, 50] ,则智能合约可能是“无效的”交易出于硬币出口(50, 100]出口之争的目的, 它并不关心这一点 。只要对收到的硬币的所有权进行了验证,其余的交易就无关紧要。

这是保持轻量级客户证明的大小的非常重要的要求。 如果Carol必须检查完整的(0, 100] ,她可能还必须检查(0, 10000]的重叠父级,然后检查其所有父级,依此类推。这种“级联”效果可能会大大增加大小交易是否相互依赖的证明。

请注意,此属性也适用于原子多重发送,它描述了被交换的多个范围。 如果Alice用1 ETH交换Bob的1 DAI,则Alice有责任在签名前检查Bob拥有1 Dai。 但是,此后,如果Bob然后将1 ETH发送给Carol,则Carol 不必验证 Bob拥有1 DAI,只需验证Alice拥有她发送给Bob的1 ETH。 爱丽丝承担了风险,因此卡罗尔不必这样做。

从智能合约的角度来看,此属性是始终针对出口内特定coinID提交挑战的直接结果。

合同如何处理交易检查

请注意,要完全在退出游戏中使用, Transaction必须通过上面证明部分所述的TransactionProof检查(有效签名,分支边界等)。 此检查在功能的合同级别执行

  def checkTransactionProofAndGetTypedTransfer( 
transactionEncoding:字节[277],
transactionProofEncoding:字节[1749],
transferIndex:int128
)->(
地址,#transfer.to
地址,#transfer.from
uint256,#transfer.start(输入)
uint256,#transfer.end(输入)
uint256#交易plasmaBlockNumber
):

这里重要的注意事项是transferIndex参数。 请记住,一个事务可能包含多个转移,并且每次转移必须包含在树中一次。 但是,由于质询是指特定的coinID ,因此仅一次转账将是相关的。 因此,挑战者和响应者会给出一个transferIndex-涉及到有争议硬币的任何转移。 该检查对TransactionProof中的所有TransferProof解码和检查,然后使用函数检查每个是否包含

  def checkTransferProofAndGetTypedBounds( 
leafHash:字节32,
blockNum:uint256,
transferProof:字节[1749]
)->(uint256,uint256):#typedimplicitstart,typedimplicitEnd

验证所有TransferProof ,将TransferProofTransferProof相关的与争议相关的值返回到退出游戏函数:即sendertypedStarttypedEndplasmaBlockNumberplasmaBlockNumber

这样一来,我们就可以为出口指定整套挑战/响应游戏。

挑战立即取消出口

有两种挑战可立即取消出口:已用硬币的出口和存款发生前的出口。

花了硬币挑战
此挑战用于证明交易的退出者已经将硬币发送给了其他人。

  @上市 
def ChallengeSpentCoin(
exitID:uint256,
coinID:uint256,
transferIndex:int128,
transactionEncoding:字节[277],
transactionProofEncoding:字节[1749],
):

It uses checkTransactionProofAndGetTypedTransfer and then checks the following:

  1. The challenged coinID lies within the specified exit.
  2. The challenged coinID lies within the typedStart and typedEnd of the transferIndex th element of transaction.transfers .
  3. The plasmaBlockNumber of the challenge is greater than that of the exit.
  4. The transfer.sender is the exiter.

The introduction of atomic swaps does mean one thing: the spent coin challenge period must be strictly less than others, because of an edge case in which the operator withholds an atomic swap between two or more parties. In this case, those parties must exit their pre-swapped coins, forcing the operator to make aa spent coin challenge and reveal whether the swap was included or not. BUT, if we allowed the operator to do that at the last minute, it would make for be a race condition where the parties have no time to use the reveal to cancel other exits. Thus, the timeout is made shorter (1/2) than the regular challenge window, eliminating “last-minute response” attacks.

Before deposit challenge
This challenge is used to demonstrate that an exit comes from an earlier plasmaBlockNumber than that coin was actually deposited for.

 @public 
def challengeBeforeDeposit(
exitID: uint256,
coinID: uint256,
depositUntypedEnd: uint256
):

The contract looks up self.deposits[self.exits[exitID].tokenType][depositUntypedEnd].precedingPlasmaBlockNumber and checks that it is later than the exit’s block number. If so, it cancels.

Optimistic exits and inclusion challenges

Our contract allows an exit to occur without doing any inclusion checks at all in the optimistic case. To allow this, any exit may be challenged directly via

 @public 
def challengeInclusion(exitID: uint256):

To which the exiter must directly respond with either the transaction or deposit they are exiting from.

 @public 
def respondTransactionInclusion(
challengeID: uint256,
transferIndex: int128,
transactionEncoding: bytes[277],
transactionProofEncoding: bytes[1749],
):
  ... 
 @public 
def respondDepositInclusion(
challengeID: uint256,
depositEnd: uint256
):

The second case allows users to get their money out if the operator censored all transactions after depositing.

Both responses cancel the challenge if:

  1. The deposit or transaction was indeed at the exit’s plasma block number.
  2. The depositer or recipient is indeed the exiter.
  3. The start and end of the exit were within the deposit or transfer’s start and end

Invalid History Challenges

The most complex challenge-response game, for both vanilla Plasma Cash and this spec, is the case of history invalidity. This part of the protocol mitigates the attack in which the operator includes an forged “invalid” transaction whose sender is not the previous recipient. The solution is called an invalid history challenge: because the rightful owner has not yet spent their coins, they attest to this and challenge: “oh yeah, that coin is yours? Well it was mine earlier, and you can’t prove I ever spent it.”

Both invalid history challenges and responses can be either deposits or transactions.

Challenging

There are two ways to challenge depending on the current rightful owner:

 @public 
def challengeInvalidHistoryWithTransaction(
exitID: uint256,
coinID: uint256,
transferIndex: int128,
transactionEncoding: bytes[277],
transactionProofEncoding: bytes[1749]
):

 @public 
def challengeInvalidHistoryWithDeposit(
exitID: uint256,
coinID: uint256,
depositUntypedEnd: uint256
):

These both call a

 @private 
def challengeInvalidHistory(
exitID: uint256,
coinID: uint256,
claimant: address,
typedStart: uint256,
typedEnd: uint256,
blockNumber: uint256
):

function which does the legwork of checking that the coinID is within the challenged exit, and that the blockNumber is earlier than the exit.

Responding to invalid history challenges

Of course, the invalid history challenge may be a grief, where really the challenger did spend their coin, and the chain of custody is indeed valid. We must allow this response. There are two kinds.

The first is to respond with a transaction showing the challenger’s spend:

 @public 
def respondInvalidHistoryTransaction(
challengeID: uint256,
transferIndex: int128,
transactionEncoding: bytes[277],
transactionProofEncoding: bytes[1749],
):

The smart contract then performs the following checks:

  1. The transferIndex th Transfer in the transactionEncoding covers the challenged coinID .
  2. The transferIndex th transfer.sender was indeed the claimant for that invalid history challenge.
  3. The transaction’s plasma block number lies between the invalid history challenge and the exit.

The other response is to show the challenge came before the coins were actually deposited — making the challenge invalid. This is similar to the challengeBeforeDeposit for exits themselves.

 @public 
def respondInvalidHistoryDeposit(
challengeID: uint256,
depositUntypedEnd: uint256
):

In this case, there is no check on the sender being the challenge recipient, since the challenge was invalid. So the contract must simply check:

  1. The deposit covers the challenged coinID .
  2. The deposit’s plasma block number lies between the challenge and the exit.

If so, the exit is cancelled.

This concludes the complete exit game specification. With these building blocks, funds can be kept safe even in the case of a maximally malicious plasma chain.

6. The Future

Plasma Group is dedicated to the creation of an open plasma implementation for the greater Ethereum community. It’s our mission to push layer 2 scaling forward by exploring the full potential of the plasma framework. There’s certinaly much more to push forward! Here are some of the things we hope to work on next.

Missing pieces in implementation

Automated Guarding
While a good start, many improvements are needed to fulfill the true potential of Plasma, for this spec and beyond. Currently, the most glaring missing piece in our implementation is guarding, the automated process which submits challenges and responses on behalf of users. Thankfully, the exit games themselves are implemented and have been manually tested, so that client software can be updated after a chain is deployed. We felt this was sufficient for a testnet release, but is the most pressing addition the code needs.

P2P History Proofs

Currently, when a user recieves a transaction, they ask the operator and re-download the full proof. This introduces a massive increase in operator overhead. What should really happen is that the sender directly transmits their locally stored proof to the recipient, bypassing the operator and making it much cheaper to run a plasma chain.

Defragmentation Strategies
Since we support atomic swaps, our current spec is compatible with any defragmentation strategy without any upgrades to the contract. However, it remains to be seen what the right approach will be, especially since we require transactions to specify a Plasma block number. We hope the plasma community can build an extensible defragmentation abstraction library which allows operators and users to try out different approaches.

Front-end wallet integration
We have some designs for a front-end wallet, but currently the client only supports command-line transacting, with no support for trading different ERC20s. Having a nice UI to give to testnet users will be a major step up in terms of UX and accessibility.

Operator fees
Because we support atomic multisends, we can support transaction fees without any protocol modifications. However, we’ve not currently implemented anything for this testnet launch.

Networked operator
Something we’re not taking advantage of yet is that merkle tree construction is highly parallelizable. If the operator was deployed as a networked cluster, we could increase block size by constructing subtrees in parallel.

Raspberry plasma, anyone?

Code review
It’s very likely that all of the client, contract, and operator implementations have critical bugs at this time. We’re hoping that part of this public launch will be an opportunity for external contributors to help point out many mistakes!

Missing pieces in the spec

Succinct Proof Schemes
As mentioned in the introduction, the most active area of Plasma research is a scheme to reduce the history proof size. P2P or not, an old (say, 1 year+) coin might have a significant amount of associated proof data, making transactions cumbersome. This is because the history proof contains, at minimum, one branch per block.

RSA accumulator constructions and STARKS/SNARKS which batch branch proofs over many blocks are currently the most likely candidates. Both would require protocol changes: for RSA (which also introduces a trusted setup), an entirely new validity condition must be added to the exit game. For the latter, the tree needs to be constructed using a SNARK/STARK friendly hashing algorithm, which has not been implemented in EVM.

Mass Exit/Deposit Schemes
If the operator does turn malicious, users must (eventually, no rush!) exit their funds. The concept of “mass exits”, in which many users consent to exiting together via a single on-chain transaction, would be a significant scalability increase. Ideally, the exit would be able to auto-deposit funds directly into a different plasma chain via a merkle root of balances. This would enable many users to switch chains, without individual balances ever being resolved on the main chain — a significant improvement in the “networkability” of multiple plasma chains.

Multi-operator networks
Though the operator cannot steal funds, they can censor transactions at will. One mitigation to this would be to replace the single-operator model with a set of operators, so that the existence of just a single honest operator is sufficient for clients to transact.

Improved exit records
Our exitable range construction allows for constant-sized checks on exited ranges. However, since each finalization updates this mapping, we will have a race condition if exits on the same range aren’t processed in ascending order. This is because the first exit’s finalization splits the range, changing the key to the self.exitable mapping and causing checkRangeExitable to fail for exits referencing the unsplit range. Those exits will revert and have to be re-submitted in the next Ethereum block. A gas-efficient alternative may exist, possibly using some sort of tree, queue, or some batchFinalizeExits method.

State channels and scripting
Recently, there’s been some great progress in the research community which suggests state channels and scripting with covenants are feasible on Plasma. Our current spec does not support either of these features, and will require a significant upgrade to the smart contract to support.

Together, we’ll build towards realizing the vision of a more decentralized future.