以太坊如何組織賬戶狀態的數據結構
以太坊采用基於賬戶的模式,系統中顯式記錄每個賬戶的余額。我們要完成的是從賬戶地址到賬戶狀態的映射,addr-->state。
在以太坊中,賬戶地址為160位,表示為40個16進制數;狀態包含了余額(balance)、交易次數(nonce),合約賬戶中還包含了code(代碼)、存儲(stroge)。
需要記住的是,在BTC和以太坊中,交易保存在區塊內部,一個區塊可以包含多個交易。通過區塊構成區塊鏈,而非交易。
直觀地來看,其本質上為Key-value鍵值對,直接可以查詢每個賬戶的狀態,所以直觀想法便用哈希表實現。若不考慮哈希碰撞,查詢直接為常數級別的查詢效率。但采用哈希表,難以提供Merkle proof。
eg:跟一個人簽合同,希望他證明他的賬戶余額有多少錢
- 一種方法是像BTC中,將哈希表的內容組織為Merkle Tree
- 將哈希表的元素組織成一個Merkle Tree,算出根哈希值,並保存在blockheader中,只要保證根哈希值不變就能保證內容不變。
- 但當新區塊發布,哈希表內容會改變,再次將其組織為新的Merkle Tree,如果這樣,每當產生新區塊(ETH中新區塊產生時間為10s左右),都要重新組織Merkle Tree,很明顯這是不現實的,代價太大。
- 而在區塊鏈中沒有這個缺點,因為比特幣系統中沒有賬戶概念,交易由區塊管理,每個Merkle Tree包含着一個區塊的所有交易,每一個區塊對應一個Merkle Tree,一旦發布就不會再改了,所以Merkle Tree不是無限增大的。而ETH中,將賬戶信息組織成Merkle Tree,很明顯其會越來越龐大。
- 第二種方法是不要哈希表了,直接使用Merkle Tree組織賬戶信息,每次修改只需要修改其中一部分即可
- 實際中,Merkle Tree並未提供一個高效的查找和更新的方案。
- 此外,將所有賬戶構建為一個大的Merkle Tree,為了保證所有節點的一致性和查找速度,必須進行排序,如果不排序,得到的Merkle Tree可能不一樣。
- 為什么區塊鏈沒有這個缺點。在區塊鏈中每個人組織的塊里的交易順序是不一樣的,但是有決定權的只有有記賬權的那個人,所以順序一不一樣無所謂。
- 第三種方法,經過排序,使用Sorted Merkle Tree可以嗎?
- 新增賬戶,由於其地址隨機,插入Merkle Tree時候很大可能在Tree中間,發現其必須進行重構,所以Sorted Merkle Tree插入、刪除(實際上可以不刪除)的代價太大。
- 而且其實創建賬戶沒必要讓別人知道,只有狀態改變才需要讓別人知道
既然哈希表和 Merkle Tree都不可以,實際中以太坊采取的數據結構:MPT(Merkle Patricia trie)
trie(字典樹、前綴樹)
先了解一個簡單的數據結構trie
如下為一個通過5個單詞組成的trie數據結構(只畫出key,未畫出value)
特點:
- trie中每個節點的分支數目取決於Key值中每個元素的取值范圍(圖中最多26個英文字母分叉+一個結束標志位)。以太坊中分支數目是17(16進制加一個結束標志位)
- trie查找效率取決於key的長度。實際應用中(以太坊地址長度為40)
- 理論上哈希會出現碰撞,而trie上面不會發生碰撞。
- 給定輸入,無論如何順序插入,構造的trie都是一樣的。
- 更新操作局部性較好
缺點:
- trie的存儲浪費。很多節點只存儲一個key,但其“兒子”只有一個,過於浪費。因此,為了解決這一問題,我們引入Patricia tree/trie
Patricia tree/trie
Patricia trie就是進行了路徑壓縮的trie。
壓縮后好處是樹的高度縮短,這樣訪問內存的次數大大減少,效率變高。
需要注意的是,如果新插入單詞,原本壓縮的路徑可能需要擴展開來。那么,需要考慮什么情況下路徑壓縮效果較好?樹中插入的鍵值分布較為稀疏的情況下,可見路徑壓縮效果較好。
在以太坊系統中,160位的地址存在2^160 種,該數實際上已經非常大了,和賬戶數目相比,可以認為地址這一鍵值非常稀疏。
因此,我們可以在以太坊賬戶管理種使用Patricia tree這一數據結構。
但實際上,在以太坊種使用的並非簡單的PT(Patricia tree),而是MPT(Merkle Patricia tree)。
Merkle Tree 和 Binary Tree
區塊鏈和鏈表的區別在於區塊鏈使用哈希指針,鏈表使用普通指針。同樣,Merkle Tree 相比 Binary Tree,也是普通指針換成了哈希指針。
所以,以太坊系統中可如此,將所有賬戶狀態組織為一個Patricia tree,用路徑壓縮提高效率,將普通指針換成了哈希指針,就可以計算出一個根哈希值,這個根哈希值存儲於block header中。
根哈希值的作用:
- 第一:防止篡改,只要根哈希值不變,所有賬戶的狀態也不變。
- 第二:提供Merkle Proof,可以證明賬戶余額,輕節點進行驗證
- 第三,證明某個發生了交易的賬戶是否存在,也就是證明某個鍵值是否存在。
MPT(Modified Patricia tree)
以太坊中針對原生版的MPT(Merkle Patricia tree)進行了修改,我們稱其為MPT(Modified Patricia tree)
下圖為以太坊中使用的MPT結構示意圖。右上角表示四個賬戶(為了直觀,賬戶地址顯示較短)和其狀態(只顯示賬戶余額)。
樹中的節點分為三種,第一個是Extension Node,當發生了路徑壓縮就會有;第二個節點是Leaf Node,表示葉子節點,是最后的;第三個是Branch Node,表示分支節點。還有一個根節點,取哈希得到根哈希值,寫入塊頭。
需要注意這里的指針都是哈希指針,普通指針里面存的是下一個節點的地址,而這里的哈希指針存的是下面節點的哈希值。
每次發布新區塊,狀態樹中部分節點狀態會改變,但改變並非在原地修改,而是新建一些分支,原本狀態是保留的。
如下圖中,僅僅有新發生改變的節點才需要修改,其他未修改節點直接指向前一個區塊中的對應節點。
所以,系統中全節點並非維護一棵MPT,而是每次發布新區塊都要新建MPT,只不過大部分節點共享,只有少數發生變化的節點是要新建分支的。
為什么要保存原本狀態?為何不直接修改?
為了便於回滾。如下1中產生分叉,而后上面節點勝出,變為2中狀態。那么,下面節點中狀態的修改便需要進行回滾。
以太坊中有智能合約,理論上能實現很復雜的功能,如果不保存歷史狀態,智能合約執行完后,想推算出以前的狀態是不可能的,因此,需要維護這些歷史記錄。
以太坊中代碼的數據結構
block header 中的數據結構
ParentHash是前一區塊塊頭的哈希值。Coinbase挖出區塊的礦工的地址。
三棵樹:狀態數、交易樹、收據樹
Difficulty也需要根據需要修改
Nouce挖礦中需要猜的隨機數
區塊結構
區塊在網上真正發布時的信息
狀態樹中保存Key-value對,key就是地址,而value狀態通過RLP (Recursive Length Prefix,一種進行序列化的方法,特點是很簡單) 編碼序列號之后再進行存儲。