`

Merkle Patricia Tree (MPT) 树详解

 
阅读更多

1.    介绍

  

  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)。

  Trie树也叫作Radix树,为了提高效率,以太坊在实现上对其做了一些改进。在一般的radix树中,key是从树根到对应value得真实的路径。即从根节点开始,key中的每个字符会标识走那个子节点从而到达相应value。Value被存储在叶子节点,是每条路径的终止。假如key来自一个包含N个字符的字母表,那么树中的每个节点都可能会有多达N个孩子,树的最大深度是key的最大长度。

  Radix的好处是具有相同前缀的key所对应的value在树中是非常靠近的,并且trie中不会有像hash-table一样的冲突。但是它也有缺陷,假如有一个很长的key,没有其他的key和它有公共的前缀,那么在遍历或存储它对应的值得时候,你就会遍历或存储相当多的节点,因为这棵树是非常不平衡的。

 

2.    特性

  以太坊对Radix树的实现做了很多改进。

  首先,为了保证树的加密安全,每个节点通过他的hash被引用,而非32bit或64bit的内存地址,即树的Merkle部分是一个节点的确定性加密的hash。一个非叶节点存储在leveldb关系型数据库中,数据库中的key是节点的RLP编码的sha3哈希,value是节点的RLP编码。代码中的实现如图:

  想要获得一个非叶节点的子节点,只需要根据子节点的hash访问数据库获得节点的RLP编码,然后解码就行了。如图所示:

  

  通过这种模式,根节点就成为了整个树的加密签名,如果一颗给定trie的跟hash是公开的,那么所有人都可以提供一种证明,通过提供每步向上的路径证明特定的key是否含有给定的值。

 

  第二,引入了很多节点类型来提高效率。MPT树中的节点包括空节点、叶子节点、扩展节点和分支节点。

  其中有空节点,简单的表示空,在代码中是一个空串。

  标准的叶子节点,表示为[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来保持整体的偶特性。

3.  操作

  下面从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为根的子树里,返回空

 

4.    总结

  相对于普通的前缀树,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

分享到:
评论

相关推荐

    c++实现merkle_tree

    c++代码实现merkle_tree树..............

    1_merkle-tree-master.rar

    merkle tree merkle tree merkle tree merkle tree merkle tree merkle tree merkle tree merkle tree merkle tree

    基于改进Merkle-Tree认证方法的可验证多关键词搜索方案.docx

    基于改进Merkle-Tree认证方法的可验证多关键词搜索方案 本文旨在解决云存储条件下密文的高效搜索问题,提出了一种基于改进Merkle-Tree认证方法的可验证多关键词搜索方案。该方案旨在解决搜索结果的可验证性问题,...

    merkle-patricia-tree:项目正在积极开发中,已移至EthereumJS VM monorepo

    概要 这是指定的修改后的merkle patricia树的实现: 修改后的Merkle Patricia树(trie)提供了一种持久性数据结构,可以在任意长度的二进制数据(字节数组)之间进行映射。 它是根据可变数据结构定义的,以在256位二...

    开源项目-keybase-go-merkle-tree.zip

    Merkle Tree,也称为哈希树或默克尔树,是一种数据结构,广泛应用于区块链、分布式存储系统以及网络安全等领域。它的基本原理是将数据组织成一棵二叉树,每个非叶子节点的值是由其子节点的哈希值计算得到的,而根...

    PyPI 官网下载 | merkle-tree-stream-0.0.1a4.tar.gz

    标题中的“PyPI 官网下载 | merkle-tree-stream-0.0.1a4.tar.gz”表明这是一个从Python Package Index(PyPI)官方下载的软件包,名为“merkle-tree-stream”,版本号为0.0.1a4,其打包格式是tar.gz。PyPI是Python...

    merkletree.rar

    java 实现 merkle tree while (true) { Event event = receiveEvent(); String hash = computeHash(event); // ... process and transmit the message to the downstream Queue sendToDownstreamQueue(hash, ...

    前端开源库-merkle-tree-stream

    前端开源库-merkle-tree-streamMerkle树流,一种基于输入数据生成Merkle树的流。

    merkle_tree.tar.xz

    使用开源merkle-tree构建了哈希sqlite数据库并将结果存入nodes文件夹,代码删除了源码部分代码,使得main.cc函数简单易懂,添加部分数据库代码,修改少量的merkle_tree.cc源码

    以太坊的数据结构(状态树、交易树、收据树)及代码分析

    文章目录一、状态树1.1 trie1.2 Patricia tree(trie)1.3 Merkle Patricia tree(trie)1.4 Modified Merkle Patricia tree(trie)1.5 账户状态值存储二、交易树、收据树2.1 概述2.2 Modified Merkle Patricia tree(trie...

    前端开源库-merkle-tree-stream.zip

    **Merkle Tree** 是一种哈希树数据结构,其特点是每个叶子节点都存储了数据块的哈希值,而非叶子节点则由其子节点的哈希值构成。这种结构在区块链、分布式系统和文件校验等领域有着广泛应用。Merkle Tree的核心优点...

    merkle-tree:Java中的简单Merkle树实现

    在提供的`merkle-tree-master`压缩包中,可能包含了实现这些功能的源代码,包括Merkle树的构建、哈希计算、证明验证等方法。通过阅读和分析这些代码,可以深入理解Merkle树的原理和Java实现细节。对于学习和实践...

    merkle-tree:Kotlin中的Merkle Tree和Merkle证明实现

    Kotlin中的Merkle Tree和Merkle证明实现MerkleTree是使用Kotlin kotlin-platform-common插件在Kotlin中实现的,该插件可将代码编译为JavaScript。 实现的方法: MerkleTree (input : List , hash : ( ByteArray ) ->...

    Merkle Tree构造及存在性证明,QT实现UI

    采用qt编写,适用于区块链初学者学习参考

    merkle-tree:可动态调整大小的二进制SHA-256哈希树(Merkle树)的AC实现

    Merkle树库是实现二进制[Merkle(hash)树]( )的C库。 该库最初是为与[安全块设备]( )一起使用而开发的。 因此,它具有以下属性: 使用SHA-256作为哈希算法的二进制哈希树 支持可变大小的数据存储 最大元素数和...

    merkle, 实现Merkle树算法的node.js 模块.zip

    merkle, 实现Merkle树算法的node.js 模块 使用 sha512,sha256,ripemd160,漩涡,sha1,md5或者无算法构建Merkle树。用法构建Merkle树var merkle = require('merkle');var abcde

    merkle-tree-stream:根据输入数据生成梅克尔树的流

    梅克尔树流 根据输入的数据生成merkle树的流。 改编自 。为什么? 签名和完整性检查是使Dat成为出色协议的一部分。 穿过系统的每个块都经过哈希处理,并成为哈希树的一部分。 由于 ,我们最终创建了散列的散列,这...

    merkle-tree:Java中的默克尔树实现

    概述我最近发现需要在数据处理系统中...默克尔树Merkle 树通常被实现为二叉树,其中每个非叶节点都是它下面两个节点的哈希。 叶子可以是数据本身,也可以是数据的哈希/签名。 因此,如果在系统之间检测到根散列的任何

    solidity-mt-vs-mmr:比较Merkle Tree和Merkle Mountain Range树木气体使用量的Solidity示例

    比较Merkle Tree和树的气体使用量的Solidity示例。 建造 npm run build 测试 npm test 输出: Test append gas used: 0 62737 append gas used: 1 47749 append gas used: 2 47749 [...] append gas used: 97 ...

    python-merkle-tree

    python-merkle-tree

Global site tag (gtag.js) - Google Analytics