http://www.7klian.com

二叉状态树的布局, Part-1

· 值(可选),即存储在用于寻得此节点的键中(stored at the key used to get to this node)的值
一个分支节点(branch node)有 16 个子节点(childeren),每个字节点都占用半个字节。凭据 RLP 的法则,分支节点只是一个其子节点哈希值的列表,也就是一个包括了 16 个字节数组的列表。假如某个子节点是空的,那就暗示为一个空的数组(只是用一个独立的 128 来标注一个长度为 0 的数组)。世界上,,列表中尚有第 17 个条目,就是分支节点自己的值,但因为实践中都不利用它,所以列表的最后一个条目老是 128。
固然尚有许多棘手的细节有待深入,但要点看起来很清楚了:RLP 难以驯服。我们再来看看它是如何与状态树交叉在一起的。
· 将账户 trie 和存储 trie 归并:维护多个布局会增加巨大性,典范的例子就是节点必需先遍历账户 trie,获得存储 trie 的根,然后再到存储 trie 上获取数据。
没错,正如你所料,这里又会发生许多贫苦。为了制止在数据库中为太小的工具建设条目,一个 RLP 编码值太小的节点就不管帐算出哈希值。在这种环境下,其 RLP 编码会直接存储为一个原始数据的列表,而不是这个列表的哈希值。功效就是你在列表中鲜少找到数组开始的标志(128),反而经常找到另一个列表开始的标志(192),又给开拓者出了许多灾题。
所有这些优化都是有用的吗?不见得呀。
· 假如 #4 bit 为有,则该 header 会有一个特另外字节来给出前缀比特中的数字;前缀比特则随着 左/右 子节点哈希值安排;

· 布局列表:header=192,overflow_header=247
默克尔化法则(Merkelization rule)
· 假如 #6 bit 为有,则随着的 32 个字节暗示右子节点的哈希值。假如该比特为空,则右子节点的哈希值也为空。
叶子节点及其十六进制前缀
在设计十六进制 trie 时,一些设计选择在其时听起来很棒,可是颠末 5 年的实践,被证明带来了许多巨大性。鉴于 ETH 1.x 想要转向二进制 trie,我们正好可以借此时机研究一下状态的存储方法。
· 字节数组:header=128,overflow_header=183
· 假如 #5 bit 为有,则 header 后载荷剩下的字节就用来暗示该值
我认可,在表明 RLP 的事情模式时,我一直在发泄利用 RLP 时累积的挫败感。

· 假如 #7 bit 为有,则此 header 后头所跟从的数据的前 32 个字节等于左子节点的哈希值。假如该比特为空,则左子节点的哈希值也为空。

问题的来源

跟本日全同步节点所需的约 300 GB 的存储空间对比,的确是微不敷道。
延伸节点
很是感激 Sina Mahmoodi 和 Martin H. Swende 的反馈。

· 假如 length(data)+h < 256,则 RLP 编码如上所示。假如数据太大,加上 h 值后高出一个字节,该怎么办?没错,你还需要再增加一个字节,即,引入 h’ 值来暗示你正在利用第二种存储方案。在这种环境下,RLP 编码是 [h’+length(length(data))] [length(data)] [data]。为 “利便” 起见,h’ 被选定为 56。
我们怎么把 RLP 编码和它那令人厌烦的逻辑用到我们的默克尔化法则中?先从默克尔树底部的叶子节点开始说起。
这个要领的一个重点是,它同样也包围到了十六进制前缀编码的成果,因从此者也可以打消掉了。
RLP —— Ramble, Lose yourself and Pester?
结论
· 扩展节点(extension nodes):这是一种非凡的节点,认真给特定子树上的所有 key 加上前缀。这些节点的浸染是可以或许限制需要被哈希的节点数量,可是也引入了巨大性,因为它们所涉及的 key 必需以特定方法打包。
分支节点和太小的子节点
· RLP 编码名目旨在高效地对任意布局举办编码。由于其巨大性,它也带来了许多贫苦:必需费尽千辛万苦打包 key 块(pack key chunk)。别的,由于每个节点的布局相当牢靠,可以利用更简朴的序列化要领来取代 RLP。

同样地,假如长度是奇数,但跟字节的终止位置对不齐(not byte-aligned),那么每一个半字节都必需位移,以使其能以少一个字节的长度来存储。

雷同地,不利用十六进制前缀编码,也只会导致多占用 100 MB 的存储,也就是多占用 0.03%。只要仔细选择二进制 trie 的编码名目,这点差距就可以补返来。
我们真的需要 RLP 吗?

一棵十六进制树上的 key 是基于半字节(nibble-based)的,所以假如我们取出一个键的时候,它大概在半字节处就中止了,那问题就来了:我该怎么对齐数据呢?这内里必需要有一个设计决议。功效是,我们利用一种叫做 “十六进制前缀(hex prefix)要领” 的函数来读取键数据,它会插手一个 header 来汇报我们所读的键的长度毕竟是偶数个照旧奇数个半字节。

· 前缀(可选),目标是替换十六进制 trie 中的延伸节点

· 与前两个问题相关的是,十六进制的前缀也会带来巨大性和杂乱。

– 例子中的树存储着键值对 (0xcafe, 0x00) 以及 (0xcaff, 0x01):根节点以键数据 “0xcafe” 和一个数字作为前缀;该数字暗示最后一个字节中仅有 7 个比特用于存储前缀。然后该节点有两个指针指向其两个子节点,同时不包括值。每一个子节点都以空值为前缀(以 0x00 为标志),都有一个值,都没有子节点。-
客户端可以用只占两个字节的 header 来编码这个节点的数据:

状态树上尚有第三种节点,叫做延伸节点(extension node),我们放在下一篇文章中会合接头。幸好,幸好,在 RLP 编码这一点上,它没有给我们增加任何贫苦。
客观来说,这个设计并不差,并且在已往五年的利用中毫无疑问也到达了它的设计目标。但跟着时间推移,变得越来越清楚的是,其巨大性是一个过于昂贵的价钱,我但愿能说服你,在与存储树相关的部门中,代替 RLP 是利大于弊的。

假如一个分支节点的 RLP 的数据总巨细大于 32,那它就会被算成哈希值。大部门时候都是如此,因为,假如没有算成哈希值,那就意味着一个子节点的 RLP 数据巨细已达最大值:16 个字节。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。