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

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

1 基本的radix trie

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

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

比如key='dog', 轉(zhuǎn)換為16進(jìn)制是64 6f 67航闺。
在底層key/value數(shù)據(jù)庫中,先找到根節(jié)點(diǎn)hash, 根節(jié)點(diǎn)就是一個其他節(jié)點(diǎn)hash的list, 然后在list的索引=6取出i5,是下一級節(jié)點(diǎn)的hash值, 在底層數(shù)據(jù)庫中根據(jù)該hash值找到該節(jié)點(diǎn)的list, 這樣一級一級往下找,最后找到對應(yīng)的value.

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

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

代碼實(shí)現(xiàn)大致如下:

# node: 節(jié)點(diǎn)數(shù)據(jù)
# path/value 數(shù)據(jù)對
def update(node,path,value): #插入一個空path, 表示新建一個trie樹
 if path == '': 
    #新建擴(kuò)展節(jié)點(diǎn)或者使用傳入的節(jié)點(diǎn)數(shù)據(jù)
   curnode = db.get(node) if node else [ NULL ] * 17 
   newnode = curnode.copy()
   newnode[-1] = value #保存該path的value
 else: # path非空
    #新建擴(kuò)展節(jié)點(diǎn)或者使用傳入的節(jié)點(diǎn)數(shù)據(jù)
   curnode = db.get(node) if node else [ NULL ] * 17
   newnode = curnode.copy()
  #創(chuàng)建下一級節(jié)點(diǎn)
   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)

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

2 radix trie結(jié)合merkle

merkle出現(xiàn)在: 指向下一級節(jié)點(diǎn)的指針是使用 節(jié)點(diǎn)的確定性加密hash ** ** (對于key/value型數(shù)據(jù)庫,每次查詢 key == sha3(rlp(value)) 潦刃, 而不是傳統(tǒng)意義上下一級節(jié)點(diǎn)地址的指針(C語言實(shí)現(xiàn))。
這為數(shù)據(jù)結(jié)構(gòu)提供了一種密碼認(rèn)證;
如果給定的trie的根哈希是公開的懈叹,則任何人都可以 通過給出給定path上的所有節(jié)點(diǎn), 來證明在給定path上存在一個給定值 乖杠,對于攻擊者,不可能提供一個不存在的(path,value)對的證明, 因?yàn)楦W罱K基于它下面的所有哈希澄成,所以任何修改都會改變根哈希胧洒。

在如上所述一次遍歷一個半字節(jié)的path中,大多數(shù)節(jié)點(diǎn)是包含17個元素的數(shù)組墨状。 前16個索引代表path中的下一個十六進(jìn)制字符(半字節(jié))所保持的每個可能值卫漫,以及在路徑已經(jīng)完全遍歷的情況下保存的最終目標(biāo)值。 這17個元素的數(shù)組節(jié)點(diǎn)被稱為 分支節(jié)點(diǎn) ** ** 肾砂。

3 Merkle Trie 結(jié)合Patricia

radix tries 從查找性能和空間占用方面效率都比較低, Patricia 樹通過擴(kuò)展節(jié)點(diǎn)解決該問題.

擴(kuò)展節(jié)點(diǎn)的形式是 [ encodedPath, key ] , encodepath是壓縮路徑編碼, 通過第一個半字節(jié)標(biāo)識和葉子結(jié)點(diǎn)進(jìn)行區(qū)分.

當(dāng)以半字節(jié)遍歷路徑時列赎,我們可能會以 奇數(shù) 個半字節(jié)來遍歷,但是因?yàn)樗械臄?shù)據(jù)都是以 字節(jié)格式 存儲的镐确,所以不可能區(qū)分例如 半字節(jié)1和半字節(jié)01 (都是 必須保存為<01>)包吝。 要指定奇數(shù)長度,部分路徑前面加上一個 標(biāo)志 源葫。如下:

image

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

def compact_encode(hexarray):
  term = 1 if hexarray[-1] == 16 else 0 # 16表示結(jié)束符
  if term: hexarray = hexarray[:-1] # 將結(jié)束符去掉
     oddlen = len(hexarray) % 2 # 計(jì)算是否是奇數(shù)長度
     flags = 2 * term + oddlen # 奇數(shù)長度帶16=>3 不帶16=>1 偶數(shù)長度帶16=>2 不帶16=>0
  if oddlen: # 奇數(shù)長度 
     hexarray = [flags] + hexarray
  else: #偶數(shù)長度需要補(bǔ)0
     hexarray = [flags] + [0] + hexarray
 # hexarray 現(xiàn)在有一個奇數(shù)長度, 第一個 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中獲取節(jié)點(diǎn)的代碼(待驗(yàn)證):

# path格式是半字節(jié)的list
# node是節(jié)點(diǎn)數(shù)據(jù)的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: # 表示是葉子節(jié)點(diǎn)或擴(kuò)展節(jié)點(diǎn)
     (k2, v2) = curnode
     k2 = compact_decode(k2) #將半子節(jié)數(shù)據(jù)組裝為字節(jié),并加上flag,去掉16
  if k2 == path[:len(k2)]: # curnode是擴(kuò)展節(jié)點(diǎn)
     return get(v2, path[len(k2):]) # 去掉部分路徑
   else:
     return ''
   elif len(curnode) == 17: # 表示是分支節(jié)點(diǎn)
 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)) # 壓入前半個字節(jié)
     path2.push(ord(path[i]) % 16) # 壓入后半個字節(jié)
     path2.push(16) # 再壓入結(jié)束符16, 表示是葉子節(jié)點(diǎn)
 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轉(zhuǎn)換為bytes類型,<>表示bytes, value實(shí)際上還是也是,方便理解還是寫成字符串:

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

在底層數(shù)據(jù)庫中存儲的key/value對: 一個trie樹


image

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

上述:

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

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

有3種類型的樹:

  1. 狀態(tài)樹 stateRoot

是全局的樹

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

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

其中storageRoot是另一個trie樹,存儲合約的所有數(shù)據(jù),每個賬戶都有各自的樹獨(dú)立存儲

  1. 交易樹 transactionsRoot

每個block都有一個交易樹

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

value = 交易記錄

該樹生成后永遠(yuǎn)不會被修改

  1. 憑證樹 receiptsRoot

同上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诗越,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子息堂,更是在濱河造成了極大的恐慌嚷狞,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件储矩,死亡現(xiàn)場離奇詭異感耙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)持隧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門即硼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屡拨,你說我怎么就攤上這事只酥∪焓担” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵裂允,是天一觀的道長损离。 經(jīng)常有香客問我,道長绝编,這世上最難降的妖魔是什么僻澎? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮十饥,結(jié)果婚禮上窟勃,老公的妹妹穿的比我還像新娘。我一直安慰自己逗堵,他們只是感情好秉氧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜒秤,像睡著了一般汁咏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上作媚,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天攘滩,我揣著相機(jī)與錄音,去河邊找鬼掂骏。 笑死轰驳,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弟灼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼冒黑,長吁一口氣:“原來是場噩夢啊……” “哼田绑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起抡爹,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤掩驱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冬竟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欧穴,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年泵殴,在試婚紗的時候發(fā)現(xiàn)自己被綠了涮帘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡笑诅,死狀恐怖调缨,靈堂內(nèi)的尸體忽然破棺而出疮鲫,到底是詐尸還是另有隱情,我是刑警寧澤弦叶,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布俊犯,位于F島的核電站,受9級特大地震影響伤哺,放射性物質(zhì)發(fā)生泄漏燕侠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一立莉、第九天 我趴在偏房一處隱蔽的房頂上張望绢彤。 院中可真熱鬧,春花似錦桃序、人聲如沸杖虾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奇适。三九已至,卻和暖如春芦鳍,著一層夾襖步出監(jiān)牢的瞬間嚷往,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工柠衅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留皮仁,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓菲宴,卻偏偏與公主長得像贷祈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子喝峦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容