以太坊詳解 之 Merkle Patricia Tree

基礎(chǔ)知識(shí)

Trie樹

Trie是一種搜索樹爽冕,又稱字典樹(digital tree)和前綴樹(prefix tree)抗碰。不同與二叉搜索樹纯续,鍵值并不是由樹中的節(jié)點(diǎn)存儲(chǔ),而是取決于其在樹中的位置蝶俱,或者說是從根到達(dá)節(jié)點(diǎn)的路徑。一個(gè)節(jié)點(diǎn)的所有子孫都有相同的前綴饥漫,也就是這個(gè)節(jié)點(diǎn)對應(yīng)的字符串榨呆,而根節(jié)點(diǎn)對應(yīng)空字符串。一般情況下庸队,不是所有的節(jié)點(diǎn)都有對應(yīng)的值积蜻,只有葉子節(jié)點(diǎn)和部分內(nèi)部節(jié)點(diǎn)所對應(yīng)的鍵才有相關(guān)的值闯割。

Trie_example.png

圖中,鍵不需要被顯式地保存在節(jié)點(diǎn)中竿拆,只是為了演示宙拉。

Patricia樹

Patricia樹又稱壓縮前綴樹(compact prefix tree),是一種更節(jié)省空間的Trie如输。對于Patricia樹的每個(gè)節(jié)點(diǎn)鼓黔,如果該節(jié)點(diǎn)是唯一的兒子的話,就和父節(jié)點(diǎn)合并不见。那么一棵Patricia樹的任何內(nèi)部節(jié)點(diǎn)有2個(gè)或以上的孩子節(jié)點(diǎn)澳化。下圖存儲(chǔ)的內(nèi)容和第一幅圖一致,可以看出節(jié)點(diǎn)數(shù)目減少了很多稳吮。


patricia_trie.png

哈希列表

哈希列表(Hash List)是存儲(chǔ)hash值的列表缎谷。通常除了哈希列表自身以外,會(huì)有一個(gè)額外的根哈希(Root Hash or top hash)用來存儲(chǔ)哈希列表的哈希灶似。


Hash_list.png

在點(diǎn)對點(diǎn)網(wǎng)絡(luò)中作數(shù)據(jù)傳輸?shù)臅r(shí)候列林,會(huì)把大的文件分割成小的數(shù)據(jù)塊,然后同時(shí)從多個(gè)機(jī)器上下載數(shù)據(jù)塊酪惭。這樣的好處是希痴,如果小塊數(shù)據(jù)在傳輸過程中損壞了,那么只要重新下載這一塊數(shù)據(jù)就行了春感,不用重新下載整個(gè)文件砌创。但是在網(wǎng)絡(luò)中很多機(jī)器是不穩(wěn)定或者不可信的,需要對數(shù)據(jù)庫的真實(shí)性和完整性進(jìn)行校驗(yàn)鲫懒。這個(gè)時(shí)候就需要用到哈希列表嫩实。在開始的時(shí)候需要為每個(gè)數(shù)據(jù)塊做Hash,并運(yùn)算得到數(shù)據(jù)的根哈希窥岩。下載數(shù)據(jù)的時(shí)候甲献,首先從可信的數(shù)據(jù)源得到正確的根Hash,就可以用它來校驗(yàn)Hash列表了颂翼,然后通過校驗(yàn)后的Hash列表校驗(yàn)數(shù)據(jù)塊晃洒。

Merkle樹

Merkle Tree,通常也被稱作Hash Tree朦乏,是存儲(chǔ)hash值的一棵樹锥累。Merkle Tree可以看做Hash List的泛化(Hash List可以看作一種特殊的Merkle Tree,即樹高為2的多叉Merkle Tree集歇。)

Hash_Tree.png

在最底層桶略,和哈希列表一樣,數(shù)據(jù)被分成小的數(shù)據(jù)塊,有相應(yīng)地哈希和它對應(yīng)际歼。上一層惶翻,并不是直接去運(yùn)算根哈希,而是把相鄰的兩個(gè)哈希合并成一個(gè)字符串鹅心,然后運(yùn)算這個(gè)字符串的哈希吕粗,這樣逐層進(jìn)行哈希合并運(yùn)算,到最后就可以生成一個(gè)根hash旭愧。

Merkle Tree和HashList的主要區(qū)別是颅筋,可以直接下載并立即驗(yàn)證Merkle Tree的一個(gè)分支。

Merkle Tree的主要作用是當(dāng)拿到Top Hash的時(shí)候输枯,這個(gè)hash值代表了整顆樹的信息摘要议泵,當(dāng)樹里面任何一個(gè)數(shù)據(jù)發(fā)生了變動(dòng),都會(huì)使得從當(dāng)前節(jié)點(diǎn)到根節(jié)點(diǎn)的Hash的值都會(huì)發(fā)生變化桃熄,而其他子樹不會(huì)有變化先口。而Top Hash的值是會(huì)存儲(chǔ)到區(qū)塊鏈的區(qū)塊頭里面去的,區(qū)塊頭是必須經(jīng)過工作量證明瞳收。

merkleinblockchain.png

以太坊中的MPT

以太坊中的MPT(Merkle Patricia Tree)與第一部分中的數(shù)據(jù)結(jié)構(gòu)相比碉京,MPT樹從結(jié)構(gòu)上看是一棵Patricia樹,每個(gè)節(jié)點(diǎn)保存一個(gè)hash值螟深,因此也可以起到Merkle Tree的作用谐宙。

對于MPT來說,其主要作用是用來存儲(chǔ)一系列的kv對界弧,如公式188定義卧惜。此處key是任意長度的二進(jìn)制數(shù)組,value也是任意長度的二進(jìn)制數(shù)組夹纫。為了方便用數(shù)字下標(biāo)來對kv對中的每個(gè)元素進(jìn)行索引,將每一個(gè)(key设凹,value)定義為公式189的形式舰讹。
\begin{equation} \mathfrak{I} = \{ (\mathbf{k}_0 \in \mathbb{B}, \mathbf{v}_0 \in \mathbb{B}), (\mathbf{k}_1 \in \mathbb{B}, \mathbf{v}_1 \in \mathbb{B}), ... \} \tag{188} \end{equation}

\begin{equation} \forall_{\mathbf{I} \in \mathfrak{I}} I \equiv (I_0, I_1) \tag{189} \end{equation}

以太坊的MPT樹中需要將key值轉(zhuǎn)化為十六進(jìn)制的表示形式,這樣在存儲(chǔ)的時(shí)候key作為一棵樹闪朱,其范圍為[0..f],即分叉的節(jié)點(diǎn)最多有16種分叉的可能月匣。將key值轉(zhuǎn)化為十六進(jìn)制的方法如公式191所示,將原先的一個(gè)字節(jié)拆成兩個(gè)半字節(jié)奋姿,而每個(gè)半字節(jié)的值都不會(huì)超過16锄开,即在[0..f]范圍內(nèi)。對key進(jìn)行處理過后的key称诗,value對萍悴,可以表示成公式190的形式。

\begin{eqnarray} y(\mathfrak{I}) & = & \{ (\mathbf{k}_0' \in \mathbb{Y}, \mathbf{v}_0 \in \mathbb{B}), (\mathbf{k}_1' \in \mathbb{Y}, \mathbf{v}_1 \in \mathbb{B}), ... \} \tag{190}\\ \forall_{n} \quad \forall_{i: i < 2\lVert\mathbf{k}_{n}\rVert} \quad \mathbf{k}_{n}'[i] & \equiv & \begin{cases} \lfloor \mathbf{k}_{n}[i \div 2] \div 16 \rfloor & \text{if} \; i \; \text{is even} \\ \mathbf{k}_{n}[\lfloor i \div 2 \rfloor] \bmod 16 & \text{otherwise} \end{cases} \tag{191} \end{eqnarray}

以太坊中的MPT中有4種節(jié)點(diǎn):

  1. NULL節(jié)點(diǎn),用一個(gè)空的字符串表示癣诱。
  2. 分之(branch)節(jié)點(diǎn)计维,17元組[v0...v15,vt]。其前16個(gè)項(xiàng)對應(yīng)于這些點(diǎn)在其遍歷中的鍵的十六個(gè)可能的半字節(jié)值中的每一個(gè)撕予。第17個(gè)字段是存儲(chǔ)那些在當(dāng)前結(jié)點(diǎn)結(jié)束了的節(jié)點(diǎn)(例如鲫惶, 有三個(gè)key,分別是 (abc ,abd, ab) 第17個(gè)字段儲(chǔ)存了ab節(jié)點(diǎn)的值)
  3. 葉子(leaf)節(jié)點(diǎn),2元組[encodedPath,value]实抡。第一個(gè)字段是剩下的Key的半字節(jié)編碼, 第二個(gè)字段是Value欠母。
  4. 擴(kuò)展(extension)節(jié)點(diǎn),2元組[encodePath,key]吆寨。第一個(gè)字段是剩下的Key的半字節(jié)編碼, 第二個(gè)字段指向另一個(gè)節(jié)點(diǎn)赏淌。

構(gòu)造過程大致為:

  • 如果當(dāng)前只有一個(gè)kv對,則直接將其構(gòu)造成一個(gè)葉子節(jié)點(diǎn)鸟废。
  • 如果當(dāng)前需要編碼的kv集合猜敢,有公共前綴,那么提取公共前綴盒延,將公共前綴構(gòu)造成一個(gè)擴(kuò)展節(jié)點(diǎn)缩擂,并將其第二個(gè)字段指向下一個(gè)節(jié)點(diǎn)(一般為分支節(jié)點(diǎn))。
  • 如果不是上面的兩種情況添寺,則構(gòu)造分支節(jié)點(diǎn)按照當(dāng)前key所在索引的16進(jìn)制對kv集合進(jìn)行劃分胯盯,[0..f]分別指向下一個(gè)節(jié)點(diǎn)。如果有kv對在此節(jié)點(diǎn)終結(jié)计露,則將其存儲(chǔ)在value中博脑。

以太坊中MPT的結(jié)構(gòu)示例如下圖:(實(shí)際的實(shí)現(xiàn)上有些地方是不一樣的)


worldstatetrie.png

十六進(jìn)制前綴編碼

十六進(jìn)制前綴編碼是將任意數(shù)量的半字節(jié)(nibble,半個(gè)字節(jié)也就是4個(gè)bit票罐,16進(jìn)制的一位數(shù)字)編碼為字節(jié)數(shù)組的有效方法叉趣。通過添加前綴,其可以存儲(chǔ)附加標(biāo)識(shí)该押,在Trie中使用時(shí)疗杉,會(huì)在節(jié)點(diǎn)類型間消除歧義。

\begin{eqnarray} \mathtt{HP}(\mathbf{x}, t): \mathbf{x} \in \mathbb{Y} & \equiv & \begin{cases} (16f(t), 16\mathbf{x}[0] + \mathbf{x}[1], 16\mathbf{x}[2] + \mathbf{x}[3], ...) & \text{if} \quad \lVert \mathbf{x} \rVert \; \text{is even} \\ (16(f(t) + 1) + \mathbf{x}[0], 16\mathbf{x}[1] + \mathbf{x}[2], 16\mathbf{x}[3] + \mathbf{x}[4], ...) & \text{otherwise} \end{cases} \tag{186} \\ f(t) & \equiv & \begin{cases} 2 & \text{if} \quad t \neq 0 \\ 0 & \text{otherwise} \end{cases} \tag{187} \end{eqnarray}

  • 當(dāng)半字節(jié)長度為偶數(shù)時(shí)蚕礼,第一個(gè)高半字節(jié)為前綴烟具,低半字節(jié)置0,后面的每個(gè)字節(jié)都是將兩個(gè)半字節(jié)組合在一起奠蹬。

  • 當(dāng)半字節(jié)長度為奇數(shù)時(shí)朝聋,第一個(gè)高半字節(jié)為前綴,低半字節(jié)為半字節(jié)的第一個(gè)半字節(jié)囤躁,后面的每個(gè)字節(jié)都是將兩個(gè)半字節(jié)組合在一起冀痕。

  • 第一個(gè)字節(jié)的高半字節(jié)包含兩個(gè)標(biāo)志荔睹,最低位表示奇偶(0為偶,1為奇)金度,第二低位編碼flag应媚。實(shí)際中就是編碼以太坊中的不同類型的節(jié)點(diǎn)。

    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

MPT的序列化

MPT在數(shù)據(jù)庫中的實(shí)際存儲(chǔ)是存儲(chǔ)的hash值與c(\mathfrak{I}, i)即節(jié)點(diǎn)的序列化形式的對猜极。

序列化主要是指把內(nèi)存表示的數(shù)據(jù)存放到數(shù)據(jù)庫里面中姜, 反序列化是指把數(shù)據(jù)庫里面的Trie數(shù)據(jù)加載成內(nèi)存表示的數(shù)據(jù)。 序列化的目的主要是方便存儲(chǔ)跟伏,減少存儲(chǔ)大小等丢胚。 反序列化的目的是把存儲(chǔ)的數(shù)據(jù)加載到內(nèi)存,方便Trie樹的插入受扳,查詢携龟,修改等需求。

Trie的序列化主要使用了前面介紹的十六進(jìn)制前綴編碼和RLP編碼格式勘高。

定義TRIE函數(shù)峡蟋,用來表示樹根的HASH值(其中c函數(shù)的第二個(gè)參數(shù),意為key值的起始index华望,所以root對應(yīng)的值為0)

\begin{equation} {\small TRIE}(\mathfrak{I}) \equiv {\small KEC}(c(\mathfrak{I}, 0)) \tag{192} \end{equation}

對于每個(gè)節(jié)點(diǎn)蕊蝗,如公式193所示。如果該節(jié)點(diǎn)的序列化長度小于32赖舟,則直接存儲(chǔ)其序列化蓬戚,否則存儲(chǔ)器序列化后的hash值。這里n(\mathfrak{I}, i)宾抓,主要是在公式194中使用子漩,是一個(gè)節(jié)點(diǎn)指向另一個(gè)節(jié)點(diǎn)時(shí),是把該子節(jié)點(diǎn)直接存儲(chǔ)在父節(jié)點(diǎn)中還是將其hash值存儲(chǔ)在父節(jié)點(diǎn)中(真正的值會(huì)單獨(dú)存的)石洗。

\begin{equation} n(\mathfrak{I}, i) \equiv \begin{cases} () & \text{if} \quad \mathfrak{I} = \varnothing \\ c(\mathfrak{I}, i) & \text{if} \quad \lVert c(\mathfrak{I}, i)\rVert < 32 \\ {\small KEC}(c(\mathfrak{I}, i)) & \text{otherwise} \end{cases} \tag{193} \end{equation}

對于每一個(gè)節(jié)點(diǎn)存儲(chǔ)時(shí)首先對節(jié)點(diǎn)進(jìn)行RLP編碼處理幢泼,如公式194所示

  • 如果當(dāng)前需要編碼的KV集合只剩下一條數(shù)據(jù),那么該節(jié)點(diǎn)是一個(gè)葉子節(jié)點(diǎn)讲衫,按照第一條規(guī)則編碼缕棵,將nibble形式的key,與表示葉子節(jié)點(diǎn)的true值一起進(jìn)行十六進(jìn)制前綴編碼焦人,并將結(jié)果和value一起進(jìn)行RLP編碼。
  • 如果當(dāng)前需要編碼的KV集合有公共前綴重父,那么提取最大公共前綴花椭。第二個(gè)字段遞歸存儲(chǔ)下一個(gè)節(jié)點(diǎn)。
  • 如果不是上面兩種情況房午,那么使用分支節(jié)點(diǎn)進(jìn)行集合切分矿辽。可以看到u的值由n進(jìn)行遞歸定義,而如果有節(jié)點(diǎn)剛好在這里完結(jié)了袋倔,那么第17個(gè)元素v就是為這種情況準(zhǔn)備的雕蔽。

\begin{equation} c(\mathfrak{I}, i) \equiv \begin{cases} {\small RLP}\Big( \big({\small HP}(I_0[i .. (\lVert I_0\rVert - 1)], true), I_1 \big) \Big) & \text{if} \quad \lVert \mathfrak{I} \rVert = 1 \quad \text{where} \; \exists I: I \in \mathfrak{I} \\ {\small RLP}\Big( \big({\small HP}(I_0[i .. (j - 1)], false), n(\mathfrak{I}, j) \big) \Big) & \text{if} \quad i \ne j \quad \text{where} \; j = \arg \max_{x} : \exists \mathbf{l}: \lVert \mathbf{l} \rVert = x :\\ & \forall_{I \in \mathfrak{I}}: I_0[0 .. (x - 1)] = \mathbf{l} \\ {\small RLP}\Big( (u(0), u(1), ..., u(15), v) \Big) & \text{otherwise} \quad \text{where} \begin{array}[t]{rcl} u(j) & \equiv & n(\{ I : I \in \mathfrak{I} \wedge I_0[i] = j \}, i + 1) \\ v & = & \begin{cases} I_1 & \text{if} \quad \exists I: I \in \mathfrak{I} \wedge \lVert I_0 \rVert = i \\ () & \text{otherwise} \end{cases} \tag{194} \end{array} \end{cases} \end{equation}

以太坊中的MPT

以太坊中的所有的merkle樹都是指Merkle Patricia Tree。

從區(qū)塊頭中可以看到有3棵MPT的根宾娜。

  1. stateRoot
  2. transcationsRoot
  3. receiptsRoot
mptinethereum.png

State樹

State樹是一棵全局的樹批狐,它的key是sha3(ethereumAddress),即賬戶地址的hash值。其存儲(chǔ)的值value為rlp(ethereumAccount)前塔,即賬戶信息的rlp編碼嚣艇。其中賬戶信息是一個(gè)[nonce,balance,storageRoot,codeHash]的四元組,其中storageRoot指向賬戶的Storage樹华弓。

Storage樹

一般的外部賬戶Storage為空食零,而合約賬戶通常會(huì)儲(chǔ)存一定的數(shù)據(jù),這些數(shù)據(jù)就是存儲(chǔ)在合約賬戶的Storage樹中寂屏,storage樹中的key與賬戶地址和其中實(shí)際存儲(chǔ)的key有關(guān)贰谣,其value值為合約定義的value值。

Transactions樹

每一個(gè)區(qū)塊都會(huì)有一棵Transactions樹迁霎,其key為rlp(transactionIndex)(交易在區(qū)塊中的編號(hào)吱抚,0,1...),其value值為經(jīng)過rlp編碼的交易欧引。在實(shí)際情況中該樹并不會(huì)真的存儲(chǔ)到數(shù)據(jù)庫中频伤,只是在生成block以及校驗(yàn)block的時(shí)候用于得到當(dāng)前區(qū)塊的TransactionsRoot。實(shí)際中一條交易連同同一區(qū)塊的其他交易以及區(qū)塊的uncles是被存儲(chǔ)在一個(gè)條目中的芝此。

Receipts樹

每一個(gè)區(qū)塊都會(huì)有一棵Receipts樹樹憋肖,其key為rlp(transactionIndex)(交易在區(qū)塊中的編號(hào),0婚苹,1...),其value值為經(jīng)過rlp編碼的交易收據(jù)岸更。在實(shí)際情況中該樹并不會(huì)真的存儲(chǔ)到數(shù)據(jù)庫中,只是在生成block以及校驗(yàn)block的時(shí)候用于得到當(dāng)前區(qū)塊的ReceiptsRoot膊升。實(shí)際中同一個(gè)區(qū)塊的交易收據(jù)會(huì)一起編碼然后存儲(chǔ)在一個(gè)條目中的怎炊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市廓译,隨后出現(xiàn)的幾起案子评肆,更是在濱河造成了極大的恐慌,老刑警劉巖非区,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓜挽,死亡現(xiàn)場離奇詭異,居然都是意外死亡征绸,警方通過查閱死者的電腦和手機(jī)久橙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門俄占,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淆衷,你說我怎么就攤上這事缸榄。” “怎么了祝拯?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵甚带,是天一觀的道長。 經(jīng)常有香客問我鹿驼,道長欲低,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任畜晰,我火速辦了婚禮砾莱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凄鼻。我一直安慰自己腊瑟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布块蚌。 她就那樣靜靜地躺著闰非,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峭范。 梳的紋絲不亂的頭發(fā)上财松,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機(jī)與錄音纱控,去河邊找鬼辆毡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甜害,可吹牛的內(nèi)容都是我干的舶掖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尔店,長吁一口氣:“原來是場噩夢啊……” “哼眨攘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嚣州,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鲫售,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后该肴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體情竹,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年沙庐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鲤妥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拱雏,死狀恐怖棉安,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铸抑,我是刑警寧澤贡耽,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站鹊汛,受9級(jí)特大地震影響蒲赂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刁憋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一滥嘴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧至耻,春花似錦若皱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疤苹,卻和暖如春互广,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背卧土。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工惫皱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人夸溶。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓逸吵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缝裁。 傳聞我的和親對象是個(gè)殘疾皇子扫皱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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