Skip to content
多维 Gas 定价

多维 gas 定价

作者:维塔利克·布特林 2024年5月9日 原文链接

特别感谢 Ansgar Dietrichs、Barnabe Monnot 和 Davide Crapis 的反馈和审阅。

在以太坊中,资源直到最近还是通过一种称为"gas"(燃料)的单一资源来限制和定价的。gas 是衡量处理给定交易或区块所需"计算资源消耗"数量的度量。gas 将多种类型的"资源消耗"合并在一起,主要包括:

  • 原始计算(例如ADD加法、MULTIPLY乘法)
  • 对以太坊存储的读写(例如SSTORE存储、SLOAD加载、ETH 转账)
  • 数据带宽
  • 生成区块的 ZK-SNARK 证明的成本

例如,我发送的这笔交易总共花费了 47,085 gas。这包括:(i)21,000 gas 的"基础成本",(ii)1,556 gas 用于交易中包含的 calldata(调用数据)字节,(iii)16,500 gas 用于存储的读写,(iv)2,149 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。这篇文章探讨了这种方法的好处,以及进一步扩展它的前景。

Blobs:Dencun 中的多维 gas

今年年初,平均区块大小为 150 kB。其中很大一部分是 rollup 数据layer 2 协议为了安全性而在链上存储的数据。这些数据的成本很高:尽管在 rollup 上的交易成本比在以太坊 L1 上的相应交易低约 5-10 倍,但对许多使用场景来说,这个成本仍然太高。

为什么不降低"calldata"的 gas 成本(目前非零字节为 16 gas,零字节为 4 gas)来让 rollup 更便宜呢?我们之前做过,现在也可以再做一次。答案是:区块的最大大小是 30,000,000÷16=1,875,000 个非零字节,而网络已经很难处理这么大的区块了。将成本再降低 4 倍会使最大值增加到 7.5 MB,这将对安全性构成巨大风险。

这个问题最终通过在每个区块中引入一个单独的、对 rollup 友好的数据空间来解决,称为"blobs"。这两种资源有着独立的价格和独立的限制:在 Dencun 硬分叉之后,一个以太坊区块最多可以包含(i)3000 万 gas,以及(ii)6 个 blobs,每个 blob 可以包含约 125 kB 的"calldata"。两种资源都有独立的价格,通过独立的类 EIP-1559 定价机制进行调整,目标是平均每个区块使用 1500 万 gas 和 3 个 blobs。

因此,rollup 变得便宜了 100 倍,rollup 上的交易量增加了 3 倍以上,而理论上的最大区块大小仅略微增加:从约 1.9 MB 增加到约 2.6 MB。

数据来源于 growthepie.xyz 的 Rollup 交易费用统计。Dencun 升级分叉(引入了具有多维定价的数据块 blob)于 2024 年 3 月 13 日发生。

多维度 gas 和无状态客户端

在不久的将来,关于无状态客户端(stateless clients)的存储证明也会出现类似的问题。无状态客户端是一种新型的客户端,它能够在本地几乎不存储任何数据的情况下验证区块链。无状态客户端通过接受区块中交易需要访问的以太坊状态特定部分的证明来实现这一点。

无状态客户端接收区块,同时接收证明,这些证明证明了区块执行所涉及的状态特定部分(例如账户余额、代码、存储)的当前值。这使得节点无需自身存储任何数据就能验证区块。

存储读取根据读取类型需要消耗 2100-2600 gas,而存储写入的成本更高。平均而言,一个区块大约会执行 1000 次存储读写(包括 ETH 余额检查、SSTORESLOAD 调用、合约代码读取以及其他操作)。然而,理论上的最大值是 30,000,000/2,100=14,285 次读取。无状态客户端的带宽负载与这个数字直接成正比。

目前,支持无状态客户端的计划是将以太坊的状态树设计从默克尔帕特里夏树转换为Verkle树。然而,Verkle树不具备抗量子特性,并且对于新一波的 STARK 证明系统来说也不是最优的。因此,许多人对于通过二进制默克尔树和 STARK 来支持无状态客户端更感兴趣——要么完全跳过 Verkle,要么在 Verkle 转换几年后,等 STARK 更加成熟时再进行升级。

STARK 证明二进制哈希树分支有许多优势,但它们有一个关键的弱点,即证明生成需要很长时间:虽然 Verkle 树 每秒可以证明超过十万个值,但基于哈希的 STARK 通常每秒只能证明几千个哈希,而且证明每个值都需要包含多个哈希的"分支"

考虑到当前高度优化的证明系统如 BiniusPlonky3,以及专门的哈希算法如 Vision-Mark-32 所展示的数据,我们很可能会在相当长一段时间内处于这样一个阶段:在不到一秒的时间内证明 1,000 个值是可行的,但证明 14,285 个值则不可行。对于普通区块来说这没问题,但在极端情况下(可能由攻击者刻意制造的区块)会导致网络瘫痪。

我们处理这种情况的"传统"方式是重新定价:提高存储读取的成本,将每个区块的最大值降低到更安全的水平。然而,我们已经多次这样做了,再次这样做会让太多应用变得过于昂贵。更好的方法是采用多维 gas 机制:对存储访问进行单独的限制和收费,将平均使用量保持在每个区块 1,000 次存储访问,但将每个区块的上限设置为例如 2,000 次。

更加广义的多维度 gas

另一个值得思考的资源是状态大小的增长:那些会增加以太坊状态大小的操作,全节点从此以后都需要存储这些状态。状态大小增长的独特之处在于,限制它的理由完全来自于长期持续使用,而不是突发性使用。因此,为状态大小增加操作(例如从零到非零的SSTORE、合约创建)添加单独的 gas 维度可能是有价值的,但目标不同:我们可以设置一个浮动价格来针对特定的平均使用量,但完全不设置每个区块的限制。

这展示了多维度 gas 的一个强大特性:它让我们能够针对每种资源分别考虑两个问题:(i)什么是理想的平均使用量,以及(ii)什么是安全的每区块最大使用量。我们不是基于每个区块的最大值来设置 gas 价格,然后让平均使用量随之变化,而是拥有 2n 个自由度来设置 2n 个参数,根据网络的安全需求来调整每个参数。

对于更复杂的情况,比如两种资源的安全考虑部分地叠加的情况,可以通过让操作码或资源消耗多种类型的 gas 来处理(例如,从零到非零的SSTORE可能需要消耗 5000 个无状态客户端证明 gas 和 20000 个存储扩展 gas)。

按交易最大值:实现多维 gas 的较弱但更简单的方式

设 x1 为数据的 gas 成本,x2 为计算的 gas 成本,那么在一维 gas 系统中,我们可以将交易的 gas 成本写作: gas=x1∗data+x2∗computation

在这个方案中,我们改为将交易的 gas 成本定义为: gas=max(x1∗data,x2∗computation)

也就是说,交易不是按数据和计算的总和来收费,而是根据它消耗较多的那种资源来收费。这可以轻松扩展到更多维度(例如 max(...,x3∗storage_access))。

不难看出这种方式如何在保证安全性的同时提高吞吐量。区块中数据的理论最大数量仍然是 GASLIMIT/x1,与一维 gas 方案完全相同。同样,计算的理论最大数量是 GASLIMIT/x2,也与一维 gas 方案完全相同。但是,同时消耗数据和计算的任何交易的 gas 成本都会降低。

这大致就是提议的 EIP-7623 采用的方案,目的是在进一步增加 blob 数量的同时减少最大区块大小。EIP-7623 中的具体机制略微复杂一些:它保持当前每字节 16 gas 的 calldata 价格,但添加了每字节 48 gas 的"底价";交易支付(16 * bytes + execution_gas)和(48 * bytes)中的较高值。因此,EIP-7623 将区块中理论最大的交易 calldata 从约 1.9 MB 减少到约 0.6 MB,同时保持大多数应用程序的成本不变。这种方法的优势在于它对当前的一维 gas 方案只做了很小的改动,因此非常容易实现。

这种方法有两个缺点:

  1. 在某一资源上消耗较大的交易仍然会被不必要地收取高额费用,即使该区块中的所有其他交易几乎不使用该资源。

  2. 它为数据密集型和计算密集型交易创造了激励,使其合并成一个交易包以节省成本。

我认为,即使存在这些缺点,类似 EIP-7623 的规则(无论是针对交易调用数据还是其他资源)都能带来足够大的收益,值得采用。然而,如果我们愿意投入(明显更多的)额外开发工作,确实存在一种更理想的方法。

多维度 EIP-1559:更难但理想的策略

让我们首先回顾一下"常规"EIP-1559 是如何工作的。我们将重点关注 EIP-4844 中引入的用于数据块(blob)的版本,因为它在数学上更优雅。

我们追踪一个参数excess_blobs。在每个区块中,我们设置: excess_blobs <-- max(excess_blobs + len(block.blobs) - TARGET, 0) 其中TARGET = 3。也就是说,如果一个区块的 blob 数量超过目标值,excess_blobs就会增加,如果区块的 blob 数量少于目标值,它就会减少。然后我们设置blob_basefee = exp(excess_blobs / 25.47),其中exp是指数函数 exp(x)=2.71828x 的近似值。

也就是说,每当excess_blobs增加约 25,blob 基础费用就会增加约 2.7 倍。如果 blob 变得太贵,平均使用量就会下降,excess_blobs开始减少,价格也会自动下降。blob 的价格不断调整,以确保区块平均处于半满状态——即每个区块平均包含 3 个 blob。

如果出现短期使用量激增,限制就会生效:每个区块最多只能包含 6 个 blob,在这种情况下,交易可以通过提高优先费用来相互竞争。然而在正常情况下,每个 blob 只需要支付blob_basefee外加一个很小的额外优先费用作为激励就可以被打包。

这种定价机制在以太坊的 gas 费用中已经存在多年:类似的机制在 2020 年就随着EIP-1559引入了。通过 EIP-4844,我们现在有了两个独立浮动的价格:gas 和 blob 的价格

2024 年 5 月 8 日一小时内的 gas 基础费用(以 gwei 为单位)。来源:ultrasound.money

原则上,我们可以为存储读取和其他类型的操作添加更多独立浮动的费用,不过这里有一个需要在下一节详细说明的注意事项。

对于用户来说,体验与现在非常相似:不是支付一个基础费用,而是支付两个基础费用,但您的钱包可以为您抽象这些细节,只显示您预期支付的费用和最高费用。

对于区块构建者来说,大多数情况下最佳策略与现在相同:包含所有有效的内容。大多数区块都未被填满——无论是gas使用量还是blob数量。唯一具有挑战性的情况是当gas或blob数量足以超过区块限制时,构建者需要解决一个多维背包问题来最大化其利润。然而,即便如此也存在相当好的近似算法,而且在这种情况下开发专有算法来优化利润所带来的收益,远小于对MEV进行同样操作所带来的收益。

对于开发者来说,主要挑战在于需要重新设计EVM的特性及其周边基础设施,将当前围绕单一价格和单一限制设计的系统改造为适应多重价格和多重限制的设计。应用程序开发者面临的一个问题是优化变得稍微困难:在某些情况下,您不能再明确地说A比B更高效,因为如果A使用更多的调用数据而B使用更多的执行资源,那么当调用数据费用较低时A可能更便宜,而当调用数据费用较高时则更贵。不过,开发者仍然可以通过基于长期历史平均价格进行优化来获得合理的结果。

多维定价、EVM和子调用(sub-calls)

有一个问题在 blob 中没有出现,在 EIP-7623 或者甚至"完整的"calldata 多维定价实现中也不会出现,但在我们尝试单独对状态访问或任何其他资源定价时会出现:子调用中的 gas 限制

EVM 中的 gas 限制存在于两个地方。首先,每个交易都设置了 gas 限制,这限制了该交易中可以使用的总 gas 量。其次,当一个合约调用另一个合约时,该调用可以设置自己的 gas 限制。这允许合约调用它们不信任的其他合约,并仍然保证在该调用之后有剩余的 gas 来执行其他计算。

一个账户抽象交易的跟踪,其中一个账户调用另一个账户,并且只给被调用者有限数量的 gas,以确保即使被调用者消耗了分配给它的全部 gas,外部调用仍然可以继续运行。

挑战在于:在不同类型的执行之间实现 gas 多维化似乎需要子调用为每种类型的 gas 提供多个限制,这将需要对 EVM 进行深层次的改变,并且与现有应用程序不兼容

这是为什么多维 gas 提案通常止步于两个维度:数据和执行的原因之一。数据(无论是交易 calldata 还是 blob)只在 EVM 外部分配,因此 EVM 内部无需改变就可以对 calldata 或 blob 单独定价。

我们可以考虑用一个**"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_operationsexecution_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=s1/g1g2,s3=s1/g1g3,以此类推。

也就是说,我们将第一类 gas(实际上是 VM 执行)视为一种特权化的"记账单位",然后分配其他类型的 gas,使得子调用在各类型中获得相同的可用 gas 百分比。这种方式虽然有些不优雅,但最大程度地保持了向后兼容性。如果我们想让这个方案在不同类型的 gas 之间更加"中立",以牺牲向后兼容性为代价,我们可以简单地让子调用 gas 限制参数表示当前上下文中剩余 gas 的一个分数(例如[1...63] / 64)。

然而,在任何情况下,都必须强调的是,一旦开始引入多维度的执行 gas,其固有的不优雅程度就会增加,这似乎难以避免。因此,我们的任务是做出一个复杂的权衡:我们是否接受 EVM 层面上略微增加的不优雅性,以安全地释放显著的 L1 可扩展性收益?如果接受,哪个具体提案最适合协议经济和应用开发者?很可能,最佳方案既不是我上面提到的这两个,仍有空间来提出更优雅、更好的解决方案。

ai

多维思维:一种理解以太坊愿景的框架

对于以太坊的愿景,存在着很多不同的解读。有人说以太坊是"世界计算机",有人说它是"去中心化的价值互联网",还有人说它是"代币发行和交易平台"。最近,我们看到了以太坊作为"去中心化互联网和经济基础设施"的愿景。这些解读虽然各自都抓住了重要的一面,但我认为它们都没有完全抓住核心。

我认为更好的理解方式是:以太坊的愿景是多维的。它不仅仅是在某个特定维度上追求卓越,而是在多个维度上同时追求进步,同时也深刻认识到这些维度之间的权衡取舍。

ETHStudy (An Ethereum Ecosystem Initiative)
Supported by Uweb(University of Web3)