以太坊 到底是什么(不了解以太坊到底是什么,這篇超長圖文告訴你答案)
你也許不太清楚以太坊具體是什么,但是我相信你一定聽過它。它最近出現在很多新聞和雜志中,但如果你不了解以太坊到底是什么,那么閱讀相關文章可能會覺得很難懂。
從本質上講,以太坊是一個永久記錄并保存數字交易的公共數據庫。 與普通數據庫不同的是,該數據庫不需要任何中央機構來維護和保護它,它是作為一個“無需信任”的交易系統運行的,個人可以在這個框架中進行點對點交易,而無需信任第三方或彼此。
讀完上面的介紹是不是仍然感受到很困惑?這就是為什么我要寫這篇文章的原因,我希望從技術層面去解釋什么是以太坊,但又不引入復雜的數學公式。即使你不是一個程序員,你也可以通過這篇文章去更好的使用區塊鏈技術。如果本文出現一些太難理解的部分,你可以不用去深究。我希望讀者能夠對以太坊有個整體的理解就行。
這篇文章里面的很多內容都來自于《以太坊黃皮書》。我只是增加了個人的解釋和圖表來讓你更好的理解以太坊。如果你有足夠的勇氣,也可以去直接閱讀從《以太坊黃皮書》開始。
讓我們開始吧!
區塊鏈定義
區塊鏈是一個:具有共享狀態的加密安全事務單例機。這概念很難理解對吧?讓我們把它拆解一下:
“加密安全”:意味著數字貨幣的創造是由復雜的數學算法保護的,這些算法非常難以破解。你可以聯想一下各類的防火墻。你在區塊鏈上的交易都不可能欺騙整個系統(例如,創建虛假交易、刪除交易等)。
“交易單例機”:意味著只有一個機器的單一實例,這個單一實例負責系統中創建的所有事務。 換句話說,每個人都相信一個單一的真理。
“共享狀態”:意味著在這臺機器上存儲的所有狀態,都是共享并且開發給所有人的。
以太坊就實現了上面所講的幾點區塊鏈范式。
區塊鏈范式解釋
以太坊區塊鏈本質上是一個基于交易的狀態機。在計算機科學中,狀態機是指能夠讀取一系列輸入,并根據這些輸入轉移到新的狀態的東西。
當以太坊上最初沒有任何交易的狀態叫「創世態」。你可以把它想象成一張什么都沒有的白紙,當交易被執行時,「創世態」轉換到某個最終狀態。 在任何時間點,這個最終狀態都代表了以太坊的當前狀態。
現在以太坊的狀態中記錄有數以億計的交易,這些交易被分組為“區塊”。一個區塊包含一系列交易,每個區塊都與其前一個區塊鏈接在一起。
要觸發從一種狀態到下一種狀態的轉換,這個交易必須得是合法。為了使交易被認為是合法,它必須經過一個我們所熟知的「挖礦」的驗證過程。 挖礦是指一組節點(即計算機)消耗它們的計算資源來創建一個合法的區塊。
網絡上任何自稱為礦工的節點都可以嘗試創建和驗證區塊。但同一時間,來自世界各地的許多礦工都在試圖創建和驗證區塊。每個礦工在向區塊鏈提交區塊時都會提供一個數學“證明”,這個證明起到了保障作用,如果該證明存在,則該區塊即是合法的區塊。
對于要添加到主區塊鏈的區塊,礦工必須比任何其他競爭礦工更快地證明它。通過讓礦工提供數學證明來驗證每個區塊的過程被稱為“工作證明”。
驗證新區塊的礦工因完成這項工作而獲得一定的獎勵。那這個獎勵是什么呢?以太坊區塊鏈使用一種稱為“以太幣”的原生數字貨幣作為獎勵。每次礦工證明一個區塊時,都會生成并獎勵新的以太幣。
你可能想知道:是什么能夠保證每個人都是在以太坊這一條區塊鏈上?一群礦工難道不會生成區塊后,組成一條新的區塊鏈嗎?
早些時候,我們將區塊鏈定義為具有共享狀態的事務單例機。使用這個定義,我們可以理解當前狀態是一個單一的全局事實,每個人都必須接受。如果擁有多個狀態(或鏈),這會破壞整個系統,因為所有節點不能就哪個狀態是正確的達成一致。如果鏈條分叉,你可能在一個鏈條上擁有 10 個以太幣,在另一個鏈條上擁有 20 個以太幣,在另一個鏈條上擁有 40 個以太幣。 在這種情況下,將無法確定哪個鏈是最“有效的”。
每當生成了不止一個路徑時,就會發生“分叉”。我們通常希望避免分叉,因為它們會破壞系統并迫使人們選擇他們“相信”的鏈。
在以太坊中,使用了“幽靈協議”(GHOST protocol)這個機制,來確定哪條路徑是有效的, 而且能避免多條鏈的出現。
幽靈協議:Greedy Heaviest Observed Subtree
簡單來說,幽靈協議是指我們必須選擇的路徑是一條擁有最多計算量的路徑。一種確定方式是選擇區塊塊號最大一個區塊。這個區塊塊號代表了當前路徑下所有區塊的數量(不包括創世區塊)。區塊的塊號越大,那么這條路徑上礦工在挖礦中付出的計算量就越大。使用這種方式,就可以對當前狀態的版本達成一致。
現在你已經對區塊鏈是什么有了一個大致的概覽,讓我們來深入了解一下以太坊區塊鏈的主要組成部分:
賬戶(accounts)
狀態(state)
gas 和 gas fee
交易(transactions)
區塊(blocks)
交易執行(transaction execution)
挖礦
工作證明
賬戶
以太坊的全球“共享狀態”,是由許多能夠通過消息傳遞框架相互交互的對象(“賬戶”)組成。每個帳戶都有一個與之關聯的狀態,和一個20字節的地址。也就是說,以太坊中的地址是采用了160位的標識符來標識賬戶地址(1字節 = 8位)。
在以太坊中,有兩種類型的賬戶:
外部賬戶:私鑰控制,沒有與之關聯的代碼。
合約賬戶:合約代碼控制,有與之關聯的代碼。
外部賬戶 vs 合約賬戶
理清外部賬戶和合約賬戶的區別是非常重要的。外部帳戶可以通過使用自己的私鑰創建和簽署交易,以此向其他外部擁有的帳戶或其他合約帳戶發送消息。兩個外部擁有的賬戶之間的消息只能是以太幣轉移的數值。但是從外部賬戶到合約賬戶的消息會激活合約賬戶的代碼,允許執行各種操作(例如轉移代幣、寫入內部存儲、鑄造新代幣、執行計算、創建新合約等)。
與外部賬戶不同,合約賬戶不能自行發起新交易。相反,合約賬戶只能響應它們收到的其他交易(來自外部賬戶或來自另一個合約賬戶)。我們將在“交易和消息”部分了解有關合約和合約調用的更多信息。
因此,在以太坊區塊鏈上發生的任何行動,都總是由外部賬戶發起的交易。
賬戶狀態(Accout State)
賬戶狀態由四個部分組成,無論是外部賬戶或者合約賬戶都有這四個部分:
nonce: 如果賬戶是外部賬戶,這個數字代表從賬戶地址發送的交易數量。如果賬戶是合約賬戶,nonce就是該賬戶創建的合約數量。
balance: 賬戶地址所擁有的以太幣的數量,這里使用wei做單位的,1Ether等于1e+18wei。
storageRoot:梅克爾帕特里夏樹(Merkle Patricia tree)的根節點的hash(我們將會在后面解釋什么是梅克爾帕特里夏樹)。這棵樹編碼了賬戶存儲數據的哈希值,默認是空的。
codeHash:此帳戶的 EVM(以太坊虛擬機?—?稍后會詳細介紹)代碼的哈希值。 對于合約賬戶,這是經過哈希處理并存儲為codeHash的代碼。對于外部賬戶,codeHash字段的哈希值是空字符串。
全局狀態
我們知道以太坊的全局狀態由帳戶地址和帳戶狀態之間的映射組成。此映射存儲在稱為梅克爾帕特里夏樹(Merkle Patricia tree)的數據結構中。
梅克爾樹(Merkle tree,也叫Markle trie,下文統一叫Markle trie)是一種由一組節點組成的二叉樹,它由以下部分組成:
包含底層數據的,位于樹底部的大量葉節點
一組中間節點,中間節點的每個節點是其余兩個子節點的哈希
單個根節點,也由其兩個子節點的哈希形成,表示樹的頂部
樹底部的數據是通過將我們要存儲的數據拆分成塊,然后把塊拆分成桶,最后取每個桶的哈希并重復相同的過程,直到剩余的哈希總數變為只有一個:根哈希(Root hash)。
這棵樹需要為每條存儲的數據存儲一個鍵值。從樹的根節點開始,這個鍵值決定子節點是哪個,從而得到對應的值,這個值存儲在葉子節點中。在以太坊的例子中,狀態樹的鍵/值映射在地址和它們關聯的賬戶之間,包括每個賬戶的balance、nonce、codeHash 和 storageRoot(其中storageRoot本身就是一棵樹)。
資源:以太坊白皮書
同樣的trie結構也能夠存儲交易和收據。更具體地說,每個塊都有一個“頭”,它存儲三個不同Merkle trie結構的根節點的哈希,包括:
State Trie
Transaction Trie
Receipts Trie
(譯者補充:上圖中StateRoot、TransactionsRoot和ReceiptsRoot分別取自上面三個MPT的計算結果:State Trie、Transaction Trie和Recipts Trie的根節點的哈希值)。
對于以太坊中中的“輕客戶端”和“輕節點”而言,Merkle trie能夠有效的存儲所有信息。記住,區塊鏈是由成千上萬個節點所維護的,這些節點可以大致分成兩類:全節點和輕節點。
全節點會下載創世區塊到當前區塊的完整鏈來同步真個區塊鏈,并執行其中包含的所有交易。通常,礦工存儲完整的存檔節點,因為他們在挖礦過程需要。 當然也可以在不執行每筆交易的情況下下載完整節點。無論如何,任何全節點都包含整個區塊鏈。
但是某些節點其實既不需要執行每一筆交易,也不需要查詢歷史記錄,這樣的節點是沒有必要保存整個區塊鏈數據的。這樣的的節點就是上文提到的輕節點。輕節點不用下載和存儲整個鏈并執行所有交易,它只下載從創世塊到當前區塊的區塊頭數據(Block Header),不執行任何交易或檢索任何關聯狀態。但輕節點因為保存了區塊頭數據,區塊頭數據又存儲了上文提到的三個Merkle trie結構的哈希值。所以輕節點依然能夠生成和接受交易、事件和余額等的驗證答案。
因為 Merkle 樹中的哈希值是向上傳播?的,如果惡意用戶試圖將虛假交易交換到 Merkle 樹的底部,這種變化將導致上面節點的哈希值發生變化,從而改變更上面節點的哈希值。依此類推,直到它最終改變樹的根。
節點可以使用Merkle證明去驗證一條數據。Merkle證明組成如下:
待驗證的數據塊及其哈希
樹的根哈希
分支(區塊到根一條上升路徑上所有的哈希)
任何使用該證明的人,都可以驗證該分支的哈希和樹的關系是具有一致性的,因此給定的塊實際上是在樹中的那個位置。
總之,使用 Merkle Patricia 樹的好處是該結構的根節點,在密碼學上依賴于存儲在樹中的數據,因此根節點的哈希可以用作該數據的安全身份。 由于區塊頭包含狀態、交易和收據樹的根哈希,因此任何節點都可以驗證以太坊的一小部分狀態,而無需存儲整個狀態,因為整個狀態這可能是無限大的。
Gas和費用(fee)
以太坊中一個非常重要的概念是費用的概念。由于以太坊網絡上的交易而發生的每次計算都會產生費用?(?天底下沒有免費的午餐!), 這筆費用以稱為“gas”的面額支付。
Gas 是用于衡量計算所需費用的單位,Gas Price是你愿意在每單位gas上花費的以太幣,單位是“gwei”。“wei”是以太幣的最小單位,1以太幣等于1 x 1018wei。1gwei是1 x 109wei。
對于每筆交易,發件人通常會設置gas limit和gas price。gas price和gas limit相乘,代表發送方愿意為執行這筆交易支付的最大wei的金額。
舉個例子,交易發送方將gas limit設置為50,000,gas price設置為20gwei,那著意味著這個發送方愿意最多為這筆交易費用花費:
50,000 x20gwei = 1,000,000,000,000,000wei = 0.001以太幣
gas limit代表的是交易發送方愿意為這筆交易花費的最大值,如果賬戶中有足夠的以太幣,那么這筆交易就會被成功執行。如果有沒有使用的gas,會以原始的以太幣價格退還給發送方。
如果發送方沒有提供這筆交易足夠的gas,那么這筆交易會由于“out of gas”而失效。出現這種情況,這筆交易會被中止,以一條失敗的記錄記錄在以太坊中,同時所有的狀態都會回滾到交易開始之前的初始狀態。而且由于在“out of gas”前已經投入了計算資源,按理來說,gas已經被消耗了,不會被退還給發送方。
你可能會好奇,gas這筆錢跑到哪里去了呢?答案是這筆錢會被發送到礦工地址上去。因為這筆交易是由礦工們參與了計算,從而驗證了交易的合法性。所以gas應該獎勵給礦工們。
通常來說,如果發送方付出更高的gas,那么礦工們因為能得到更高的獎勵,所以會優選選擇驗證這筆交易。如果gas費用太低,礦工也可能會主動忽視這筆交易。為了指導發送方設置一個合理的gas價格,礦工們可以選擇公布他們將執行交易的最低gas價格。
存儲費用
gas并不僅僅是上文提到的,只會在交易計算時消耗,它同時也用于支付存儲費用。存儲的總費用與使用的 32 字節的最小倍數成正比。
存儲費用的計算有較多的細節。比如,增加的存儲空間會同時增加以太坊上所有的節點的存儲空間,所以努力保持更小的存儲空間是更有利的。由于這個原因,如果一個交易有著釋放存儲空間的操作,那么執行這個操作的計算費用將會被免除,這筆計算將退還并釋放存儲控件。
費用的作用是什么?
我們知道,在以太坊網絡中任何一個交易的執行都會同時影響所有的節點。然而,在EVM上的計算成本是十分高的。所以,以太坊的智能合約理應用來執行一些簡單的工作,例如運行一個簡單的業務邏輯、驗證簽名或者其他加密場景,而不應該用來執行像是存儲數據、發送郵件,或者是機器學習這樣的可能會花費巨大的計算資源,讓整個以太坊網絡超負荷的工作。費用就是因為這個原因而被設計出來的。
以太坊是一個圖靈完備的區塊鏈,要知道一個圖靈機是無法判斷一段代碼是否最終會結束。但是在以太坊中運行的智能合約又能夠執行循環操作,所以如果計算沒有費用的話,智能合約的開發者可能會寫出一個無限循環的代碼段,這會拖垮整個以太坊網絡。因此,費用的設計能夠保證整個網絡不會陷入這樣的惡意攻擊。
您可能還會想:“為什么我們還要為存儲付費呢?”。其實就像計算一樣,以太坊網絡上的存儲也是整個網絡必須承擔的成本。
交易和消息
上文有提到,以太坊是一個基于交易的狀態機。在不同賬戶之間發生交易后,整個以太坊網絡會從一個狀態轉移到另一個狀態。
交易本質上是外部賬戶生成的一條加密簽名指令,這條指令序列化后然后提交到區塊鏈。
交易分為兩類:消息調用和合約創建。
這兩類交易由以下字段組成:
nonce:發件人發送的交易數量
gasPrice:發送方想要執行這筆交易,愿意付出的每單位gas價格
gasLimit:發送方愿意為執行此交易支付的最大 gas 量。 該金額需要在交易執行前預先設定并支付
to:接收方的地址。在合約創建類型的交易中,合約賬戶地址還沒有生成,這個時候這個字段為空值
value:發送方項接收方發送的以太幣數量(單位Wei)。在合約創建類型的交易中,這個值是新合約地址的余額值。
v,r,s:用于生成標識交易發送方的簽名
init(僅存在于創建合約的交易中):用于初始化新合約賬戶的 EVM 代碼片段。 init 只運行一次,然后被丟棄。 首次運行 init 時,它會返回賬戶代碼的主體,這是與合約賬戶永久關聯的一段代碼
data(僅存在于消息調用的可選字段):消息調用的輸入數據(即參數)。 例如,如果智能合約用作域注冊服務,則對該合約的調用可能需要輸入字段,例如域名和 IP 地址。
我們在上面“賬戶”章節了解到,交易?始終是由外部賬戶發起并提交到區塊鏈,消息調用和合約創建這兩個交易分類都是如此。 我們可以說,交易是外部世界與以太坊內部狀態的橋梁。
但是這并不意味著一個合約不能和另一個合約對話。一個存在于以太坊狀態全局范圍內的合約是可以和同一范圍內的其他合約對話。這種方式是通過“消息”或“內部交易”的方式實現的的, 你可以把這類實現方式類比為交易,但是區別在于發起方不是外部賬戶,而是合約賬戶。不像交易行為,它們沒有被序列化且只是以虛擬對象的方式存在于以太坊的執行環境中。
如果一個合約向另一個合約發送了內部交易,此時作為接收方的合約中的代碼會被執行。
一個內部交易或者消息并不需要gasl limit,因為gas limit是由原始交易的外部賬戶決定的。外部賬戶設置的gas limit需要涵蓋這個交易行為衍生的子交易行為,例如合約和合約之間的消息。如果在這個交易的執行鏈條過程中,有一個特定的消息由于gas耗盡而執行失敗,那么所有已經被執行的交易都將回滾回初始狀態。唯一的例外情況是如果您在合約中使用原始 CALL 操作碼(除非不得不這么做,否則不建議這樣做)。在這種情況下,對另一個合約的 CALL 可能會失敗,但如果父合約不檢查并處理該錯誤,則執行會正常進行,并且結果可能會顯示成功。
區塊
所有的交易都會被分組成為區塊,一系列的鏈接在一起的區塊組成了區塊鏈。
在以太坊中,一個區塊包含:
區塊頭
這個區塊的交易集數據
當前區塊的一組叔塊(ommer)的區塊頭
什么是叔塊(ommer)?
叔塊(ommer)也是一個區塊,它的父級是當前塊的父級的父級(這就是為什么它被稱作叔塊的原因,有時也會被稱為uncle)。讓我們簡單了解一下叔塊的用途,為什么一個區塊塊需要包含叔塊的塊頭?
由于以太坊的構建方式,出塊時間(約 15 秒)相比于比特幣等其他區塊鏈(約 10 分鐘)要短得多,這可以實現更快的事務處理。 但是較短的出塊時間也是有缺點的,缺點之一是同一時間可能產生多個區塊,這些由于競爭時產生的區塊也被稱為“孤塊”(即塊不會進入主鏈的區塊)。
叔塊的設計目的是用于同時獎勵開采出有效孤塊的礦工,因為這些礦工也付出了計算工作。
但是叔塊收到的獎勵是要比打包進入主鏈的區塊要低的。盡管如此,因為有獎勵的存在,礦工仍然有動力將這些孤塊打包在內并獲得獎勵。
區塊頭
讓我們回到區塊本身,上文中有提到每個區塊都有個區塊頭,那么區塊頭具體是什么什么呢?
區塊頭由一下字段組成:
parentHash:父塊的hash值(這也是為什么區塊組成了區塊鏈的原因所在)
ommersHash:多個叔塊的hash
beneficiary:生成出這個區塊的礦工地址獲得的獎勵
stateRoot: Merkle trie 的根哈希,存儲系統的整個狀態
transactionsRoot:此區塊中列出的所有交易接收方的 trie 根節點的哈希
logsBloom:包含日志數據的布隆過濾器(一種數據結構)
diffculty:這個區塊開采的難度
number:當前塊的計數(創世塊的塊號為零;每個后續塊的塊號增加 1)
gasLimit:區塊中的交易設置的總 gas limit
gasUsed:這個區塊中的所有交易消耗的總gas
timestamp:礦工開采該區塊的時間戳
extraData:礦工附加在區塊中的任何額外數據
mixHash:一個hash值,和nonce字段結合時,可以用于證明該區塊是由足夠計算量產生的
nonce:一個hash值,和mixHash字段結合時,可以用于證明該區塊是由足夠計算量產生的
每個區塊頭包含我們前文中提到的梅克爾帕特里夏樹的三種trie結構:
state(stateRoot)
transactions(transactionsRoot)
receipts(receiptsRoot)
上面的幾個術語我們會在接下來的篇幅中細講。
日志
以太坊設計了日志用于追中不同的交易和消息。一個合約如果想要記錄日志,可以通過定義一個事件來實現。
一個日志包括:
記錄日志的賬戶地址
不同交易攜帶的topics
與這個事件相關的任何數據
日志存儲在布隆過濾器中,這個數據結構能夠高效的存儲大量數據。
交易收據
區塊頭中存儲的日志來自交易收據中包含的日志信息。就像你去一個超市購買商品后收到一個收據一樣,以太坊也會為每筆交易生成一個收據。收據中都包含這筆交易的信息,包括:
區塊數(the block number)
區塊hash(block hash)
交易hash(transaction hash)
交易使用的gas
在當前交易執行后這個區塊使用的gas累計值
執行當前交易的日志生成量
等等..
區塊難度
區塊的“難度”用來衡量挖出一個區塊平均所需要的運算次數,反映了在一定難度下用多長時間才能挖到一定數量的區塊。 創世區塊的難度為131,072,之后使用特殊的公式計算每個區塊的難度。 如果某個區塊的驗證速度比前一個區塊快,則以太坊協議會增加該區塊的難度。
難度將會影響nonce,因為這個hash必須通過礦工通過挖礦計算而來,使用工作量證明算法。
區塊難度和nonce之間關系可以用下面的數學公式表達(表示區塊難度):
找到滿足難度閾值的隨機數的唯一方法是使用工作量證明算法來枚舉所有可能性。 找到解決方案的預期時間與難度成正比?——?難度越高,找到 nonce 就越難,因此驗證區塊就越難,這反過來又增加了驗證新區塊所需的時間。因此,通過調整區塊的難度,就可以調整驗證區塊所需的時間。
另一方面,如果驗證時間變慢,則協議會降低難度。 這樣,驗證時間會自我調整以保持恒定速率?—?平均每 15 秒一個塊。
交易執行
我們來到了以太坊協議最復雜的一部分:交易執行。當你在以太坊網絡上發送一筆交易,以太坊經歷了怎樣的一個狀態轉移?
首先,所有的交易都必須滿足一組基本要求才能夠被執行:
交易必須是格式正確的 RLP。 “RLP”代表“Recursive Length Prifix(遞歸長度前綴)”,是一種用于編碼二進制數據嵌套數組的數據格式。 RLP 是以太坊用來序列化對象的格式。
合法的交易簽名
合法的交易nonce。回想一下我們之前提到的,賬戶的 nonce 是從該帳戶發送的交易計數。所以,交易的noce必須等于交易發送方賬戶地址的nonce
一個交易的gas limit必須大于等于這個交易的固有gas,固有gas包括:執行交易的預定義成本 21,000 gas與交易一起發送的數據的 gas 費(每個等于 0 的數據或代碼字節需要 4 個 gas,每個非零數據或代碼字節需要 68 個 gas)如果這個交易包括合約創建交易,額外增加32,000gas交易執行過程中的操作步驟消耗的gas
發送方的賬戶余額必須有足夠的以太幣來支付必須支付的“前期”gas費用。 前期 gas 成本的計算很簡單:首先,交易的 gas limit乘以交易的 gas price來確定最大的 gas 成本。 然后,將此最大成本添加到從發送方轉移到接收方的總價值中。
如果一筆交易通過上面的驗證,那么它將進入到下一階段:
首先,我們先從發送方余額直接減去剛才的提到的前期gas費用,并在發送方賬戶的nonce加1。此時,我們可以計算出剩余氣體 = 交易的總gas limit - 已使用的固有氣體。
下一步,這個交易開始執行。在整個交易執行過程中,以太坊都會跟蹤“子狀態”。 子狀態是一種記錄在事務期間累積的信息的方式,這些信息將在交易完成時使用。 它包含:
需要銷毀的賬戶集合:交易完成后將被丟棄的一組賬戶(如果存在的話)。
日志系列:虛擬機代碼執行的存檔和索引的檢查點
退款余額:交易完成后要退還給發送方賬戶的金額。 還記得我們如何提到以太坊中的存儲需要花錢,并且發件人會因清理存儲而獲得退款嗎? 以太坊使用退款計數器跟蹤這一點, 退款計數器從0開始,每次合約刪除存儲中的內容時都會遞增。
接下來,處理交易所需的各種計算。
如果交易所需的所有步驟都處理完成了,而且沒有出現異常狀態,則通過確定要退還給發送方的沒消耗gas的數量來最終確定狀態。 除了沒消耗的gas,發送方還會從我們上面描述的“退款余額”中得到一些補償。
一旦發送方收到退款:
gas的費用會被獎勵給礦工
使用的gas會被添加記錄到區塊的gas計數器上(用于記錄這個區塊消耗的總gas,這個值會在驗證區塊的時候被使用到)
銷毀的丟棄的賬戶(如果存在的話)
最后,這筆交易留下了一個新的狀態和一系列的日志。
現在,我們已經大概理解了交易執行過程的全部知識點。接下來我們來看看合約創建和消息調用這兩種情況有什么不同之處吧。
合約創建
回想一下上文里面有提到,以太坊中有兩種類型的賬戶:合約賬戶和外部賬戶。當我們說一個交易是一個“合約創建”交易的時候,這意味著這筆交易的目的是創建一個合約賬戶地址。
為了創建一個合約賬戶的地址,我們需要先用一個特定的方式聲明新賬戶的地址。然后初始化一個新的賬戶地址:
設置合約賬戶地址的nonce為0
如果發送方在這個交易中有發送以太幣,那么將這些以太幣用作這個賬戶的余額
從發送方的賬戶上減去要發送的以太幣的數值
設置存儲為空
設置這個合約的codeHash為一個空字符串的hash值
一旦我們初始化了賬戶,我們就可以使用與交易一起發送的初始化代碼來創建賬戶。 在執行此初始化代碼期間可能會發生很多情況。根據合約的構造函數,它可能會更新賬戶的存儲、創建其他合約賬戶、進行其他消息調用等。
在執行初始化合約的代碼時,它會使用 gas。 交易消耗的gas不得超過剩余的gas。 如果是這樣,執行將遇到gas耗盡 (OOG)的異常并退出,此時狀態將恢復到交易之前的狀態。發送方不會退還用完之前消耗的gas。
但是如果發送方在交易中發送了任何以太幣值,即使合約創建失敗,以太幣也會被退還。
如果初始化代碼成功執行,則支付最終的合約創建成本。 這個成本是一個存儲成本,與創建的合約代碼的大小成正比(記住天下沒有免費的午餐!)。如果沒有足夠的剩余gas 來支付這個最終的存儲成本,那么交易會出現聲明一個gas耗盡異常并且被中止。
如果一切順利的話,那么沒有被消耗的 gas 都將退還給交易的發送方。
消息調用
消息調用的執行和合約創建過程基本類似,但也有一些不同點。
消息調用不會包含任何需要初始化的代碼,但是它可以輸入數據,這個輸入數據是由交易的發送方提供的。消息調用還有一個包含輸出數據的額外組件,如果后續執行需要這個輸出數據,則需要使用該組件。
與創建合約一樣,如果消息調用執行過程中出現gas耗盡或交易無效(例如堆棧溢出、無效跳轉目標或無效指令)而退出,則已經消耗的 gas 不會退還給原始發送方 。所有剩余的未使用gas都被消耗掉,并且狀態被重置到之前的狀態。
最初以太坊是不允許在消耗提供的gas前停止或者恢復交易的,例如,如果你的一個合約被一個沒有權限的調用者給調用了,那么在之前的以太坊中,剩余的gas將依然被消耗,不會退還給發送方。但在拜占庭更新后,以太坊新增了一個revert的代碼,允許合約執行中斷或者恢復,并且退還還沒有消耗的gas。
執行模式
前文中我們已經看到了一個交易從開始執行到結束的各個步驟。現在,我們來看一下交易具體在EVM(以太坊虛擬機)中是如何執行的。
EVM 是一個圖靈完備的虛擬機。 EVM 的唯一限制是 gas,這點在其他的圖靈完備的虛擬機上是沒有的。所以在EVM上可以完成的計算總量,本質上受到gas提供量的限制。
EVM是基于棧的,一個基于棧的機器是使用后進先出堆棧來保存臨時值的計算機。
EVM 中每個棧項的大小為 256 位,棧最大的大小為 1024位。
EVM 是有內存的,其中的數據存儲為字尋址字節數組。 內存中存儲的數據不是永久性數據。
EVM 也有外存。與內存不同,外存作為系統狀態的一部分進行維護,能夠存儲永久性數據。 EVM 將程序代碼單獨存儲在一個只能通過特殊指令訪問的虛擬 ROM 中。通過這種方式,EVM 與典型的馮諾依曼架構不同,后者將程序代碼存儲在內存或存儲器中。
EVM 也有自己的語言:“EVM 字節碼”。 當程序員編寫在以太坊上運行的智能合約時,我們通常會使用更高級別的語言(例如 Solidity)編寫代碼,然后將其編譯為 EVM 可以理解的 EVM 字節碼。
介紹完EVM的基礎概念,我們來看看具體的執行環節。
在執行一個計算前,處理器會去報下面的信息是可用和合法的:
系統狀態
計算剩余的gas量
合約代碼的創建賬戶地址
發起此次執行的交易的發送方地址
導致代碼執行的帳戶地址(可能與原始發送方不同)
交易執行的gas pirce
輸入數據
傳入的以太幣數值(單位wei)
被執行的機器碼
當前區塊的區塊頭
當前消息調用或合約創建的棧的深度
當開始執行時,內存和棧都應該是空的,且程序計數器的值為0。
然后,EVM 遞歸地執行交易,計算每個循環的系統狀態和機器狀態。 系統狀態就是以太坊的全局狀態。 機器狀態包括:
可用的gas
程序計數器
內存數據
內存中的活躍字數
棧內容
每次循環,相應的gas都會從剩余的總gas中被減去,程序計數器加1。
機器達到異常狀態(例如gas 不足、指令無效、棧內存不足、無效的 JUMP/JUMPI 目標等)。此時計算必須停止,而且更改會被作廢
繼續處理進入下一個循環
機器達到受控停止(執行過程結束)
假設執行沒有遇到異常狀態并達到“受控”或正常停止,機器會生成結果狀態、執行后的剩余氣體、應計子狀態和結果輸出。
現在,我們一覽了以太坊最復雜的部分之一。 即使你沒有完全理解這部分,也沒關系。 除非你想成為區塊鏈架構師,需要了解很多底層,否則你實際上并不需要了解具體的執行細節。
區塊是如何確定的?
最后,讓我們來了解一下包含許多交易的區塊是如何被確定的?
一個區塊的確定可能意味著不同的事情,這取決于這個區塊是新產生的還是已經存在的。如果這是一個新的區塊,我們指的是產生區塊的挖礦過程。如果這是已經存在區塊,指的是區塊驗證過程。這兩種情況的區塊確定,需要4個要素:
驗證叔塊
塊頭中的每個叔塊必須有著合法的區塊頭,并且在當前區塊的第六代之內。
驗證交易
區塊上的gasUsed的數量,必須等于區塊中交易使用的累積gas數量。
獎勵(只有在挖礦時才有)
礦工地址因開采該區塊而獲得 5 Ether。(根據以太坊提案 EIP-649,這個 5 ETH 的獎勵已減少到 3 ETH)。 此外,對于每個叔塊,當前區塊的礦工將額外獲得當前區塊獎勵的 1/32。最后,叔塊的引用區塊也會獲得一定的獎勵(有一個特殊的計算公式)。
驗證狀態和隨機數
在確保所有交易和結果的狀態都發生了更改后,區塊獲得獎勵,此時區塊的新狀態最終被確定并定義。
工作量證明
“區塊”部分簡要介紹了區塊難度的概念。 賦予區塊難度的算法稱為工作證明 (PoW)。
以太坊的工作量證明算法稱為“Ethash”(以前稱為 Dagger-Hashimoto)。
該算法的正式定義如下:
m是mixHash,n是nonce。Hn是新產生區塊的頭部(不包括mixHash和nonce,因為這兩部分是需要被計算得出的),Hn是區塊頭部的nonce,d是DAG,是一個很大的數據集。
在“區塊”的章節中,我們有提到過區塊頭是由幾個不同的字段組成的。上面的mixHash和nonce正是區塊頭中的字段:
mixHash:一個hash,和nonce一起使用能夠證明該區塊提供了足夠的計算量
nonce:一個hash,和mixHash一起使用能夠證明該區塊提供了足夠的計算量
工作量證明就是用來計算這兩個hash的。
至于計算mixHash和nonce的具體細節是比較復雜的,我們未來可以單獨寫一篇比較深入的專欄去分析。工作量證明算法大致如下[1]:
為每個區塊計算一個種子seed
根據種子可以計算一個初始大小為 16MB的偽隨機緩存cache。輕客戶端保存這個 cache,用于輔助校驗區塊和生成數據集
根據 cache, 可以生成一個初始大小為 1GB的DAG數據集。數據集中的每個條目(64字節)僅依賴于 cache 中的一小部分條目。數據集會隨時間線性增長,每30000個區塊間隔更新一次。數據集僅僅存儲在完整客戶端和礦工節點,但大多數時間礦工的工作是讀取這個數據集,而不是改變它
挖礦則是在數據集中選取隨機的部分并將他們一起哈希。可以根據 cache 僅生成驗證所需的部分,這樣就可以使用少量內存完整驗證,所以對于驗證來講,僅需要保存 cache 即可。
挖礦的安全機制
?總體而言,POW(工作量證明)的目的是以加密安全的方式證明花費了特定數量的計算來生成一些輸出(例如nonce)。采用這個方式,是因為找不到比窮舉法更好的方法來找到低于所需閾值的隨機數。重復利用哈希函數的解在空間中具有均勻分布的特性。因此我們可以確信,平均而言,找到這樣一個隨機數所需的時間取決于難度閾值。 難度越高,解決隨機數所需的時間就越長。 通過這種方式,PoW 算法能通過調整難度,來加強區塊鏈的安全性。
區塊鏈安全是什么意思? 很簡單,我們想創建一個每個人都信任的區塊鏈。 正如前文有提到的,如果存在多個鏈,用戶將無法合理地確定哪個鏈是“有效”鏈,從而讓該鏈失去用戶的信任。 為了讓用戶接受存儲在區塊鏈上的底層狀態,我們需要讓人相信該區塊鏈是一個遵循單一規范的區塊鏈。
這正是 PoW 算法所做的:它確保特定區塊鏈保持規范,使攻擊者難以創建覆蓋歷史特定部分的新區塊(例如,通過清除交易或創建虛假交易) 或維護一個分叉。 為了驗證他們的區塊,攻擊者需要始終比網絡中的任何其他人更快地算出nonce,從而讓整個網絡認為他們的鏈是最重的鏈(基于我們前面提到的 GHOST 協議的原則)。 除非攻擊者擁有超過一半的網絡挖礦能力,否則這是不可能的,這種情況被稱為 51% 攻擊。
挖礦的財富分配機制
除了提供安全的區塊鏈之外,PoW還是一種財富分配機制,將財富分配給那些為保證區塊鏈安全做出了計算貢獻的礦工。上文中有提到,礦工因挖出一個區塊而獲得獎勵,包括:
創建新區塊獎勵的3ETH
獲取這個區塊的交易中的gas
叔塊獎勵
為了確保使用 PoW 共識機制進行安全和財富分配的長期可持續性,以太坊努力實現這兩點:
讓盡可能多的人可以訪問區塊鏈。人們不應該需要專門的或專用硬件來運行算法。 這樣做的目的是使財富分配模型盡可能開放,讓任何人都可以提供任意數量的計算能力來賺取以太幣。
減少單一節點獲取巨額以太幣的可能性。如果這樣的事情發生了,就說明這個單一節點對區塊鏈的影響很大,這將會降低整個網絡的安全性。
在比特幣區塊鏈網絡中,與上述兩點相關的一個問題是 PoW 算法是一種 SHA256 哈希函數。 此算法弱點在于,使用專用硬件(也稱為 ASIC)可以更高效地計算出哈希。
為了緩解這個問題,以太坊采用的是Ethash算法,該算法經過精心設計。采用Ethash計算 nonce需要大量內存和帶寬。 大內存的要求,讓計算機很難有足夠大的內存同時發現多個 nonce,而高帶寬要求使得即使是超級計算機也難以同時發現多個 nonce。 這降低了中心化的風險,并為進行節點驗證創造了一個更公平的競爭環境。
需要注意的一點是,以太坊正在從 PoW 共識機制轉變為所謂的“PoS(股權證明)”,預計2022年下半年實現遷移。
總結
這篇文章翻譯自《How does Ethereum work, anyway?》。
文章中也許有很多要消化的東西,你可能需要多次閱讀才能理解。 本人也是多次閱讀以太坊黃皮書、白皮書和代碼庫的各個部分,然后才寫出這篇文章。
盡管如此,我希望您發現此概述對你有所幫助。 如果有任何錯誤,請在評論區支出。
參考資料
[1] 也許是國內第一篇把以太坊工作量證明從算法層講清楚的 - Tiny熊
[2] How does Ethereum work, anyway?