如何扩展DeFi吞吐量:Layer1篇(下)
Gas 上限
矿池决定着区块的 Gas 上限。我们先来快速回顾下矿工和矿池的运营模式:几乎所有矿工都会选择将自己的算力起源与其他人的汇集在一起。他们会将资源集中到一起,然后获得稳定的收益流,而不是冒着长期没有收益的风险、单打独斗地挖矿。因此,矿池应运而生,负责验证每位矿工的贡献,并分配下一个区块的挖矿任务。大多数区块都是由大矿池挖出的。下图显示了以太坊诞生以来,矿池的出块占比情况:
由上图可见,星火矿池、Ethermine 和鱼池的出块占比遥遥领先。
除了参与硬分叉之外,矿池运营者还承担一项重要的治理职责:设置以太坊的 Gas 上限。不同于出块时间和 Gas 价格等特性,每个区块的 Gas 上限都需要主动设置。新区块的 Gas 上限与上一个区块之间的差距必须在 0.1% 以内,因此每个区块的大小只能轻微上下浮动(参见黄皮书:公式 47)。如果矿池运营者达成共识,可以大幅调整 Gas 上限:在 2.5 个小时内,Gas 上限可以加倍或减半。如果没有达成共识,Gas 上限就是矿池规模的加权中位数。
目前,由于缺乏关于矿工真实行为的详细信息,我们采取了一种比较简单的方法:投票系统。希望我们将来能通过软分叉将这一方法转变成更精确的算法。
—— 以太坊的基本设计原理 (2015 年 3 月)
从以太坊发展初期开始,由矿工设置 Gas 上限就是一个权宜之计。由于其它方案都不够好,这个权宜之计就一直延续了下来。EIP 1559 提出了一个不同的机制,目前社区正在讨论是否将其加入柏林硬分叉。在那之前,矿池运营者可以像 OPEC(石油输出国组织)管理石油开采那样管理 Gas 供应。
- Ethermine 矿池运营者 -
最近,全球最大的两个以太坊矿池做出了一个有争议的决定,将每日 Gas 产量提高 25% 。此举的目的是通过增加 Gas 供应量来抑制过高的交易费。到目前为止,我们发现,再怎么提高 Gas 上限,也跟不上用户对交易需求的增长速度。这就意味着,Gas 价格在经历短暂下降后,最终还是会再度回升。
提高 Gas 上限会给以太坊带来很高的隐形安全成本。正如我们所见,提高 Gas 上限会提高叔块率和空块率。在交易负载量正常的情况下,这一影响不大。但是出于安全性考虑,我们只对极端情况感兴趣,并不关心正常情况。根据 Perez 和 Livshits(2019)的研究,在最糟糕的情况下,如果生成一批处理效率极低的交易(在 Gas 成本相同的情况下,处理这类交易的速度比普通交易慢 100 倍),并将其打包到区块内,节点需要花 90 秒的时间来处理这个区块,从而导致节点落后,矿池挖出大量空块和叔块。自这项研究发布以来,已经出现了一些缓解措施,但是问题并未得到彻底解决。因此,两位主要的节点开发者 Péter Szilágyi 和 Alexey Akhunov 批评了关于提高 Gas 上限的决定。
因此,达到 Gas 上限时,Gas 价格就会上涨,而且我们似乎不应该进一步提高 Gas 上限。那么我们还能采取什么措施?我们能够降低交易所需的 Gas 成本吗?
Gas 耗用量
交易的 Gas 消耗量基本上就是其 EVM 操作的消耗量。一笔交易是由许多 EVM 基本操作组成的,而每一个操作的 Gas 耗用量都通过 EIP 和硬分叉来治理的。在过去的硬分叉中,主要操作的 Gas 消耗量有升(EIPs 150、160、1884)有降(EIPs 1108、2028、2200)。计划中的 “柏林” 硬分叉也在考虑引入几个 Gas 消耗量变更(EIPs 1380、1559、2046、2565、2537)。
所有这些变更的目的都是为了让手续费能更精确地反映操作的真实成本。因为计算操作的成本会随着计算机性能和算法效率的提升而变得更低。但存储操作的成本则完全不同。存储和查找操作的成本会跟随区块链状态规模的变动而变动,而以太坊的状态规模是一直在变大的。这种增长不会因为存储设备的升级或者数据库技术的改变而改变。
这就意味着存储仍然会是 DeFi 成本中的一大块。创建一个新的余额需要 2 万 Gas,而修改一个已有的余额要耗费 5 千。一笔转账至少要改变两个月,一次交换则要改变至少 4 个,更复杂的 DeFi 交易会包含更多昂贵的状态操作。看起来实在不容易降低相关存储的数量和成本,这个趋势九头牛都拉不回来。但好消息是,Layer-2 扩展方案会尝试做成存储粗放且计算密集的,看起来会更讨喜一些。
最后,提高区块的 Gas Limit 也同样有安全性方面的隐忧:提高 Gas Limit 是有效果的办法中最差的。单纯根据当前真实操作的平均成本来改变 Gas 消耗量也是非常危险的(Naively optimizing the Gas cost for the current true average cost of operations is very dangerous.)。
到此为止,为什么扩展以太坊是一个棘手的问题,已经很显然了。在我们讨论大家提出的解决方案以前,我们希望再探讨一个当前以太坊可能伤害 DeFi 用户的缺点。
矿工可抽取价值
区块生产者虽然受到共识规则的限制,但共识规则也为他们提供了一些重要的自由(freedom),比如交易选择和排序。对于一般的代币转账来说,生产者的自由并不会造成很大影响,但对于 DeFi 交易比如交易所交易来说,抢跑(front-running,指发送 Gas Price 更高的交易来抢先使交易上链)可能收获巨大的经济价值。更复杂的问题是,一笔目标交易被夹在两笔交易中间,像夹三明治一样。Daian et al. (2019) 将能够从抢跑交易中获得的价值称为 矿工可抽取价值(miner extractable value)。
没有证据表明现在的矿池在恶意利用他们的交易排序自由,但如果他们做过,也必然是有获利的。矿池一般都使用 Geth 客户端,该客户端会根据 Gas Price 来排序交易(见 1,2)。这导致了 Gas 的价格竞拍,对 Gas 出价最高的交易能最先打包上链。这一机制的负面效果是,任何人都能通过提高出价来抢跑一笔交易。相互竞争的交易者会持续提高出价,直至交易的利润被 Gas 费完全耗尽。到了这个时候,交易可获得的所有价值都会变成 Gas 费,进到矿工的口袋(译者注:这是一个理论上的结果,也是 “矿工可抽取价值” 概念的由来;实际上,高 Gas 费的交易可能很快就会打包上链,而不会存在相互竞争的交易者像拍卖那样安逸地相互出价;所以矿工可抽取价值也没有想象中那么高,毕竟矿池之间有竞争)。
另一方面,让自己的交易紧跟在另一笔交易之后上链,可能也是有价值的,比如在价格信息传输机制更新信息后第一个清算仓位(liquidate a position)。这个叫做 back-running,最后也会变成矿工的收益。
矿工可抽取价值最终是从普通的 DeFi 用户处得来的,表现是更大的价差、更差的价格、更高的手续费,以及更多失败的交易。要想获得更好的 DeFi 体验,就应该解决这个问题。解决办法之一是限制交易排序自由,比如要求一个区块里的交易按 Gas Price 由低到高排序。
现在我们已经对以太坊的局限性及其对 DeFi 的影响有了全面的了解。当然,所有天王级的团队都在开发解决方案,对吧?
一个 Layer-1,两种愿景
现在有很多厉害的团队在开发不同的可扩展性解决方案。解决方案主要有两个方向,“Layer-1” 和 “Layer-2”。Layer-1 的方案致力于打造一个更可扩展的区块链来取代当前的以太坊,Layer-2 方案则是尝试在当前以太坊的基础上接入更可扩展的基础设施。我们这里就谈 Layer-1,Layer-2 方案留给下一篇文章。
我们从最明显的一个方案开始:提高当前以太坊区块链的性能。也就是 “Eth1.x” 同仁在做的事。单论以太坊客户端的性能提升,就有很多工作可以做。不过,Eth1.x 还没有得到他们应得的支持力度,所以进展缓慢。想要对这个方向的性能提升可达到的程度有个基本概念,只需了解一下 Solana 就好,Solana 实现了以太坊的 1000 倍吞吐量,而且还有空间可以提高;缺点在于需要很高级的硬件才能运行全节点。
大部分其他解决方案都有三点共同之处:(1)使用 WebAssembly 作为虚拟机;(2)最小化状态的架构;以及最重要的,(3)分片。当前的以太坊是串行执行所有交易的。事实上,让交易能排成一个队列可以说是区块链的全部意义所在。但这种模式的缺点就在于,难以并行执行交易,所以我们无法通过投入直接更多资源来解决扩展问题。那么把区块链分割成多个松散互联的领域,一个线程就叫一个 “分片”,就能实现并行化处理。在一个分片内部,事务仍然是串行发生的,但不同分片的事务是移步发生的。这就使得所有分片能并行运行,以分片数量为倍数扩大网络的吞吐量。我们用来分割网络的领域也不需要与分片一一对应,可以把多个领域分配给同一个分片(线程),甚至可以移动它们做负载均衡。看 Near Protocol 的 “夜影” 协议白皮书可更深入地了解分片的大体。
具体如何将区块链分割成多个领域,下一代的区块链各有想法。可以认为,这是一条从细粒度(许多细小领域)到粗粒度(少数几个大型领域)的光谱。
fine parallelization coarse parallelization DFinity ETH 2.0 Polkadot actors (TBD. Contracts?) chains两个项目分别代表这条光谱的两端。DFinity 在细粒度这一端,每一个 “actor” 都有自己的细小领域,而且每个 actor 之间的交互都是异步的。粒度稍大的的 Near Protocol,其中每个合约都有自己的领域。粗粒度一端的是 PolkaDot,一个领域就是一整个分片,更具体一点应该叫做一条 “平行链”。从一个 dApp 开发者的视角来看,断言 Ethereum 2.0 位于哪个位置还为时尚早。ETH1 EE(执行环境)应该是粗粒度型的,其边界恰好与一个分片的边界一致,就是当前的以太坊所化成的一个分片(也就是 Eth1 在 Eth2 实现后的未来)。一个专门的 Eth2EE 可能会选择一个更细粒度的方案。细粒度方案的长处在于它们是透明的;可以将所有的跨合约调用一视同仁,无论这些调用是否跨越了分片边界。这就能反过来允许我们通过在分片间移动合约来实现负载均衡。
缺点在于,跨越领域的事务就不再是原子化的了,它们会变成 并行的,甚至是部分 不可逆 的。在 DFinity 和 Near 中,这一点表现为跨合约的交易变成 async
状态,并返回一个承诺:你需要 await
(等待)。在一场 await
期间,所有到时已经发生的交易都会被提交到链上。然后其他人的交易可以堆积在上面。到这时你就无法回滚已经发生的事情了。当 await
最终解决的时候,它会从合约调用中返回 “成功” 或者 “失败”。有许多提案尝试避免这一点、找回某种形式的跨分片原子性,但都有自己的缺点。看起来,拥抱非原子性会是自然结果。
但对 DeFi 应用来说,一个异步的 transferFrom
调用会带来相当大的挑战。设想一个两方之间的简单交换,Alice 和 Bob 希望交换 Eth 和 Dai。基本合约看起来会像这样:
但现在我们需要能处理错误。如果第一笔交易失败,我们可以简单地终止交易。但如果第一步交易成功而第二笔交易失败,我们要能返还给 Alice 一个 ETH。问题在于,Bob 可能已经在我们处理错误之前就花掉了这笔钱。解决这个问题的一个办法是使用一个 escrow:
很好,现在就不会有人丢币了。但现在 Bob 就对 Alice 的出价有了一个免费的排他性期权(free exclusive option)。虽然现在 Alice 的钱保管在托管方手里,但这些钱既不能接受其他人的交易请求,也不能保证跟 Bob 的交易一定会成功。你可以用惩罚恶意行为来解决这个问题,但是你很难确定合理的惩罚力度,因为不同 DeFi 交易的价值差别非常大。你也可以要求所有市场参与者从一开始就把钱都放在同一个储蓄合约(托管方)里,但这样就重新使得状态集中化、取消了分片的意义。
另一个需要注意的事情是,这些并发问题可能非常棘手。在现实的交易所中,订单的填充状态(fill-state)需要不断更新,这就使得写一遍的更加复杂。如果说困扰以太坊 1.0 的可重入漏洞(reentrancy bug)是蝴蝶,可能引起巨大的后果,那并发问题就是床上的臭虫(bug),无孔不入。并发漏洞是不确定的,而且在测试中可能根本看不出来。就像我们在上面的简单的交换的例子中看到的,开发者需要从头重新思考整个架构,就像床上的臭虫,清除它们的唯一办法就是把一切都推倒,从头重建。
交易所是 DeFi 世界其余部分的基石,而且显然是个川流不息的连续过程。我们已经看过了订单簿类型的交易所面临怎样的挑战。自动化做市商(Automated market maker)交易所还简单点,因为参与者已经将资金都存储在托管方(合约)处,但在这种情境下,储备起来的余额本身就阻碍了并行化处理。即使在更快的传统交易所里,结算活动最终也是在一个撮合引擎里面按顺序处理的,不会用到并发,(但比较好的是传统交易所还可以有冗余)。想深入了解传统交易所是如何运作的,可观看 Brian Nigito 的演讲。
上面说的不代表这些问题是不可能解决的。最简单的解决方案就是要求所有这些协议在每一个分片上都部署一个独立的实例,然后让套利者来保证不同实例之间没有价差。又或者,我们可以让一个同步的分片拥有足够强大的性能,强大到可以包含所有的 DeFi 交易,然后就不用烦恼并发问题了。
在本文中,我们历数了以太坊在扩展 DeFi 容量时的局限性。如你所见,这是一个复杂的问题,没有显而易见的解决办法。在后续的文章中,我们会深入了解具体的一类解决方案,“Layer-2 方案”,并阐述 0x 自己的想法。
微信扫描关注公众号,及时掌握新动向
2.本文版权归属原作所有,仅代表作者本人观点,不代表比特范的观点或立场
2.本文版权归属原作所有,仅代表作者本人观点,不代表比特范的观点或立场