Merkle Patricia Tree (MPT) 树详解之数据持久化(二)
在「Merkle Patricia Tree (MPT) 树详解之数据结构(一)」介绍了以太坊MPT树的结构,但是如何将MPT树持久化呢?以太坊采用LevelDB做数据库,如何将MPT树的节点映射成键值对存储到LevelDB呢?
以太坊的MPT树提供了三种不同的编码方式来满足不同场景的不同需求,三种编码方式为;
- Raw编码(原生字符)
- Hex编码(扩展16进制编码)
- Hex-Prefix编码(16进制前缀编码)
三者的关系如下图所示,分别解决的是MPT对外提供接口的编码,在内存中的编码,和持久化到数据库中的编码。
Raw编码
MPT对外提供的API采用的就是Raw编码方式,这种编码方式不会对key进行修改,如果key是“foo”, value是”bar”,编码后的key就是[“f”, “o”, “o”]。
假设我们要把a
作为key放入MPT树,key可以直接用a
的ASCII表示97就可以了。
Hex编码
可以发现采用Raw编码以后,从a-z一共26个字母,如果采用分支节点(BranchNode)
存储的话需要26个空间,如果再加上0-9一共10个数字和一个value,总共需要37个空间,以太坊的开发者权衡了一下觉得太多了,于是就改良了编码方式,有了Hex编码。
以太坊先定义了一个新单位nibble
,一个nibble
表示4个bit,0.5个byte。然后按照如下规则编码;
- 将Raw输入的每个字符(1byte)拆分成2个nibble,前4位和后4位各一个nibble;
- 将每个nibble扩展为1个byte(8个bit);
- 然后分别将Raw编码后的十六进制结果的每个
b
进行如下操作- b / 16;
- b % 16;
有疑惑的话看一下这个例子就懂了
树的最后一位value是一个标识符,如果存储的是真实的数据项,即该节点是叶子节点,则在末尾添加一个ASCII值为16的字符作为终止标识符。添加后的结果是[3,15,3,13,4,10, 16]
采用Hex编码以后,可以看到原本需要的37个空间存储的消耗都被压缩到了17个空间,横向压缩,但是增加了纵向空间的消耗,是一种工程的妥协。根据Key的数量多少,压缩与否各有优劣。
HP编码(Hex-Prefix Encoding 16进制前缀编码)
前面介绍的Hex编码后的数据是在内存中的,如果要对Hex编码后的数据进行持久化,就会发现一个问题,我们对原数据进行了扩展,本来1个byte的数据被我们变成了2个byte,显然这对于存储来说是不可接受的,于是就又有了HP编码。
HP编码的过程如下;
- 输入key(Hex编码的结果)如果有标识符,则去掉这个标识符。
- key的头部填充一个nibble,填充的规则如下
- 如果key的nibble长度是偶数则最后一位0
- 如果key的nibble长度是奇数则最后一位1
- 如果key是
扩展节点
则倒数第二位是0 - 如果key是
叶子节点
则倒数第二位是1
例子:nibble长度是奇数的扩展节点填充为0001。
举个例子:
相较与cat的Raw编码,经过上面的Hex编码和HP编码,既可以能在内存中构建出MPT树,又可以尽可能减小存储所占用的空间,不得不说以太坊设计的巧妙。
安全的MPT
在上面介绍三种编码并没有解决一个问题,如果我们的key非常的长,会导致树非常的深,读写性能急剧的下降,如果有人不怀好意设计了一个独特的key甚至是可以起到DDOS攻击的作用,为了避免上面的问题,以太坊对key进行了一个特别的操作。
将所有的key都进行了一个keccak256(key)
的操作,在「EVM深度分析之数据存储(二)」中也可以看到EVM持久化的数据也都进行了哈希操作,这样就可以避免上面问题的发生。
持久化MPT
MPT树节点Key的三种编码形式,但是这三种编码都是对key进行的操作,最终持久化到LevelDB中是k-v的形式,还需要对value进行处理。
在以太坊存储键值对之前会采用RLP编码对键值对进行转码,(RLP编码可以参照之前的文章「以太坊RLP编码」),将键值对编码后作为value,计算编码后数据的哈希(keccak256)作为key,存储在levelDB中。
在具体的实现中,为了避免出现相同的key,以太坊会给key增加一些前缀用作区分,比如合约中的MPT树,会增加合约地址,区块数据会增加表示区块的字符和区块号。
MPT树是以太坊非常非常核心的数据结构,在存储区块,交易,交易回执中都有用到,下图展示了MPT树的全貌,可以再感受一下MPT树的精巧
小结
其实随着数据的膨胀,LevelDB的读写速度都会变慢,这个是LevelDB实现导致的,目前也有一些针对性的优化。还有一种和MPT树非常相近的另一种数据结构BucketTree,也可以实现相同的功能,各有优劣。