Time:2023-10-01 Click:112
原文标题:Should Ethereum be okay with enshrining more things in the protocol?
原文作者:Vitalik Buterin
Odaily星球日报译者 | 念银思唐
特别感谢 Justin Drake, Tina Zhen 和 Yoav Weiss 的反馈和审查。
从以太坊项目开始,就有一个强烈的理念,即试图使核心以太坊尽可能简单,并通过在其上构建协议来尽可能实现这一点。在区块链领域,“在L1上构建”与“专注于L2”的争论通常被认为主要是关于扩展的,但实际上,在满足多种以太坊用户的需求方面存在类似的问题:数字资产交换、隐私、用户名、高级加密、账户安全、抗审查、抢先交易保护,等等。然而,最近有一些谨慎的想法愿意将更多这些功能封装(enshrine)进核心以太坊协议中。
这篇文章将深入探讨最初的最小封装哲学背后的一些哲学推理,以及一些最近思考这些想法的方法。目标将是开始建立一个框架,以便更好地确定可能的目标,在这些目标中,封装某些功能可能值得考虑。
Gavin Wood 在 2015 年初绘制白板图的近似重建,当时我在谈论以太坊 2.0 的样子。
“状态转换函数”(处理区块的函数)将只是单个 VM 调用,所有其他逻辑将通过合约发生:一些系统级合约,但主要是由用户提供的合约。这个模型的一个非常好的功能是,即使是整个硬分叉也可以被描述为对于区块处理器合约而言的单笔交易,该交易将通过链下或链上治理进行批准,然后以升级的权限运行。
2015 年的这些讨论特别适用于我们考虑的两个领域:账户抽象和扩容。在扩容的情况下,我们的想法是尝试创建一个最大程度的抽象形式的扩容,感觉就像上面图表的自然扩展。合约可以调用大多数以太坊节点未存储的数据,协议将检测到这一点,并通过某种非常通用的扩展计算功能来解决调用。从虚拟机的角度来看,该调用将进入某个单独的子系统,然后一段时间后神奇地返回正确的答案。
我们对这种思路进行了简短的探索,但很快就放弃了,因为我们太专注于验证任何类型的区块链扩容都是可能的。尽管我们稍后会看到,数据可用性采样和 ZK-EVM 的结合意味着以太坊扩容的一个可能的未来实际上看起来非常接近这个愿景!另一方面,对于帐户抽象,我们从一开始就知道某种实现是可能的,因此研究立即开始尝试使一些尽可能接近“交易只是一个调用”的纯粹出发点的东西变为现实。
在处理交易和从发送方地址发出实际的底层 EVM 调用之间,会出现大量的样板代码,之后还会出现更多的样板代码。我们如何将这些代码尽可能减少到接近于零?
这里的主要代码之一是 validate_transaction(state, tx),它负责检查交易的 nonce 和签名是否正确。从一开始,帐户抽象的实际目标就是允许用户用自己的验证逻辑替换基本的非增量验证和 ECDSA 验证,这样用户就可以更轻松地使用社交恢复和多签名钱包等功能。因此,找到一种方法将 apply_transaction 重新架构为一个简单的 EVM 调用,这不仅仅是一个“为了使代码干净而使代码干净”的任务;相反,它是关于将逻辑移动到用户的帐户代码中,为用户提供所需的灵活性。
然而,坚持让 apply_transaction 包含尽可能少的固定逻辑的做法最终带来了很多挑战。我们可以看下最早的帐户抽象提案之一 EIP-86 。
如果按原样包含 EIP-86 ,它将降低 EVM 的复杂性,代价是大量增加以太坊堆栈其他部分的复杂性,需要在其他地方编写本质上完全相同的代码,除了引入全新的怪异类别之外,例如具有相同哈希值的同一交易可能会在链中多次出现,更不用说多重失效问题了。
账户抽象中的多重失效问题。在链上包含的一笔交易可能会使内存池中的数千笔其他交易无效,从而使内存池很容易被廉价地充斥。
从那时起,帐户抽象分阶段发展。EIP-86 后来变成了 EIP-208 ,最终出现了实际可行的 EIP-2938 。
然而,EIP-2938 一点也不简约。其内容包括:
新的交易类型
三个新交易范围的全局变量
两个新的操作码,包括非常笨拙的 PAYgas 操作码,用于处理 gas 价格和 gas 限制检查,作为 EVM 执行断点,以及临时存储 ETH 以一次性支付费用
一组复杂的挖矿和转播策略,包括交易验证阶段禁止的操作码列表
为了在不涉及以太坊核心开发人员(专注于优化以太坊客户端和实现合并)的情况下实现账户抽象,EIP-2938 最终被重新架构为完全是协议外的 ERC-4337 。
gas 效率:在 EVM 内部进行的任何操作都会导致一定程度的虚拟机开销,包括在使用诸如存储槽之类 gas 费昂贵的功能时效率低下。目前,这些额外的低效率加起来至少要消耗 2 万 gas,甚至更多。将这些组件放入协议中是消除这些问题的最简单方法。
代码 bug 风险:如果 ERC-4337 “入口点合约”有一个足够可怕的 bug,所有与 ERC-4337 兼容的钱包都可能看到他们所有的资金枯竭。用协议内功能取代合约会产生一种隐含的责任,即通过硬分叉修复代码错误,从而消除用户的资金枯竭风险。
支持 EVM 操作码,如 txt.origin。ERC-4337 本身使 txt.origin 返回将一组用户操作打包到交易中的“捆绑器(bundler)”的地址。原生帐户抽象可以解决这个问题,方法是使 txt.origin 指向发送交易的实际帐户,使其运作方式与 EOA 相同。
抗审查:提议者/构建者分离的挑战之一是审查单笔交易变得更容易。在以太坊协议可以识别单笔交易的世界中,包含列表可以极大地缓解这个问题,该列表允许提议者指定在几乎所有情况下必须包含在接下来两个插槽中的交易列表。但是协议外的 ERC-4337 将“用户操作”封装在单笔交易中,使得用户操作对以太坊协议不透明;因此,以太坊协议提供的包含列表将无法对 ERC-4337 用户操作提供审查阻力。封装 ERC-4337 ,并使用户操作成为一种“适当的”交易类型,将解决这个问题。
值得一提的是,在目前的形式下,ERC-4337 比“基本”以太坊交易要贵得多:交易成本为 21, 000 gas,而 ERC-4337 的成本约为 42, 000 gas。
理论上,应该有可能对 EVM gas 成本系统进行调整,直到协议内成本和协议外访问存储的成本相匹配;当其他类型的存储编辑操作更便宜时,转移 ETH 没有理由需要花费 9000 gas。事实上,与即将到来的 Verkle 树转换相关的两个 EIP 实际上试图做到这一点。但是,即使我们这样做了,有一个显而易见的原因可以解释为什么无论 EVM 变得多么高效,封装的协议功能都将不可避免地比 EVM 代码便宜得多:封装代码不需要为预加载支付 gas。
功能齐全的 ERC-4337 钱包很大,编译并放到链上的这个实现占用了约 12, 800 字节。当然,你可以一次部署这段代码,并使用 DELEGATECALL 来允许每个单独的钱包调用它,但是仍然需要在使用它的每个区块中访问该代码。在 Verkle 树 gas 成本 EIP 下, 12, 800 字节将组成 413 个 chunk,访问这些 chunk 将需要支付 2 倍的 witness branch_cost(总共 3, 800 gas)和 413 倍的 witness chunk_cost(总共 82, 600 gas)。这甚至还没有开始提到 ERC-4337 入口点本身,在 0.6.0 版本中,链上有 23, 689 字节(根据 Verkle 树 EIP 规则,约有 158, 700 个 gas 要加载)。
这就导致了一个问题:实际访问这些代码的 gas 成本必须以某种方式在交易中分摊。ERC-4337 使用的当前方法不是很好:bundle 中的第一笔交易消耗了一次性存储/代码读取成本,使其比其他交易昂贵得多。协议内封装将允许这些公共共享库成为协议的一部分,所有人都可以免费访问。
当固定成本较高时,“将复杂性推向边缘”的基于市场的方法最容易失败。事实上,长期的账户抽象路线图看起来每个区块都有很多固定的成本。244, 100 gas 用于加载标准化钱包代码是一回事;但是聚合可能会为 ZK-SNARK 验证增加数十万的 gas,以及证明验证的链上成本。没有一种方法可以在不引入大量市场低效率的情况下向用户收取这些成本,而将其中一些功能转化为所有人都可以免费访问的协议功能则可以很好地解决这个问题。
社区范围内对代码 bug 的响应。如果一些代码片段被所有用户或非常广泛的用户使用,那么区块链社区承担硬分叉的责任来修复出现的任何错误通常更有意义。ERC-4337 引入了大量的全局共享代码,从长远来看,通过硬分叉修复代码中的错误无疑比导致用户损失大量 ETH 更合理。
有时,可以通过直接利用协议的功能来实现其更强形式。这里的关键例子是协议内的抗审查功能,如包含列表:协议内的包含列表可以比协议外的方法更好地保证审查阻力,为了使用户级操作真正受益于协议内的包含列表,单个用户级操作需要对协议“易读”。另一个鲜为人知的例子是, 2017 年时代的以太坊权益证明设计对权益密钥进行了账户抽象,这被放弃了并转而支持封装 BLS,因为 BLS 支持一种“聚合”机制,必须在协议和网络层面实现,这可以使处理大量签名的效率更高。
但重要的是要记住,与现状相比,即使是封装协议内账户抽象,仍然是一种巨大的“去封装化”。如今,顶级以太坊交易只能从外部拥有的账户(EOA)发起,这些账户使用单个 secp 256 k 1 椭圆曲线签名进行验证。帐户抽象消除了这一点,并将验证条件留给用户自行定义。因此,在这个关于账户抽象的故事中,我们也看到了反对封装的最大理由:灵活地满足不同用户的需求。
让我们通过查看最近被考虑封装的其他几个特征示例来进一步充实这个故事。我们将特别关注:ZK-EVM,提议者-构建者分离,私有内存池,流动性质押和新的预编译。
EVM ZK-rollup 领域最近的一个争议与如何处理 ZK 代码中可能出现的 bug 有关。目前,所有这些正在运行的系统都有某种形式的“安全理事会”机制,可以在出现 bug 的情况下控制证明系统。去年,我试图创建一个标准化的框架,以鼓励项目明确他们对证明系统的信任程度,以及对安全理事会的信任程度,并随着时间的推移,逐渐减少对该组织的权力。
从中期来看,rollup 可能依赖于多个证明系统,而安全理事会只有在两个不同的证明系统产生分歧的极端情况下才有权力。
然而,有一种感觉是,其中一些工作感觉是多余的。我们已经有了以太坊基础层,它有一个 EVM,我们已经有了一个处理实现中 bug 的工作机制:如果有 bug,客户端将进行更新来修复,然后链继续运作。从有 bug 的客户端角度来看,似乎已经最终确认的区块将不再确认,但至少我们不会看到用户损失资金。同样,如果 rollup 只是想保持与 EVM 等同的作用,那么它们需要实施自己的治理来不断更改其内部 ZK-EVM 规则以匹配对以太坊基础层的升级,这感觉是错误的,因为最终它们是在以太坊基础层本身之上构建的,它知道何时升级以及根据什么新规则。
由于这些L2 ZK-EVM 基本上使用与以太坊完全相同的 EVM,我们能否以某种方式将“验证 EVM 在 ZK 中的执行”纳入协议功能,并通过应用以太坊的社会共识来处理异常情况,如 bug 和升级,就像我们已经为基础层 EVM 执行本身所做的那样?
这是一个重要而富有挑战性的话题。
关于原生 ZK-EVM 中数据可用性的一个可能的争论主题是有状态性(statefulness)。如果 ZK-EVM 不需要携带“见证(witness)”数据,它们的数据效率就会高得多。也就是说,如果某个特定的数据已经在之前的某个区块中被读取或写入,我们可以简单地假设证明者可以访问它,并且不必再次使它可用。这不仅仅是不重新加载存储和代码;事实证明,如果一个 rollup 正确地压缩了数据,那么与无状态压缩相比,有状态压缩最多可以节省 3 倍的数据。
这意味着对于 ZK-EVM 预编译,我们有两个选项:
1.预编译要求所有数据在同一区块中可用。这意味着 prover 可以是无状态的,但也意味着使用这种预编译的 ZK-rollup 比使用自定义代码的 rollup 要昂贵得多。
2.预编译允许 pointer 指向先前执行使用或生成的数据。这使得 ZK-rollup 接近最优,但它更复杂,并引入了一种必须由 prover 存储的新状态。
我们能从中学到什么?以某种方式将 ZK-EVM 验证封装有一个很好的理由:rollup 已经在构建自己的自定义版本,并且以太坊愿意将其多个实现和链下社会共识的权重置于L1上执行 EVM,这感觉是错误的,但是做完全相同工作的L2必须实现涉及安全理事会的复杂小工具。但另一方面,细节中有一个大问题:有不同版本的 ZK-EVM,它们的成本和收益不同。有状态和无状态的区分只是触及了表面;尝试支持“近 EVM(almost-EVM)”,这些定制代码已经被其他系统证明,这可能会暴露出更大的设计空间。因此,封装 ZK-EVM 既带来了希望,也带来了挑战。
然而,MEV-Boost 在一个新的参与者类别中进行了信任假设,称为中继(relay)。在过去的两年里,有很多人提议创建“封装 PBS”。这样做的好处是什么?在这种情况下,答案非常简单:通过直接使用协议功能构建的 PBS 比不使用它们构建的 PBS 更强大(在具有更弱的信任假设的意义上)。这与封装协议内价格预言机的情况类似——尽管在这种情况下,也存在强烈的反对意见。
对中心化运营商进行加密,例如 Flashbots Protect。
时间锁加密,该加密形式经过一定的顺序计算步骤后,任何人都可以解密,并且不能并行化;
阈值加密,信任一个诚实的多数委员会来解密数据。具体建议请参见封闭信标链概念。
可信硬件,如 SGX。
不幸的是,每一种加密方式都有不同的弱点。虽然对于每个解决方案,都有一部分用户愿意信任它,但没有一个解决方案的信任程度足以让它实际上被 Layer 1 接受。因此,至少在延迟加密得到完善或其他一些技术突破之前,在 Layer 1 封装反提前交易功能似乎是一个困难的命题,即使它是一个足够有价值的功能,许多应用程序解决方案已经出现。
每个版本的质押 ETH 都需要有一些机制来确定谁可以成为底层节点运营商。它不能是无限制的,因为这样攻击者就会加入并利用用户的资金扩大攻击。目前,排名前两位的是 Lido 和 Rocket Pool,前者拥有 DAO 白名单节点运营商,后者允许任何人在存入 8 枚 ETH 的情况下运行一个节点。这两种方法有不同的风险:Rocket Pool 方法允许攻击者对网络进行 51% 的攻击,并迫使用户支付大部分成本;至于 DAO 方法,如果某质押代币占主导地位,就会导致一个单一的、可能受到攻击的治理小工具控制所有以太坊验证者的很大一部分。值得肯定的是,像 Lido 这样的协议已经实施了防范措施,但一层防御可能还不够。
在短期内,一种选择是鼓励生态系统参与者使用多样化的流动性质押提供商,以减少一家独大带来系统性风险的可能性。然而,从长期来看,这是一种不稳定的平衡,过度依赖道德压力来解决问题是危险的。一个自然的问题出现了:在协议中封装某种功能以使流动性质押不那么中心化是否有意义?
这里的关键问题是:什么样的协议内功能?简单地创建一个协议内可替代的“质押 ETH”代币存在一个问题,即它要么必须有一个封装以太坊范围内治理来选择谁来运行节点,要么是开放的,但这会把它变成攻击者的工具。
一个有趣的想法是 Dankrad Feist 关于流动性质押最大化的文章。首先,我们咬紧牙关,如果以太坊受到 51% 攻击,可能只有 5% 的攻击 ETH 被罚没。这是一个合理的权衡;目前有超过 2600 万枚 ETH 被质押,其中三分之一(约 800 万枚 ETH)的攻击成本是过度的,特别是考虑到有多少种“模型外”攻击可以以更低的成本完成。事实上,类似的权衡已经在“超级委员会”关于实施 single-slot finality 的提案中进行了探讨。
如果我们接受只有 5% 的攻击 ETH 被罚没,那么超过 90% 的质押 ETH 将不会受到罚没的影响,因此它们可以作为协议内可替代流动性质押代币,然后被其他应用程序使用。
这条路径很有趣。但它仍然留下了一个问题:具体封装什么?Rocket Pool 的运作方式与此非常相似:每个节点运营商提供一些资金,流动性质押者提供其余的资金。我们可以简单地调整一些常量,将最大罚没惩罚限制为 2 枚 ETH,Rocket Pool 现有的 rETH 将变得无风险。
通过简单的协议调整,我们还可以做其他聪明的事情。例如,假设我们想要一个系统,有两种“层”的质押:节点运营商(高抵押品要求)和储户(没有最低抵押品要求,可以随时加入和离开),但我们仍然希望通过赋予一个随机抽样的储户委员会权力来防止节点运营商的中心化,比如建议必须包括的交易列表(出于抗审查的原因),在不活动泄漏期间控制分叉选择,或者需要在区块上签名。这可以通过一种基本上脱离协议的方式来实现,方法是调整协议,要求每个验证器提供(i)一个常规的质押密钥,以及(ii)一个 ETH 地址,该地址可以在每个槽间被调用以输出二级质押密钥。该协议将赋予这两项密钥权力,但在每个槽中选择第二个密钥的机制可以留给质押池协议。直接封装一些功能可能仍然更好,但值得注意的是,这种“包含一些东西,把其他东西留给用户”的设计空间是存在的。
预编译(或“预编译合约”)是实现复杂加密操作的以太坊合约,其逻辑在客户端代码中原生实现,而不是 EVM 智能合约代码。预编码是以太坊开发之初采用的一种折衷方案:由于虚拟机的开销对于某些非常复杂和高度专业化的代码来说太大了,我们可以在本地代码中实现一些对重要应用程序有价值的关键操作,以使其更快。如今,这基本上包括一些特定的散列函数和椭圆曲线运算。
目前有人在推动为 secp 256 r 1 添加预编译,这是一种与用于基本以太坊账户的 secp 256 k 1 略有不同的椭圆曲线,因为它得到了可信硬件模块的良好支持,因此广泛使用它可以提高钱包安全性。近年来,社区还推动为 BLS-12-377、BW 6-761、广义配对和其他功能添加预编译。
对这些要求更多预编译文件的反驳是,之前添加的许多预编译(例如 RIPEMD 和 BLAKE)最终的使用量远低于预期,我们应该从中吸取教训。与其为特定操作添加更多的预编译,我们也许应该专注于一种更温和的方法,该方法基于 EVM-MAX 和休眠但始终可恢复的 SIMD 提案等思想,这将使 EVM 实现能够以更低的成本执行广泛的代码类。也许即使是现有的很少使用的预编译也可以被删除,并用相同函数的 EVM 代码实现(不可避免地效率较低)代替。也就是说,仍然有可能存在特定的加密操作,这些操作的价值足以加速,因此将它们作为预编译添加是有意义的。
在许多情况下,这些其他的例子与我们在帐户抽象中看到的类似。但我们也学到了一些新的教训:
封装功能可以帮助避免堆栈中其他区域的中心化风险:
通常,保持基本协议的最小化和简单性会将复杂性推到一些协议之外的生态系统。从 Unix 哲学的角度来看,这很好。然而,有时存在协议外生态系统将中心化的风险,通常(但不仅仅是)因为高固定成本。封装有时可以减少事实上的中心化。
封装太多内容,可能会过度扩大协议的信任和治理负担:
这是前一篇关于“不要让以太坊共识过载”文章的主题:如果封装一个特定的功能削弱了信任模型,并使以太坊作为一个整体变得更加“主观”,这就削弱了以太坊的可信中立性。在这些情况下,最好将特定功能作为以太坊之上的机制,而不是试图将其引入以太坊本身。在这里,加密内存池是最好的例子,它可能有点难以封装,至少在延迟加密技术改进之前是这样。
封装太多内容可能会使协议过于复杂:
协议复杂性是一种系统性风险,在协议中添加太多功能会增加这种风险。预编译就是最好的例子。
长期来看,封装功能可能会适得其反,因为用户的需求是不可预测的:
一个很多人认为很重要并且会被很多用户使用的功能,很可能在实践中并没有被经常使用。
此外,流动性质押、ZK-EVM 和预编译案例显示了一条中间道路的可能性:最小可行封装(minimal viable enshrinement)。协议不需要封装整个功能,而可以包含解决关键挑战的特定部分,使该功能易于实现,而不会过于偏执或过于狭隘。这样的例子包括:
与其封装一个完整的流动性质押系统,不如改变质押惩罚规则,让去信任流动性质押更可行;
与其封装更多的预编译器,不如封装 EVM-MAX 和/或 SIMD,以使更广泛的操作类别更容易有效地实现;
可以简单地封装 EVM 验证,而不是封装 rollup 的整个概念。
我们可以将前面的图表扩展如下:
有时候,去封装一些东西是有意义的,去除很少使用的预编译就是一个例子。帐户抽象作为一个整体,正如前面提到的,也是一种重要的去封装形式。如果我们想支持现有用户的向后兼容性,那么该机制实际上可能与去封装预编译的机制惊人地相似:其中一个提案是 EIP-5003 ,它将允许 EOA 将其帐户转换为具有相同(或更好)功能的合约。
哪些功能应该被引入协议,哪些功能应该留给生态系统的其他层,这是一个复杂的权衡。随着我们对用户需求的理解以及可用想法和技术套件的不断改进,这种权衡有望随着时间的推移而继续改进。