-

状态规模日益恶化?以太坊状态规模治理诸提议

来源: 数字货币 时间:2021-02-23 08:10:42
导读: 以太坊协议所面临的一个最为长久且尚未解决的挑战,就是由于状态数据规模不断增长而带来的问题。


比特币连续创新高 有哪些相关的基金股票?

继春节假期期间强势突破50000美元大关后,比特币又连破2大关口,刷新52000美元的新高。然而近年来数字货币交易所跑路、故障的消息频发,让普通投资者苦于难找有效投资渠道。实际上,只要选择正确的投资标的,通过传统交易渠道也能够获取比特币上涨的收益。

以太坊协议所面临的一个最为恒久且尚未解决的挑战,就是由于状态数据规模不停增进而带来的问题。以太坊区块链上的许多操作(建立账户、写入一个合约存储槽、发送 ETH 到一个新的账户……)都市给以太坊添加状态内容(也即是给状态数据增添数据工具),而所有全节点都必须存储全量的状态数据,这样才气验证新区块以及制造新区块。这些操作只需事务的发送者一次性缴交按 gas 用量来计量的手续费,但会给整个网络造成永远的连续性成本,由于节点需要存储这些新数据(而未来加入的节点也需要在同步过程中下载这些数据)。

这是系统设计中的一个显著的失衡,可能会让以太坊系统变得越来越难用,由于状态中充斥着不再有用处的 “垃圾数据”。本文的目的是详细注释问题发生的泉源,以及一些解决该问题的方式。若是我们能实现某个解决方案,这将为安全地大幅提高区块 Gas 上限 铺平道路。

本文所叙述的研究领域仍在推进中,随时有可能泛起更新、更好的想法和更优雅的权衡。

弁言:问题出在哪?

“状态” 指的是节点若想处置新发生的区块和事务就必须存有的信息。状态与 “历史” 完全差别,后者是关于已往时间的信息,节点可以保留这些信息以便日后重新广播或归档,但并不是处置区块链所必须的。

以太坊协议中,状态信息包罗:

账户的 ETH 余额 和 nonce(流水号)

智能合约的代码

智能合约的存储项(storage)

与共识机制相关的数据(近期的区块哈希值,叔块;权益证实的共识数据还包罗验证者的公钥以及及其记录在信标链上的流动,等等)

历史信息则由旧的区块和收条组成。EVM 中没有操作码可以让你接见旧区块、旧事务和内容和收条输出,以是节点抛弃这些数据也仍然能验证新区块,以是这些是历史信息。

上述状态信息列表中的最后一项 —— 共识机制相关数据 —— 在设计上已经经心限制了其规模,因此我们不太需要为此困扰。但前面三项,就令人头大了。这三类状态信息的规模会随着时间推移而不停增大,由于不停会有新用户加入网络,他们会建立新的账户、新的合约,还会加入合约、收到 token 什么的。

难办的是,许多状态用过之后就会静静地躺在那里(不会再被触及);一旦某个用户停用某个应用之后,就会发生一些 “垃圾状态” —— 不会再派上用场,但会永远存在那里。

理论上,用户可以做到 “垃圾不落地”。用户可以仅公布带有 SELFDESTRUCT 条件的合约,等他们再也用不上这个合约的时刻,就挪用这个操作码移除这个合约、清空其 token 余额;他们还可以使用智能合约钱包,通过一个已有的外部持有账户(EOA)来发送买卖,而无需天生一个新的 EOA(EOA 状态是没法删除的)。

然则在实践中,这样的激励异常少,而适当的状态清算的手艺庞大性又太大了。在许多合约中,给任何人赋予这样挪用 SELFDESTRUCT 的权限都是不合适的(人们想要的就是 “无法终止” 的应用!),而且,也会给用户体验和代码上也会增添许多庞大性。现实上,由于 SELFDESTRUCT 用处极其有限而副作用极大,我更倾向于永远移除这个操作码。若是我们真想控制状态数据的规模,我们需要的是一个网络中的节点可以 默认 抛弃不再被使用的 “垃圾状态” 的方式。

无状态客户端

这个问题的一类解决方案基于 “无状态客户端” 的看法(此文是叙述这个看法的出处 ,此处是演讲视频)。

基本原理是,让区块验证不再以持有全局状态为条件。相反,区块会自带证据(或者叫 “见证数据(witness)”),证实其所接见状态的值。就跟现在的设计一样,区块内会包罗一个 “状态根(state root)”,所接见的值可以对应着状态根获得证实(译者注:默克尔证实即是一种常见的证实手艺)。以太坊现在的状态树方案(默克尔帕特里夏树)支持这样的证实手艺,像二进制树或者 Verkle Trie 这样更高效的方案也可以。见证数据也会证实处置完该块后新状态根的正确性。

无状态性有两种形式:

弱无状态性:出块者仍然需要完整的状态,以为(自己制造的)区块天生见证数据;但验证区块的阶段可以是无状态的;

强无状态性:没有任何节点需要完整的转台。反过来,是买卖发送者需要提供见证数据,而出块者可以聚合这些数据。买卖发送者自己卖力存储为所关切的账户天生见证数据所需的部门状态树。

强无状态性是一个异常 “优雅” 的解决方案,由于它把责任完全转移给了用户,虽然为了保证实践中的优越用户体验,我们需要缔造某些类型的协议来辅助不运行小我私家节点的用户维护状态、并处置用户需要与意料之外的账户交互的情形。打造这样的协议异常难。

此外,所有类型的无状态性都提高了网络所需的数据带宽;而强无状态性还需要买卖声明其所交互的账户及存储项的键(观点上这个叫做 “接见列表”)。

一个更温顺的解决方案:状态过时

更温顺的解决方案可以归结为差别形式的 “状态过时” 方案。必须连续获得接见的状态才气保持 “激活状态”;而历久无人问津的状态会酿成 “失活”(或者叫 “过时的”)。具体用什么机制来更新状态,有许多选择(例如预付 “租金”,或者只需接见谁人状态),但一样平常原则是,除非某个状态工具被显式地更新,否则就以某种形式处于失活状态。因此,任何建立新状态工具(以及更新已有状态工具)的流动,都只能成为节点在一段时间内的肩负,而不像现在这样酿成永远肩负。

失活状态,故名思义,就不是 “状态” 的一部门;想要处置区块或建立区块的节点无需存储失活状态。不外,失活状态不是被完全删除了!在所有类型的状态过时提案中,都预设了某种方式可以 “复生” 已经失活的状态。

一样平常原则是,激活状态的使用与当前相同,而失活状态则需通过上述无状态客户端的机制来使用。复生一个过时状态工具的事务需要提供一个证据(见证数据),来证实该工具是失活状态的一部门。为了能够天生这样的证据,用户自己需要存储和维护至少一部门失活状态(对应于其所关切的失活状态工具的那部门)。

何时过时

决议过时条件的设计也有许多种。最常见的几种是:

直接租金:逐块逐块收取 “租金”,直接以每个账户(或其他状态工具)的余额来支付;状态工具的余额降到了零,该账户就过时了。

剩余存活时间值:每个状态工具都存储一个 ”剩余存活时间“ 值,这个值可以通过支付用度来增添

触达即刷新:每个状态工具都存储一个 ”剩余存活时间“ 值,而且每逢读取或写入该账户都市增添该值

所有状态工具定期过时(例如每 6 个月一次):也就是 ReGenesis 提案(中文译本)

我自己越来越喜欢 ”触达即刷新“ 方案,由于(1)它制止了应用需要缔造庞大的经济模子来让用户负担状态租金;以及(2)它保证了激活状态的规模有一个清晰的上限(区块 Gas 上限 / 触达状态工具的 Gas 消耗量 × 状态存活的时长)。让大量状态根据纪律的时间距离过时的方案(也就是 ReGenesis)也有同样的利益,但也有一些有趣的权衡:要害利益是,过时方案更简朴(无需遍历整棵状态树而逐个逐个地灭活状态工具),但要害不足是,跨过一个过时时点后,你再激活自己的状态工具时,需要若干见证数据会跟你触达状态工具的时间点有关。

账户层面的过时 vs. 存储槽层面的过时

状态过时的逻辑既可以运营到账户层面,也可以运用到单个存储槽层面。当前,我强烈偏向于在存储槽层面实现状态过时方案。由于许多合约账户的存储槽数目是不受限制的,随便用户都能加入合约并增添合约名下的存储槽的数目(例如,空投就是一个已经泛起过的案例)。不管使用什么样的账户层过时方案,想要现实限制状态的规模,租金的数目都必须与合约内存储槽的数目成比例(或者存活时间与之成反比)。效果是,用户照样能够仅支付一次性的用度就给合约及其用户施加 永远的连续性成本。

要解决这个问题,合约要么加入庞大的内部逻辑,将存储操的租金 “转嫁” 给用户,要么重新设计自己合约的模式,转向使用 CREATE2 操作码建立新的合约并使用这些合约来充当存储槽。不管是哪种设施,最后都市酿成等价于存储槽层面的过时方案。因此,我小我私家认为,我们应该仅在合约存储槽层面实现状态过时方案。

然则,存储槽层面的过时方案也有自己的瑕玷:每个存储槽都要增添一个元数据,指明它何时过时(或者说是否已经失活),这也意味着 “复生冲突问题”(详见下文)不仅会影响账户,也会影响存储槽。

从状态树上移除 vs. 给状态树放置一个 “退休” 部门

另一个区分差别状态过时提议的手艺角度是 “一树流” 和 “二树流”。也就是说,我们到底是像现在这样,只有一棵状态树,只不外把某些状态符号为过时;照样直接把失活的状态从主状态树上移除,转移到另一棵专门的(只包罗过时状态的)树(或者其他数据)上?

一树流

激活节点以白色符号,失活节点以灰色符号

注重,纵然是树上的中心节点,也会被符号为激活或者失火(或者,更现实一点的方案,每个节点都市带有失活日期的符号,以是能够容易检查其活性);符号事情可以在状态树上的每个节点(叶子节点和中心节点)处完成。

二树流

白色的树包罗激活状态;灰色的树存储失活状态

一树流的利益是,最起码,其事情方式看起来会跟当前的状态树相似,失活和复生的流程也比较简朴:复生流程只需刷新树上相关节点的 “过时日期” 参数,而失活则是自动化的。但它的瑕玷在于:它需要一种能够在节点中以此种方式存储过渡信息(intermediate information)的树结构,而且不能很好地扩展到 Verkle 树。此外,它还需要分外的默克尔证实元件,不仅要能够下沉到叶子节点,还要能够(在需要证实某部门状态已经过时时)停在中心节点处。

二树流的利益是:当前的、形式纯粹的状态累加器就能支持这类方案,而无需为每个节点增添元数据。瑕玷是,它需要对整个协议做一些更深条理的调换,而且需要一个显式的流程来灭活状态(以是过时不再是自动化的了)。另外,它也没有为复生冲突两难(见下一节)提供内置的解决方案,以是需要在两种设施中作出选择。

注重,在二树流中,存储失活状态的数据结构不是非树不能。事实上,完全有可能泛起这样一种设计:需要复生一个状态工具时,只需提供一个指向该工具失活时刻收条的默克尔树,再附上一些密码学证据,证实此前该工具未被复生过(或者最近又重新过时),即可。

复生冲突

然后我们就到了状态过时方案的一个要害难题上:“复生冲突”。复生冲突的观点如下。假设某个账户由地址 A 天生;这个账户过时了;然后,地址 A 又建立了一个新的账户(例如,使用 CREATE2 操作码保证两次天生的账户的地址时统一个);最后,地址 A 再实验复生谁人最最先的账户。这时刻会泛起什么情况?

这里有几种可能的解决方案:

显式的 “账户合并” 流程:类似于划定 “除了两个账户的 ETH 余额相累加以外,以旧账户的状态为准”或者 “除了累加 ETH 之外,以新账户的状态为准”;甚至于,可以由旧账户的合约代码来划定特殊的合并流程

通过消除统一地址重复部署的功效来确保复生冲突不会发生:也就是调整 CREATE2 的功效,比如在最终哈希成地址的数据原像中包罗当前时间,因此纵然未来使用同样的数据来天生,也无法获得同样的地址

向状态工具增添一个 “存根”,以防止在统一位置天生新账户(上述一树流方式自动实现了这一功效)

要求天生新账户时都必须附带该账户此前未过时的证实:某种意义上等价于存根方案,只不外这种设施是把存根放在状态的一个单独部门中,以是任何想要建立合约账户的用户都必须跟踪这部门状态

(注重,若是我们使用存储槽过时方案,则上述任一解决方案都必须延伸到单个存储槽层面,而不能止步于账户层)

主要的担忧有:(1)会给应用增添许多庞大性,他们需要加入合并的逻辑;(2)这样做了之后,除非在链上 “注册” 一个地址,否则用户就没法再容易获得可以与之交互、可以积累资产(例如 ERC20 token)的地址了。未注册的地址是很主要的:任何第一次收到 ETH 的用户都是在使用一个尚未注册的地址。这第 (2) 的担忧的泉源是:未注册的地址现实上有了时间限制,若是用户天生了一个地址、收到了资金,但在接下来一年里忘了发送买卖(也就是忘了 “注册”),那他的资金就会被锁住。

注重,EOA 也不能幸免。虽然看起来能够,由于 EOA 的合并流程比较简朴(只需把旧的 ETH 余额加到新的里,对 nonce 则有 EIP 169)这样的方案。不外,这里也有两个问题。首先,账户抽象的目的是用合约来替换 EOA,而账户抽象化的合约的合并流程可能并不简朴。其次,会受过时和复生事宜影响的不仅有 EOA 自己,另有该 EOA 所介入的应用中的相关存储建(例如 ERC20 token 余额),以是照样需要庞大的合并逻辑。

因此,从我的角度来看,破坏性最小的是某种形式的存根方案。不外,存根方案里存在一个信息理论问题,会导致一些新鲜的效果。为了防止新的状态工具在 N 个已经过时的状态工具位置处建立,一个笼罩(cover)了这 N 个地址(以及/或者 存储键)的聚集必须是状态的一部门。若是这个聚集是信息最小化的(即,只包罗了这些地址),那么这个聚集的巨细会是 O(N),因此其状态规模也是 O(N);那么,激活状态的规模就将与失活状态的规模成比例,以是现实上我们并没有解决这个问题。

Tree rot

解决这个问题的唯一设施就是笼罩跨越那 N 个账户的信息;现实上,我们将不得不让整棵树都变得不能接见(再次提醒,这就是一树流解决方案的实质:若是两个账户过时了,它们之间的所有空间都市隐式过时( if two accounts get expired, all the space in between them also implicitly gets expired))。

而这里另有一个问题:这发生了一种形式的 “树发霉(tree rot)”,随着时间推移,对于新帐户的建立来说,状态树的所有部门都是不能接见的,至少对那些没有跟踪该区域过时状态的用户来说是这样的。

而树发霉导致的次生问题也必须解决。举个例子:若是一个合约要建立子合约,它必须能够在要么未发霉,要么用户具有见证数据的状态区域建立合约(也许需要用户提供的 “提醒”)。数发霉问题的一个解决方案见此处:连续地开放状态的新区域以供账户建立。另一种思绪是每个用户都选择状态的某些区域(例如状态的 1/256),跟踪该区域的转变(包罗过时状态)以便能建立见证新闻,而且只在该区域建立帐户。

树发霉的另一个问题是,它需要一个显式的数据结构来存储和检查局限。若是一棵树有能够放在节点中、指明该节点以下的哪些部门已经过时的数据(就像一树流解决方案所用的那样),那是最好的,但一个键值对存储要做到这一点照样相当有难度的。

转头再看强无状态性

在状态过时方案中使用树结构所发生的许多问题,都可以被追溯到这样一个事实:我们需要对哪些状态是活跃的、哪些状态是失活的,杀青共识。在二树流模式中,这一点加倍显著;但纵然是在一树流模式中,状态树上也需要有显式的符号,以便近期使用快速同步下载了状态的以太坊节点能够确定一笔实验接见某个账户、但又没有提供见证新闻的买卖,应该乐成照样失败。那我们能不能做到不需要明确这个区别呢?

若是我们实现了完全的无状态性,然后能辅助买卖发送者和区块生产者可靠地获得见证新闻天生所需的状态,不就解决这个问题了吗?那什么设施能辅助买卖发送者和区块生产者做到这些呢?

一种自然而然的设施是:网络中的节点都仅保留状态树的一部门,例如,在已往一年中接见到的那部门。只需在客户端设定中加入一个自愿的设定即可。若是我们想要更可靠一些,我们可以通过引入一种 proof of custody 方案,强制至少矿工(后面就是 PoS 的验证者)存储一些数据。

有一点需要注重:若是共识层不能感知哪些状态是活跃的、哪些状态是失活的,那接见近期状态和老旧状态的 Gas 开销就是一样的。这会导致两个效果:

接见近期状态的 Gas 开销也需要进一步提高

包罗了见证新闻的区块巨细上限可能异常之大,若是一个区块里满是接见老旧状态的事务的话(大概是 800 bytes * 12.5 m gas / 2400 gas per access ~= 4.1 MB,已假设实行了 EIP-2929,转成了二进制树)

若是我们想制止这些不利因素,就需要在共识中跟踪哪些状态工具(包罗尚未填满的地址空间区域)是活跃状态,这又会让我们回到接近于状态过时方案的属性。这再一次地说明晰,“无状态性 vs. 状态过时(状态租金)” 是一条光谱,是一个庞大的权衡空间,而不是一个非此即彼的选择。

Rollup 也需要,也可以,使用同样的解决方案

以太坊的一种主要的中期可扩展性解决方案是 rollups(中文译本)。不外,rollup 自己并非不再需要担忧状态数据规模问题;现实上,rollup 系统的状态规模问题,与以太坊链自己的,性子完全相同。

幸运的是,若是我们能推出一种解决方案,则至少 EVM rollup(实验最大水平复制以太坊运行环境的 rollup 方案)能够使用同样的解决方案,来解决其内部状态的规模问题。因此,状态规模治理方案,与 rollup 和 分片等可扩展性方案是互补的(state size management is complementary to rollups, sharding and other scaling strategies)。

(译者注:小我私家认为此处的 “互补”一词有严重误导性。)

结论

状态规模是一个日益恶化的问题,而状态规模的解决方案也能为大幅提高区块 Gas 上限铺平道路。我们应该对某种形式的状态过时方案杀青共识并加以实现。不外,差别的解决方案之间存在重大手艺权衡,尤其若是我们还想要保持当前设计的一些主要属性的话。

一些我们可能需要牺牲的属性包罗:

用户可以离线天生账户并以该地址吸收资金、而且在使该地址在链上显明之前可以静默随便时长的属性

地址保持 20 字节的长度(rolling state expansion 方案需要更大的地址空间,虽然地址的长度可能原本就需要为抗碰撞的缘故很快改变)

状态可以被视为 “纯粹的” 键值对存储的属性,以及无需在状态树上每个节点内存储元数据的属性

现有的应用需要水平不等的重写,以保证用户无需存储所有失活状态就能天生见证数据

Gas 消耗量;或者建立新合约、写入新存储槽的难度

我们若是已经准备好作出牺牲,有些方案可以很快最先着手实现。另一方面,也许假以时日,我们能修补或者更好地汇总这些看法,削减问题,尤其是使它们在手艺上更容易实现(例如,允许使用 “纯粹的” 键值对存储)。我们应该更深入地明白我们 更愿意/更不愿意 接受哪些方面的牺牲,并继续努力研究改善提案。

加入新手交流群:每天早盘分析、币种行情分析

添加助理微信,一对一专业指导:chengqing930520

加入新手交流群:每天早盘分析、币种行情分析,添加助理微信

一对一专业指导:chengqing930520