以下报告旨在收集数据,希望这些数据能够帮助 ACD 就 EIP-7907 做出决定。
此外,这有望建立一种新的方法,即用尽可能多的数据来支持 EIP 或提案,这肯定有助于在确定分支范围时做出更好、更明智的决策。
我要感谢@rjl493456442提交的 PR,他在 Geth 中添加了相关指标,并在基准测试数据收集过程中提供了极大的帮助和建议。我希望最终能将这些指标标准化,以便在所有客户端中通用,方便我们轻松比较和收集数据,从而为重新定价和扩展规模的决策提供依据。
相关问题:** EIP-7907
日期: 2026年1月13日
基准测试环境: Geth(开发模式),数据库规模与主网相同(约 2400 万个区块),内部缓存已禁用。
测试配置:每个区块约 18,106 次 EXTCODESIZE 操作(所有字节码合约均不同),约 50M gas
硬件: WD Black SN850X NVMe (8TB)
执行摘要
本报告分析了在禁用 Geth 内部代码缓存的情况下,读取不同字节码大小(0.5KB 至 64KB)合约时EXTCODESIZE操作码的性能。这代表了最坏情况下的攻击场景,攻击者部署数千个不同的合约来强制执行冷磁盘读取。
该迭代还具有最低的开销,从而避免了CREATE2确定性地址生成的问题。
更多相关信息请参见:
- 新增功能:为部署 EXTCODESIZE 基准合约添加 extcodesize_setup 场景,作者:CPerezz · Pull Request #161 · ethpandaops/spamoor · GitHub
- feat(benchmark): 添加 EXTCODESIZE 字节码大小基准测试,用于冷访问测试,作者:CPerezz · Pull Request #1961 · ethereum/execution-specs · GitHub
主要发现
| 寻找 | 价值 |
|---|---|
| 代码读取时间范围 | 107毫秒 - 904毫秒(约18000次代码读取) |
| 每次通话延迟范围 | 5.9微秒 - 49.9微秒 |
| 代码读取时间缩放 | 增长8.5倍(0.5KB → 64KB) |
| 64KB 数据块执行时间 | 约1006毫秒 |
| 代码读取占块时间的百分比 | 51% (0.5KB) → 90% (64KB) |
| Geth 效率对比原始 NVMe | 24-51% |
EIP-7907 裁决
| 尺寸 | 块时间 | 1秒预算的百分比 | 判决 |
|---|---|---|---|
| 24KB(当前) | 535毫秒 | 54% | 安全的 |
| 32KB | 685毫秒 | 69% | 安全的 |
| 64KB | 1006毫秒 | 约100% | 在60M气体条件下可行 |
| 128KB+ | 预计1.5秒以上 | 100%以上 | 可能需要重新调整天然气价格。在 BALs 和 ePBS 之后,我们需要更多数据。 |
建议:将新的最大合约大小设定为 64KB。超过 64KB 则需要在 BAL 和 ePBS 的优化功能部署到所有客户端后重新收集数据。
如果在上述数据收集之后需要重新定价,则此类定价还需要能够对其他客户进行基准测试,并参考其他EXTCODE*操作码。
1. 方法论与基准测试设置
1.1 测试环境
| 范围 | 价值 |
|---|---|
| Geth 版本 | v1.16.8-不稳定版(包含大量修改) |
| 数据库 | 主网已同步(约2400万个区块) |
| Geth 缓存 | 已禁用(强制磁盘读取) |
| 合同规模测试 | 0.5、1、2、5、10、24、32、64 KB |
| EXTCODESIZE 操作 | 每块约 18,106 个 |
| 每块气体 | 约5000万 |
| 已部署合同 | 每种规模的合同超过 18,100 份。 |
| 每个尺寸的迭代次数 | 8 |
| 硬件 | WD Black SN850X NVMe 8TB |
1.2 攻击场景设计
此基准测试代表了针对EXTCODESIZE最坏情况攻击:
- 每个规模部署超过 18,100 个独特的合约(导致代码缓存未命中)
- 每个区块都会从所有唯一合约中读取字节码,且读取次数不得少于一次。
- 代码缓存命中率:<2%(实际上已禁用)
- 基准测试运行之间清除操作系统页面缓存
1.3 原始磁盘基线 (fio)
为了确定理论上的最大性能,我们测量了NVMe的原始性能:
| 块大小 | IOPS | 吞吐量 | 平均延迟 |
|---|---|---|---|
| 512B | 337K | 172 MB/s | 95 微秒 |
| 1KB | 320K | 328 MB/s | 100 微秒 |
| 4KB | 272K | 1.1 GB/s | 117 微秒 |
| 24KB | 171K | 4.2 GB/s | 185 微秒 |
| 32KB | 155K | 5.1 GB/s | 204 微秒 |
| 64KB | 85K | 5.6 GB/s | 366 微秒 |
2. 基准测试结果
2.1 代码读取时间与字节码大小
核心发现:当缓存无效时,代码读取时间与字节码大小成正比。
| 尺寸 | 代码读取时间(毫秒) | 增长量(相对于 0.5KB) |
|---|---|---|
| 0.5KB | 107毫秒 | 1.0倍(基线) |
| 1KB | 135毫秒 | 1.3倍 |
| 2KB | 142毫秒 | 1.3倍 |
| 5KB | 145毫秒 | 1.4倍 |
| 10KB | 161毫秒 | 1.5倍 |
| 24KB | 428毫秒 | 4.0x |
| 32KB | 584毫秒 | 5.5倍 |
| 64KB | 904毫秒 | 8.5倍 |
关键发现:字节码大小增长 128 倍时,代码读取时间增长 8.5 倍。这是亚线性增长(并非 1:1),但绝对时间影响非常显著。
2.2 字节读取时间与代码读取时间(相关性)
强正相关性(R² ≈ 0.96)证实,当缓存无效时,代码读取时间与读取的总字节数成正比。
2.3 每次呼叫延迟
每次调用延迟随字节码大小的增加而增加:
| 尺寸 | 每次呼叫延迟 | 生长 |
|---|---|---|
| 0.5KB | 5.9 微秒 | 1.0x |
| 1KB | 7.5 微秒 | 1.3倍 |
| 10KB | 8.9 微秒 | 1.5倍 |
| 24KB | 23.7 微秒 | 4.0x |
| 32KB | 32.3 微秒 | 5.5倍 |
| 64KB | 49.9 微秒 | 8.5倍 |
3. 执行时间细分
3.1 成分分析
字节码尺寸较大时,代码读取量成为主要因素:
| 尺寸 | 代码读取 | 账户读取 | EVM执行 | 数据库写入 | 其他 | 全部的 |
|---|---|---|---|---|---|---|
| 0.5KB | 107毫秒(51%) | 54毫秒 | 34毫秒 | 12毫秒 | 2毫秒 | 209毫秒 |
| 1KB | 135毫秒(57%) | 53毫秒 | 37毫秒 | 12毫秒 | 1毫秒 | 238毫秒 |
| 10KB | 161毫秒(59%) | 53毫秒 | 40毫秒 | 12毫秒 | 5毫秒 | 271毫秒 |
| 24KB | 428毫秒(80%) | 44毫秒 | 46毫秒 | 15毫秒 | 2毫秒 | 535毫秒 |
| 32KB | 584毫秒(85%) | 38毫秒 | 47毫秒 | 13毫秒 | 3毫秒 | 685毫秒 |
| 64KB | 904毫秒(90%) | 38毫秒 | 51毫秒 | 12毫秒 | 1毫秒 | 1006毫秒 |
观察:在 64KB 缓存大小下,代码读取消耗了 90% 的块执行时间。这与热缓存场景下代码读取仅占 8-10% 的情况截然不同。
4. 时间块预算分析(EIP-7907 重点)
4.1 时间与预算目标
使用 1 秒作为区块执行目标:
| 尺寸 | 块时间 | 1秒预算的百分比 | 地位 |
|---|---|---|---|
| 0.5KB | 209毫秒 | 21% | 远低于预算 |
| 1KB | 238毫秒 | 24% | 远低于预算 |
| 2KB | 248毫秒 | 25% | 远低于预算 |
| 5KB | 252毫秒 | 25% | 远低于预算 |
| 10KB | 271毫秒 | 27% | 远低于预算 |
| 24KB | 535毫秒 | 54% | 预算内 |
| 32KB | 685毫秒 | 69% | 预算内 |
| 64KB | 1006毫秒 | 约100% | 极限 |
结论:在6000万gas区块的最坏攻击条件下,64KB合约是可行的。约1秒的执行时间虽然接近预算极限,但尚可接受。需要注意的是,考虑到ePBS和BAL很可能在不久的将来改变我们对安全预算的定义,这个极限相当保守。
4.2 天然气处理率(定价错误分析)
| 尺寸 | 燃气使用 | 块时间 | 兆气体/秒 |
|---|---|---|---|
| 0.5KB | 4940万 | 209毫秒 | 236 |
| 1KB | 4940万 | 238毫秒 | 208 |
| 10KB | 4940万 | 271毫秒 | 182 |
| 24KB | 4940万 | 535毫秒 | 92 |
| 32KB | 4940万 | 685毫秒 | 72 |
| 64KB | 4940万 | 1006毫秒 | 49 |
发现定价错误: gas 成本相同,但执行时间却相差 5 倍(236 Mgas/s → 49 Mgas/s)。这表明,在最坏情况下,规模更大的合约会给验证者带来不成比例的更高成本。
对 128KB 以上文件的影响:超过 64KB 后,需要对 gas 模型进行调整——可能是一个基本成本加上一个与大小相关的组件。
请注意,这还是相当保守的估计。因为要“瘫痪”网络或“严重影响慢速验证者”,所需的设置数量将是 18000 个独立合约的数百倍。这将带来巨大的成本(我们无法重复使用这些合约,因为它们会在第一个区块执行后被缓存)。
5. 原始磁盘基准(Geth 与 NVMe 效率对比)
5.1 效率比较
| 尺寸 | Geth IOPS | 原始 NVMe IOPS | 效率 | Geth 吞吐量 | 原始 NVMe | 效率 |
|---|---|---|---|---|---|---|
| 0.5KB | 171K | 337K | 51% | 83 MB/s | 172 MB/s | 48% |
| 1KB | 142K | 320K | 44% | 139 MB/s | 328 MB/s | 42% |
| 24KB | 43K | 171K | 25% | 1.0 GB/s | 4.2 GB/s | 24% |
| 32KB | 31K | 155K | 20% | 979 MB/s | 5.1 GB/s | 19% |
| 64KB | 2万 | 85K | 24% | 1.26 GB/s | 5.6 GB/s | 23% |
观察结果: Geth 仅能达到磁盘原始性能的 20% 至 51%。造成这种差距的原因可能是:
- Pebble/LevelDB 开销(索引遍历、布隆过滤器)
- 密钥哈希和查找
- 值反序列化
6. 与热缓存场景的比较
6.1 缓存与非缓存性能比较
| 尺寸 | 暖缓存 | 冷缓存 | 减速 |
|---|---|---|---|
| 0.5KB | 5.3毫秒 | 107毫秒 | 21倍 |
| 1KB | 4.4毫秒 | 135毫秒 | 31倍 |
| 2KB | 4.5毫秒 | 142毫秒 | 32倍 |
| 5KB | 4.6毫秒 | 145毫秒 | 31倍 |
| 10KB | 4.7毫秒 | 161毫秒 | 34倍 |
| 24KB | 4.8毫秒 | 428毫秒 | 89倍 |
| 32KB | 4.9毫秒 | 584毫秒 | 119x |
| 64KB | 4.9毫秒 | 904毫秒 | 181倍 |
在正常操作情况下,基于热缓存基准测试得出的“固定成本”结论仍然有效。冷缓存条件则需要极端攻击场景(超过 18,000 个独立合约)。
7. 对 EIP-7907 的影响及建议
7.1 研究结果总结
- 在攻击条件下,代码读取时间随代码大小而增加(从 0.5KB 到 64KB 增加到 8.5 倍)。
- 64KB 的数据在 6000 万个 gas 块的情况下是可行的——最坏情况下执行时间约为 1 秒,在预算范围内。
- 这代表了最糟糕的情况——部署和维护 18000 多个独特的合约是不切实际的(每次要运行攻击的区块都需要一个新的合约集)。
- 正常运行不受影响——热缓存场景下开销约为 5 毫秒。
- Gas定价错误问题正受到攻击(相同gas的执行时间差异可达5倍)
7.2 EIP-7907 建议
| 行动 | 推荐 |
|---|---|
| 64KB限制 | 继续执行——在最坏情况下的攻击下仍然可行。无需 EIP。 |
| 128KB+限制 | 需要使用支气管肺泡灌洗液 (BAL) 和电子磷酸盐缓冲液 (ePBS) 进行重新测量。 |
看来我们可以“保持简单”,在不改变协议本身的情况下,为智能合约开发者提供代码大小限制方面的良好升级,除了 64kB 的限制和 initcode 大小的增加之外。
一旦 BAL 和 ePBS 达到更完善的状态,我们将能够利用数据更好地指导我们做出关于重新定价/直接进入 256kB 的良好决策。
但即使在最糟糕的情况下,这东西也并不需要重新定价,所以现在重新定价似乎没有必要。
7.3 为什么 64KB 是可以接受的
攻击不切实际:部署超过 18000 个唯一的 64KB 合约需要:
- 每个合约部署约需 1300 万 gas(3.2 万基础 gas + 6.4 万 gas × 200 gas/字节)
- 仅设置阶段就需要数百个模块。
- 维护攻击面需要持续投入大量成本
阻塞时间在预算范围内:即使最坏情况下约 1 秒,对于 6000 万个 gas 块来说也是可以接受的。
缓存实际有效性:主主网区块会复用合约;代码缓存命中率通常很高。
亚线性扩展:规模增长 128 倍所需时间仅为 8.5 倍,这表明摊销仍然有效。













