每个基本块不会改变控制流(如没有JUMP)。因此,我们可以确定,一旦我们开始执行一个基本块,要么它将运行到最后,要么它将耗尽Gas。我们假定这方案会更为高效,但仍未测试其替代方案来作对比。
为了提高效率,相邻的基本块将会合并直到每个基本块的最小长度为128字节(这是一个可配置的参数)。然后将它们插入trie树中,使用其第一个字节的索引作为键。客户端最终将此trie树的根存储在记录该合约的新创建的账户中。如下所示,代码trie树实际上成为了状态trie树的子树(类似于存储trie树)。
默克尔化的合约代码成为了状态trie树的子树。为了简化图表,我使用了二进制trie树(而不是以太坊中使用的MPT树)。路径和键值也不太准确。
让我们通过提交调用合约的交易来进行测试。矿工执行交易并标记在执行过程中触及的块(在本例中为块1和块3)。当发布区块时,矿工会纳入合约账户状态证明和触及代码块的turbo证明[9]。
触及块与验证代码根所需的哈希作为turbo证明进行传输
收到该区块后,无状态客户端可以验证合约是否为状态的一部分以及是否有着正确的属性:余额,nonce值,状态根和代码根。然后,它可以根据代码根去验证代码块及其键值。上述信息足以让客户端从这些块中重构出部分字节码并让其它块留空。值得注意的是,根据我们采用的块分割算法,客户端知道每个块(第一个除外)都以JUMPDEST开始,因而可以安全地执行跳转。
从trubo证明,我们可以重构字节码。给定交易所不需要的块则留空。
实验
为了测试,我们编写了一个原型[10],其通过Geth的RPC端口抓取主网区块及初始状态。然后,原型在这些区块中运行交易,每当遇到新合约时,把合约分割成块并对触及块进行标记。当区块中的所有交易被处理后,原型会为这些块生成turbo证明。我们在更新后的初始状态(仅仅把合约代码替换为由证明计算得到的重构代码)下重新运行这些交易。为了检查正确定,我们比较了使用的Gas量以及区块的布隆过滤器。对最近的50个区块进行处理,我们可以看到代码量的减少在40%到60%之间。
警告:这些数据虽然看上去不错,但请记住,我们需要数万个区块的数据来得出有说服力的结论,而且原型正处于初始阶段,因此很可能有Bug。
何去何从
你可能仍记得,每个块的最小长度是一个可配置的参数(上文把其设为128字节)。修改该参数会对块见证的大小有着两种相反影响。例如减少至32字节,让块的粒度更细,从而减少了需要发送的代码总量。但同时也增加了trie树的深度,最终导致证明所需的哈希数增大。下一步将会对最小块大小的设定进行更彻底的分析,看看是否有一个最为节约空间的值。不管最小块大小的值,从十六进制trie树切换为二进制 trie 树会将证明所需的哈希值减少为原来的1/4(见这里[11]),从而进一步减小块见证的大小。
对于该原型,我们选择将代码分割为一个个基本块,但也存在着其它各种各样的分割算法,有些更为简单,有些更为复杂。最简单的方法是把代码分割为固定大小的块(如每32个字节为一个块)。目前,该方案的唯一问题围绕在 PUSH 数据和 JUMPDEST 分析之上。
此文由 今日比特币价格 编辑,未经允许不得转载!:首页 > 比特币新闻 » 引介 | EVM 字节码的默克尔化