以太坊中状态树(MPT树)详解
概述
以太坊中采用的是基于账户的模式,也就是说系统中显示记录着每个账户的当前状态信息。
每个账户都是由一个160位的地址组成,对应的账户中的状态包含余额(balance)、交易次数(nonce)及合约账户中的code(代码)、存储(stroge)。
那么以太坊中采用什么数据结构来管理所有账户地址对应的状态的呢?这里先说结论:采用的是MPT(Merkle Patricia Trie)树,即默克尔前缀树。
为了更好的介绍MPT树,接下来会依次介绍:Trie树和Patricia Trie树。
Trie树
trie树又称为字典树或前缀树,是树形数据结构中的一种。
trie树体现在使用公共的前缀作为树的组成部分,例如:英文组合分别有以下6种:
- taa
- tan
- tc
- in
- inn
- int
上面6个英文组合最终的整个trie树如下图所示:
从上图中可以看到,trie树有如下的一些特点:
- 根节点不包含字符,除根节点以外每个节点只包含一个字符
- 从根节点到某一个节点,自上而下,路径上经过的字符连接起来就为目的节点对应的字符串。
- 每个节点的分支数目取决于key值的元素的取值范围,比如上图中每个节点会产生26个英文字母的分叉。
- trie树的查找效率取决于key的长度,key长度越长当然也就越耗时。
因此trie树最大的优点在于针对字符串的搜索方面有很好的性能,在查找过程中最大限度的减少了无关字符的比较。
trie树缺点
从上图可以看到,非根节点存储的是一个字符,这样会导致树的层级会比较高,因此导致trie树会消耗大量的内存。
下面通过Patricia Trie树来改进。
Patricia Trie树
Patricia Trie树是一种升级版的trie树,不同之处在于:非根节点可以存储字符串,而不是只能存储字符,也就是路径压缩了的trie,因此节省了在内存空间的开销。
同样6个英文组合分别如下:
- taa
- tan
- tc
- in
- inn
- int
patricia trie树如下图所示:
从上图可看到:patricia trie树进行了路径压缩,占用的空间更少。
需要注意的一点是,上图中若再插入一个新单词,原本压缩的路径可能需要扩展开来,这取决于插入的键的分布的稀疏情况,分布越稀疏,压缩的效果就越好。所以在以太坊中的地址为160位的,因此地址可能有2^160种,这个数值非常大,所以非常稀疏,因此patricia trie树很适合以太坊。
而以太坊真正使用的树是MPT(Merkle Patricia Trie)树。
MPT(Merkle Patricia Trie)树
MPT(Merkle Patricia Trie)树,即默克尔前缀树,是默克尔树和前缀树的结合。(默克尔树前面文章讲过,这里不再讲解,有兴趣的可以翻阅我之前文章)。
MPT树节点类型
MPT树的节点有以下4种类型:
- 扩展节点(Extension Node):只能有一个子节点。
- 分支节点(Branch Node):可以有多个节点。
- 叶子节点(Leaf Node):没有子节点。
- 空节点:空字符串。
节点类型如下图示:
从上图看到,Key只在扩展节点和叶子节点中存在,分支节点中没有Key。Value用来存储节点数值的,不同的节点类型对应的Value值也会不同,主要如下几种情况:
1.若节点类型是叶子节点,Value值存储的是一个数据项的内容。
2.若节点类型是扩展节点,Value值存储的是孩子节点的哈希值。
3.若节点类型是分支节点,Value值存储的是刚好在分支节点结束时的值,若没有节点在分支节点中结束时,Value值没有存储数据。
如下图所示,分支节点中Value值存储的是ab的值11:
一个完整的MPT树示例
需要存储的(key,value)如下所示:
最后的MPT树如下所示:
需要注意的点
1.(key,value)中的value是经过RLP序列化后再存储的。
2.MPT由下至上计算出的根哈希值存储在区块头中Root字段中,如下图所示:
3.每次发布新区块,MPT树中部分节点状态会改变,但改变并非在原地修改,而是新建一些分支,保留原本状态。当仅仅有新发生改变的节点才需要修改,其他未修改节点直接指向前一个区块中的对应节点,如下图所示:
思考:由于以太坊中出块时间15s左右,会产生很多的分叉,保持历史记录的一个好处是,当某些分叉需要回滚时可以更好的查看历史记录。
总结
以太坊为了实现账户模式,其内部的数据结构复杂性也比比特币系统高一些。从中可以学习到以太坊的设计妙处所在。