Merkle tree简介
Merkle树又称为哈希树,是一种二叉树,由一个根节点、若干中间节点和一组叶节点组成。最底层的叶节点存储数据,在它之上的一层节点为它们对应的Hash值,中间节点是它下面两个子节点的Hash值,根节点是最后顶层的Hash值,这个一般成为Merkle根。
Merkle树的层层Hash计算,任何底层叶节点或者说某个节点的数据变动都会传递到父亲节点,并直达树根。当叶子节点发生数据改变时,如果要比较两个集合的数据是否相同,则只需比较两次数据的树根即可,若底层叶节点数据相同,则树根相同;反之,树根便不相同。因此Merkle树的典型应用场景具体如下:
- 快速比较大量数据:当两个Merkle树根相同时,则意味着所代表的数据必然相同
- 快速定位变更:如果上图L1的数据被修改,则hi影响到Hash0-0,Hash0 和Root。因此沿着Root->Hash0->Hash0-0,可以快速定位到发生改变的L1。
MPT 状态树
Trie树是一种有序的树形结构,也被称为前缀树或者字典树,一般用于保存关联数组,其中的键通常是字符串,键不保存在节点中,而是由节点在树中的位置决定,根节点对应空字符串,键对应的值标注在节点之下。
Patricia树是一种节省空间的压缩前缀树。相当于Trie树存在的缺点,每个前置节点仅能表示一个字母,不管key和其它key有没有共享内容,key越长,树的深度也就越长。Patricia树的主要改进在于如果存在一个父节点只有一个子节点,那么这个父节点将与其子节点合并,这样可以减少Trie中树的深度,加快搜索节点速度,同时也减少了空间的消耗。
MPT是Merkle 和Patricia 结合后的产物,在以太坊中,MPT包含4种不同的节点: 空节点、叶子节点、扩展节点和分支节点。
- 空节点:无实际内容节点,但占用一个元数据存储。
- 叶子节点:是一个键值对,其中key是原始内容的一种特殊十六进制编码,value是内容的RLP编码.
- 扩展节点: 也是一个键值对,但是value是其他节点的Hash值,即指向其他节点的链接
- 分支节点:由于key被编码成一种特殊的十六进制的表示,还有最后的value,所以分支节点是一个长度为17的列表,前16个元素对应着key中的16个可能的十六进制字符,如果有一个(key,vlaue)键值对在这个分支节点终止,那么最后一个元素代表一个值,即分支节点既可以是所以搜路径的终止也可以是搜索路径的中间节点。分支节点的父节点基本上就是扩展节点。
十六进制字母表中有16个字符(0…9 A…F),如果某个节点有16个子节点,那么每个子节点对应一个字符占用4位,半个字节,被称为nibble。一个nibble被加到key之前,用到对终止符的状态和奇偶性进行编码。其中,最低位表示奇偶性,**0表示偶数,1表示奇数;**倒数第二位表示终止符状态。如果key是偶数位,则需要加上另外一个nibble。
MPT树实现图(图片来源:以太坊黄皮书)
首先,根节点ROOT实际上是一个扩展节点,该节点进行SHA-3Hash计算后的值就是所谓三个Merkle根之一的stateRoot。这个扩展节点的key为其它实际节点的共有前缀(a7,key)两位字符,需要在前面补充前缀半字节nibble;value指向第一个分支节点,这个分支节点key包含(1,7,f)字符。其中1指向叶子节点,这个叶子节点为1335,偶数位补充前缀,因为是终止节点,nibble是0010=2,value是balance 45.0 ETH。第一个分支节点key中的7指向第二个扩展节点,他的key是后续两个叶子节点的共有前缀d3,偶数位补字符0,value指向第二个分支节点。这个分支节点包含3和9,其中3指向叶子节点1.00WEI,9指向0.12ETH的叶子节点,这两个叶子节点key都只有1位,而且是终止节点,所以补充的nibble前缀0011 =3 。第一个分会节点key还包含f,他指向1.1 ETH的叶子节点,这个叶子节点的key为9365,偶数位而且是终止节点,所以在前面补充前缀nibble0010=2。
叶子键值对
keys | values |
---|---|
a711355 | 45.0ETH |
a77d337 | 1.00WEI |
a7f9365 | 1.1ETH |
a77d397 | 0.12ETH |
综上所述,以太坊MPT树具有如下特点
- 叶子节点和分支节点可以保存value,扩展节点保存key
- 没有公共的key就成为2个叶子节点
- 如有公共的key则应该提取为一个扩展节点
- 如果公共的key也是一个完整的key,那么数据应该保存到下一级的分支节点中
以太坊的应用
在区块结构中包含区块头header,header里面包含3种树根
状态树:stateRoot
状态树是全局的树
path = sha3(ethereumAddress): 以太坊账户地址
value=rlp([nonce,balance,storageRoot,codeHash]):交易次数、账户余额、存储树、合约代码Hash
其中storageRoot是另一个trie树,用于存储合约中的所有数据,每个账户都有一个独立的storageRoot树
交易树: transactionsRoot
每个block都有一个交易树。
path = rlp(transactionIndex): 该交易在block中的索引,顺序由矿工决定
value=交易记录
该树生成后永远不会被修改
收据树:receiptsRoot
每个block都有一个收据树。
path = rlp(receiptIndex): 该交易在block中生成receipt的索引,顺序由矿工决定
value=receipt记录
该树生成后永远不会被修改