Alert Source Discuss
Standards Track: Core

EIP-1884: 重定价依赖于trie大小的操作码

Authors Martin Holst Swende (@holiman)
Created 2019-03-28
Requires EIP-150, EIP-1052

简单总结

本 EIP 提议对某些操作码进行重定价,以在 gas 消耗和资源消耗之间取得良好的平衡。

摘要

以太坊状态的增长导致某些操作码在当前比以前消耗更多的资源。本 EIP 提议提高这些操作码的 gasCost

动机

操作的价格与资源消耗(CPU 时间、内存等)之间的不平衡有几个缺点:

  • 它可能被用于攻击,通过用低估的操作填充区块,导致过度的区块处理时间。
  • 低估的操作码会导致区块 gas 限制倾斜,有时区块完成速度很快,但其他 gas 使用量相似的区块完成速度却很慢。

如果操作是平衡的,我们可以最大化区块 gas 限制并获得更稳定的处理时间。

规范

在区块 N

  • SLOAD (0x54) 操作从 200 gas 修改为 800 gas,
  • BALANCE (0x31) 操作从 400 gas 修改为 700 gas,
  • EXTCODEHASH (0x3F) 操作从 400 gas 修改为 700 gas,
  • 0x47 引入一个新的操作码 SELFBALANCE
    • SELFBALANCE 从堆栈中弹出 0 个参数,
    • SELFBALANCE 将当前地址的 balance 推送到堆栈中,
    • SELFBALANCE 的价格为 GasFastStep,即 5 gas。

原理

以下是两张图表,取自使用 Geth 进行的完整同步。测量了每个操作码的执行时间,并汇总了 1 万个区块的数据。这些条形图显示了 5M 到 6M 和 6M 到 7M 范围内前 25 个“重”操作码:

bars1 bars2

注意:还可以看到 SLOAD 正在向顶部位置移动。我认为 GASPRICE (0x3a) 操作码位于第一位,可以在客户端内部进行优化,而 SLOAD/BALANCE 则不能。

这是另一张图表,显示了使用 Geth 进行的完整同步。它表示区块 05.7M,并突出显示了区块处理时间花费在哪里。

geth

可以看出,storage_readsaccount_reads 是导致区块处理时间的最重要因素。

SLOAD

SLOADEIP-150 中被重定价,从 50200。 以下图表显示了 go-ethereum 的完整同步,其中每个数据点代表 1 万个区块。在这 1 万个区块期间,汇总了操作码的执行时间。

graph

可以看出,在 EIP-150 处进行重定价导致了急剧下降,从大约 67 降至 23。 在区块 5M 附近,它开始达到 EIP-150 之前的水平,而在区块 7M 它平均约为 150 - 是 eip-150 之前水平的两倍多。

SLOAD 的成本提高 4 会将其降至 40 左右。 预计将来它将再次上升,并且可能需要将来的重定价,除非 在此之前实施状态清除工作。

BALANCE

BALANCE(也称为 EXTBALANCE)是一种从状态 trie 中获取数据的操作。它在 EIP-150 中被重定价,从 20400

graph

它与 EXTCODESIZEEXTCODEHASH 相当,后者的价格已经为 700

它具有内置的高方差,因为它通常用于检查 this 的余额, 这是一种固有廉价的操作,但是,它可以用于查找任意帐户的余额,这通常需要访问 trie(磁盘)。

事后看来,最好有两个 操作码:EXTBALANCE(address)SELFBALANCE,并且有两个不同的价格。

  • 本 EIP 提议扩展当前的操作码集。
    • 不幸的是,操作码范围 0x3X 已经满了,因此建议将 SELFBALANCE 放在 0x4X 范围内。
    • 至于为什么它的价格为 5 (GasFastStep) 而不是 2 (GasQuickStep),与其他类似操作一样:EVM 执行引擎仍然需要查找(缓存的)trie,并且 balancegasPricetimeStamp 不同,在执行期间不是恒定的,因此它具有更多的固有开销。

EXTCODEHASH

EXTCODEHASH 是在 Constantinople 中引入的,带有 EIP-1052。它的定价为 400,理由如下:

gas 成本与 BALANCE 操作码的 gas 成本相同,因为 EXTCODEHASH 的执行需要与 BALANCE 相同的帐户查找。

因此,如果我们增加 BALANCE,我们也应该增加 EXTCODEHASH

向后兼容性

这些更改需要硬分叉。这些更改具有以下后果:

  • 某些调用将变得更加昂贵。
  • 访问存储的默认函数,在某些情况下可能需要超过 2300 gas(调用中始终可用的最小 gas)。
  • 假设调用(或内部部分)具有特定固定 gas 成本的合约可能会停止运行。
    • ERC-165 中指定了固定的 gas 成本,并且此接口的实现确实使用了受影响的操作码。
      • ERC-165 方法 supportsInterface 必须返回一个 bool,并且最多使用 30,000 gas。
      • 在撰写本文时,EIP 中的两个示例实现是
        1. 任何输入都需要 586 gas,并且
        2. 236 gas,但随着支持的接口数量的增加而线性增加
    • 任何 ERC-165 supportsInterface 实现都不太可能超过 30.000 gas。这将需要使用第二个变体,并且支持三十多个接口。
    • 但是,这些操作已经更早地被重定价,因此有一个历史先例,即“这些操作的 gas 成本可能会更改”,这应该可以防止实现这种固定 gas 成本的假设。

我预计某些模式的使用会减少,例如,将 SLOAD 相同操作码的多个修饰符合并为一个。它还可能导致包含并非绝对必要的 SLOAD 值的 log 操作减少。

测试用例

应实现的测试用例:

  • 测试 selfbalance == balance(address)
  • 测试 balance(this) 的成本与之前相同,
  • 测试 selfbalance 不从堆栈中弹出
  • SLOADEXTCODEHASHSELFBALANCE 的 gas 成本验证
  • 验证 SELFBALANCE 在 Istanbul 之前无效

一些测试用例已在 https://github.com/holiman/IstanbulTests/tree/master/GeneralStateTests 中作为状态测试实现

实现

此 EIP 尚未在任何客户端中实现。 这两个操作码之前都已重定价,并且用于管理重定价的客户端内部结构已就绪。

SELFBALANCE

这是 go-ethereum 中新操作码的实现:


func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
	stack.push(interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address())
	return nil, nil
}

安全考虑

  • 请参阅向后兼容性部分。
  • 如果我们将 SELFBALANCE 定义为具有 address 而不是从堆栈中弹出一个地址的 BALANCE,则没有关于 SELFBALANCE 的特殊边缘情况 - 因为 BALANCE 已经被很好地定义。
  • 应该调查 Solidity 是否包含对这些操作的 gas 成本的任何硬编码的期望。
  • 在许多情况下,来自 CALLether 的接收者将想要发出一个 LOGLOG 操作的成本为 375 加上每个主题 375。如果 LOG 还想执行 SLOAD,则此更改可能会导致某些此类传输失败。

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Martin Holst Swende (@holiman), "EIP-1884: 重定价依赖于trie大小的操作码," Ethereum Improvement Proposals, no. 1884, March 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1884.