以太坊中的Merkle Patricia Tree(3):源碼分析

Merkle Patricia Trie是完全確定性的,這意味著具有相同(鍵,值)綁定的Patricia trie具有相同的根hash,具有O(log(n) )插入查找和刪除的效率,而且比基于比較的查找方法如紅黑樹更容易理解和編碼。

1 基本的radix trie

基本的radix trie像下面這樣(節點類型只有一種:分支節點):

[i0, i1 ... in, value]  //前面存放索引, 后面存放value(可選)

比如key='dog', 轉換為16進制是64 6f 67。
在底層key/value數據庫中,先找到根節點hash, 根節點就是一個其他節點hash的list, 然后在list的索引=6取出i5,是下一級節點的hash值, 在底層數據庫中根據該hash值找到該節點的list, 這樣一級一級往下找,最后找到對應的value.

root → 6 → 4 → 6 → 15 → 6 → 7

這個和底層扁平數據庫查詢不一樣, 扁平數據庫對key查找一次就行,在trie中要在底層數據庫中查找多次才能找到value, 為了消除歧義,讓我們將后者稱為 **路徑**

代碼實現大致如下:

# node: 節點數據
# path/value 數據對
def update(node,path,value): #插入一個空path, 表示新建一個trie樹
 if path == '': 
    #新建擴展節點或者使用傳入的節點數據
   curnode = db.get(node) if node else [ NULL ] * 17 
   newnode = curnode.copy()
   newnode[-1] = value #保存該path的value
 else: # path非空
    #新建擴展節點或者使用傳入的節點數據
   curnode = db.get(node) if node else [ NULL ] * 17
   newnode = curnode.copy()
  #創建下一級節點
   newindex = update(curnode[path[0]],path[1:],value)
   newnode[path[0]] = newindex
   db.put(hash(newnode),newnode)
 return hash(newnode)

def delete(node,path):
 if node is NULL:
   return NULL
 else:
   curnode = db.get(node)
   newnode = curnode.copy()
 if path == '':
   newnode[-1] = NULL
 else: 
   newindex = delete(curnode[path[0]],path[1:])
   newnode[path[0]] = newindex

 if len(filter(x -> x is not NULL, newnode)) == 0:
   return NULL
 else:
   db.put(hash(newnode),newnode)
 return hash(newnode)

可見上述代碼沒有考慮數據存儲空間和查找性能, Patricia Trie是壓縮trie樹, 更適合工程上使用.

2 radix trie結合merkle

merkle出現在: 指向下一級節點的指針是使用 節點的確定性加密hash ** ** (對于key/value型數據庫,每次查詢 key == sha3(rlp(value)) , 而不是傳統意義上下一級節點地址的指針(C語言實現)。
這為數據結構提供了一種密碼認證;
如果給定的trie的根哈希是公開的,則任何人都可以 通過給出給定path上的所有節點, 來證明在給定path上存在一個給定值 ,對于攻擊者,不可能提供一個不存在的(path,value)對的證明, 因為根哈希最終基于它下面的所有哈希,所以任何修改都會改變根哈希。

在如上所述一次遍歷一個半字節的path中,大多數節點是包含17個元素的數組。 前16個索引代表path中的下一個十六進制字符(半字節)所保持的每個可能值,以及在路徑已經完全遍歷的情況下保存的最終目標值。 這17個元素的數組節點被稱為 分支節點 ** ** 。

3 Merkle Trie 結合Patricia

radix tries 從查找性能和空間占用方面效率都比較低, Patricia 樹通過擴展節點解決該問題.

擴展節點的形式是 [ encodedPath, key ] , encodepath是壓縮路徑編碼, 通過第一個半字節標識和葉子結點進行區分.

當以半字節遍歷路徑時,我們可能會以 奇數 個半字節來遍歷,但是因為所有的數據都是以 字節格式 存儲的,所以不可能區分例如 半字節1和半字節01 (都是 必須保存為<01>)。 要指定奇數長度,部分路徑前面加上一個 標志 。如下:

image

odd是奇數長度, 不需要附加一個0, even是偶數長度,需要附加一個0

def compact_encode(hexarray):
  term = 1 if hexarray[-1] == 16 else 0 # 16表示結束符
  if term: hexarray = hexarray[:-1] # 將結束符去掉
     oddlen = len(hexarray) % 2 # 計算是否是奇數長度
     flags = 2 * term + oddlen # 奇數長度帶16=>3 不帶16=>1 偶數長度帶16=>2 不帶16=>0
  if oddlen: # 奇數長度 
     hexarray = [flags] + hexarray
  else: #偶數長度需要補0
     hexarray = [flags] + [0] + hexarray
 # hexarray 現在有一個奇數長度, 第一個 nibble 是 flags.
 o = ''
 for i in range(0,len(hexarray),2):
     o += chr(16 * hexarray[i] + hexarray[i+1])
 return o

> [ 1, 2, 3, 4, 5, ...]
'11 23 45'
> [ 0, 1, 2, 3, 4, 5, ...]
'00 01 23 45'
> [ 0, f, 1, c, b, 8, 16]
'20 0f 1c b8'
> [ f, 1, c, b, 8, 16]
'3f 1c b8'

Merkle Patricia trie中獲取節點的代碼(待驗證):

# path格式是半字節的list
# node是節點數據的hash值
def get_helper(node,path):
   if path == []: return node
   if node = '': return ''
     curnode = rlp.decode(node if len(node) < 32 else db.get(node))
   if len(curnode) == 2: # 表示是葉子節點或擴展節點
     (k2, v2) = curnode
     k2 = compact_decode(k2) #將半子節數據組裝為字節,并加上flag,去掉16
  if k2 == path[:len(k2)]: # curnode是擴展節點
     return get(v2, path[len(k2):]) # 去掉部分路徑
   else:
     return ''
   elif len(curnode) == 17: # 表示是分支節點
 return get_helper(curnode[path[0]],path[1:])

# path是正常key的字符串
def get(node,path):
   path2 = []
   for i in range(len(path)):
     path2.push(int(ord(path[i]) / 16)) # 壓入前半個字節
     path2.push(ord(path[i]) % 16) # 壓入后半個字節
     path2.push(16) # 再壓入結束符16, 表示是葉子節點
 return get_helper(node,path2) # 格式如: [ f, 1, c, b, 8, 16]

4 例子

4個path/value pairs :

('do', 'verb'), 
('dog', 'puppy'),
('doge', 'coin'),
('horse', 'stallion') .

將path和value轉換為bytes類型,<>表示bytes, value實際上還是也是,方便理解還是寫成字符串:

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

在底層數據庫中存儲的key/value對: 一個trie樹


image

一個節點被另一個節點引用時, 引用方式是H(rlp.encode(x)), 其中 H(n) = sha3(n) if len(n) ≥ 32 else n ; rlp.encode表示rlp編碼

上述:

  1. 根節點中<16>: 1表示擴展奇數節點, 6表示公共的key
  2. hashA是分支節點: hashB和hashC的位置表示key=4, key=8
  3. hashC中: 20表示葉子偶數節點,6f 72 73 65表示剩余的key
  4. hashD是分支節點: 'verb'表示上一級擴展節點的value

6 以太坊block中的默克爾-帕特里夏樹

有3種類型的樹:

  1. 狀態樹 stateRoot

是全局的樹

path = sha3(ethereumAddress) 以太坊賬戶地址

value = rlp([nonce,balance,storageRoot,codeHash]) 交易次數, 賬戶余額,存儲樹,合約代碼hash

其中storageRoot是另一個trie樹,存儲合約的所有數據,每個賬戶都有各自的樹獨立存儲

  1. 交易樹 transactionsRoot

每個block都有一個交易樹

path = rlp(transactionIndex) 該交易在block中的索引, 順序由礦工決定

value = 交易記錄

該樹生成后永遠不會被修改

  1. 憑證樹 receiptsRoot

同上

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容