本篇文章讓你了解以太坊中的幾個重要概念,有助于您理解智能合約,包括其執行過程,以及Solidity語言在開發智能合約時的相關概念
以太坊虛擬機(EVM)是以太坊中智能合約的運行環境。它不僅被沙箱封裝起來,事實上它被完全隔離,也就是說運行在EVM內部的代碼不能接觸到網絡、文件系統或者其它進程。甚至智能合約與其它智能合約只有有限的接觸。
賬戶
以太坊中有兩類賬戶,它們共用同一個地址空間。外部賬戶,該類賬戶被公鑰-私鑰對控制(人類)。合約賬戶,該類賬戶被存儲在賬戶中的代碼控制。
外部賬戶的地址是由公鑰決定的,合約賬戶的地址是在創建改合約時確定的(這個地址由合約創建者的地址和該地址發出過的交易數量計算得到,地址發出過的交易數量也被稱作“nonce”)
合約賬戶存儲了代碼,外部賬戶則沒有,除了這點以外,這兩類賬戶對于EVM來說是一樣的。
每個賬戶有一個key-value形式的持久化存儲。其中key和value的長度都是256bit,名字叫做storage.
另外,每個賬戶都有一個以太幣余額(單位是“Wei“),該賬戶余額可以通過向它發送帶有以太幣的交易來改變。
交易
一筆交易是一條消息,從一個賬戶發送到另一個賬戶(可能是相同的賬戶或者零賬戶,見下文)。交易可以包含二進制數據(payload)和以太幣。
如果目標賬戶包含代碼,該代碼會執行,payload就是輸入數據。
如果目標賬戶是零賬戶(賬戶地址是0),交易將創建一個新合約。正如上文所講,這個合約地址不是零地址,而是由合約創建者的地址和該地址發出過的交易數量(被稱為nonce)計算得到。創建合約交易的payload被當作EVM字節碼執行。執行的輸出做為合約代碼被永久存儲。這意味著,為了創建一個合約,你不需要向合約發送真正的合約代碼,而是發送能夠返回真正代碼的代碼。
Gas
以太坊上的每筆交易都會被收取一定數量的gas,gas的目的是限制執行交易所需的工作量,同時為執行支付費用。當EVM執行交易時,gas將按照特定規則被逐漸消耗。
gas price(gas價格,以太幣計)是由交易創建者設置的,發送賬戶需要預付的交易費用 = gas price * gas amount。 如果執行結束還有gas剩余,這些gas將被返還給發送賬戶。
無論執行到什么位置,一旦gas被耗盡(比如降為負值),將會觸發一個out-of-gas異常。當前調用幀所做的所有狀態修改都將被回滾。
存儲,主存和棧
每個賬戶有一塊持久化內存區域被稱為存儲。其形式為key-value,key和value的長度均為256比特。在合約里,不能遍歷賬戶的存儲。相對于另外兩種,存儲的讀操作相對來說開銷較大,修改存儲更甚。一個合約只能對它自己的存儲進行讀寫。
第二個內存區被稱為主存。合約執行每次消息調用時,都有一塊新的,被清除過的主存。主存可以以字節粒度尋址,但是讀寫粒度為32字節(256比特)。操作主存的開銷隨著其增長而變大(平方級別)。
EVM不是基于寄存器,而是基于棧的虛擬機。因此所有的計算都在一個被稱為棧的區域執行。棧最大有1024個元素,每個元素256比特。對棧的訪問只限于其頂端,方式為:允許拷貝最頂端的16個元素中的一個到棧頂,或者是交換棧頂元素和下面16個元素中的一個。所有其他操作都只能取最頂的兩個(或一個,或更多,取決于具體的操作)元素,并把結果壓在棧頂。當然可以把棧上的元素放到存儲或者主存中。但是無法只訪問棧上指定深度的那個元素,在那之前必須要把指定深度之上的所有元素都從棧中移除才行。
指令集
EVM的指令集被刻意保持在最小規模,以盡可能避免可能導致共識問題的錯誤實現。所有的指令都是針對256比特這個基本的數據類型的操作。具備常用的算術,位,邏輯和比較操作。也可以做到條件和無條件跳轉。此外,合約可以訪問當前區塊的相關屬性,比如它的編號和時間戳。
消息調用
合約可以通過消息調用的方式來調用其它合約或者發送以太幣到非合約賬戶。消息調用和交易非常類似,它們都有一個源,一個目標,數據負載,以太幣,gas和返回數據。事實上每個交易都可以被認為是一個頂層消息調用,這個消息調用會依次產生更多的消息調用。
一個合約可以決定剩余gas的分配。比如內部消息調用時使用多少gas,或者期望保留多少gas。如果在內部消息調用時發生了out-of-gas異常(或者其他異常),合約將會得到通知,一個錯誤碼被壓在棧上。這種情況只是內部消息調用的gas耗盡。在solidity中,這種情況下發起調用的合約默認會觸發一個人工異常。這個異常會打印出調用棧。
就像之前說過的,被調用的合約(發起調用的合約也一樣)會擁有嶄新的主存并能夠訪問調用的負載。調用負載被存儲在一個單獨的被稱為calldata的區域。調用執行結束后,返回數據將被存放在調用方預先分配好的一塊內存中。
調用層數被限制為1024,因此對于更加復雜的操作,我們應該使用循環而不是遞歸。
代碼調用和庫
存在一種特殊類型的消息調用,被稱為callcode。它跟消息調用幾乎完全一樣,只是加載自目標地址的代碼將在發起調用的合約上下文中運行。
這意味著一個合約可以在運行時從另外一個地址動態加載代碼。存儲,當前地址和余額都指向發起調用的合約,只有代碼是從被調用地址獲取的。
這使得Solidity可以實現”庫“。可復用的庫代碼可以應用在一個合約的存儲上,可以用來實現復雜的數據結構。
日志
在區塊層面,可以用一種特殊的可索引的數據結構來存儲數據。這個特性被稱為日志,Solidity用它來實現事件。合約創建之后就無法訪問日志數據,但是這些數據可以從區塊鏈外高效的訪問。因為部分日志數據被存儲在布隆過濾器(Bloom filter) 中,我們可以高效并且安全的搜索日志,所以那些沒有下載整個區塊鏈的網絡節點(輕客戶端)也可以找到這些日志。
創建
合約甚至可以通過一個特殊的指令來創建其他合約(不是簡單的向零地址發起調用)。創建合約的調用跟普通的消息調用的區別在于,負載數據執行的結果被當作代碼,調用者/創建者在棧上得到新合約的地址。
自毀
只有在某個地址上的合約執行自毀操作時,合約代碼才會從區塊鏈上移除。合約地址上剩余的以太幣會發送給指定的目標,然后其存儲和代碼被移除。
評論
查看更多