明明白白以太坊Merkle Patricia Trie

在以太坊数据结构中,Merkle Patricia Trie始终是个绕不过去的坎,世界状态,交易,交易收据等都是以这种树的形式存储在区块链数据库中,并将树root hash保存在区块头里。可以说不弄懂这种树的原理就没有办法真正明白以太坊的数据存储方式。

大家都知道在以太坊cpp版本和go版本采用的是levelDb这种数据库,这种数据库存储的是[key, value]对。而实际上以太坊存储的数据结构也是[key, value]对,以状态state为例,keyaddressvalue[nonce, balance, storageRoot, codeHash]。那么问题来了,以太坊为什么不直接按[key, value来存储数据,而要费那么大劲用树型结构来存储呢?
答案是为了安全性。

而为了引进Merkle Patricia Trie,我们要先来看一种简单的树。

Radix Trees

中文翻译为前缀树,这是一种用来查询的数据结构。每次遍历key的一个字符,直到找到对应的值。
比如有下面几个数据:('do', 'verb'), ('dog', 'puppy'), ('doge', 'coin'), ('horse', 'stallion'),存储在Radix Trees里是这样的:

Radix Trees

其中圆圈里是值,key的字符组成查询路径,路径是doge时的查询过程为d->o->g->e,需要查询4次,而路径为horse时查询需要5次,因此这种树查询虽然可行,但是效率不高。我们后面可以看到以太坊是怎么解决这个问题的。
解决了查询,然后为了安全性,我们引入了Merkle Trees

Merkle Trees

Merkle Trees又名Hash Trees,数据都存储在叶节点,父节点则存储叶节点的hash值,一级一级向上, 直到根节点。因此如果修改了叶节点的数据,则父节点hash值就会改变,根节点hash值也会改变,从而很快就可以验证数据是否正确。

Merkle Trees

保证数据不被篡改,这是区块链技术的基础。

Merkle Patricia Trie

以太坊Merkle Patricia Trie是结合了上面两种树的数据结构,兼顾了查询和安全性。

查询

前面不是说过Radix Trees查询效率不行吗?那就进行优化,将路径里相同部分提取出来,作为共同路径,从而减少树的高度,提供查询效率。
还是用上面的例子,合并路径后的树变为:

Modified Radix Trees

可以看到将dohorse作为共同路径后,查询doge的路径变成了do->g->e,只需要查询3次,而horse只需要查询一次就够了。

安全性

节点与节点之间的联系不再采用内存指针的方式,而是采用hash值的方式,比如上图中的puppy这个值的节点存储执行下一个节点的hash值,然后将这个hash值与实际节点对应关系存储在[key, value]的数据库中。当有人篡改coin节点值时,也必须要修改puppy节点里的hash值,然后verb节点里hash值也需要修改,直到根节点,所以我们只需要验证根节点的hash值,就知道底层数据是否正确。

节点类型

按功能不同,存在4种节点:

  1. NULL节点
  2. 分支节点,含有17项数据,[ v0 ... v15, vt ]
  3. 叶节点,含有2项数据,[ encodedPath, value ]
  4. 扩展节点,含有两项数据,[ encodedPath, key ]

Merkle Patricia Trie查询路径是以nibble(半个字节)作为单位。
分支节点中的前16项数据v0-v15分别对应16进制的0-0xF,是用nibble作为索引,可以快速进行查询,比如路径nibble是0xa,则取分支节点0xa位置的值。分支节点对应图上的分叉节点。
叶节点和扩展节点都是两项,只是value不同,那么怎么区分他们呢?
以太坊是采用添加前缀的方式,根据节点类型和路径长度是否是奇偶数来添加不同的前缀。
有一个对应表:

hex char bits node type partial path length
0 0000 extension even(偶数)
1 0001 extension odd(奇数)
2 0010 terminating (leaf) even(偶数)
3 0011 terminating (leaf) odd(奇数)

可以看到前缀为0或者1时表示扩展节点,2或者3时表示叶节点。
另外前缀为0或者2时,需要变更为00或20.

例子1

我们用一个例子来说明:

0_t4ziOSL-71LPnkjE.png

图中右上角有4个[key, value]对,我们要存储这4对数据。key每个方框里是一个nibble

  1. 从这四个路径中可以提取出公共路径a7,因此可以建立一个扩展节点A, [00 a7, hashB],a7是一个偶数长度的扩展节点,前缀为00,hashB是下一个节点B的hash值。
  2. 下一个nibble取值有1, 7, f,因此节点B为一个分支节点,其中index为1,7,f的位置保存下一个节点的hash值,value为空。这个分支节点为:[ EMPTY, hashC, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, hashD, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, hashE, value]。
    hashC, hashD, hashE为下一层节点的hash
  3. 因为出现了分支,我们来看1这个分支,这个分支后没有分支,所以后续的1355可以作为一个叶节点,为[20 1355, 45.0ETH],这里1355为偶数长度的叶节点,所以前缀为20。
  4. 再来看7这个分支,后续又有d3这共同路径,因此创建一个扩展节点D,[00 d3, hashF],'d3'是一个偶数长度的扩展节点,前缀为00,hashF是下一个节点F的hash值。
  5. d3之后又出现分支3和9,因此又出现一个分支节点,其中index为3和9的位置保存下一个节点的hash值,value为空。这个节点为:[ EMPTY, EMPTY, EMPTY, hashG, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, hashH, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, value]
  6. 剩下的就是两个叶节点,分别为[37, 1.00WEI]和[37, 0.12ETH],这两个都是奇数长度的叶节点,因此前缀为3。
  7. 其他节点可以按同样的方法分析。

例子2

我们再用前面的('do', 'verb'), ('dog', 'puppy'), ('doge', 'coin'), ('horse', 'stallion')做例子来说明分析过程。
由于是采用nibble作为路径单位,所以先将路径和值都写为字节形式:

<64 6f> : 'verb'
<64 6f 67> : 'puppy'
<64 6f 67 65> : 'coin'
<68 6f 72 73 65> : 'stallion'

那么在数据库中存储形式为:

rootHash: [ <16>, hashA ]
hashA:    [ <>, <>, <>, <>, hashB, <>, <>, <>, hashC, <>, <>, <>, <>, <>, <>, <>, <> ]
hashC:    [ <20 6f 72 73 65>, 'stallion' ]
hashB:    [ <00 6f>, hashD ]
hashD:    [ <>, <>, <>, <>, <>, <>, hashE, <>, <>, <>, <>, <>, <>, <>, <>, <>, 'verb' ]
hashE:    [ <17>, hashF ]
hashF:    [ <>, <>, <>, <>, <>, <>, hashG, <>, <>, <>, <>, <>, <>, <>, <>, <>, 'puppy' ]
hashG:    [ <35>, 'coin' ]

我们来模拟一下真实的查询过程,假如我们要查询doge这个key对应的值是多少。

  1. rootHash已知(存储在区块头中),那么从levelDb中读出keyrootHash的值,也就是[ <16>, hashA ],这是一个扩展节点,路径为6,剩下路径为4,6,f,6,7,6,5,并得到下一个节点hashA
  2. levelDb中读出keyhashA的值,也就是 [ <>, <>, <>, <>, hashB, <>, <>, <>, hashC, <>, <>, <>, <>, <>, <>, <>, <> ],nibble为4,在位置4读出下一个节点hashB,剩余路径为6,f,6,7,6,5
  3. levelDb中读出keyhashB的值,也就是[ <00 6f>, hashD ],这是一个路径为6f的扩展节点,因此剩余路径为6,7,6,5,并得到下一个节点hashD
  4. levelDb中读出keyhashD的值,也就是 [ <>, <>, <>, <>, <>, <>, hashE, <>, <>, <>, <>, <>, <>, <>, <>, <>, 'verb' ],nibble为6,在位置6读出下一个节点hashE,剩余路径为7,6,5
  5. levelDb中读出keyhashE的值,也就是 [ <17>, hashF ],这是一个路径为7的扩展节点,因此剩余路径为65,并得到下一个节点hashF
  6. levelDb中读出keyhashF的值,也就是[ <>, <>, <>, <>, <>, <>, hashG, <>, <>, <>, <>, <>, <>, <>, <>, <>, 'puppy' ],nibble为6,在位置6读出下一个节点hashG,剩余路径为5
  7. levelDb中读出keyhashG的值,也就是[ <35>, 'coin' ],这是一个路径为5的叶节点,正好和我们的剩余路径吻合,因此我们就得到了最终的值coin

可见以太坊为了安全性真的增加了不少复杂性,降低了效率。

以太坊中的应用

以太坊中实际情况还要复杂,数据还需要通过RLP编码。

  1. State Trie(世界状态树)
    路径是sha3(ethereumAddress)valuerlp([nonce,balance,storageRoot,codeHash])
  2. Transactions Trie(交易树)
    路径是rlp(transactionIndex)valuerlp(transaction)
  3. Receipts Trie(交易收据树)
    路径是rlp(transactionIndex)valuerlp(transaction receipt)

参考

Understanding Trie Databases in Ethereum
Modified Merkle Patricia Trie Specification (also Merkle Patricia Tree)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容