Merkle Patricia Tree(简称MPT树,其实是一种trie前缀树)是以太坊中的一种加密认证的数据结构,能够用来存储全部的(key,value)对。以太坊区块的头部包括一个区块头,一个交易的列表和一个uncle区块的列表。在区块头部包括了交易的hash树根,用来校验交易的列表。在p2p网络上传输的交易是一个简单的列表,它们被组装成一个叫作trie树的特殊数据结构,来计算根hash。值得注意的是,除了校验区块外,这个数据结构并非必须的,一旦区块被验证正确,那么它在技术上是能够忽略的。可是,这意味着交易列表在本地以trie树的形式存储,发送给客户端的时候序列化成列表。客户端接收到交易列表后从新构建交易列表trie树来验证根hash。RLP(Recursive length prefix encoding,递归长度前缀编码),用来对trie树种全部的条目进行编码(参考:http://www.cnblogs.com/fengzhiwu/p/5565559.html)。html
Trie树也叫做Radix树,为了提升效率,以太坊在实现上对其作了一些改进。在通常的radix树中,key是从树根到对应value得真实的路径。即从根节点开始,key中的每一个字符会标识走那个子节点从而到达相应value。Value被存储在叶子节点,是每条路径的终止。假如key来自一个包含N个字符的字母表,那么树中的每一个节点均可能会有多达N个孩子,树的最大深度是key的最大长度。node
Radix的好处是具备相同前缀的key所对应的value在树中是很是靠近的,而且trie中不会有像hash-table同样的冲突。可是它也有缺陷,假若有一个很长的key,没有其余的key和它有公共的前缀,那么在遍历或存储它对应的值得时候,你就会遍历或存储至关多的节点,由于这棵树是很是不平衡的。git
以太坊对Radix树的实现作了不少改进。github
首先,为了保证树的加密安全,每一个节点经过他的hash被引用,而非32bit或64bit的内存地址,即树的Merkle部分是一个节点的肯定性加密的hash。一个非叶节点存储在leveldb关系型数据库中,数据库中的key是节点的RLP编码的sha3哈希,value是节点的RLP编码。代码中的实现如图:数据库
想要得到一个非叶节点的子节点,只须要根据子节点的hash访问数据库得到节点的RLP编码,而后解码就好了。如图所示:安全
经过这种模式,根节点就成为了整个树的加密签名,若是一颗给定trie的跟hash是公开的,那么全部人均可以提供一种证实,经过提供每步向上的路径证实特定的key是否含有给定的值。网络
第二,引入了不少节点类型来提升效率。MPT树中的节点包括空节点、叶子节点、扩展节点和分支节点。数据结构
其中有空节点,简单的表示空,在代码中是一个空串。wordpress
标准的叶子节点,表示为[key,value]的一个list,其中key是key的一种特殊十六进制编码,value是value的RLP编码。函数
扩展节点,也是[key,value]的列表,可是这里的value是其余节点的hash,这个hash能够被用来查询数据库中的节点。也就是说经过hash连接到其余节点。
最后分支节点,由于MPT树中的key被编码成一种特殊的16进制的表示,再加上最后的value,因此分支节点是一个长度为17的list,前16个元素对应着key中的16个可能的十六进制字符,若是有一个[key,value]对在这个分支节点终止,最后一个元素表明一个值,即分支节点既能够搜索路径的终止也能够是路径的中间节点。
除了四种节点,MPT树中另一个重要的概念是一个特殊的十六进制前缀(hex-prefix, HP)编码,用来对key进行编码。由于字母表是16进制的,因此每一个节点可能有16个孩子。由于有两种[key,value]节点(叶节点和扩展节点),引进一种特殊的终止符标识来标识key所对应的是值是真实的值,仍是其余节点的hash。若是终止符标记被打开,那么key对应的是叶节点,对应的值是真实的value。若是终止符标记被关闭,那么值就是用于在数据块中查询对应的节点的hash。不管key奇数长度仍是偶数长度,HP多能够对其进行编码。最后咱们注意到一个单独的hex字符或者4bit二进制数字,即一个nibble。
HP编码很简单。一个nibble被加到key前,对终止符的状态和奇偶性进行编码。最低位表示奇偶性,第二低位编码终止符状态。若是key是偶数长度,那么加上另一个nubble,值为0来保持总体的偶特性。
下面从MPT树的更新,删除和查找过程来讲明MPT树的操做。
函数_update_and_delete_storage(self, node, key, value)
i. 若是node是空节点,直接返回[pack_nibbles(with_terminator(key)), value],即对key加上终止符,而后进行HP编码。
ii. 若是node是分支节点,若是key为空,则说明更新的是分支节点的value,直接将node[-1]设置成value就好了。若是key不为空,则递归更新以key[0]位置为根的子树,即沿着key往下找,即调用_update_and_delete_storage(self._decode_to_node(node[key[0]]), key[1:], value)。
iii. 若是node是kv节点(叶子节点或者扩展节点),调用_update_kv_node(self, node, key, value),见步骤iv
iv. curr_key是node的key,找到curr_key和key的最长公共前缀,长度为prefix_length。Key剩余的部分为remain_key,curr_key剩余的部分为remain_curr_key。
a) 若是remain_key==[]== remain_curr_key,即key和curr_key相等,那么若是node是叶子节点,直接返回[node[0], value]。若是node是扩展节点,那么递归更新node所连接的子节点,即调用_update_and_delete_storage(self._decode_to_node(node[1]), remain_key, value)
b) 若是remain_curr_key == [],即curr_key是key的一部分。若是node是扩展节点,递归更新node所连接的子节点,即调用_update_and_delete_storage(self._decode_to_node(node[1]), remain_key, value);若是node是叶子节点,那么建立一个分支节点,分支节点的value是当前node的value,分支节点的remain_key[0]位置指向一个叶子节点,这个叶子节点是[pack_nibbles(with_terminator(remain_key[1:])), value]
c) 不然,建立一个分支节点。若是curr_key只剩下了一个字符,而且node是扩展节点,那么这个分支节点的remain_curr_key[0]的分支是node[1],即存储node的value。不然,这个分支节点的remain_curr_key[0]的分支指向一个新的节点,这个新的节点的key是remain_curr_key[1:]的HP编码,value是node[1]。若是remain_key为空,那么新的分支节点的value是要参数中的value,不然,新的分支节点的remain_key[0]的分支指向一个新的节点,这个新的节点是[pack_nibbles(with_terminator(remain_key[1:])), value]
d) 若是key和curr_key有公共部分,为公共部分建立一个扩展节点,此扩展节点的value连接到上面步骤建立的新节点,返回这个扩展节点;不然直接返回上面步骤建立的新节点
v. 删除老的node,返回新的node
删除的过程和更新的过程相似,并且很简单,函数名:_delete_and_delete_storage(self, key)
i. 若是node为空节点,直接返回空节点
ii. 若是node为分支节点。若是key为空,表示删除分支节点的值,直接另node[-1]=‘’, 返回node的正规化的结果。若是key不为空,递归查找node的子节点,而后删除对应的value,即调用self._delete_and_delete_storage(self._decode_to_node(node[key[0]]), key[1:])。返回新节点
iii. 若是node为kv节点,curr_key是当前node的key。
a) 若是key不是以curr_key开头,说明key不在node为根的子树内,直接返回node。
b) 不然,若是node是叶节点,返回BLANK_NODE if key == curr_key else node。
c)若是node是扩展节点,递归删除node的子节点,即调用_delete_and_delete_storage(self._decode_to_node(node[1]), key[len(curr_key):])。若是新的子节点和node[-1]相等直接返回node。不然,若是新的子节点是kv节点,将curr_key与新子节点的能够串联当作key,新子节点的value当作vlaue,返回。若是新子节点是branch节点,node的value指向这个新子节点,返回。
查找操做更简单,是一个递归查找的过程函数名为:_get(self, node, key)
i. 若是node是空节点,返回空节点
ii. 若是node是分支节点,若是key为空,返回分支节点的value;不然递归查找node的子节点,即调用_get(self._decode_to_node(node[key[0]]), key[1:])
iii. 若是node是叶子节点,返回node[1] if key == curr_key else ‘’
iv. 若是node是扩展节点,若是key以curr_key开头,递归查找node的子节点,即调用_get(self._decode_to_node(node[1]), key[len(curr_key):]);不然,说明key不在以node为根的子树里,返回空
相对于普通的前缀树,MPT树能有效减小Trie树的深度,增长Trie树的平衡性。并且经过节点的hash值进行树的节点的连接,有助于提升树的安全性和可验证性。因此说MPT树是Trie和Merkle树混合加上平衡操做后的产物。
参考:
https://easythereentropy.wordpress.com/2014/06/04/understanding-the-ethereum-trie/
https://github.com/ethereum/wiki/wiki/Patricia-Tree
https://github.com/ebuchman/understanding_ethereum_trie
https://github.com/ethereum/pyethereum