当前位置: 首页 > 币圈资讯 > 文章正文

V神:以太坊协议是否应该封装更多功能?

作者: 投资币 时间: 2024-11-01 01:31 阅读: 1238

从以太坊项目开始,就有一个强烈的理念,即试图使核心以太坊尽可能简单,并通过在其上构建协议来尽可能实现这一点。在区块链领域,在L1上构建与专注于L2”的争论通常被认为主要是关于扩展的,但实际上,在满足多种以太坊用户的需求方面存在类似的问题:数字资产交换、隐私、用户名、高级加密、账户安全、抗审查、抢先交易保护,等等。然而,最近有一些谨慎的想法愿意将更多这些功能封装(enshrine)进核心以太坊协议中。

这篇文章将深入探讨最初的最小封装哲学背后的一些哲学推理,以及一些最近思考这些想法的方法。目标将是开始建立一个框架,以便更好地确定可能的目标,在这些目标中,封装某些功能可能值得考虑。

关于协议极简主义的早期哲学

在当时被称为“以太坊2.0”的早期历史中,人们强烈希望创建一个干净,简单和美观的协议,该协议尽可能少地尝试通过自身来构建,并将几乎所有这类工作都留给用户。理想情况下,协议只是一个虚拟机,而验证一个区块只是一个虚拟机调用。

这是我和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。

ERC-4337。它确实完全依赖于EVM调用!

因为这是一个ERC,它不需要硬分叉,并且在技术上存在于“以太坊协议之外”。所以……问题解决了吗?事实证明并非如此。ERC-4337当前的中期路线图实际上涉及最终将ERC-4337的大部分转变为一系列协议功能,这是一个有用的指导示例,可以了解为什么要考虑这条路径。

封装ERC-4337

有几个关键原因讨论了最终将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)发起,这些账户使用单个secp256k1椭圆曲线签名进行验证。帐户抽象消除了这一点,并将验证条件留给用户自行定义。因此,在这个关于账户抽象的故事中,我们也看到了反对封装的最大理由:灵活地满足不同用户的需求。

让我们通过查看最近被考虑封装的其他几个特征示例来进一步充实这个故事。我们将特别关注:ZK-EVM,提议者-构建者分离,私有内存池,流动性质押和新的预编译。

封装ZK-EVM

让我们把注意力转移到以太坊协议的另一个潜在封装目标:ZK-EVM。目前,我们有大量的ZK-rollup,它们都必须编写相当相似的代码来验证ZK-SNARK中类似以太坊区块的执行。有一个相当多样化的独立实现生态系统:PSE ZK-EVM、Kakarot、Polygon ZK-EVM、Linea、Zeth等等。

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既带来了希望,也带来了挑战。

封装提议者与构建者分离(ePBS)

MEV的兴起使区块生产成为一种大规模经济活动,复杂的参与者能够生产出比默认算法产生更多收入的区块,而默认算法只是观察交易的内存池并包含它们。到目前为止,以太坊社区试图通过使用MEV-Boost等协议外的提议者-构建者分离(proposer-builder separation)方案来解决这个问题,该方案允许常规验证者(“提议者”)将区块构建外包给专门的参与者(“构建者”)。

然而,MEV-Boost在一个新的参与者类别中进行了信任假设,称为中继(relay)。在过去的两年里,有很多人提议创建“封装PBS”。这样做的好处是什么?在这种情况下,答案非常简单:通过直接使用协议功能构建的PBS比不使用它们构建的PBS更强大(在具有更弱的信任假设的意义上)。这与封装协议内价格预言机的情况类似——尽管在这种情况下,也存在强烈的反对意见。

封装私有内存池

当用户发送交易时,该交易立即公开并对所有人可见,甚至在它被包含在链上之前。这使得许多应用程序的用户容易受到经济攻击,例如抢先交易。

最近,有许多项目专门致力于创建“私有内存池”(或“加密内存池”),它将用户的交易加密,直到它们被不可逆转地被接受到一个区块中。

然而,问题是,这样的方案需要一种特殊的加密:为了防止用户涌入系统并率先进行解密,加密必须在交易确实被不可逆转地被接受后自动解密。

为了实现这种形式的加密,有各种不同权衡的技术。Jon Charbonneau曾做过很好的描述:

  • 对中心化运营商进行加密,例如Flashbots Protect。
  • 时间锁加密,该加密形式经过一定的顺序计算步骤后,任何人都可以解密,并且不能并行化;
  • 阈值加密,信任一个诚实的多数委员会来解密数据。具体建议请参见封闭信标链概念。
  • 可信硬件,如SGX。

不幸的是,每一种加密方式都有不同的弱点。虽然对于每个解决方案,都有一部分用户愿意信任它,但没有一个解决方案的信任程度足以让它实际上被Layer1接受。因此,至少在延迟加密得到完善或其他一些技术突破之前,在Layer1封装反提前交易功能似乎是一个困难的命题,即使它是一个足够有价值的功能,许多应用程序解决方案已经出现。

封装流动性质押

以太坊DeFi用户的一个共同需求是能够同时使用他们的ETH进行质押和作为其他应用程序中的抵押品。另一个常见的需求仅仅是为了方便:用户希望能够在没有运行节点并保持其始终在线的复杂性的情况下进行质押(并保护线上质押密钥)。

到目前为止,满足这两种需求的最简单质押“接口”只是一种ERC20代币:将你的ETH转换为“质押ETH”,持有它,然后再转换回来。事实上,Lido和RocketPool等流动性质押提供商已经开始这样做了。然而,流动性质押有一些自然的中心化机制在起作用:人们自然会进入最大版本的质押ETH,因为它是最熟悉和最具流动性的。

每个版本的质押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智能合约代码。预编码是以太坊开发之初采用的一种折衷方案:由于虚拟机的开销对于某些非常复杂和高度专业化的代码来说太大了,我们可以在本地代码中实现一些对重要应用程序有价值的关键操作,以使其更快。如今,这基本上包括一些特定的散列函数和椭圆曲线运算。

目前有人在推动为secp256r1添加预编译,这是一种与用于基本以太坊账户的secp256k1略有不同的椭圆曲线,因为它得到了可信硬件模块的良好支持,因此广泛使用它可以提高钱包安全性。近年来,社区还推动为BLS-12-377、BW6-761、广义配对和其他功能添加预编译。

对这些要求更多预编译文件的反驳是,之前添加的许多预编译(例如RIPEMD和BLAKE)最终的使用量远低于预期,我们应该从中吸取教训。与其为特定操作添加更多的预编译,我们也许应该专注于一种更温和的方法,该方法基于EVM-MAX和休眠但始终可恢复的SIMD提案等思想,这将使EVM实现能够以更低的成本执行广泛的代码类。也许即使是现有的很少使用的预编译也可以被删除,并用相同函数的EVM代码实现(不可避免地效率较低)代替。也就是说,仍然有可能存在特定的加密操作,这些操作的价值足以加速,因此将它们作为预编译添加是有意义的。

我们从这一切中学到了什么?

尽可能少封装的愿望是可以理解的,也是好的;它源自Unix哲学传统,即创建极简的软件,可以很容易地适应用户的不同需求,避免软件膨胀的诅咒。然而,区块链不是个人计算操作系统,而是社会系统。这意味着在协议中封装某些功能是有意义的。

在许多情况下,这些其他的例子与我们在帐户抽象中看到的类似。但我们也学到了一些新的教训:

  • 封装功能可以帮助避免堆栈中其他区域的中心化风险:

通常,保持基本协议的最小化和简单性会将复杂性推到一些协议之外的生态系统。从Unix哲学的角度来看,这很好。然而,有时存在协议外生态系统将中心化的风险,通常(但不仅仅是)因为高固定成本。封装有时可以减少事实上的中心化。

  • 封装太多内容,可能会过度扩大协议的信任和治理负担:

这是前一篇关于“不要让以太坊共识过载”文章的主题:如果封装一个特定的功能削弱了信任模型,并使以太坊作为一个整体变得更加“主观”,这就削弱了以太坊的可信中立性。在这些情况下,最好将特定功能作为以太坊之上的机制,而不是试图将其引入以太坊本身。在这里,加密内存池是最好的例子,它可能有点难以封装,至少在延迟加密技术改进之前是这样。

  • 封装太多内容可能会使协议过于复杂:

协议复杂性是一种系统性风险,在协议中添加太多功能会增加这种风险。预编译就是最好的例子。

  • 长期来看,封装功能可能会适得其反,因为用户的需求是不可预测的:

一个很多人认为很重要并且会被很多用户使用的功能,很可能在实践中并没有被经常使用。

此外,流动性质押、ZK-EVM和预编译案例显示了一条中间道路的可能性:最小可行封装(minimal viable enshrinement)。协议不需要封装整个功能,而可以包含解决关键挑战的特定部分,使该功能易于实现,而不会过于偏执或过于狭隘。这样的例子包括:

  • 与其封装一个完整的流动性质押系统,不如改变质押惩罚规则,让去信任流动性质押更可行;
  • 与其封装更多的预编译器,不如封装EVM-MAX和/或SIMD,以使更广泛的操作类别更容易有效地实现;
  • 可以简单地封装EVM验证,而不是封装rollup的整个概念。

我们可以将前面的图表扩展如下:

有时候,去封装一些东西是有意义的,去除很少使用的预编译就是一个例子。帐户抽象作为一个整体,正如前面提到的,也是一种重要的去封装形式。如果我们想支持现有用户的向后兼容性,那么该机制实际上可能与去封装预编译的机制惊人地相似:其中一个提案是EIP-5003,它将允许EOA将其帐户转换为具有相同(或更好)功能的合约。

哪些功能应该被引入协议,哪些功能应该留给生态系统的其他层,这是一个复杂的权衡。随着我们对用户需求的理解以及可用想法和技术套件的不断改进,这种权衡有望随着时间的推移而继续改进。