以太坊状态访问剖析 - 执行层研究

以太坊中文 发布于 2026-07-04 阅读 24

文章通过分析以太坊历史数据,揭示了状态访问的模式:写入主要是创建新状态(55%的存储槽创建后从未再被访问),而非更新;读取集很小且多为空探测(83-93%为零值读取);状态访问高度集中(顶部1%账户占据96-98%的读取)。文章进一步评估了基于写入时间戳的状态分层方案(EIP-8295),发现30天窗口可覆盖约94%的更新操作,仅需保持约3%的状态为活跃状态,是平衡性能和存储成本的理想选择。

1. 引言

每一笔以太坊交易都会读写链状态的一部分:账户余额、nonce、代码以及存储槽。随着链的增长,越来越多的状态长时间不被触及,因此有多个提案探讨了处理状态增长的不同方式,例如将休眠状态与活跃状态分离、让休眠状态过期以及创建新的状态形式。为了最好地支持研究,本报告旨在回答以下问题:

  • 在以太坊的历史上,状态访问和创建呈现出怎样的模式?
  • 在给定时间段内,有多少状态被触及?
  • 写入主要是创建、更新还是删除?
  • 读取是在获取真实数据,还是仅仅在探查存不存在?
  • 活动有多集中?
  • 协议内状态分层方案会有多有效?

2. 总结

  • 写入是状态增长,而非状态更替。 大约 55% 的已写入槽位被创建一次后就再未触及。占写入事件三分之二的更新集中在一个被反复命中的小型热集合上。
  • 只读集合很小,且多数是空值探查。 只被读取但从未被写入的槽位不到状态的 8%,其中 83–93% 是存在性检查,而非读取有效数据。
  • 状态访问极度集中。 自 2022 年以来,前 1% 的读取账户捕获了约 96–98% 的读取访问,主要由稳定币、去中心化交易所和区块构建者主导。
  • 温热集合本质上就是写入集合。 即使加上有效读取,它在写入之上也只增加 4–9%。
  • 写龄分层可以廉价地覆盖更新 Gas。 在 30 天窗口内,94% 的槽位更新 SSTORE(账户为 97%)已经位于温热状态上,因此非活跃溢价仅影响 3–6% 的更新。
  • 约 30 天的窗口是最佳选择。 它覆盖了约 94% 的温热更新,同时仅保持约 3% 的状态为活跃。更宽的窗口为多得多的温热状态换来的额外覆盖很少。

3. 数据与方法

T 是以天为单位的窗口长度。文中的窗口化表格均截止于主网区块 24,870,000,并且第 5.1 节第 6 节还会在合并后的历史中每周重放这些窗口。窗口为 T ∈ {1, 7, 14, 30, 60, 90, 180, 365} 天。对象类型为账户和存储槽。

所有源表均从 Xatu 提取。SQL 查询包含在此处

集合
写入(账户) canonical_execution_balance_diffscanonical_execution_nonce_diffscanonical_execution_contracts
写入(槽位) canonical_execution_storage_diffs
读取(槽位) canonical_execution_storage_reads
读取(账户,直接) canonical_execution_balance_readscanonical_execution_nonce_reads
读取(账户,派生) canonical_execution_address_appearances

集合定义

对于每个 (T, object_type)

  • W:在窗口内创建、修改或删除的对象。
  • R:在窗口内仅被读取,从未被写入的对象。
  • R⁺:R 中那些读取返回非零(有效)值的对象。
  • R∪W = W + R:在窗口内被触及的所有对象。R⁺∪W = W + R⁺ 是有效温热集合,用作第 5.1 节中的温热度量。

我们用 |W| 表示 W 中的对象数量,|R| 同理。

实际上,每个被写入的对象在同一窗口内也会被读取。一个槽位的 SSTORE 之前会有一个读取,发送者的 nonce 会被读取以验证进行写入的交易。因此 R 只统计那些显式的、超出 W 之外的读取(例如 SLOAD)。

粒度和已知空缺

  • 系统调用的状态未被记录。 每个区块协议对 EIP-4788 信标根、EIP-2935 区块哈希历史和 EIP-7002/7251 请求队列合约的写入并未出现。
  • 共识层的提款未被记录。 验证者提款会贷记执行层地址,但不涉及 EVM 写入,因此仅通过提款接收的地址会从 W 中缺失。这涉及数万个地址,远低于账户写入集的 1%。

4. 状态访问和创建的模式

一个槽位或账户可以通过多种方式被触及。本节尝试探索状态写入和读取的模式。

4.1 写入结构

存储写入是三种转换之一,且大多数已写入的槽位创建后便再未被触及。以下提供两种视角:所有时间的事件总数,然后是在某个窗口内写入的槽位按生命周期的分解。

整个链历史中的写入事件

从第一个状态活动(约区块 46k,2015 年 7 月)到区块 24,870,000 的每个写入事件。

槽位写入事件(总计 92.0 亿次):

转换 事件数 占比
更新 (x→y) 6,109,404,842 66.4%
创建 (0→x) 2,323,710,153 25.3%
删除 (x→0) 765,554,231 8.3%

账户写入事件

来源 度量 事件数 占比
余额变更 (85.5 亿) 调整 (x→y) 7,965,568,085 93.1%
充值 (0→x) 385,657,967 4.5%
清空 (x→0) 203,518,204 2.4%
Nonce 变更 (34.2 亿) 后续 3,043,409,094 89.0%
首次使用 (从 0 起) 376,865,812 11.0%
合约创建 创建 100,078,703 不适用

两个突出点:

  • 写入流量以更新为主:所有槽位写入事件中超过 66% 是更新。
  • 所有已创建槽位中有三分之一已被删除。
已写入槽位的生命周期

每个写入事件属于三种转换类型之一(值为每笔交易的净值,见第 3 节):

  • C(创建):0 → x,空槽位变为有值。
  • U(更新):x → y,有值槽位的值发生变化。
  • D(删除):x → 0,有值槽位被清零。

然后每个已写入的槽位根据其在窗口内看到的转换类型进行分类,一个槽位可能多次看到同一类型:

  • C:创建一次,之后未触及。第二次创建需要先删除,因此 C 槽位恰好有一次创建。
  • U:更新一次或多次,从未被创建或删除。在窗口之前已存在并在原地修改的槽位。
  • D:删除一次,从未被创建或更新。预先存在的槽位被清零。
  • C+U:创建一次,然后更新一次或多次,未删除。由于没有删除,它恰好有一次创建,而 +U 覆盖一次或多次更新。
  • C+D:创建并删除但从未更新,发生一次或多次生灭循环。
  • U+D:更新一次或多次,然后删除。在窗口之前已存在。
  • C+U+D:创建、更新一次或多次,并删除,完整的生命周期。

下表显示了合并后扫描的平均组成,每个周锚点权重相等:

T(天) C C+U U C+U+D C+D U+D D
30 53.3% 8.2% 14.4% 2.8% 12.1% 0.8% 8.4%
90 54.9% 9.2% 11.4% 3.3% 12.9% 0.7% 7.7%
180 55.5% 10.0% 9.4% 3.7% 13.6% 0.6% 7.2%
365 55.4% 11.4% 7.0% 4.2% 14.7% 0.5% 6.8%

sweep_write_composition

创建在每个窗口都占主导地位,约占 |W| 的 ~55%。 大多数槽位在窗口内被初始化后便再未触及,这是状态增长而非更替。C 也是波动最大的类别。在 T=30 时,它在周与周之间在 38% 和 68% 之间摆动,在 2024 年活动激增期间下降,之后恢复。

C+D 是最大的混合类别,约占 |W| 的 ~12–15%。 这些是在窗口内产生并消亡的短暂槽位。在整个时间线上的每个窗口中,该占比保持稳定。

在更长的窗口中,创建更加主导。 包含创建的类别(C,C+U,C+D,C+U+D)在 T=30 时占 |W| 的 ~76%,在 T=365 时占 ~86%,而纯原地更新 (U) 从 14% 降至 7%。更长的窗口捕获了每个槽位更多的诞生过程,因此写入集在回溯更远时看起来更受增长驱动。

4.2 读取结构

读取可能有两种不同的返回值:零值或非零值。与写入类似,这里提供两种视角:所有时间的总计和随时间变化的每个窗口分解。

整个链历史中的读取事件

整个历史中的每个读取事件。

槽位读取事件(总计 236.9 亿次,是写入事件的 2.6 倍):

返回值 事件数 占比
非零值 16,552,716,483 69.9%
零值 7,138,221,664 30.1%

账户读取事件

来源 度量 事件数 占比
余额读取 (151.0 亿) 非零值 9,845,422,896 65.2%
零值 5,251,717,095 34.8%
Nonce 读取 (136.4 亿) 非零值(递增后,不为 0) 13,637,765,012 100%
出现次数 (419.8 亿) 内部调用目标/调用者 各 157.4 亿 各 37.5%
交易发送者/费用接收者/交易接收者 各 33.9 亿 各 8.1%
合约创建者/新合约 各约 1 亿 各 0.24%
selfdestruct 调用者/退款接收者 各约 6000 万 各 0.14%

费用接收者是获得交易优先费(priority fee)的区块提议者,而非共识层提款(那些未被记录)。

如图所示,大多数读取事件对账户和槽位都是非零的。这些读取总数包括与写入耦合的读取,因为每次写入之前都有一个读取(每个 SSTORE 对应一个 SLOAD)。与写入耦合的读取数量恰好等于写入数量,因此对于槽位,它们约占所有读取事件的 ~35%,剩下的 ~65% 是超出写入集的真正读取。

读取返回什么

R 中的每个读取返回 zero(空槽探查,“这个槽位是否已设置?”)或 nonzero(有效数据)。R 仅包含在窗口内被读取但未写入的对象。作为 |R| 的占比,在合并后的每周扫描中取平均值:

T(天) 仅零值 仅非零值
30 82.5% 17.5%
90 87.1% 12.8%
180 89.8% 10.1%
365 92.6% 7.4%

sweep_read_composition

R 的大部分是空槽探查。 只有约 7–18% 的 R 是实际的有效读取,并且随着窗口变宽,该占比会缩小。

这并不与上面全历史表格中的 69.9% 非零值相矛盾。它们统计的是不同的东西。69.9% 是 每次事件:在所有 SLOAD 中,约 70% 命中了有效槽位,因为一小组热门有效槽位被反复读取。这里的约 7–18% 是只读集合 R 中每个不同槽位:窗口中仅被读取的槽位大多数是一次性的存在性探查,每个被命中一次。按读取事件加权,有效读取占主导。按对象加权,空探查确实占据了较大份额。

5. 温热程度与集中度

本节检查有多少状态是“温热”的(在一个窗口内被触及)以及访问在各个对象之间的集中程度。

5.1 温热程度:多少状态是活跃的

每个值是合并后每周扫描的平均值,每个窗口一次。只读列是 R⁺,仅统计返回非零(有效)值的读取,因此空槽探查被排除在温热集之外。

槽位(平均占活跃槽位的比例):

T(天) W R⁺ R⁺∪W
30 2.82% 0.21% 3.03%
90 7.55% 0.39% 7.95%
180 13.60% 0.55% 14.15%
365 24.17% 0.70% 24.88%

账户(平均占活跃账户的比例):

T(天) W R⁺ R⁺∪W
30 3.30% 0.46% 3.76%
90 7.94% 1.07% 9.01%
180 13.45% 1.79% 15.24%
365 23.08% 2.51% 25.59%

合并(槽位和账户平均占整个状态的百分比):

T(天) W R⁺ R⁺∪W
30 2.91% 0.26% 3.16%
90 7.63% 0.51% 8.14%
180 13.58% 0.77% 14.36%
365 23.99% 1.04% 25.03%

温热集随着 T 的增长而增长,因为更长的窗口捕获了更多状态。有效读取集始终只占写入集的一小部分。

随时间变化的温热程度

以下面板显示了槽位、账户以及合并集合分别占其活跃状态分母的比例:

sweep_warmth_W

sweep_warmth_Rp

sweep_warmth_RpW

在写入集图表中,槽位占比在合并后的时间线上呈下降趋势(活跃状态的增长速度快于窗口内写入集),而账户占比则保持得更稳定。两者均从 2025 年底开始上升。有效读取集 R⁺ 在每个窗口都很小,且逐渐增长,账户侧的增长多于槽位侧。

5.2 集中度

对于每个访问集合和窗口,前 1% 的对象捕获的访问占比。访问是按每(交易,对象)事件计算的(第 3 节)。

q3_concentration_top1_slot

q3_concentration_top1_account

槽位,前 1% 的访问占比:

T(天) W R R∪W
1 48.0% 65.7% 56.4%
30 62.3% 83.8% 72.3%
90 66.1% 87.1% 76.1%
365 68.2% 87.7% 77.9%

账户,前 1% 的访问占比:

T(天) W R R∪W
1 40.8% 78.9% 57.5%
30 60.5% 96.0% 77.7%
90 64.0% 98.0% 82.0%
365 69.0% 98.7% 86.4%

R 在任何地方都比 W 更集中。 在 T=365 天时,前 1% 的 R 槽位占据了约 88% 的槽位读取访问,而前 1% 的 R 账户则占据了约 98%。少数热门合约吸收了几乎所有的读取操作。

集中度随 T 增长。 更宽的窗口会拉入很少被访问的尾部键,因此头部的相对权重上升。从 T=1 天到 T=30 天跃升最为明显(槽位 R 约增 18 个百分点,账户 R 约增 17 个百分点),之后趋于平缓。

账户的集中度远高于槽位。 在 T=30 天时,前 1% 的 R 账户捕获了 96% 的访问,而槽位为 84%。账户读取落在一小部分热门合约上,而槽位读取分散在许多合约的存储中。

随时间变化的集中度

sweep_concentration

账户读取的集中度极高,并且在整个样本期间一直如此。在最新锚点(区块 24,870,000)处,前 1% 的 R 账户持有96–98% 的读取访问,在更宽的窗口下,2022–2023 年就已经达到这个水平(T=90 约 93%,T=180 约 95%,T=365 约 97%)。唯一变化的窗口是短暂的 30 天窗口,从约 87% 攀升至约 96%,因为读取流量集中到了一小部分被大量调用的合约上。槽位的集中度较低且上升更平缓(T=365 时从约 78% 升至约 88%)。

谁占据头部

在 T=365 天窗口(区块 24,870,000)中,按所有来源的访问次数排序,访问最多的十个(R∪W)账户:

排名 账户 访问次数 说明
1 0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97 11.9 亿 Titan Builder
2 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 7.29 亿 USDC
3 0xdadb0d80178819f2319190d340ce9a924f783711 7.29 亿 BuilderNet
4 0xdac17f958d2ee523a2206206994597c13d831ec7 6.15 亿 USDT
5 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 5.79 亿 WETH
6 0x43506849d7c04f9138d1a2050bbf3a0c054402dd 4.28 亿 未知
7 0x396343362be2a4da1ce0c1c210945346fb82aa49 2.41 亿 QuasarBuilder
8 0x000000000004444c5dc75cb358380d2e3de08a90 2.17 亿 Uniswap V4 PoolManager
9 0x609e0f0cb16e53878ba5e959a22fc7fcd81b124a 2.09 亿 未知
10 0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5 2.05 亿 beaverbuild

头部分为两部分。区块构建者(Titan、BuilderNet、QuasarBuilder、beaverbuild)位居榜首,因为在合并后,每笔交易都会贷记区块的费用接收者。这是因为构建者在它构建的每个区块中的每笔交易上都是费用接收者。合约则是真正被大量访问的账户:稳定币(USDC、USDT)、WETH 和 Uniswap V4 单例。

因此,账户集中度的头部是混合的:几个主导的构建者因费用贷记而被拉高,外加少数吸收了大部分调用流量的合约。

同一窗口中存储槽位被访问最多的十份合约:

排名 合约 槽位访问次数 说明
1 0xdac17f958d2ee523a2206206994597c13d831ec7 13.1 亿 USDT
2 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 7.99 亿 USDC
3 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 3.32 亿 WETH
4 0x06450dee7fd2fb8e39061434babcfc05599a6fb8 3.17 亿 XEN
5 0x000000000004444c5dc75cb358380d2e3de08a90 1.33 亿 Uniswap V4 PoolManager
6 0xc7bbec68d12a0d1830360f8ec58fa599ba1b0e9b 5400 万 Uniswap V3 USDC/USDT 池
7 0x2b591e99afe9f32eaa6214f7b7629768c40eeb39 5200 万 HEX
8 0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb 5100 万 Morpho
9 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 4500 万 Aave V3 Pool
10 0xe0554a476a092703abdb3ef35c80e0d76d32939f 4400 万 Uniswap V3 USDC/ETH 池

代币余额和授权映射(USDT、USDC、WETH)、DEX(Uniswap V4 单例和两个繁忙的 Uniswap V3 池)、借贷(Aave V3、Morpho)以及高频铸造/质押代币(XEN、HEX)。这些合约的存储被读取和写入的次数远远超过其他所有合约。

6. EIP-8295:状态分层反事实分析

前几节描述了在整个以太坊历史中状态访问和创建的样子。本节探讨状态分层方案的有效性。

EIP-8188 为每个账户和存储槽添加了一个 last_written_block 字段,这是一种共识层元数据,记录了每部分状态最后被修改的时间。它本身并不改变任何 Gas 成本。EIP-8295 是建立在该元数据之上的分层方案。它将为新近写入的状态定价较低(活跃),而长期休眠的状态定价较高(非活跃),将其活跃阈值视为一个滚动的 T 天窗口。以下各节将该层作为反事实来建模。下面的“活跃”和“非活跃”均指此方案。

6.1 温热更新覆盖率

定义

对于每个窗口,将每个更新事件分类为温热或寒冷:

  • 温热:该对象在同一窗口中较早时已被创建或更新。
  • 寒冷:该更新是该对象在窗口中的第一个加热事件。删除不计入加热。

因此,对象在窗口中的第一个创建或更新可能是寒冷的,而其后的每一次更新都是温热的。

覆盖率

每个值是合并后每周扫描中温热更新事件的平均百分比。

T(天) 槽位 % 温热 账户 % 温热
1 85.2% 92.0%
7 91.2% 95.1%
30 94.1% 97.0%
90 95.7% 98.0%
180 96.7% 98.6%
365 97.7% 99.0%

sweep_update_coverage

活跃层能很好地覆盖更新 Gas。 在 T=30 天时,约 94% 的槽位更新 SSTORE 保持便宜的活跃价格,在 T=90 天时约 96%,因此非活跃溢价仅影响约 3–6% 的槽位更新。即使是 1 天的窗口,也已经覆盖了 85% 的槽位更新 Gas 和 92% 的账户更新 Gas。

在低端,槽位比账户对窗口更敏感。 将窗口从 30 天缩短到 1 天,槽位覆盖率从 94% 降至 85%,但账户覆盖率仅从 97% 降至 92%。账户状态(热门合约)在短窗口内被多次重写,因此几乎每次账户更新之前都已在同一天有过一次写入。

收益迅速饱和。 槽位覆盖率从 T=30 天时的约 94% 上升到 T=365 天时的约 98%,仅增加 4 个百分点,而窗口扩大了 12 倍;账户则更加平缓(约 97% 至约 99%)。超过约 30 天对更新 Gas 的帮助不大。

约 30 天的窗口是最佳选择。 从 T=30 天到 T=365 天,槽位覆盖率仅增加 3.6 个百分点(从 94.1% 到 97.7%),但必须保持温热的活跃集从活跃槽位的 2.8% 增加到 24.2%第 5.1 节),廉价层中的状态数量增加了约 9 倍,而额外覆盖的 Gas 几乎没有。大约 30 天的阈值捕获了几乎所有的温热更新 Gas,同时保持活跃集很小,这正是分层方案所期望的。

6.2 读取侧周期提升

在一个假设的扩展下,对非活跃对象的第一次读取也会提升其周期,使得读取对用户来说像写入一样,那么哪些对象需要支付这个代价?

不良用户体验集是那些第一次窗口事件是有效读取的对象。例如,第一次事件是返回有效值的 SLOAD 的槽位,或者第一次事件是返回非零的余额或 nonce 读取的账户。作为第一次事件的写入或零值读取不会产生额外成本,因为写入已经刷新了元数据,而零值读取不会填充元数据。

EIP-8188 只在写入时更新写龄,从不在读取时更新。一种在读取时提升周期的方案将耗费与写入一样多的成本,因此我们想看看它会产生多大的影响。

槽位:首次操作分类

R∪W 中的槽位按其首次事件分类的占比:

T(天) 首次 = 写入 首次 = 零值读取 首次 = 非零值读取
30 67.90% 26.32% 5.77%
90 69.34% 26.57% 4.09%
180 69.86% 26.93% 3.21%
365 70.28% 27.37% 2.35%

在 T=30 天时,R∪W 中 5.8% 的槽位会受到读取侧提升的影响,它们的第一次事件是有效的 SLOAD。这一比例在 T=365 天时降至 2.4%,因为更宽的窗口更可能包含较早的写入。

账户:首次操作分类

R∪W 中的账户按其首次事件分类的占比:

T(天) 首次 = 写入 首次 = 零值读取 首次 = 非零值读取
30 89.65% 0.91% 9.44%
90 89.67% 1.04% 9.29%
180 89.96% 1.14% 8.89%
365 90.91% 1.19% 7.90%

不良用户体验集在 T=30 天时约占温热账户的 ~9%,在 T=365 天时降至约 ~8%。零值读取带在整个过程中都很小(约 1%)。

sweep_first_op

在时间线上,不良用户体验集始终是少数,随着只读集中填充了更多有效账户而缓慢上升,在短窗口的情况下(2023 年初和 2025 年)会向 ~20% 波动。

结论

以太坊的状态增长远快于其更替。大多数写入创建了新状态,之后便再未触及,并且在任何固定窗口中活跃状态的占比随着链的老化而持续下降。结果是大量休眠状态被每个有状态节点永久携带,而真正的活动仍然集中在一个由少数应用程序主导的小型热集合上。

这正是使活跃状态与休眠状态分离变得有价值的原因。一个基于写龄的分层将几乎所有与 Gas 相关的写入活动放在一个小的活跃集中,30 天的窗口已经覆盖了约 94% 的更新 Gas,同时仅保持约 3% 的状态为温热,并将其余标记为非活跃。由于休眠尾部随链增长,区别对待它的价值会随着时间的推移而累积。

分层是该想法的一种表达。相同的结构支撑着状态过期、部分无状态化以及任何停止将所有状态视为同等活跃的设计。活跃集是小的且有界的,因此以太坊应该基于这种不对称性可持续地扩展其状态。

  • 原文链接: ethresear.ch/t/the-anato...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论