以太坊 MPT

MPT全称Merkle Patricia Trie,是以太坊用来存储数据的一种数据结构。
MPT融合了Trie、Patricia Trie、Merkle Tree这3种数据结构的优点,从而实现快速查找并节省存储空间。下面依次介绍一下这几种结构的基本概念和原理。

1. Trie

Trie也称为Radix Tree或者Prefix Tree,这个单词来源于retrieval(检索)这个单词的中间4个字母,中文翻译为字典树或者前缀树,是一种用于快速检索的多叉树。
原理很简单,以检索英文单词为例,只需要把每个单词按字母进行拆分然后到树上查找,查找深度和单词的长度相同。下图是一个Trie的示例:
这里写图片描述

2. Patricia Trie

显然,上面这种方式需要耗费较大的存储空间,因为每个结点都必须存储26个指针指向下一级。
实际上我们并没有那么多的单词组合,有一些结点可能只有一个有效child。我们可以把这些只有一个child的结点们合并成一个结点,这就是Patricia Trie。
Patricia这个单词是一个缩写,全称是Practical Algorithm To Retrieve Information Coded In Alphanumeric。下图是一个Patricia Trie的示例:
这里写图片描述

3. Merkle Tree

Merkle Tree是一种用于快速验证内容完整性的数据结构,是有一个叫Ralph Merkle的人发明的。下图是一个Merkle Tree的示例:
这里写图片描述
其基本原理是分别计算树的叶子结点的hash值,然后把叶子结点们的hash值拼接在一起,再计算一次hash作为其父结点的hash值,依次向上直到根结点,根结点的hash值称为Merkle Root。可以看出,如果想要获得相同的Merkle Root,所有子结点的内容都必须相同,这样就可以用来验证树的内容有没有被篡改。

4. MPT

MPT融合了上面3种数据结构,但是又有一些细微的差别。
上面举的例子都是英文单词,因此是26叉树,而MPT是用来检索字节数据的,因此是16叉树,分别代表0x0~0xF。
首先MPT定义4种结点类型:

  • 空结点(NULL)
  • 分支结点(branch node):包含16个分支,以及1个value
  • 扩展结点(extension node):只有1个子结点
  • 叶子结点(leaf node):没有子结点,包含一个value

举个例子:[123,‘1ETH’],[12345,‘2ETH’],[123789, ‘3ETH’]这三个数据,就会组织成下面这个样子:
这里写图片描述
最上面是一个扩展结点,体现了Patricia Trie的思想,把公共的关键字123压缩到了一个结点中。
中间的是一个分支结点,体现了Trie的思想,有16个分叉。但是由于123本身也是一个关键字,不属于任何一个分支,因此会在分支结点的最后一个字段存储对应的value。
最下面是两个叶子结点,存储对应的value。

那么Merkle Tree是怎么体现的呢?还记得吗,挖矿的时候ethash的Finalize()函数里会调用StateDB的IntermediateRoot()获取Merkle Root。这个过程中会对结点进行“折叠”(collapse),也就是把所有的子结点替换成子结点的hash值,然后再分别计算每个结点的hash值。折叠后的效果参见下图:
这里写图片描述
因此,我们只要有Merkle Root,就可以从数据库中恢复出根结点,再依次根据结点中存储的子结点hash值,从数据库中恢复出所有子结点。

在以太坊中,MPT是通过下面4种结点类型实现的:

type (
	fullNode struct {
		Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
		flags    nodeFlag
	}
	shortNode struct {
		Key   []byte
		Val   node
		flags nodeFlag
	}
	hashNode  []byte
	valueNode []byte
)
  • fullNode用于实现分支结点
  • shortNode用于实现扩展结点和叶子结点(区别是看Val字段指向的是valueNode还是其他类型的node)
  • valueNode用于存储数据(存在于fullNode或者叶子结点shortNode中)
  • hashNode用于实现结点的折叠

另外fullNode和shortNode中还有个nodeFlag类型的字段,用来缓存结点的hash值,或者标记结点为dirty。
因此以太坊实现的MPT结构参见下图:
这里写图片描述
在MPT执行插入操作时,可能会导致结点的分裂,比如一个shortNode裂变成一个shortNode加一个fullNode。执行删除操作时,可能导致结点的合并,比如两个shortNode合并成一个shortNode。具体逻辑参考Trie的insert()/delete()函数,这里就不详细分析了。

以太坊源码分析 数据存储:本文分析以太坊数据存储相关的流程。首先介绍一下MPT的存储流程,然后依次分析StateDB、Transactions、Receipts的存储,这3棵树的Merkle Root最终会存储到区块Header中 ...