以太坊虛擬機(EVM)是以太坊中智能合約的運行環境。它不只被沙箱封裝起來,事實上它被徹底隔離,也就是說運行在EVM內部的代碼不能接觸到網絡、文件系統或者其它進程。甚至智能合約與其它智能合約只有有限的接觸。html
以太坊中有兩類帳戶,它們共用同一個地址空間。外部帳戶,該類帳戶被公鑰-私鑰對控制(人類)。合約帳戶,該類帳戶被存儲在帳戶中的代碼控制。安全
外部帳戶的地址是由公鑰決定的,合約帳戶的地址是在建立改合約時肯定的(這個地址由合約建立者的地址和該地址發出過的交易數量計算獲得,地址發出過的交易數量也被稱做"nonce")網絡
合約帳戶存儲了代碼,外部帳戶則沒有,除了這點之外,這兩類帳戶對於EVM來講是同樣的。數據結構
每一個帳戶有一個key-value形式的持久化存儲。其中key和value的長度都是256bit,名字叫作storage.區塊鏈
另外,每一個帳戶都有一個以太幣餘額(單位是「Wei"),該帳戶餘額能夠經過向它發送帶有以太幣的交易來改變。日誌
一筆交易是一條消息,從一個帳戶發送到另外一個帳戶(多是相同的帳戶或者零帳戶,見下文)。交易能夠包含二進制數據(payload)和以太幣。code
若是目標帳戶包含代碼,該代碼會執行,payload就是輸入數據。htm
若是目標帳戶是零帳戶(帳戶地址是0),交易將建立一個新合約。正如上文所講,這個合約地址不是零地址,而是由合約建立者的地址和該地址發出過的交易數量(被稱爲nonce)計算獲得。建立合約交易的payload被看成EVM字節碼執行。執行的輸出作爲合約代碼被永久存儲。這意味着,爲了建立一個合約,你不須要向合約發送真正的合約代碼,而是發送可以返回真正代碼的代碼。遞歸
以太坊上的每筆交易都會被收取必定數量的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) 中,咱們能夠高效而且安全的搜索日誌,因此那些沒有下載整個區塊鏈的網絡節點(輕客戶端)也能夠找到這些日誌。
合約甚至能夠經過一個特殊的指令來建立其餘合約(不是簡單的向零地址發起調用)。建立合約的調用跟普通的消息調用的區別在於,負載數據執行的結果被看成代碼,調用者/建立者在棧上獲得新合約的地址。
只有在某個地址上的合約執行自毀操做時,合約代碼纔會從區塊鏈上移除。合約地址上剩餘的以太幣會發送給指定的目標,而後其存儲和代碼被移除。
注意,即便一個合約的代碼不包含自毀指令,依然能夠經過代碼調用(callcode)來執行這個操做。
原文:https://solidity.readthedocs.org/en/latest/introduction-to-smart-contracts.html
譯者: @rink1969
校對:@jan