本文探讨了以太坊中的多维gas定价机制,详细介绍了EIP-4844引入的blobs机制及其对rollup成本的影响,并讨论了未来无状态客户端的存储证明问题和多维gas的潜在应用。
特别感谢 Ansgar Dietrichs、Barnabe Monnot 和 Davide Crapis 的反馈和审阅。
在以太坊中,资源直到最近一直受到限制,并通过一种叫做“gas”的单一资源进行定价。Gas 是一个衡量处理给定交易或区块所需“计算努力”的指标。Gas 将多种类型的“努力”合并在一起,最显著的包括:
ADD
、MULTIPLY
)SSTORE
、SLOAD
、ETH 转账)例如,我发送的 这笔交易 的总费用为 47085 gas。这笔费用分为以下几个部分:(i) “基础费用” 21000 gas,(ii) 1556 gas 用于交易中包含的 calldata 字节,(iii) 16500 gas 用于存储的读取和写入,(iv) 2149 gas 用于发出 日志,其余部分用于 EVM 执行。用户必须支付的交易费用与交易消耗的 gas 成正比。一个区块最多可以包含 3000 万 gas,而 gas 价格会通过 EIP-1559 目标机制 不断调整,确保平均每个区块包含 1500 万 gas。
这种方法有一个主要的效率:由于所有内容都合并成一个虚拟资源,这导致市场设计非常简单。优化一个交易以最小化成本很容易,优化一个区块以尽可能高的费用收集也相对容易(不包括 MEV),而且没有奇怪的激励机制促使某些交易与其他交易捆绑以节省费用。
但这种方法也有一个主要的低效:它将不同资源视为可以互相转换,而网络实际能够处理的底层限制并非如此。理解这个问题的一种方式是查看下面的图示:
gas 限制强制执行 x1∗data+x2∗computation<N 的约束。实际的底层安全约束往往更接近于 max(x1∗data,x2∗computation)<N。这种差异导致 gas 限制过于排除实际上安全的区块,或接受实际上不安全的区块,或二者的某种混合。
如果有 n 种具有不同安全限制的资源,则一维 gas 理论上将吞吐量降低了最多 n 倍。正因如此,长期以来人们对 多维 gas 的概念一直感兴趣,并且通过 EIP-4844我们实际上在以太坊上实现了多维 gas。这篇文章探讨了这种方法的好处,以及进一步增加它的前景。
在今年初,平均区块大小为 150 kB。这其中很大一部分是 rollup 数据 :第二层协议 将数据存储在链上以确保安全性。这些数据成本昂贵:尽管在 rollup 上的交易费用大约是以太坊 L1 上的 5-10 倍便宜,但即使这个费用对于许多用例来说仍然太高。
为什么不降低 calldata gas 成本(目前每个非零字节 16 gas,每个零字节 4 gas),以使 rollup 更便宜呢?我们 之前做过,我们可以再做一次。这里的答案是:区块的 最坏情况大小 为 30,000,00016=1,875,000 个非零字节,网络已经勉强能够处理如此大小的区块。进一步将成本降低 4 倍将把最大值提高到 7.5 MB,这对安全性构成了巨大风险。
这个问题最终通过在每个区块中引入一组单独的适合 rollup 的数据空间,称为“blobs”,得到了解决。这两种资源有着各自的价格和限制:在 Dencun 硬分叉后,一个以太坊区块最多可以包含 (i) 3000 万 gas,和 (ii) 6 个 blobs,每个 blobs 可以包含 ~125 kB 的 calldata。这两种资源有不同的价格,通过 分开的 EIP-1559 风格定价机制 调整,旨在每个区块平均使用 1500 万 gas 和 3 个 blobs。
因此,rollup 的成本降低了 100 倍,rollup 上的交易量增加了超过 3 倍,而理论上的最大区块大小仅略微增加:从 ~1.9 MB 增加到 ~2.6 MB。
在不久的将来,将出现一个关于 无状态客户端 的存储证明的类似问题。无状态客户端是一种新型客户端,能够在没有或几乎没有本地存储数据的情况下验证链。无状态客户端通过接受需要在该区块内触及的以太坊状态特定片段的证明来实现这一点。
存储读取成本根据读取类型的不同,花费 2100-2600 gas,而存储写入成本更高。平均来说,一个区块大约会进行 1000 次存储读取和写入(包括 ETH 余额检查、SSTORE
和 SLOAD
调用、合约代码读取等操作)。但理论最大值为 30,000,0002,100=14,285 次读取。无状态客户端的带宽负载与这个数字直接成比例。
目前计划通过将以太坊的状态树设计从 Merkle Patricia 树 转向 Verkle 树 来支持无状态客户端。然而,Verkle 树仍然不是量子抗性的,并且对新一波 STARK 证明系统而言并不理想。因此,许多人对通过二进制 Merkle 树和 STARKs 支持无状态客户端更感兴趣——要么完全跳过 Verkle,要么在 Verkle 过渡几年来到后升级,一旦 STARKs 变得更加成熟。
二进制哈希树分支的 STARK 证明具有许多优势,但它们的关键弱点是证明生成时间较长:虽然 Verkle 树 每秒可以证明 超过十万个值,而基于哈希的 STARKs 通常只能每秒证明几千个哈希,并且证明每个值需要一个包含多个哈希的“分支”。
鉴于今天从像 Binius 和 Plonky3 这样的超优化证明系统以及像 Vision-Mark-32 所预测的数字,似乎我们将在一段时间内处于理论上 practical 证明 1,000 个值少于一秒的状态,但不能证明 14,285 个值。平均区块的状况还可以,但最坏情况的区块,可能由攻击者发布,将破坏网络。
我们处理此类场景的“默认”方法是重新定价:提高存储读取的成本,以将每个区块的最大值减少到更安全的水平。但我们 已经 多次做过 这种事情,再做一次会使太多应用程序的成本太高。一个更好的方法将是多维 gas:单独限制和收费存储访问,将平均使用量保持在每个区块1,000 次存储访问,但是设置例如 2,000 的每个区块上限。
另一个值得考虑的资源是 状态大小增长:增大以太坊状态大小的操作,而全节点将需要从此之后持有这些数据。状态大小增长的独特属性在于,限制它的理由完全来源于长期的持续使用,而不是峰值。因此,增加一个单独的 gas 维度来处理状态大小增长的操作(例如从零到非零的 SSTORE
、合约创建)可能会有价值,但目标不同:我们可以设置一个浮动价格来针对特定的平均使用,但不设定每个区块上的限制。
这显示了多维 gas 的一个强大特性:它让我们可以分别询问每种资源的 (i) 理想平均使用量,和 (ii) 安全的每区块最大使用量。而不是根据每个区块最大值设定 gas 价格,让平均使用量跟随,我们有 2n 个自由度去设定 2n 个参数,基于对网络安全的考虑来调整每一个。
更复杂的情况,例如有两个资源的安全因素是 部分 可加的,可以通过使某个操作码或资源成本在多种 gas 类型中取某个数量来处理(例如,零到非零的 SSTORE
可能花费 5000 无状态客户端证明 gas 和 20000 状态扩展 gas)。
令 x1 为数据的 gas 成本,x2 为计算的 gas 成本,因此在一维 gas 系统中我们可以表示交易的 gas 成本为:
gas=x1∗data+x2∗computation
在这个方案中,我们将交易的 gas 成本重新定义为:
gas=max(x1∗data,x2∗computation)
即,交易的费用不再是数据 加 计算的费用,而是以资源消耗的 更多 的量进行收费。这可以很容易地扩展到涵盖更多维度(例如 max(...,x3∗storage_access))。
应该很容易看出,这在保持安全性的同时提高了吞吐量。一个区块中理论上数据的最大量仍然是 GASLIMITx1,与一维 gas 方案完全相同。同样,理论上计算的最大量是 GASLIMITx2 ,也与一维 gas 方案完全相同。然而,消耗 数据和计算 的任何交易的 gas 成本都下降了。
这大致是提议的 EIP-7623 中采用的方案,以在进一步增加 blobs 数量的同时减少最大区块大小。EIP-7623 中的具体机制略复杂:保持当前 calldata 每字节 16 gas 的价格,但添加一个每字节 48 gas 的“底价”;交易支付的费用为(16 * 字节 + 执行 gas
)和(48 * 字节
)中较高的一个。因此,EIP-7623 将区块中理论上最大交易 calldata 从 ~1.9 MB 降低到 ~0.6 MB,同时保持大多数应用的费用不变。这种方法的好处在于这是从当前单维 gas 方案的非常小的变更,因此非常易于实施。
有两个缺点:
我主张,EIP-7623 的风格规则,不论是对于交易 calldata 还是其他资源,即使存在这些缺点也能带来足够大的好处值得实施。然而,如果我们愿意投入(显著更高的)开发精力,那么就有一种更理想的方法。
首先让我们回顾一下“常规” EIP-1559 的工作原理。我们将关注在 EIP-4844 中引入的版本,因为它在数学上更优雅。
我们跟踪一个参数 excess_blobs
。在每个区块中,我们设置:
excess_blobs <-- max(excess_blobs + len(block.blobs) - TARGET, 0)
其中 TARGET = 3
。即,如果一个区块的 blobs 超过目标,excess_blobs
增加,如果低于目标,则减少。然后我们设置 blob_basefee = exp(excess_blobs / 25.47)
,其中 exp
是指数函数的近似值 exp(x)=2.71828x。
也就是说,每当 excess_blobs
增加约 25 时,blob 基础费用增加 ~2.7 倍。如果 blobs 变得太昂贵,平均使用量下降,excess_blobs
开始减少,从而自动降低价格。每个 blob 的价格不断调整,以确保平均每个区块都是半满的—即每个区块平均包含 3 个 blobs。
如果存在 短期 的使用峰值,则限制就会生效:每个区块最多只能包含 6 个 blobs,在这种情况下,交易可以通过竞价提高其优先费来相互竞争。然而,在正常情况下,每个 blob 只需支付 blob_basefee
以及一个微小的额外优先费用作为激励确保包含在内。
这种定价机制在以太坊中已经存在了多年:与 EIP-1559 在 2020 年引入的机制非常相似。通过 EIP-4844,我们现在有两个各自浮动的 gas 价格和 blob 价格。
原则上,我们可以为存储读取和其他操作添加更多的浮动费用,但有一个警告,我将在下一节扩展。
对于 用户 来说,体验与今天非常相似:你不再仅支付一笔基础费用,而是支付两笔基础费用,但你的钱包可以将这两者抽象出来,只显示你期望支付的费用和最大费用。
对于 区块构造者 来说,大多数情况下,最优策略与今天相同:包括任何有效的内容。大多数区块并不满—无论是在 gas (https://etherscan.io/blocks)还是 blobs (https://blobscan.com/)上。唯一具有挑战性的情况是当 gas 或 blobs 足以超出区块限制时,构造者可能需要解决 多维背包问题 来最大化其利润。然而,即便如此,也存在比较好的近似算法,而在这种情况下,通过制作专有算法来优化利润所产生的收益远小于通过 MEV 实现的收益。
对于 开发者 而言,最大的挑战是需要重新设计以 EVM 为中心的特性及周边基础设施,这些特性目前围绕着一个价格和一个限制进行设计,而为了适应多种价格和多种限制而进行转变。对于应用开发者而言,优化会变得稍微困难:在某些情况下,你不能明确地说 A 比 B 更有效,因为如果 A 使用更多的 calldata 而 B 使用更多的执行,那么在 calldata 便宜时,A 可能更便宜,而在 calldata 昂贵时,A 可能更贵。然而,开发者还是能够通过根据长期历史平均价格进行优化来获得合理的结果。
有一个问题不会出现在 blobs 中,也将不会在 EIP-7623 或甚至“完整”的多维定价实现中出现,但如果我们尝试单独定价状态访问或任何其他资源,将会出现:子调用中的 gas 限制。
EVM 中有两个地方设置 gas 限制。首先,每笔交易设置一个 gas 限制,这限制了该交易可以使用的最大的 gas 总量。其次,当一个合约调用另一个合约时,可以设置自己的 gas 限制。这使得合约可以调用不受信任的合约,并仍然保证在该调用后有足够的 gas 进行其他计算。
挑战在于:在不同类型的执行之间使 gas 变得多维似乎需要对子调用提供每种类型 gas 的多个限制,这将需要对 EVM 的深层更改,并且与现有应用程序不兼容。
这也是多维 gas 提案经常停留在两个维度:数据和执行的原因之一。数据(无论是交易 calldata 还是 blobs)仅是在 EVM 外部分配,因此为了使 calldata 或 blobs 单独定价,EVM 内部不需要进行任何更改。
我们可以考虑一种 “EIP-7623 样式的解决方案” 来应对这个问题。这里有一个简单的实现:在执行期间,存储操作的费用提高 4 倍;为简化分析,假设每个存储操作为 10000
gas。在交易结束时,退还 min(7500 * storage_operations, execution_gas)
。结果是,在扣除退款后,用户被收费:
execution_gas + 10000 * storage_operations - min(7500 * storage_operations, execution_gas)
这相当于:
max(execution_gas + 2500 * storage_operations, 10000 * storage_operations)
这反映了 EIP-7623 的结构。另一种方法是实时跟踪 storage_operations
和 execution_gas
,并根据 max(execution_gas + 2500 * storage_operations, 10000 * storage_operations)
在调用操作码时收费 2500 或 10000。这样可以避免交易为那些他们大部分会通过退款得到回报的操作分配过多的 gas。
我们没有为子调用提供细粒度的权限控制:子调用可能会耗尽一笔交易的“配额”,用于廉价的存储操作。但我们仍然能得到一个足够好的解决方案,合约可以在进行子调用时设置限制,并确保主调用在子调用结束执行后仍有足够的 gas 进行任何后续处理。
我能想到的最简单的 “完整多维定价解决方案” 是:将子调用的 gas 限制视为 成比例的 。也就是说,假设存在 k 种不同类型的执行,每笔交易设定一个多维限制 L1...Lk。假设在当前执行点,剩余的 gas 为 g1...gk。假设一个 CALL
操作码被调用,带有子调用 gas 限制 S。设 s1=S,然後 s2=s1g1∗g2,s3=s1g1∗g3,如此类推。
也就是说,我们将第一种类型的 gas(实际上是 VM 执行)视为一种特权的“单位账户”,然后根据可用 gas 的百分比分配其余类型的 gas。虽然这看起来有些复杂,但它能最大化向后兼容性。如果我们希望在不同类型的 gas 之间使这个方案更“中立”,并且愿意牺牲向后兼容性,我们可以简单地让子调用 gas 限制参数表示当前上下文中剩余 gas 的一个分数(例如 [1...63] / 64
)。
然而,在任何情况下,一旦开始引入多维执行 gas,固有的复杂程度就会增加,这似乎难以避免。因此,我们的任务是做出一个复杂的权衡:我们是否接受在 EVM 级别稍微更复杂的设计以安全地释放显著的 L1 扩展性收益;如果接受,那么哪种特定提案对协议经济学和应用开发者而言效果最佳?很可能我提到的都不是,仍然有空间来提出更优雅和更好的方案。
- 原文链接: vitalik.eth.limo/general...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!