理解以太坊存储
- 原文链接:medium.com/@francomango...
- 译者:AI翻译官,校对:翻译小组
- 本文链接:learnblockchain.cn/article…
这是关于区块链的系列文章的一部分。如果这是你遇到的第一篇文章,我强烈推荐从系列的开头开始。
上次,我们对以太坊进行了高层次的介绍。
尽管我们讨论的许多想法与比特币相比是新颖的,但它们在大多数区块链系统中实际上是非常基础的——尤其是创建和执行我们称之为智能合约的程序的能力。
我们目前所掌握的抽象级别隐藏了以太坊架构的所有内部工作。这作为第一步是可以的,因为有些概念在与比特币比较时确实显得非常新颖和陌生。
但你我都知道——你不是来寻求更高水平的抽象的。
在接下来的几篇文章中,我们将深入探讨以太坊的机械原理,慢慢揭示它的秘密。
为了开始,让我们来看一下数据是如何存储的。
我们可以吗?
我们需要首先理解的是,区块链的状态在以太坊中是如何建模的,以便我们能够确定存储它的策略。在这一点上,比特币要简单得多:状态只是一个可用的 UTXO 列表。
尽管存储这些 UTXO 有其复杂之处,但从概念上来说,它相当简单。我们可以把它想象成一个包含完整状态的表,如下所示:
那么,以太坊呢?显然不会这么简单。状态是一个抽象概念,代表几乎所有事物。我们需要比我们简单的概念表更复杂一些的东西……
既然我们已经知道我们正在处理的是账户,并且基本上以太坊的全部状态可以用单个账户的状态进行描述,那么让我们问自己:来自一个账户的状态到底由什么定义?
在上一篇文章中,我们提到了几个值得注意的内容:
此外,还有几个我们需要考虑的项目。其中之一是nonce,它是交易的递增计数。我们还需要再讨论另一个。
我们可以将账户视为这些包含账户信息的数据口袋,数据以一组键值对的形式存在:
现在,有一点是我们希望实现的(尽管我们在比特币的探讨中没怎么提到),就是为整个区块链的状态拥有某种指纹,以便我们可以通过单个值来识别一个状态,并确保如果任何内容发生变化,这个指纹将会变化。
这是标记或识别区块链当前状态的有用方式。
我们已经知道一种结构可以将这种消化过程简化为一个根: 默克尔树 !
但默克尔树单独在这个场景中显然不够。在这方面,它们有两个重要的缺点:
我认为第二点在这里是最重要的:给定一个账户的地址,我们希望能够快速找到它的属性。
为了解决这些问题,采用了一种稍微不同的数据结构来编码账户:一个 默克尔-帕特里夏树。
什么?
我们需要的是一种允许某种索引的结构。默克尔树有非常简单的递增索引——这些值仅用于保持叶子有序,但与其内容无关。
但还有另一种结构将节点的标识符置于其设计的中心:一个 树。
树(也称为前缀树)是如你所期望的那样,特殊类型的树。从根节点开始,每个叶子与一个字母数字字符关联。因此,沿着树的任何一条路径将形成一个字符字符串,每次一个节点。
蓝色的节点是终端节点,标记一个单词的结束。请注意,向下的路径形成单词——索引!
这种结构的酷之处在于,通过其索引找到一个节点就像逐字母(节点)遍历树一样简单!
当然,终端节点将包含存储在指定索引下的信息。
但这些树的一个问题是,其深度由最长索引决定,以字符长度为准。这可能会导致存储问题,因为我们需要在内存中跟踪大量的中间节点。此外,节点可能只会有一个子节点,这对我们来说会产生问题,原因在于我们稍后将看到。
为了尝试解决这些问题,我们可以使用一个简单的变体:基数树。从本质上讲,我们将具有单个子节点的节点浓缩为一个节点。
好多了,对吧?
你可以在这个网站上看到插入和删除的实际操作。
需要注意的一点是一个节点可以拥有的最大子节点数量。在字母数字字符的情况下,一个节点最多可以有62 个不同的子节点,每个独特的字符对应一个子节点。不出所料,这个数字被称为基数——由我们使用的“字母表”决定,而不是结构本身。
这一切都是为了最终说明:
帕特里夏树实质上是基数树,并且基数 = 2,还有几个变化。
为了完整性:PATRICIA 代表“从字母数字编码中检索信息的实用算法”。
这意味着字母表是二进制的——仅使用数字0和1。
在构建帕特里夏树时,还有其他细微之处需要考虑,其中之一是包括一个跳过值,使遍历更高效。
最后(且重要的是),帕特里夏树可以以一种方式组织,使没有单子节点。换句话说,每个节点要么是一个叶子节点(没有子节点),要么是一个内部节点(两个子节点)。
虽然基数树通常可以有单子节点,但可以强制任何基数树不允许单子节点。
这是一个例子:
假设我们想在一个帕特里夏树中组织这些键
- 10010000 (144)
- 10010100 (148)
- 10011000 (152)
- 10100000 (160)
- 11000000 (192)
- 11000001 (193)
这就是帕特里夏树的可能样子:
帕特里夏树非常紧凑且内存高效,同时还允许快速插入和删除,这对于需要处理不断变化状态的系统(例如区块链)是一个有趣的特性!
剩下的就是为帕特里夏树添加这种在默克尔树中非常重要的“指纹”功能。我们可以将两者的优点结合起来吗?
希望我们不会像这两者通常那样搞砸。
当然!只需两个非常简单的考虑:
尤其是,这种在父节点上的哈希整合“向上流动”至树,以便根节点将包含一个对树唯一的哈希,依赖于每一个叶子,就像在默克尔树中一样!
这里有一个突发消息:尽管我们已经多次暗示以太坊使用帕特里夏默克尔树——你通常会在互联网上看到这种说法的文档——但事实是它并没有。
以太坊目前使用的是基数树,基数为16。我们可以称之为十六进制树!
尽管不完全正确,社区似乎仍然接受“帕特里夏默克尔树”这个术语。我们至少可以说我们正在使用一个修改过的版本,但现实是它们只是基数-16 默克尔树,具有每个节点要么没有子节点,要么至少有两个(最多16)子节点的限制。
上面的限制使得默克尔化成为可能,所以在这方面我们没有任何担忧——内部节点将始终有足够的子节点供该过程使用。这很不错!
所以我们已经决定了存储账户的结构。在这一点上,我们应该注意到修改过的帕特里夏默克尔树将会非常庞大,因为需要跟踪很多账户。
这意味着麻烦——毕竟,状态树必须存储在某处,而那个地方需要有大量可用内存。这种情况被称为区块链膨胀或状态膨胀,这是区块链仍然在努力解决的问题。
以太坊团队完全了解这个问题,并且这个区块链发展的一个主要里程碑是试图减少膨胀,在他们称之为“清除”的未来事件中。这里有一篇很棒的文章 ,由以太坊的真正策划者写的,深入探讨了这个问题!
同样值得一提的是,以太坊团队设想了其他可能的未来,并进行自己的研究。其中一个替代方案被称为“边缘”,提议用另一种结构 Verkle 树替换帕特里夏默克尔树存储,可能结合 STARK 证明 。
这真让人觉得就像是在审视那面墙,对吧?
这需要很多的消化——我相信明智的做法是先了解当前的状况!
不管怎样,信息是存储在网络 节点中的。但并不是每个节点都需要存储这庞大的状态——其中一些节点专门用于此目的,还有一些其他功能。存储状态树的节点是全节点或档案节点——而其他节点,如 修剪节点 或轻节点仅存储状态的一部分(或没有)。所有这些节点都有不同的功能,我们可能会在未来的文章中探讨。
到目前为止,我们已经说账户数据是存储在帕特里夏默克尔树中。但这并不完全正确。
虽然说这个数据结构对于将整个区块链的状态浓缩为一个根非常有用,但我们从未提到过这些树在存储中在哪里。事实是,它们并没有以这种方式存储。
事实上,以太坊使用键值数据库来存储信息。
我知道,这很惊人。
那么为什么要费心寻找一个合适的数据结构呢?在我们开始思考这个启示后的生活意义之前,先考虑一下这里的影响。
每个账户由某个标识符(例如一个地址)和一些属性——随机数、余额等表示。帕特里夏默克尔树为我们提供了一种优雅的方式来整合以太坊所有账户的数据到一个根节点,这在检查数据一致性时非常有效。但树并没有说明每个账户的数据存储在哪里。
因此,我们可以决定将每个账户的数据存储在我们喜欢的任何地方。键值数据库恰好是一个不错的选择。它可以存储标识符-账户对,同时没有为这个应用程序添加任何不必要的额外功能(看着你,关系数据库)。
仅供参考,Go Ethereum(GETH)节点实现使用 LevelDB 作为其键/值存储。
只要我们在键值数据库中的标识符用作帕特里夏默克尔树中的路径,我们就可以重建树(通过对节点内容进行哈希),并根据需要计算其根。万事大吉!
太棒了!我们有了我们的帕特里夏默克尔树,存储在键值数据库中,主机部署在区块链的某些节点上。树中的叶子保存账户信息,我们在几分钟前大致定义为一组属性(也是以键值的方式)。
你不觉得有什么遗漏吗?我们是不是忘记了什么?
嗯……
账户并不是唯一需要存储的东西——我们还需要处理与每个智能合约相关的数据!
每个智能合约定义自己的一组规则(我们稍后会深入讨论),并且关键是它自己的状态。定义这种自定义状态本身就是一种艺术——但归根结底,我们需要将其存储在某处。你能猜到我们将如何存储该信息吗?
你可能猜到了:在另一个帕特里夏默克尔树中!
所以,是的,每个智能合约将拥有自己的树(我们称之为合约树)来表示其状态,并且该树将具有一个根哈希。我们可以将这个哈希存储在账户树中,以便我们能够快速检查存储在合约树中的信息是否未被更改。
这完成了我们的账户表示——我们只需要添加 storageRoot 键,在这里我们将保存合约树的根哈希,以防账户是合约账户:
差不多就是这样!最后并没有那么神秘,不是吗?
呃,真的?
这次,我们讨论了用于存储以太坊状态的数据结构。
我认为了解这些数据结构不仅有趣,而且可能对其他应用程序也有用。Patricia 树被用来解决与区块链完全无关的其他类型问题,例如 IP 地址查找。
我故意将一些重要内容排除在本文的范围之外。在账户的情况下,我们已经完全定义了要在我们的 Patricia 默克尔树中“存储”的内容。但 智能合约 呢?
我们还没有解决以太坊的状态是如何 建模 的。换句话说,我们知道 如何 存储智能合约的数据,但我们并不真正知道 我们在存储什么!
因此,我们冒险的下一步将是研究智能合约的操作原理——我们将首先了解它们的 状态 是如何定义的,然后深入探讨它们如何接收和处理 用户交互(调用)。下次见!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!