默克爾樹
在討論以太坊的主要資料物件之前,我想先向各位簡要介紹一下默爾克樹到底是什麼,以使得它得以發揮作用的屬性特徵。
黃皮書中假設由定製的默克爾-帕特里夏樹維護世界狀態和交易。附錄 D 描述了這個資料結構。
默克爾-帕特里夏樹有許多有意思的屬性,如果你想更深入地瞭解其在以太坊中的應用,我推薦你閱讀這篇文章。
在默克爾樹中,由葉子節點儲存區塊資料的雜湊,而由非葉子節點儲存其子節點的雜湊。
默克爾樹所指向資料的任何改動都會引起節點雜湊的變化。由於每一個父節點中所儲存的雜湊值都取決於子節點所包含的資料,所以子節點中資料的變更都會引起父節點雜湊的變化。並且這樣的影響是連鎖反應,從葉子節點直達根節點的。因此對葉子節點所指向資料的改動會引起根節點所儲存雜湊的變化。由上述結構特徵,我們可以引申出兩條重要的屬性:
- 在判斷兩棵默克爾樹所指向資料是否完全相同時,我們不需要比較每個葉子節點,而只需比較根節點所儲存的雜湊。
- 在判斷特定資料是否被樹所指向時,我們可以使用 默克爾證明 技術。此處不對該技術作過多介紹,只需知道這是證明資料存在於默克爾樹中的一種簡單、高效的方法。
第一種屬性的重要之處在於,我們能夠僅利用根節點的雜湊值,就標示某一時刻整棵樹所指向的資料。這意味著僅透過儲存根節點的雜湊值就能標示區塊(無需儲存區塊鏈中所有的資料),且維護資料的不可篡改。
至此我們理清了默克爾樹中根節點雜湊的作用,下面來介紹以太坊中的主要物件。
世界狀態
世界狀態是地址(賬戶)到賬戶狀態的對映。雖然世界狀態不儲存在區塊鏈上,但在黃皮書的描述中,世界狀態也由樹來儲存資料(此樹也被稱為狀態資料庫或者狀態樹)。世界狀態可以被視作為隨著交易的執行而持續更新的全域性狀態。以太坊就像一個去中心化的計算機,世界狀態則是這臺電腦的硬碟。
以太坊中所有的賬戶資訊都體現在世界狀態之中,並由世界狀態樹儲存。如果你想知道某一賬戶的餘額,或者某智慧合約當前的狀態,就需要透過查詢世界狀態樹來獲取該賬戶的具體狀態資訊。下文中我也會簡要介紹這些資訊是如何儲存的。
賬戶狀態
以太坊中有兩種賬戶型別:外部所有賬戶(Externally Owned Accounts 簡稱 EOA)以及合約賬戶。我們用來互相收發以太幣、部署智慧合約的賬戶就是 EOA 賬戶,而部署智慧合約時自動生成的賬戶則是合約賬戶。每一個智慧合約都有其獨一無二的以太坊賬戶。
賬戶狀態反映了一個以太坊賬戶的各項資訊。例如,它儲存了當前賬戶以太幣的餘額資訊、當前賬戶傳送過的交易數量...每一個賬戶都有賬戶狀態。
下面就來看看賬戶狀態中都包括什麼:
nonce
從此地址傳送出去的交易數量(如果當前為 EOA 賬戶)或者此賬號產生的合約建立操作(現在先別管合約建立操作是什麼)。
balance
此賬號所擁有的以太幣數量(以 Wei 計量)。
storageRoot
賬戶儲存樹的根節點雜湊值(稍後介紹賬戶儲存是什麼)。
codeHash
對於合約賬戶,就是此賬戶儲存 EVM 程式碼的雜湊值。對於 EOA 賬戶,此處留空。
賬戶狀態中不容忽視的一個細節是,上述物件在內的所有物件都可變(除了 codeHash)。舉例來說,當一個賬戶向其他賬戶傳送以太幣時,除了 nonce 會增加,賬戶的餘額也會相應改變。
而 codeHash 的不可變性使得,如果部署了有漏洞的智慧合約,也無法修復更新此合約。對應的,只能部署一個新合約(而有漏洞的版本會一直存在於區塊鏈上)。這也是為什麼使用 Truffle 進行智慧合約的開發和部署十分必要,並且用 Solidity 程式設計時要遵循 最佳實踐 的要求。
賬戶儲存樹是儲存與賬戶相關聯資料的結構。該項只有合約賬戶才有,而在 EOA 中, storageRoot 留空、 codeHash 則是一串空字串的雜湊值。所有智慧合約的資料都以 32 位元組對映的形式儲存在賬戶儲存樹中。此處不再贅述賬戶狀態樹如何維持合約資料。如果讀者對其內部實現感興趣,強烈建議閱讀這篇文章。賬戶狀態中的 storageRoot 區域負責維持賬戶儲存樹根節點雜湊值。
交易
交易推動當前狀態到下一狀態的轉變。在以太坊中有三種交易:
- EOA 之間傳輸值的交易(例如,改變傳送方和接收方餘額大小)。
- 傳送訊息來呼叫合約的交易(例如,透過傳送訊息呼叫來觸發 setter 方法,以設定合約中的值)。
- 用於部署合約的交易(由此建立了合約賬戶)。
(從技術角度來講,前兩種交易是一樣的...它們都是透過訊息呼叫來改變賬戶狀態的交易,只不過一個是 EOA 賬戶,一個是合約賬戶。此處將交易分為三種是為了方便讀者的理解。)
交易由以下部分組成:
nonce
此賬戶發出的交易序號數(校對注:可以粗略理解為“這是該賬戶的第幾筆交易”)。
gasPrice
執行此交易、進行計算時為每單位 gas 所支付的費用(以 Wei 計量)。
gasLimit
執行此交易時可以使用的最大 gas 數量。
to
- 如果此交易用於傳送以太幣,此處為接收以太幣的 EOA 地址。
- 如果此交易用於向合約傳送訊息(例如,呼叫智慧合約中的方法),此處為合約的地址。
- 如果此交易用於建立合約,此處值為空。
value
- 如果此交易用於收發以太幣,此處為接收賬戶以 Wei 計量的代幣數量。
- 如果此交易用於傳送對合約的訊息呼叫,此處為向接收此訊息智慧合約所給付的 Wei 數量。
- 如果此交易用於建立合約,此處為合約初始化時賬戶存放的以 Wei 計量的以太幣數量。
v, r, s
在交易的密碼學簽名中用到的值,可以用於確定交易的傳送方。
data(只用於價值傳輸以及向智慧合約傳送訊息呼叫)
傳送訊息呼叫時附帶的輸入資料(例如,假設你想要執行智慧合約中的 setter 方法,資料區就應該包括 setter 方法的識別符號,以及你想要設定的引數值)。
init(只用於合約建立)
用於初始化合約的 EVM 程式碼。
別想著一下子就把這些概念消化完... 必須對以太坊的內部機理有更深的認識才真正理解、使用像 data 區、init 區這樣的概念。
相信不出你的意料,區塊中所有的交易也是儲存在默克爾樹中的。並且這棵樹的根節點雜湊值由區塊頭儲存!下面我們就來剖析一下以太坊區塊結構。
區塊
區塊分為兩部分,即區塊頭和區塊體。
區塊頭就是以太坊中的區塊鏈部分。它儲存了前一個區塊(也可稱為父區塊)的雜湊值,透過區塊頭的連線形成了一條由密碼學背書的鏈。
區塊體包含了此區塊中記錄的一系列交易,以及叔塊(ommer)區塊頭列表。如果想要進一步瞭解叔塊,我推薦閱讀這篇文章。
下面就來介紹區塊頭包括哪些部分。
parentHash
前一個區塊的區塊頭雜湊值。每個區塊都包含前序區塊的雜湊值,一路可回溯至鏈上的創世塊。這也就是維護資料不會被篡改的結構設計(任何對前序區塊的篡改都會影響後續所有區塊的雜湊值)。
ommersHash
叔塊頭以及部分割槽塊體的雜湊值。
beneficiary
因為挖到此區塊而獲得收益的以太坊賬戶。
stateRoot
世界狀態樹的根節點雜湊值(在所有交易被執行後)。
transactionsRoot
交易樹根節點的雜湊值。這棵樹包含了區塊體的所有交易。
receiptsRoot
每當交易執行時,以太坊都會生成對應結果的交易收據。此處就是這個交易收據樹的根節點雜湊。
logsBloom
布隆過濾器,用於判斷某區塊的交易是否產生了某日誌(如果對這方面感興趣,可以查閱 Stack Overflow 的這個答案)。這避免了在區塊中儲存日誌資訊(節省了大量空間)。
difficulty
此區塊的難度值。這是當前區塊挖礦難度的度量值(此處不對此概念的細節和計算作介紹)。
number
前序區塊的總數。這標示了區塊鏈的高度(即區塊鏈上有多少區塊)。創世區塊的 number 為 0 。
gasLimit
每一個交易都需要消耗 gas 。gas limit 標示了該區塊所記錄的所有交易可以使用的 gas 總量。這是限制區塊內交易數量的一種手段。
gasUsed
區塊中各條交易所實際消耗的 gas 總量。
timestamp
區塊建立時的 Unix 時間戳。謹記由於以太坊網路去中心化的特性,我們不能信任這個值,特別是撰寫智慧合約、涉及到時間相關的商業邏輯時不能依靠這個值。
extraData
能輸入任何東西的不定長位元組陣列。當礦工建立區塊時,可以在這個區域新增任何東西。
mixHash
用於驗證一個區塊是否被真正記錄到鏈上的雜湊值(如果想要真正理解這個概念,建議閱讀這篇文章 Ethash proof-of-work function )。
nonce
和 mixHash 一樣,用於驗證區塊是否被真正記錄到鏈上的值。
哎呀...真是講到我嘴都酸了...建議你彆著急,慢慢吸收!不過我要再次強調,閱讀本文不應以記住每一個名詞及其作用為目標(在谷歌上這些都能搜到)。我的寫作初衷是想用一種簡單的方式(至少比黃皮書簡單)介紹以太坊物件的方方面面,來幫助新手理解那些專業名詞代表什麼。把這篇文章當作“笨方法學以太坊物件”就好了!🙂
結論
讓我們簡要回顧一下學到了什麼!總體而言,以太坊有四種字首樹:
- 世界狀態樹包括了從地址到賬戶狀態之間的對映。 世界狀態樹的根節點雜湊值由區塊儲存(在 stateRoot 欄位),它標示了區塊建立時的當前狀態。整個網路中只有一個世界狀態樹。
- 賬戶儲存樹儲存了與某一智慧合約相關的資料資訊。由賬戶狀態儲存賬戶儲存樹的根節點雜湊值(在 storageRoot 欄位)。每個賬戶都有一個賬戶儲存樹。
- 交易樹包含了一個區塊中的所有交易資訊。由區塊頭(在 transactionsRoot 區域)儲存交易樹的根節點雜湊值。每個區塊都有一棵交易樹。
- 交易收據樹包含了一個區塊中所有交易的收據資訊。同樣由區塊頭(在 receiptsRoot 區域)儲存交易收據樹的根節點雜湊值;每個區塊都有對應的交易收據樹。
我們今天討論的物件有:
- 世界狀態: 以太坊這臺分散式計算機的硬碟。它是從地址到賬戶狀態的對映。
- 賬戶狀態: 儲存著每個以太坊賬戶的狀態資訊。賬戶狀態同樣儲存著賬戶狀態樹的 storageRoot,後者包含了該賬戶的儲存資料。
- 交易: 標示了系統中的狀態轉移。它可以是資金的轉移、訊息呼叫或是合約的部署。
- 區塊: 包括對前序區塊(parentHash)的連結,並且儲存了當執行時會在系統中產生新狀態的交易。區塊同時儲存了 stateRoot 、transactionRoot 、 receiptsRoot 、 世界狀態樹的根節點雜湊、交易樹以及對應的交易收據樹。
我想用一張圖來表示文中提及的各種概念資訊。
根據我的經驗,直接從黃皮書中學習以太坊並不方便,且需要巨大的耐心。如前所述,本文的主要目標就是用區塊鏈初學者能聽得懂的語言描述以太坊的主要物件。衷心希望這篇文章能指引你的學習之路!