通过创建新的状态形式来实现超大规模状态扩展

本文讨论了以太坊状态扩展的挑战和解决方案。由于ZK-EVM和PeerDAS分别解决了执行和数据扩展问题,状态扩展成为瓶颈。文章分析了强无状态性和状态过期的局限性,并提出了分层状态的概念,包括临时存储和UTXO,以实现更高效的状态管理,同时保持良好的开发者体验。

特别感谢 Guillaume Ballet、Marius van der Wijden、Jialei Rong、CPerezz、Han、soispoke、Justin Drake、Maria Silva 和 Anders Elowsson 的反馈和审阅

为了在未来五年内扩展以太坊,我们需要扩展三种资源:执行(EVM 计算、签名验证……)、数据(交易发送者、接收者、签名、calldata……)和 状态(帐户余额、代码、存储)。我们的目标是将以太坊扩展约 1000 倍。但这三种资源的前两种和第三种之间存在根本的不对称性。

短期 长期
执行 ePBS、区块级别访问列表 (BAL) 和 gas 重新定价 → 增加约 10-30 倍 ZK-EVM(大多数节点可以完全避免重新执行区块)→ 增加约 1000 倍<br>对于一些特定类型的计算(签名、SNARK/STARK),链下聚合可以使我们 增加约 10000 倍
数据 p2p 改进、多维 gas → 增加约 10-20 倍 Blobs 中的区块 + PeerDAS → 8 MB/秒(增加约 500 倍)
状态 使用 BAL 进行同步、p2p 改进、数据库改进 → 增加约 5-30 倍

在短期内,我们可以提高所有三种资源的效率。但为了实现长期所需的 1000 倍的增长,我们需要杀手锏。ZK-EVM 是执行的杀手锏。PeerDAS 是数据的杀手锏。但是没有状态的杀手锏。那么我们该怎么办?

这篇文章提出了一个雄心勃勃的解决方案:在对当前状态进行增量改进(例如,二叉树、叶子过期)以及像多维 gas 这样的定价改进的同时,我们在现有状态旁边引入新的(更便宜但限制性更强的)状态形式

主要的权衡是,如果我们设置状态增长的“目标”水平,例如今天的 20 倍,执行水平为今天的 1000 倍,那么执行和存储之间的“相对价格”将与今天相比发生巨大变化。创建一个新的存储槽可能真的比验证 STARK 更昂贵(!!)。可能两者都会很便宜,但关于什么更 便宜 的心智模型将会发生巨大变化。

因此,开发人员将有一个选择:如果他们以与今天相同的方式继续构建应用程序,他们可以获得 相当低 的交易费用,或者如果他们重新设计他们的应用程序以利用较新的存储形式,则可以获得 非常低 的交易费用。对于常见的用例(例如,ERC20 余额、NFT),将有标准化的工作流程。对于更复杂的用例(例如,defi),他们必须自己想出特定于应用程序的技巧来实现它。

为什么我们不能按原样将状态扩展超过约 10-20 倍?

920×542 31.5 KB

今天,状态正以每年约 100 GB 的速度增长。如果我们将它增加 20 倍,这将变为每年 2 TB。4 年后,这意味着状态大小为 8 TB。

此状态将不必由完全验证节点存储(因为它们可以通过 ZK-EVM + PeerDAS 获得所需的一切),甚至不必由 FOCIL 节点存储(VOPS 明显更小;即使没有任何更改,它也将小于 1 TB,可能小于 512 甚至 256 GB,并且可以进一步优化)。但它将是构建者所需要的(不仅仅是成熟的构建者,还有运行“vanilla”软件的本地“家庭构建者”)。

将 8 TB 存储在磁盘上本身并不困难。我们可能需要一些技巧来仅存储部分“热”状态,并对其他部分收取额外的 gas 费用,以便可以将它们存储在平面文件中而不是数据库中,但最终 8 TB 磁盘很容易获得。有两个“困难的部分”:一个短期和一个长期:

  • 短期:数据库效率。客户端中当前的数据库并非旨在处理数 TB 级别的状态。今天的一个主要问题是状态写入需要对树进行 log(n) 次更新,并且这些更新中的每一次本身就是数据库中的 log(n) 个操作,并且一旦 n 比 RAM 大很多倍,具体因素就会迅速爆炸。已经有解决此问题的数据库设计;它们只需要改进并广泛采用。
  • 长期:同步。我们希望成为构建者是无需许可的,并且设置起来并非不合理。即使效率很高,8 TB 也需要很长时间才能同步,并且在许多情况下甚至会达到每月带宽限制。

除了 你从谁那里同步状态、谁运行 RPC 节点以及许多其他问题之外,所有这些都是额外的。作为第一个近似值,随着状态大小每增加一倍,愿意利他地(甚至收费地)使用状态执行某些功能的参与者的数量将减少一半。数据库效率的提高、使用 HDD 和平面文件代替 SSD 和数据库等,仅部分地解决了一些问题,而根本没有解决其他问题。

与数据和计算不同,这些不是我们可以说“我们依靠专业构建者进行扩展,如果他们都消失了,常规构建者可以只构建大小为 10% 的区块”的领域。要构建一个区块,你需要完整状态。减少 gas 限制并不会使状态变小。

这意味着 (i) 我们需要对状态比对计算和数据更保守,并且 (ii) 许多适用于计算和数据的“分片”技术 适用于状态存储。

为什么强无状态可能不足

已经提出的一种解决此问题的主要策略是 强无状态。我们将要求进行交易的用户 (i) 指定其交易读取和写入的帐户和存储槽,并规定任何超出此集的尝试读取或写入都将失败,并且 (ii) 提供 Merkle 分支来证明其状态访问。

如果我们这样做,我们不再需要构建者存储完整状态。相反,用户自己将存储与他们自己的使用相关的状态(和更新的证明),或者一个去中心化网络将存储状态(例如,每个节点将保留随机的 1/16)。

581×491 25.1 KB

这种方法有三个主要缺点:

  • 链下基础设施依赖性:实际上,用户将无法存储与其自身使用相关的状态和证明,因为“与其自身使用相关”的内容将不断变化,并且因为许多用户不会一直在线以下载更新的分支。因此,我们需要一个用于状态存储和检索的去中心化网络。这也对用户有隐私影响。
  • 向后不兼容性:所访问的存储是交易执行的动态功能的应用程序从根本上与绑定访问列表不兼容。这可能包括链上订单簿、可附加列表和许多其他类型的结构。对于这些应用程序,用户需要在本地进行模拟,创建一个访问列表,然后大部分时间交易将简单地在链上失败(同时消耗 gas),他们将不得不重复重试。
  • 带宽成本:对于每个访问的存储槽,交易都需要提供约 1000 字节的证明。这大大增加了交易大小(例如,一个简单的 ERC20 转移可能需要 4 kB 的见证数据:发送者帐户的一个分支,ERC20 代币的一个分支,发送者余额的一个分支,接收者余额的一个分支,而今天约为 200 字节)

一个较弱的强无状态版本是一个被称为“冷状态”的想法。这个想法是,某些状态(例如,一年多未访问的状态)仍然可以访问,但具有异步延迟(实际上,在一秒到一个槽之间)。这将允许构建者在没有该存储的情况下运行,因为每当他们遇到读取时,他们都会 ping 去中心化网络以获取数据。

这个解决方案可能有效,但由于其紧密的基础设施依赖性,它很脆弱,并且它具有相同的向后不兼容性问题。最坏情况下的交易具有通过冷状态的不同区域的深度多跳依赖关系图:调用 A,取决于输出调用其他地址 B,取决于该调用的输出调用其他地址 C……

为什么状态过期很难向后兼容

十年来,人们一直试图提出 状态过期:如果长时间未访问的状态会自动从活动状态中删除,并且想要继续使用该状态的用户必须主动“复活”它的设计。

这些设计不可避免地遇到的问题是:当你创建新状态时,你如何证明以前从未有任何东西在那里

如果你在地址 X 创建一个帐户,你必须证明在地址 X 没有创建任何其他东西,不仅是今年或去年,而是以太坊历史上的每一年。

531×261 12.7 KB

如果我们每年创建一个新树来表示该年修改的状态(“重复再生”),那么在第 N 年创建一个新帐户需要 N 次查找。

发明的一种缓解此问题的想法是 地址周期 机制:我们添加一个新的帐户创建规则集(“CREATE3”),让你创建一个地址,该地址显然仅在 >= N 年有效。当你创建这样的帐户时,无需为任何 < N 年提供证明,因为那时存在这样的帐户是不可能的。

但是,一旦我们开始尝试将其集成到真正的以太坊世界中,这种设计就会有很大的局限性。特别是,请注意,对于每个新帐户,我们不仅需要创建 帐户,还需要创建 存储槽。如果你创建一个新帐户 X,那么你必须为要持有的每个代币创建 X 的 ERC20 余额。由于地址周期机制无法被现有的 ERC20“理解”,这使得情况变得更糟:即使以太坊协议可能理解到一个帐户 X,其地址以某种方式通过新的地址生成机制对“2033”进行编码,不可能在 2033 年之前被创建,但 X 的余额的存储槽是 sha256(…, X),这是一种协议无法理解的不透明机制。

我们 可以 通过一些巧妙的技巧来解决这个问题。一个想法是设计新的 ERC20,使其以每年生成一个新的子合约的方式,该子合约持有与该年相关的帐户的余额。另一个想法是创建一种可以由代币合约拥有的新型存储,但“与”你的帐户“共存”,因此可以与你的帐户一起过期和复活。但所有这些想法都需要 ERC20 代币合约完全重写其逻辑,因此它不向后兼容。

所有使状态过期向后兼容的尝试似乎都遇到了类似的问题,所有这些问题都源于没有好的方法来证明不存在。同样不幸的是,没有办法创建一个“以前未创建哪些地址/存储槽”的映射,它明显小于或简单于当前状态。映射{32 字节键:{0,1}}具有映射{32 字节键: 32 字节值}的所有实现复杂性。

从这些探索中汲取的教训

我们可以将我们对强无状态和状态过期的探索中获得的大局经验压缩为两个大的“程式化事实”:

  1. 用 Merkle 分支替换所有状态访问太多(在带宽方面),用 Merkle 分支替换特殊情况状态访问是可以接受的。 “状态过期”实际上是后者的一个例子:如果我们假设例如 90% 的状态访问接触到的状态不到六个月,10% 接触到的状态较旧,那么我们可以只将最近的状态保存在“构建者必须”的存储桶中,并要求提供较旧状态的见证,而不是支付 20 倍的开销,我们平均只会支付 2 倍的开销(加上原始交易)。

    这种探索似乎不可避免地导致我们需要 分层状态:区分我们知道会被经常访问的高价值状态,以及我们预计会被很少访问的低价值状态。分层可以通过 (i) 像状态过期提案中那样按时间顺序进行,或者 (ii) 通过显式的单独状态类进行(例如,VOPS 是一个非常有限的版本)。

  2. 向后兼容性非常困难。在某些方面(尤其是动态同步调用)操纵较低级别的状态不仅 更昂贵,而且 根本无法以这种方式操纵。因此,如果我们想避免彻底破坏应用程序,唯一的选择可能是将现有应用程序推入更昂贵的状态层,并要求开发人员主动选择使用更新的更便宜的状态层。

创建新的更便宜的状态类型会是什么样子?

如果无法使现有状态向后兼容地过期,那么自然的解决方案是接受缺乏向后兼容性,而是采取 "哑铃解决方案":

  • 几乎完全保持现有状态不变,但允许它变得相对更昂贵(即,执行可能会便宜 1000 倍,但创建新状态可能只会便宜 20 倍)
  • 创建新型状态,从一开始就设计为对极高水平的可扩展性友好。

在设计这些新型状态时,我们应该牢记今天构成以太坊状态的大部分是哪些类型的对象:如 ERC20 代币和 NFT 之类的余额。

临时存储

一个想法是创建一个新的临时存储类别,其持续时间为中等长度。例如,我们可以有一个新的树,每次新的期间(例如 1 个月)开始时,该树都会归零。

这种类型的存储非常适合处理链上事件的临时状态:拍卖、治理投票、游戏中的单个事件、欺诈证明挑战机制等。其 gas 成本可以设置得非常低,允许它与执行一起扩展 1000 倍。

要在此类状态之上实现 ERC20 风格的余额,不可避免地必须解决一个问题:如果有人进入洞穴超过一个月会发生什么?

关于 ERC20 用例的一件好事是它对乱序恢复很友好。例如:如果你在 2025 年收到 100 DAI,然后忘记了它,然后在 2027 年在同一帐户中收到 50 DAI,然后最终记得你有较早的余额并恢复它,你将获得 150 DAI。你甚至不必提供任何证明说明 2026 年你的帐户是否发生了任何事情(如果你也收到了代币,你可以稍后提供该证明)。我们唯一想要防止的是使用相同的历史状态来恢复你的余额两次。

以下是如何进行恢复的草图:

  • 作为树设计的一部分,在每个节点中存储“在”该节点“下”的叶子的总数。这使得在任何叶子的证明中,同时验证叶子在树中的索引(从左到右的顺序)变得切实可行:你可以将证明中所有左向姐妹节点的总数加起来。
  • 对于每个月,我们将存储一个永久状态的位域,每个值在删除时在树中对应一个位。此位域初始化为全零。
  • 如果在 N 月期间,帐户 X 有权写入槽 Y,则在任何 > N 月的期间,该帐户都可以编辑其相应的位域值。此外,任何人在任何时候都可以简单地提供证明。

961×301 9.01 KB

  • ERC20 合约默认将余额存储在当前树中,但它会有一个余额恢复功能,该功能将证明发送者历史状态条目的分支作为输入,并在将位从 0 翻转到 1 时恢复余额,因此无法再次恢复。

今天,状态每年增长约 100 GB。如果我们以 1000 倍的速度扩展以太坊,我们可以想象相同的状态增长水平乘以 1000,但所有这些都存储在临时树中。这将意味着大约 8 TB 的状态(假设 1 个月的有效期),加上可以忽略不计的数量(每个 64 字节状态条目 1 位,因此 8 TB * 1/8 / 64 = 每月 16 GB)的永久存储。

UTXO

或者,我们可以将临时存储的想法发挥到极致:将有效期设置为零。本质上,合约可以创建记录,这些记录将被哈希到该区块的树中,并立即直接进入历史记录。对于每个区块,你都会在状态中有一个位域,它将存储记录是“已花费”还是“未花费”,这将是永久状态,但除此之外就没了。

791×378 13.7 KB

可能,可以通过简单地重用现有的 LOG 机制并在其上添加状态位域机制来构建 UTXO。如果我们 真的 想要,它甚至可以作为一个完全链外的 ERC 来完成,尽管这有一些缺点:一些随机用户将有责任支付比其他人多 256 倍的费用,才能成为第一个创建表示位域大小为 256 部分的新存储槽的人。

ERC20、NFT 和各种其他状态都可以建立在这些类型的 UTXO 之上。

这些想法之间也存在一个混合版本。我们进行 UTXO,但我们使最新例如一个月的树可以直接从状态访问,而无需见证。这减少了带宽负载(以更高的存储大小为代价)。这与临时存储想法之间的主要区别在于,这里只有一种“接口”用于处理先前创建的对象(作为 UTXO),而不是两种(作为当前纪元存储槽和作为先前纪元 UTXO)。

强无状态存储树

我们也可以创建一个单一的存储树,该树需要 Merkle 分支才能访问(或者,节点必须存储它的顶 N-8 层(即大小的 1/256),并且节点将提供 256 字节的见证)。这个新的存储树将与现有的存储树并存,因此我们将有两个存储树:一个需要见证的更便宜的树和一个不需要见证的更昂贵的树。

但是,我的观点是,这不如临时存储提案。原因是,从长远来看,如果这样一棵树的大部分都是被遗忘的垃圾,并且人们只关心其中的一小部分,那么网络最多必须存储包括垃圾在内的整个树,或者最多存储证明有用状态的超长 Merkle 分支(因为每个有用的状态对象都将被树中的垃圾“包围”)。而双树设计只允许两个“层”的存储,而临时存储设计则为建立在其之上的状态过期机制创造了空间,允许时间分层存在于经济分层之上。

对开发者体验的影响

以下是一些应用程序和工作流程如何使用这些类型的分层架构的草图:

  • 用户帐户(包括与原生 AA 关联的每个帐户的新状态)将位于永久存储中。因此,用户帐户可以随时以低廉且轻松的方式访问。
  • 智能合约代码 都将位于永久存储中。
  • NFT、ERC20 代币余额等 将位于 UTXO 或临时存储中。
  • 与短期事件关联的状态(例如,拍卖、欺诈证明游戏、预言机游戏、治理行动)将位于临时存储中。
  • 核心 defi 合约 将位于永久存储中,以最大化可组合性。
  • defi 活动的各个单元(例如,CDP)在许多情况下将位于 UTXO 或临时存储中。

一开始,开发人员可以继续将所有内容放入现有的永久存储中。可能,NFT 和 ERC20 代币余额将是最容易移动到 UTXO 或临时存储中的。从那时起,生态系统将随着时间的推移优化更高效的存储使用。

这里的基本假设是,“混合永久存储和 UTXO”比“仅 UTXO”更容易开发。以太坊作为开发者平台如此成功的一个主要原因是,帐户和存储槽比 UTXO 更便于开发者使用。如果我们能够提供便于开发者使用的抽象,允许例如 95% 的状态使用转移到 UTXO 而没有明显的痛苦,并将剩余的 5% 作为永久存储(并让开发者即使感到困惑也能够以更高的成本继续使用永久存储来完成所有内容),那么我们可以同时获得 UTXO 的大部分扩展好处和以太坊风格帐户和存储槽的大部分开发者友好性好处。

  • 原文链接: ethresear.ch/t/hyper-sca...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
以太坊中文
以太坊中文
以太坊中文, 用中文传播以太坊的最新进展