這個系列是目標受眾是區塊鏈開發者和有其他開發經驗的CS專業學生
在本文的上篇中,我們從交易模型入手,針對鎖定指令碼和解鎖指令碼的工作原理,探討了比特幣指令碼引擎的設計邏輯,並且對P2PKH和P2SH兩種重要的機制進行了分析。相對於比特幣,以太坊致力於搭建一個圖靈完備的可程式設計的區塊鏈平臺。為了這個目標,它做了那些不同於比特幣的設計呢?
以太坊虛擬機器
區塊鏈正規化
Gavin Wood在黃皮書中將區塊鏈系統抽象為基於交易的狀態機:

公式(1)中S是系統內部的狀態集合,f是交易狀態轉移函式,T是交易資訊,初始狀態即Gensis狀態;
公式(2)中F是區塊層面狀態轉移函式,B是區塊資訊;
公式(3)定義B是一系列交易的區塊,每個區塊都包括多個transaction;
公式(4)G是區塊定稿函式,在以太坊中包括uncle塊校驗、獎勵礦工、POW校驗等。
這個數學模型不僅是以太坊的基礎,也是目前大多數基於共識的去中心化交易系統的基礎。
以太坊相對比特幣的提升,本質體現在這個正規化中的f和S。它的核心理念——具備圖靈完備和不受限制的內部交易儲存空間的區塊鏈。分別對應:
- 功能強大的函式f,能夠執行任何計算,比特幣不支援loop;
- 狀態S記錄任意型別的資料(包括程式碼),而比特幣的UTXO模型只能計算出地址的可花費額度。
資料結構
在資料儲存方面,比特幣通過UTXO模型計算地址餘額,不鼓勵使用者存入其他資料;通過P2SH指令碼機制,理論上可以設計各種智慧合約,但受限於指令碼語言的表達能力,難以支援複雜的合約開發。這種設計對於加密貨幣來說是合理的。
以太坊為了支援記錄任意的資訊、執行任意函式,需要重新設計資料結構。
Merkle Patricia Trie
以太坊中重度使用Merkle Patricia Trie組織、儲存資料,下面我們會看到,這個新的資料結構是通過對雜湊樹和字首樹的組合創新來達到目的。
約定:下面使用MPT來代替Merkle Patricia Trie。
Merkle Tree
又稱hash tree:樹的每個葉子結點是某個資料塊的雜湊值,而每個非葉子結點是孩子結點的雜湊值。如圖所示,這棵樹不儲存Data blocks本身。在P2P網路環境中,惡意網路節點如果修改了這顆樹上的資料,將無法通過校驗(Merkle Proof),從而保證了資料的完整、有效性。這依賴於單向雜湊加密的性質。這種性質讓它廣泛應用在分散式系統的資料校驗中,比如IPFS、Git等。


Patricia Trie
又叫Radix Trie,是字首樹的空間優化變種:如果樹上某個節點是其父節點的唯一子結點,則這兩個結點可以合併起來。它在這裡的應用是對長整型資料的對映,由某個20bytes的以太坊地址對映到其賬戶,形如<Address,Account>,Address會加密編碼成16進位制的數字——在Patricia Trie上,表現為非葉結點連成的路徑。
比如,在Patricia Trie上儲存<"dog","Snoopy">,"dog"會被編碼為"64 6f 67",先找到根節點,則查詢路線為root->6->4->6->15->6->7->value,value也就是一個指向"Snoopy"的hash。這種方式相對hash表的好處在於不會出現衝突;但如果不做優化,查詢步驟太長。
改良點
為了提高效率,以太坊對樹上結點資料型別進行了專門的設計。包括以下四類結點
- null結點 代表空字串
- branch結點 17個元素的非葉節點,形如<i0,i1...i15,v>
- leaf結點 2個元素的葉結點,形如<encodedPath,value>,encodedPath是地址加密編碼後的長整型數字串的一部分
- extension結點 2個元素的非葉結點,形如<encodedPath,k>,extension的作用是把沒有分叉的路徑上結點合併起來,節省空間資源
如圖,是一個簡化的狀態樹(狀態樹後文很快會詳細解釋,這裡不妨礙作示意圖),右上角就是<地址,餘額>的對映。prefix項的作用是輔助編碼,可以忽略。4個賬戶的地址,按照MPT組織起來。其中所有的extension節點只是優化作用,都可以用多個branch結點替代。
使用MPT需要有後端資料庫(以太坊中使用levelDB)維護每個結點間的連線關係,這個資料庫叫做狀態資料庫。使用MPT的好處包括:(1)這個結構的根節點是加密的且依賴於所有的內部資料,它的雜湊可以用於安全性校驗,這是merkle樹的性質,但和merkle樹不儲存資料塊本身不同的是,MPT樹結點儲存了地址資料,這是Patricia樹的性質(2)允許任何一個之前狀態(根部雜湊已知的條件下)通過簡單地改變根部雜湊值而被召回。
狀態
上面在解釋MPT時已經介紹了狀態樹的概念。以太坊中的世界狀態(World State)的概念,通過MPT對映儲存去中心化交易系統記錄的任意狀態。這對應了區塊鏈正規化中的S,是以太坊設計的一個核心概念。

對以太坊的賬戶模型需要專門做個介紹。
Account
比特幣使用UTXO模型計算餘額,無法滿足記錄任意狀態的需求。以太坊設計了Account模型,它會儲存包括:
[nonce, balance, storageRoot, codeHash]
其中nonce是交易計數器,balance是餘額資訊,storageRoot對應另外一個MPT,通過它能夠在資料庫中檢索到合約的變數資訊,codeHash是程式碼hash值,建立後不可更改。

- 外部賬戶(externally owned accounts)
外部賬戶由私鑰控制,對應Account模型裡,storageRoot、codeHash並不存在,也就是不會儲存、執行程式碼。如果只有外部賬戶,那麼以太坊只能支援轉賬功能。 - 合約賬戶(contract accounts)
合約賬戶可以通過外部賬戶發起交易建立,也可以是由另一個合約賬戶建立。合約賬戶在收到訊息呼叫時,會載入程式碼,通過EVM執行相應的邏輯,修改內部儲存的狀態。
交易
在UTXO模型下,交易本質上是(通過簽名的資料)對input的解鎖和對output的鎖定。在Account模型下,交易分為兩種:
- 建立合約,通過程式碼建立新的合約
- 訊息呼叫,可以轉賬也可以觸發合約的某個函式
兩種型別的交易都包括以下欄位:
[nonce,gasPrice,gasLimit,to,value,[v,r,s]]
- nonce: 賬戶發出交易數量
- gasPrice,gasLimit: 用於限制交易執行時間,防止程式死迴圈
- to:交易的接受者
- value:轉賬額度,如果是建立合約,就是捐贈給合約的額度
- v,r,s:交易簽名相關資料,可以用來確定交易傳送者
合約建立還需要:
- init:一段不限大小的位元組陣列表示的EVM程式碼,僅在合約建立時執行一次;init執行後返回body程式碼片段,之後的合約呼叫都會執行body程式碼內容。
合約賬戶的地址由sender和nonce共同決定,所以任意兩次成功的合約部署得到的地址都是不同的。從上圖能看出,程式碼和狀態的儲存是分開的。實際上編譯後的位元組碼會儲存在一個virtual ROM中,且不可修改。
訊息呼叫還需要:
- data:一段不限大小的位元組陣列,表示訊息呼叫時的輸入
訊息呼叫會修改賬戶的狀態,可能是EOA賬戶也可能是合約賬戶。
交易既可以由外部賬戶發起,也可以由合約發起。比如第5228886區塊包含170個交易和7個內部合約交易。
區塊
以太坊的區塊了加入更多的資料項,相對比特幣要複雜很多,但其實本質上區別不大。比如加入了叔鏈雜湊,優化激勵措施,這是為了支援挖礦協議;區塊本身還會有大量的有效性驗證、序列化。這些內容不在本文主題範圍,不深入討論。

EVM設計與執行
以太坊虛擬機器(Ethereum Virtual Machine)是執行以太坊的狀態轉移函式的執行環境。 有個簡單的問題,以太坊是否可以不專門開發一款底層VM,而是複用Java、Lisp、Lua等呢?理論上是完全可以的,Corda專案就完全基於JVM平臺開發。但是更多的區塊鏈專案會選擇專門開發底層設施,包括比特幣的指令碼引擎。以太坊官方給出的解釋:
- 以太坊的VM規格更簡單,而別的通用VM有很多不必要的複雜性
- 容許定製開發,比如32bytes的字
- 避免其他VM帶來的外部依賴問題,可能導致安裝困難
- 採用其他VM,需要做完全的安全性審查,權衡下不一定能省多少事
記憶體模型
EVM也是基於棧式計算機模型,但除了stack外還涉及memory和storage:
- stack 棧上元素大小為32bytes,這和一般的4bytes,8bytes不同,主要是針對以太坊運算物件多為20bytes的地址和32bytes的密碼學變數;棧的大小不超過1024;棧的呼叫深度不超過1024,主要防止出現記憶體溢位。
- memory 雖然運算都在棧上進行,但臨時變數可以存在memory裡,memory大小不做限制
- storage 狀態變數都放在storage裡,不像stack和memory上的量隨著EVM例項銷燬消失,storage裡面的資料修改後都會持久化
如圖,是一個EVM架構的示意圖,這種設計對以太坊應用開發有著深遠的影響,包括設計模式和安全考量。 一個經典的問題是合約的升級:
合約部署後編譯成位元組碼儲存在virtual rom中,程式碼是不可修改的,這對很多DAPP來說是嚴重的制約。一種思路是,將程式碼分佈在不同的合約中,合約間呼叫通過儲存在storage中的地址來進行,這樣實現了實際上的合約升級操作。
執行模型
EVM準確來說是一個準圖靈機,文法上它能夠執行任意操作,但為了防止網路濫用、以及避免由於圖靈完整性帶來的安全問題,以太坊中所有操作都進行了經濟學上的限制,也就是gas機制,有三種情況:
- 一般操作消耗費用,比如SLOAD,SSTORE等
- 子訊息呼叫或者合約建立而消耗燃料,這是執行CREATE、CALL、CALLCODE費用中的一部分
- 記憶體使用消耗費用,與所需要的32bytes的字數量成正比
下圖展示了EVM執行的內部流程,從EVM code中取指令,所有的操作在Stack上進行,Memory作為臨時的變數儲存,storage是賬戶狀態。執行受到gas avail限制。

現在結合EVM我們再來看看之前介紹的交易的執行細節。正如區塊鏈正規化定義的,T是以太坊狀態轉移函式,也是以太坊最複雜的部分。所有的交易在執行前,都需要先經過內部的有效性驗證:
- 交易是RLP格式資料,沒有多餘的字尾位元組;
- 交易的簽名是有效的;
- 交易的隨機數是有效的;
- 燃料上限不小於實際交易過程中用的燃料;
- 傳送者賬戶的餘額至少大於費用v0,需要提前支付;
下圖是訊息呼叫的過程,每個交易可能會形成很深的呼叫棧,交易內部由不同的合約之間的呼叫。呼叫通過CALL指令,引數和返回值通過memory傳遞。

錯誤處理
EVM在合約執行時會發生若干種錯誤:
- 燃料不足
- 無效指令
- 缺少棧資料
- 指令JUMP JUMPI的目標地址無效
- 新棧大小大於1024
- 棧呼叫深度超過1024
EVM的錯誤處理有個簡單的原則,叫做revert-state-and-consume-all-gas,即狀態恢復到交易執行前的checkpoint,但消耗的gas不會再退還。虛擬機器把錯誤全看作是程式碼出錯,不作特定的錯誤處理。
EVM分析工具
關於EVM分析的工具可以參考Ethereum Virtual Machine (EVM) Awesome List
類EVM的圖靈完備虛擬機器(WIP)
完整的EVM規格是很複雜的,但具備一定的彙編基礎和簡化模型的能力,實現一個類EVM的虛擬機器是可以嘗試的挑戰。等有空我再把自己的實現放上來吧。有興趣的同學可以自己動手試試。
參考
1.A Next-Generation Smart Contract and Decentralized Application Platform
2.ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER
3.Design Rationale
4.Stack Exchange: Ethereum block architecture
5.Go Ethereum
6.evm-illustrated
7.Diving Into The Ethereum VM