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