Loom网络:ERC721专属Plasma Cash
每晚八点,我们在社区分享知识,等你。
NervosFans 微信公号:Nervosfans
入群请加乐乐微信:sensus113 美果大冰微信:xj73226
备注入群,谢谢!
Hello everyone:
过去3个月我们一直在Loom网路上开发专门用于ERC721通证的一款Plasma Cash实现。有鉴于ERC721通证本身是独特不可替代的,因此该实现并不存在通证拆分或合并的问题。
下文会对实现做出的简单的解释,包括:使用的部分现行研究、构建的优势及劣势等。
代码见:
https://github.com/loomnetwork/plasma-erc721/
非技术读者见Medium文章:
Plasma合约
ERC721的Plasma Cash接收端
智能合约通过定义应付回退函数在接收ETH时执行部分功能,但不适用接收其他类型通证。ERC721标准中定义了一种onERC721Received的方法,当ERC721通证被发送到智能合约账户时被调用,且在接收ERC721通证后可模拟回退函数的回调功能。我们把自己的Plasma合约实施为ERC721TokenReceiver,在收到硬币后立即触发存款函数。
松散Merkle树
Plasma Cash中的每个硬币都独一无二,考虑到有使用Merkle树来组织区块内的交易,但是树中交易索引为硬币uid。我们把这种成为Merkle树中的“slot”。此外,认为slot是以随机方式生成的(譬如,对唯一输入做哈希),且有鉴于所有硬币不会都跑到1个区块中,那么该Merkle树应该是稀疏的。根据之前的一些研究,我们在Javascript和Golang中创建了一个稀疏Merkle树(SMT)实现。(Solidity SMT验证程序代码见etherscan)。简言之,SMT带来了包含和排除的紧凑证明(slot排除证明指的是证明SMT中slot里存在空哈希的Merkle分支)
交易格式
Plasma Cash交易为(slot,previousBlock,denomination,owner,hash)的元组。交易的previousBlock为0时,该交易为存款“凭空出现”的交易,意思是硬币存款后立即创建的交易。为使得验证更为简便,我们使用硬币slot的keccak256哈希做存款交易的哈希以示区分。 交易面额设置目前保持在1(视未来拆分/合并研究或Plasma Debit整合等进展,可以调整)。
存款
存款只有在Plasma合约通过onERC721Received收到ERC721通证后才会启动。此操作会在Plasma合约上创建一个仅包含存入新硬币的存款交易的新区块,并发出一个事件,通知Plasma链这笔硬币存款。
硬币状态—退出—挑战
硬币有uid字段,指存入的ERC721硬币uid。字段由ERC721通证合约分配,与先前引用的slot无关。当前设置为1的面额字段始终用于跟踪硬币面额,为实现未来支持ETH和ERC20合约提供端口,原因是(ETH和ERC20合约)基本都有空uid字段且denomination字段> 1。 添加contractAddress字段是为了让合约支持多类型通证。
我们还将硬币状态建模为状态机形式,有DEPOSITED,EXITING,CHALLENGED,EXITED等状态。
新存入硬币最初处于DEPOSITED(存入)状态。用户可通过引用自己拥有的交易(或该交易的直系祖先)以及包含对应引用交易的区块,启动硬币退出。开始硬币退出后,状态变为EXITING(退出中)。
正常情况下,成功(启动)退出,且已处于EXITING(退出中)状态一段时间(一段时间=挑战/成熟期)的硬币得以完全退出且状态变为EXITED(已退出)。完全退出相当于硬币提现成功,如下图所示。
那么‘不正常’情况下,硬币退出的成熟期出现挑战。根据Plasma Cash规范中描述,对后来交易或退出引用区块之间的交易提出挑战时,退出即刻被删除;且将退出人的债券(bonds)交与挑战者。用早先区块中交易挑战退出时,硬币状态被设为已挑战(CHALLENGED),且必须在挑战期结束时对挑战做出响应。响应后,硬币状态返回至EXITING,退出以‘已配合挑战’的形式完成;若未响应,则退出被删除。
这个机制存在以下不足:
· 退出只能由退出交易所有者发起。
· challengeAfter只能通过对被挑战退出的退出交易进行DIRECT SPEND(直接花费)发起。
· challengeBetween必须提供被挑战退出的先前交易的所有者的支出。
· challengeBefore指用户证实自己可以退出硬币,但没有;而是对恶意退出提出挑战。
· respondChallengeBefore必须带被响应挑战交易的后续花费。
我们要求退出人在启动退出时首先提交0.1ETH(数额可调整)的债券。挑战成功时,债券划拨给挑战人。由于challengeAfter和challengeBetween是非交互的,因此不需要债券,但是challengeBefore则要求用户提交一笔挑战债券(a bond for the challenge),响应成功债券被划拨(给响应人)。
验证人
我们实现了一个最小验证人管理员合约(Validator Manager Contract),允许所有者授权其他地址执行验证。这么做是为了与Plasma链合约治理做区分,允许链运营商以自定义的方式进行治理。
结论
稳健/安全性
我们测试了退出/挑战的各种可能场景以及各种无效块/扣块的情形,目前的实现非常强大,且不存在无效退出无法被挑战引发资金失窃的情况。
基准测试
为模拟其他场景,我们还克隆了OmiseGO Plasma Cash代码库并对其python子链实现做了修改了以支持我们自己的设置。
当前实现的基准测试见:https://github.com/loomnetwork/plasma-cash/issues/50。
发现如下(认为对每个ERC721合约的safeTransferFrom调用消耗102569 gas):
· 每个区块的gas上限约为800万,那么每个区块中最多可以移动78个ERC721通证,大概相当于每秒6.4次交易。有了Plasma,可以将一个Plasma块的内容有效的压缩在1个根链中,并允许任意数量的硬币在1个根链区块中变更所有权(硬币数量受Plasma块大小限制,姑且认为块大小很大)。我们的基准测试中,每个根链提交区块支持2000个硬币变更所有权不是问题。(注意,容量是2^64笔交易的区块处理2000笔交易算是很简单了。)
· 完全退出的gas成本指存款、启动并完成退出以及硬币提现的gas消耗总和,大概需要800k gas。也就是说,一枚硬币变更所有权的次数要大于8,才能完全分摊Plasma链中的gas成本。我们认为,对于那些积极利用Plasma链优势的用户来讲,这个(8次)交易次数算是合理。
· 运营商每次提交每块消耗约74k gas(这个费用可以借助交易费进行补偿),且每笔交易需要存储约134个字节,那么对于有2000笔易的区块,存储大约在261Kb。因此,Plasma链运营商的运行要求自然也就落在了生成并提交区块到根链的频率以及区块大小上。
Tradeoffs
优势:
· 有鉴于Plasma Cash中的每枚硬币都是唯一的,因此无需MVP构建中的退出优先级队列。因为独一无二,所以能单独挑战硬币退出。同理,运营商也无法创建能窃取所有硬币的大型交易,因此也无需出于安全性考量的大批量退出机制(但是用于gas优化也未尝不可)
· 退出/挑战机制较为简单
劣势:
· 不可替代性=硬币不可拆分。虽说目前还不算是个问题,但是把Plasma Cash扩展成能灵活运行可拆分ETH/ERC20也是好的,或许这一点上Plasma Debit能有些帮助。
· 证明大小呈线性增长,可通过Plasma XT缓解下。
后期工作
· ERC20/ETH配适
· 探索Plasma XT/Plasma Debit实现的可能
· 探索可支持的其他标准(譬如ERC998、ERC1155)
· 解决借由中间退出(limbo exits)的恶意破坏(用户为完成扣留交易被运营商强迫支付债券)
· 探索‘向一个根连区块提交多个Plasma区块以支持更高吞吐’的可能。这个方案可能存在暂不凸显的完结问题。
欢迎克隆代码库:https://github.com/loomnetwork/plasma-erc721.
感谢关注!
https://ethresear.ch/t/loom-network-plasma-cash-for-erc721-tokens/2385