原作者 | Preethi Kasireddy

編譯 | 老曹、Aholiab

原文鏈接:https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369

本質上來說,以太坊就是一個保存了數字交易永久記錄的公共數據庫。重要的是,這個數據庫不需要任何中間方來維護和雙方的權益。相反,它可以作爲一種「無需信任」交易系統來運作,也就是你可以在不需要第三方的情況下進行點對點交易。

說來說去,還是這些啊?別急,今天就從技術層面來更深入的看看以太坊的機制到底是什麼。在解釋這一概念時,我們儘量不去用複雜或看着嚇人的數學公式,即使不是程序員,也能在閱讀後對以太坊的運營原理有更清晰的認識。在閱讀的時候,你沒必要去理解文中的每一個細節,可以聚焦在寬泛的層面上來理解以太坊。

區塊鏈的定義

區塊鏈是具有「共享狀態的加密安全交易單機」。聽起來有點拗口,我們來分析一下。

  • 「加密安全」是指,數字貨幣的創造是通過複雜的數學算法來保證的,而這些算法很難破解,類似於系統的防火牆,你無法在區塊鏈中創建虛假的交易或刪除交易等。

  • 「交易單機」是指,有一個機器的單個實例,就可以負責系統中產生的所有交易。換句話說,每個人都相信「一個單一的全局真相」。

  • 「共享狀態」意思是,在這一系統中所存儲的狀態對每個人都是透明和開放的。

知道了區塊鏈的定義,我們就來看看以太坊區塊鏈到底是什麼?

以太坊區塊鏈算法

以太坊區塊鏈本質上是一個爲交易服務的狀態機。在計算機科學中,一個狀態機指的是這樣一種東西,它可以讀取一系列的輸入,並基於這些輸入產生一個新的狀態。

0 基礎以太坊 DAPP 開發啓蒙

以太坊狀態機的運行從一個「元狀態」開始,這類似於在網絡上沒有發生任何交易之前的一塊空白石板。當交易執行時,這個元狀態就轉變爲一些最終狀態。在任何時候,這個最終狀態都代表着以太坊區塊鏈的現狀。

0 基礎以太坊 DAPP 開發啓蒙

以太坊系統中運行着數百萬筆交易,這些交易被分組歸類爲「區塊」。一個區塊包含一系列交易,每個塊與其前面的區塊串聯在一起。

0 基礎以太坊 DAPP 開發啓蒙

要從一個狀態轉到另一個狀態,必須證明交易是有效的。如果一個交易被認爲是有效的,就必須通過一個驗證過程,這一過程稱爲「挖礦」。挖礦是指一組節點(即計算機)消耗它們的計算資源來創建一個有效交易的區塊。

網絡中任何聲明自己是「礦工」的節點都可以嘗試創建和驗證區塊,全世界有許多礦工試圖同時創建和驗證區塊。每個礦工在向區塊鏈提交一個區塊時同時,都要提供一個數學的「證明」,且把這個證明作爲一個保證:如果這個數學證明存在,則該區塊必然是有效的。

如果要在主區塊鏈上添加一個區塊,礦工必須比其他競爭對手更快地對其證明。通過讓礦工提供數學證明來驗證每個區塊的過程被稱爲「工作量證明」。

一個礦工如果驗證了一個新的區塊,這個驗證工作就會得到一定數額的價值回報。這個價值是多少呢?以太坊區塊鏈使用了一種內部數字令牌,叫做「以太幣」。每當一個礦工證明了一個區塊,就會生成並得到一個新的以太幣。

你可能會想:什麼每個節點都在一條鏈上?礦工如果想創造新的的區塊鏈怎麼辦?

正如我們在上文給區塊鏈的定義,區塊鏈是一個具有共享狀態的交易單機。這個定義決定了,區塊鏈的當前狀態是一個單一的全局狀態,每個人都必須接受。如果擁有多個狀態(或鏈條)會破壞整個系統,因爲人們不可能就哪個狀態是正確的狀態達成一致意見。如果這些鏈條是分開的,就會出現一個人在一條鏈上有 10 個以太幣,在另一條鏈上有 20 個的情況。在這種情況下,我們沒有辦法確定哪一個鏈條最「有效」,無法確定哪個人有多少硬幣。

多條鏈的產生,被稱爲「分叉」。因爲分叉會破壞系統,因此我們通常會避免分叉,迫使人們選擇他們「相信」的鏈條。

0 基礎以太坊 DAPP 開發啓蒙

爲了確定哪個路徑是最有效的,並防止分叉的發生,以太坊使用了一種叫做「GHOST 協議」的機制。

GHOST = Greedy Heaviest Observed Subtree

簡單地說,GHOST 協議讓我們必須選擇在鏈上做最多計算的路徑。確定該路徑的一種方法是使用最新區塊的數量,來表示當前路徑中的區塊總數(不計算起源塊)。塊數越多,路徑越長,挖礦的難度越大,最終就一定會到達最新區塊。使用這個方式讓我們對當前區塊鏈狀態的唯一版本達成一致。

0 基礎以太坊 DAPP 開發啓蒙

到這裏,我們就對以太坊區塊鏈就有了一個宏觀的認識,接下來我們就更深入地看看以太坊系統的主要組成部分 :

  • 帳戶;

  • 狀態;

  • Gas 與費用;

  • 交易;

  • 區塊;

  • 交易執行;

  • 挖礦;

  • 工作量證明。

以太坊的帳戶

以太坊的全球「共享狀態」是由許多賬戶組成的,它們能夠通過一個消息傳遞框架相互通信。每個帳戶都有一個與它關聯的狀態和一個 20 字節的地址。以太坊的地址是一個 160 位比特的標識符,用於識別帳戶。

以太坊有兩種賬戶類型 :

  • 外部帳戶由私人密鑰控制,沒有與之相關的代碼。

  • 合約賬戶由其合約代碼控制,並具有與其相關的代碼。

外部賬戶與合約賬戶

外部賬戶可以通過創建和使用其私人密鑰簽署一項交易,向其他外部賬戶或其他合約賬戶發送消息。兩個外部賬戶之間的消息只是一種價值轉移。但從一個外部帳戶到一個合約賬戶的消息會激活合約賬戶的代碼,使它能夠執行各種操作(例如轉移代幣、寫入內存、生成新的代幣、執行一些計算、創建新合約等)。

與外部賬戶不同,合約賬戶不能自行啓動新的交易。相反,合約賬戶只能根據它們收到的其他交易(從外部賬戶或從另一個合約賬戶)進行交易,這點我們會在下文進行探討。

0 基礎以太坊 DAPP 開發啓蒙

因此我們可以得出結論:在以太坊區塊鏈上發生的任何操作都是由外部控制賬戶的交易引起的。

0 基礎以太坊 DAPP 開發啓蒙

帳戶狀態

無論帳戶是哪種類型,帳戶狀態都由以下四個部分組成。

  • nonce:如果帳戶是一個外部帳戶,這個數字代表從帳戶地址發送的交易數量。如果帳戶是一個合約帳戶,nonce 是帳戶創建的合約數量。

  • balance:這個地址擁有的 Wei (以太坊貨幣單位)數量,每個以太幣有 1e+18 Wei。

  • storageRoot :一個 Merkle Patricia 樹根節點的哈希,它對帳戶的存儲內容的哈希值進行編碼,並默認爲空。

  • codeHash:EVM (以太坊虛擬機)的哈希值代碼。對於合約帳戶,這是一個被哈希後並存儲爲 codeHash 的代碼。對於外部帳戶,codeHash 字段是空字符串的哈希。

0 基礎以太坊 DAPP 開發啓蒙

全局狀態

我們知道以太坊的全局狀態包括帳戶地址和帳戶狀態之間的映射,這個映射存儲在一個數據結構中,這種結構被稱爲 Merkle Patricia 樹。

Merkle Patricia 樹是一種由一組樹狀節點構成的二進制結構,它包括 :

  • 底層有大量的葉子節點,其中包含了潛在的數據;

  • 一組中間節點,其中每個節點是其兩個子節點的哈希;

  • 一個單個的根節點,也是由它的兩個子節點的哈希形成的,代表樹的頂部。

0 基礎以太坊 DAPP 開發啓蒙

樹的底部數據是通過把我們想要存儲的數據分割成塊後而生成的,然後將這些數據塊分成幾個桶,然後對每個桶的進行哈希迭代,直到剩下的哈希總數變爲一個根哈希。

0 基礎以太坊 DAPP 開發啓蒙

此外,樹需要存儲在裏面的每一個值的密鑰。從樹的根節點開始,密鑰告訴你要遵循哪個子節點來獲取相應的值,這些值存儲在葉子節點中。在以太坊中,狀態樹的鍵值對是地址和相關帳戶之間的映射,包括每個帳戶的 balance、nonce、codeHash 和 storageRoot (storageRoot 本身就是一棵樹)。

0 基礎以太坊 DAPP 開發啓蒙

同樣的樹結構也用於存儲交易和收據。更具體地說,每個塊都有一個「header」,它存儲三個不同 Merkle 樹結構根節點的哈希,包括 :

  1. 狀態樹;

  2. 交易樹;

  3. 收據樹。

0 基礎以太坊 DAPP 開發啓蒙

Merkle 樹能夠高效存儲信息的特性在以太坊系統中十分被看重,我們可以稱之爲「輕節點」或「輕客戶端」,其實區塊鏈的節點有兩種:完整節點和輕節點

一個完整的節點需要下載完整的鏈,從元區塊到當前的頭部塊,執行所有的交易也都包含其中。通常情況下,礦工儲存完整的檔案節點,因爲他們必須這樣做才能完成挖礦的過程。當然,也可以在不執行交易的情況下下載完整的節點。無論如何,任何完整的節點都包含整條鏈。

輕節點的概念與之相對,除非一個節點需要執行每個交易或查詢歷史數據,否則就沒有必要存儲整個鏈。這就是輕節點的意義所在。輕節點並不下載和存儲完整鏈並執行所有的交易,而是隻下載從元區塊到當前頭部區塊的信息,而不執行任何交易或檢索任何關聯狀態。因爲輕節點可以訪問包含三個樹的區塊頭部哈希,所以仍然可以很容易地生成和接收關於交易、事件、餘額等可驗證的結果。

這樣做的原因是因爲 Merkle 樹中的哈希會向上傳播ーー如果一個惡意用戶試圖將一個僞造的交易交換到 Merkle 樹的底部,這種變化將導致上面節點的哈希變化,也將改變上面節點的哈希值。

0 基礎以太坊 DAPP 開發啓蒙

任何想要驗證一段數據的節點都可以使用所謂的「Merkle 證明」來執行。一個 Merkle 證明包括:

  1. 需要驗證的大量數據及其哈希值;

  2. 樹的根哈希;

  3. 「分支」(所有的參與者的哈希沿着路徑上升,一直到「樹根」)。

0 基礎以太坊 DAPP 開發啓蒙

任何讀取該證明的人都可以驗證樹上所有的分枝是否一致,因此給定的數據塊實際上位於樹中的某個位置。

總之,使用 Merkle 樹的好處是,該結構的根節點依據樹中存儲的數據進行加密,因此根節點的哈希可以作爲該數據的安全證明。由於區塊頭包括狀態、交易和收據樹的根哈希。因此任何節點都可以在不需要存儲整個狀態的情況下,驗證以太坊的一小部分狀態,而整個狀態的大小可能是無限的。

Gas 和支付

在以太坊中,費用的計算是一個非常重要的概念。在以太坊網絡上進行的每一筆交易都會產生費用ーー沒有免費的午餐!這筆費用被稱爲「Gas」。

Gas Price 是指:你願意花在每一個單位 Gas 上的以太幣數量,是用「gwei」來計算的。Wei 是以太幣中最小的單位,其中 1018 Wei 代表 1 個以太幣。一個 gwei 是 1,000,000,000 Wei。

每次交易,發送方都要設置一個 Gas Limit 和 Gas Price。Gas Limit 和 Gas Price 代表發送方願意爲執行交易支付的最大金額。

例如,發送方將 Gas Limit 設置爲 50,000,一個 Gas Price 設置爲 20 gwei。這意味着發送者願意花費最多 50,000 x 20 gwei,也就是:1,000,000,000,000,000 Wei (0.001 以太幣)來執行這一交易。

0 基礎以太坊 DAPP 開發啓蒙

這裏需要留意的是,Gas 限額是發送方願意花錢的最大限度。如果他們的賬戶餘額中以太幣的數量大於這個最大值,那麼他就可以進行交易。在交易結束時,發送方將被退還的那些未使用的 Gas,按原來的價格進行兌換。

0 基礎以太坊 DAPP 開發啓蒙

如果發送方沒有提供執行交易所必需的 Gas,則該交易運行的結果會是「餘額不足」,並被認爲無效。在這種情況下,交易處理中止,其間的產生的任何狀態都會發生逆轉,這樣就可以在交易發生之前返回到以太坊區塊鏈。此外,交易失敗的記錄會被記錄下來,顯示嘗試過哪些交易,失敗了哪些交易。由於系統已經在 Gas 用光之前做完了運算工作,所以從邏輯上看,Gas 不會被退還給發送方。

0 基礎以太坊 DAPP 開發啓蒙

那麼,這些 Gas 的錢到底去哪了呢?發送方花在 Gas 上的所有錢都寄給了「受益人」地址,也就是礦工地址。由於礦工們正在努力運行計算和驗證交易,所以收到了 Gas 作爲獎勵。

0 基礎以太坊 DAPP 開發啓蒙

通常情況下,發送方願意支付的 Gas 價格越高,礦工從交易中獲得的價值就越大,礦工們也就越有可能選擇這個交易。通過這種方式,礦工可以自由地選擇交易。爲了給發送者設置 Gas Price 做參考,礦工們可以直接提出他們執行交易所需的最低 Gas Price。

存儲費用

Gas 不僅用於支付計算的費用,還用於支付存儲的使用費用。存儲的總費用與使用的 32 字節的最小倍數成正比。

存儲費用與交易費用有一些不同。由於增加的存儲量增加了所有節點上的以太坊狀態數據庫的大小,所以存儲數據的數量會變小。由於這個原因,如果一個交易有一個步驟可以清除存儲中的條目,則可以免除執行該操作的存儲費用,並且還能因此得到退款。

費用的目的是什麼?

以太坊工作方式的一個重要方面是,網絡執行的每一個操作都同時受到每個完整節點的影響。然而,在以太坊虛擬機上的計算步驟非常昂貴。因此,以太坊智能合約更適合簡單的任務,比如運行簡單的業務邏輯或驗證簽名和加密其他對象,而不適合更復雜的用途,比如文件存儲、電子郵件或機器學習,這些都會給網絡帶來壓力。收費的目的就是使整個網絡不會因用戶的不當使用而變得負擔過重。

除此之外,以太坊是一種圖靈完整語言(圖靈機是一種能夠模擬任何計算機算法的機器)。這就允許了循環,使得以太坊區塊鏈容易受到暫停問題的影響,因爲在這個問題中,無法確定一個程序是否會無限運行。如果沒有費用,意圖不良的人可以通過在交易中執行一個無限循環來擾亂網絡,從而產生不良的影響。因此,費用保護了網絡免受蓄意攻擊。

那麼,爲什麼我們還要支付存儲費用呢?就像計算一樣,在以太坊網絡上的存儲也是整個網絡必須承擔的一個成本。

交易與消息

我們在上面說到,以太坊是一個基於交易的狀態機。換句話說,不同賬戶之間發生的交易正是以太坊從一個狀態轉移到另一個狀態的原因。

因此,交易可以看做是一個由外部擁有的帳戶生成的序列化加密簽名指令,然後提交給區塊鏈。

交易分爲兩類:「消息調用」和「合約創建」(創建新的以太坊合約的交易)。不管哪一類,所有交易都包含以下組件:

  • Nonce:發送方發送的交易數量的計數;

  • gasPrice:發送方願意支付每單位 Gas 所需執行交易的 Wei 數量;

  • gasLimit:發送方願意支付的執行這一交易的 Gas 最大數量。這個數額是預先設定和支付的;

  • to:接收方的地址,在創建合約的交易中,合約帳戶地址還不存在,因此使用了空值;

  • Value:從發送方轉移到收件方的金額,在創建合約的交易中,這個 Value 作爲新創建合約賬戶內的起始餘額;

  • v, r, s:用於生成識別交易發送方的簽名;

  • Init_只存在於創建合同的交易中):_用於初始化新合約帳戶的 EVM 代碼片段,它只運行一次,然後被丟棄,當 init 第一次運行時,它會返回帳戶代碼的主體,這個代碼是與合約帳戶永久關聯的一段代碼;

  • data_只存在於消息調用中的可選字段):_消息調用的輸入數據(即參數)。例如,如果一個智能合約充當域名註冊服務,那麼對該合約的調用可能會有諸如域名以及 IP 地址等輸入字段。

0 基礎以太坊 DAPP 開發啓蒙

在說「賬戶」的時候,我們看到,交易(包括消息調用和合約創建的交易),總是由外部賬戶啓動並提交給區塊鏈的。另一種思考方式是,交易是連接外部世界與以太坊內部狀態的橋樑。

0 基礎以太坊 DAPP 開發啓蒙

但這並不意味着一個合約不能與其他合約對話。在全局範圍內存在的合約,可以與同一範圍內的其他合約進行交流。它們是以通過「消息」或「內部交易」的方式來實現的。我們可以認爲消息或內部交易類似於交易,其主要區別在於它們不是由外部賬戶所產生的,相反,是由合約產生的,是虛擬對象。與交易不同,合約不是序列化的,而是隻存在於以太坊的執行環境中。

當一個合約將一個內部交易發送到另一個合約時,存在於接收方合約賬戶上的關聯代碼就會被執行。

0 基礎以太坊 DAPP 開發啓蒙

需要注意的一點是,內部交易或消息不包含 Gas Limit。這是因爲 Gas Limit 是由原始交易的外部創建者(即部分外部帳戶)來決定的。外部賬戶集合的 Gas Limit 必須足夠高,以便進行交易,這包括由這一交易而導致發生的任何次級處理運行,例如合約對合約的消息。如果在交易和消息鏈中,特定的消息執行耗盡了 Gas,那麼該消息的執行將與執行引發的所有後續消息一起恢復。不過,上一級的執行不需要恢復。

以太坊的區塊

所有的交易都被組合成「區塊」,區塊鏈則包含一系列這樣被鏈接在一起的區塊。在以太坊中,一個區塊包括「區塊頭」、關於包含在此區塊中交易集的信息,與當前塊的 ommers 相關的一系列其他區塊頭 Ommer 解釋

Ommer 是什麼?

比起比特幣之類的區塊鏈,以太坊的構建方式使區塊生成時間要低很多。這樣可以更快地處理交易。然而,縮短區塊生成時間的一個缺點是,礦工們要找到更多相互競爭的區塊解決方案。這些相互競爭的區塊也被稱爲「孤兒區塊」,不能進入主鏈。

Ommer 的目的是幫助獎勵礦工,也包括這些孤兒區塊。礦工的 ommer 必須是「有效的」,也就是說在目前區塊的第六代或更小的範圍內。六代之後,陳舊的孤兒區塊就不能再被引用。比起完整的區塊,Ommer 塊獲得的獎勵要小一些。儘管如此,礦工們仍然有一定的動力去挖掘這些孤兒區塊並獲得回報。

區塊頭

回到區塊本身,之前提到每個區塊都有一個區塊頭,但到底什麼什麼是區塊頭?區塊頭是區塊的一部分,包括:

  • Parenthash:一個父區塊頭的哈希(這就是爲什麼區塊鏈被稱爲區塊「鏈」);

  • Ommershash:當前區塊 ommer 列表的哈希;

  • beneficiary:收取採礦費用的帳戶地址;

  • Stateroot:狀態樹的根節點哈希;

  • transactionsRoot:包含在此區塊中列出的所有交易樹根節點的哈希值;

  • receiptsRoot :包含本區塊中列出的所有交易樹根節點的哈希的收據;

  • logsBloom:一個由 log 組成的 Bloom 過濾器(數據結構);

  • difficulty:這個區塊的難度水平;

  • 編號:當前區塊的記數(元區塊的編號爲 0;每個後續區塊的塊數增加 1);

  • gasLimit:當前每個區塊的 Gas 限制;

  • gasUsed:本區塊交易所使用的總 Gas 之和;

  • 時間戳:這個區塊注入的 unix 時間戳;

  • extraData:與此區塊相關的其他數據;

  • mixHash:當與 nonce 結合時,證明這個區塊執行了足夠計算的哈希值;

  • Nonce:當與 mixHash 結合時,證明這個區塊已經執行了足夠計算的哈希值;

0 基礎以太坊 DAPP 開發啓蒙

每個區塊頭包含三個樹結構:

  • 狀態根(stateRoot);

  • 交易根(transactionsRoot);

  • 收據根(receiptsRoot)。

這些樹結構只不過是之前討論過的 Merkle 樹而已,沒有什麼特別的。不過,從上面的描述中可以看到,有一些術語還需要進一步說說。

日誌(Log)

以太坊允許 log 跟蹤各種交易和消息。合約也可以通過定義需要記錄的「事件」來顯式生成 log。

一條 log 包含:

  • 記錄器的帳戶地址;

  • 一系列主題,它們表示此交易所進行的各種事件,以及,

  • 任何與這些事件有關的數據。

log 存儲在一個 bloom 過濾器中,它以有效的方式存儲海量的日誌數據。

交易收據

區塊頭中存儲的日誌來自於交易收據中包含的日誌信息。就像在商店買東西時收到收據一樣,以太坊會爲每筆交易生成一張收據。不出所料,每張收據都包含有關交易的某些信息。這樣的收據包括以下內容 :

  • 區塊編號;

  • 區塊哈希;

  • 交易哈希;

  • 當前交易所使用的 Gas;

  • 在當前交易執行後,當前區塊中使用的 Gas;

  • 執行當前交易時創建的日誌。

區塊的難度

區塊的「難度」用於在驗證區塊的時間內來加強一致性。元區塊的難度爲 131,072,並用一個特殊的公式來計算後面每個區塊的難度。如果某個區塊比前一個區塊更快地被驗證,那麼以太坊協議會增加該區塊的難度。

該區塊的難度會影響 nonce,這是一個哈希,必須在挖礦時使用工作量證明算法來計算。

區塊的難度與 nonce 之間的關係在數學上表示爲 :

0 基礎以太坊 DAPP 開發啓蒙

這裏 Hd 代表了難度。找到滿足難度閾值的 nonce 的唯一方法是使用工作量證明算法來枚舉所有的可能性。尋找解決方案的預期時間與難度成正比,難度越大,找到 nonce 就越困難,因此驗證區塊的難度就越大,這反過來增加了驗證新區塊的時間。通過調整區塊的難度,協議可以調整驗證區塊的時長。

另一方面,如果驗證時間變慢,那麼協議就會減少難度。通過這種方式,驗證時間可以自我調整從而保持一個常量ーー平均每 15 秒一個區塊。

交易的執行

看到這,你已經來到了以太坊協議中最複雜的部分之一。假設將一個交易發送到以太坊網絡進行處理,如果以太坊狀態要將你的交易包括在內,會發生什麼?

0 基礎以太坊 DAPP 開發啓蒙

首先,所有交易都必須滿足初始的一組需求才能執行。其中包括以下幾個部分。

  • 交易必須是正確的 RLP 格式(RLP 是「遞歸長度前綴」的縮寫,是用於二進制數據編碼嵌套數組的數據格式,RLP 是以太坊使用的序列化對象的格式)。

  • 有效的交易簽名。

  • 有效的交易 nonce,回想一下,一個帳戶的 nonce 是從該帳戶發送的交易的統計,爲了有效,交易 nonce 必須與發送方帳戶的 nonce 相等。

  • 交易的 Gas 限額必須等於或大於交易所使用的內部 Gas, 內部 Gas 包括:1)爲執行交易預先確定的費用爲 21,000 Gas;2)與該交易一起發送的數據 Gas 費用(對於每一個等於零的數據或代碼的每個字節收取 4 個 Gas,每個非零字節的數據或代碼爲 68 個 Gas);3)如果這筆交易是一筆合約創建交易,則額外收取 32,000 Gas。

0 基礎以太坊 DAPP 開發啓蒙

  • 發送方的賬戶餘額必須有足夠的以太幣來支付前期的 Gas 費用。前期 Gas 成本的計算很簡單:首先,交易的 Gas Limit 乘以交易的 Gas Price,以確定最大的 Gas 成本。然後,這個最大的成本被算在從發送方轉移到接收方的總額中。

0 基礎以太坊 DAPP 開發啓蒙

如果交易符合上述有效性的所有要求,那麼,就可以進入下一個步驟。

首先,從發送方的餘額中扣除執行的前期成本,並將發送方帳戶的 nonce 加 1。我們可以計算剩餘的 Gas,因爲交易的 Gas Limit 要減去所使用的內在 Gas。

0 基礎以太坊 DAPP 開發啓蒙

然後,交易開始執行。在交易的整個執行過程中,以太坊都跟蹤「子狀態」。子狀態記錄交易中產生的信息,這些信息也是交易完成後所馬上需要用到的。具體來說,它包含:

  • 自毀集合:交易完成後將丟棄的一組帳戶(如果有的話);

  • 日誌序列:虛擬機代碼執行的存檔和可索引的檢查點;

  • 退款餘額:交易完成後退還給發送者賬戶的餘額。

一旦處理完交易中的所有步驟,並假定沒有無效狀態,則通過確定向發送方退還未使用的 Gas 數量,來最終判定最終狀態。除了未使用的 Gas 外,發送方還從上文所述的「退款餘額」中退還了一些餘額。

一旦發送者獲得退款:

  • Gas (以太幣)就會給到給礦工;

  • 該交易所使用的 Gas 被添加到區塊的 Gas 計數器(該計數器記錄該區塊中所有交易使用的總 Gas);

  • 刪除自毀集合中的所有帳戶(如果有的話);

最後,只剩下了新的狀態和已創建交易的一組 log。至此,我們就講完了交易執行的基本原理,下面再來看看合約創建的交易和消息調用之間的一些差異。

合約創建

前面說過,以太坊的賬戶分爲兩類:合約帳戶和外部賬戶。當交易是「契約創建」(Contract Creating)時,意思是,交易的目的是創建一個新的合約賬戶

爲了創建一個新的合約帳戶,我們首先使用一個特殊的公式來聲明新賬戶的地址,然後通過以下方式初始化新帳戶:

  • 將 nonce 設置爲零;

  • 如果發送方在交易中發送了一定數量的以太幣作爲價值,則將帳戶餘額設置爲該價值;

  • 從發送方的餘額中扣除這個新賬戶餘額的增加部分;

  • 將存儲設置爲空;

  • 將合約的 codeHash 設置爲空字符串的哈希值;

一旦帳戶完成了初始化就可以創建帳戶了,使用與交易一起發送的 init 代碼。在執行這個 init 代碼的過程中,可能發生很多情況。根據合約的構造函數,它可能更新帳戶的存儲,創建其他的合約賬戶,或其他的消息調用,等等。

一旦初始化合約的代碼被執行就將開始消耗 Gas,交易使用的 Gas 不能超出賬戶的餘額,一旦超出,將會出現「Gas 耗光」的異常並且退出。如果交易由於 Gas 耗光的異常而退出。

但是,如果發送方在交易中發送了一些以太幣,這時合約創建失敗也會退還以太幣嗎?答案是,不會。

如果初始化代碼執行成功,則支付最終的合約創建成本。這是一個存儲成本,並且與創建合約代碼的大小成正比。如果剩餘的 Gas 不足以支付這筆最終成本,那麼這筆交易將再次聲明爲一個「Gas 耗光」異常。

如果一切順利,而且沒有遇到任何異常,那麼剩餘的 Gas 都會退還給交易的原始發送方,並允許改變狀態繼續存在!

消息調用

消息調用的執行類似於合約創建,但有一些不同之處。

消息調用的執行不包含任何 init 代碼,因爲沒有創建新的帳戶。但是,如果這些數據是由交易發送方提供的,它可以包含輸入數據。一旦執行,消息調用也有一個額外的組件,其中包含輸出數據,如果後續執行需要此數據,則使用這些數據。

如同合約創建一樣,如果由於 Gas 耗盡或交易無效(例如堆棧溢出、無效的跳轉目的地或無效指令),則所使用的任何 Gas 都不會退還給原來的調用者,取而代之的是,所有剩餘的 Gas 都會被消耗掉,並且狀態被重置到餘額轉移之前的情況。

執行模式

現在,我們來看看在 VM 中,交易實際上是如何執行的。

實際處理交易的部分是以太坊自己的虛擬機,被稱爲 EVM。就像之前定義的那樣,EVM 是一個「圖靈完備」的虛擬機。唯一的不同是 EVM 有內在 Gas 的約束。因此,可以完成的計算總量本質上受到所提供 Gas 數量的限制。

0 基礎以太坊 DAPP 開發啓蒙

此外,EVM 有一個基於棧機器的架構。棧機器是一種使用「後入先出」的堆棧來保存臨時值的計算機。EVM 中每個棧條目的大小爲 256 位,最大爲 1024 位。

EVM 具有內存,其中存儲的條目是字地址字節數組(word-addressed byte arrays)。內存是易失性的,這意味着它不是永久性的。

EVM 還有存儲空間。與內存不同,內存的存儲是非易失性的,並作爲系統狀態的一部分來維護。EVM 在一個虛擬 ROM 中獨立存儲程序代碼,只能通過特殊的指令訪問虛擬 ROM。這就是 EVM 與典型的馮·諾伊曼結構的不同,馮·諾伊曼結構中程序代碼是在內存或存儲中。

0 基礎以太坊 DAPP 開發啓蒙

EVM 也有自己的語言——EVM 字節碼。當程序員在以太坊上寫智能合約的時候,通常用高級語言寫代碼,比如 Solidity。然後,可以編譯成 EVM 字節碼,以便 EVM 可以理解執行。

接下來我們來看看 EVM 如何運行。在執行特定的計算之前,處理器要確保以下信息是可用且有效的:

  • 系統狀態;

  • 用於計算的剩餘 Gas;

  • 擁有執行代碼的帳戶地址;

  • 產生此執行交易的發送方地址;

  • 引發代碼執行的帳戶地址(可能與原始發送方不同);

  • 產生此次執行交易的 Gas Price;

  • 此執行的輸入數據;

  • 作爲當前執行的一部分,價值(以 Wei 爲單位)傳遞到這個帳戶;

  • 要執行的機器碼;

  • 當前塊的區塊頭;

  • 當前消息調用或合約創建的棧深度;

  • 在執行開始時,內存和棧是空的,程序計數器歸零。

0 基礎以太坊 DAPP 開發啓蒙

然後,EVM 遞歸執行交易,計算系統狀態和每個循環的機器狀態。簡單地說,這個系統狀態就是全局狀態。機器狀態包括 :

  • 可用的 Gas;

  • 程序計數器;

  • 內存的內容;

  • 內存中的字活躍數;

  • 棧內容。

棧中條目是從該系列最左邊的部分中添加或刪除。表現爲,在每個循環中,從剩餘的 Gas 中減少適當的 Gas,並且程序計數器遞增。

在每個循環的結束時,有三種可能性 :

  1. 機器達到一個特殊狀態(例如 Gas 不足、指令無效、棧條目不足、棧條目溢出超過 1024、無效的 JUMP/JUMPI 等),因此必須停止,任何更改都將被丟棄;

  2. 這個序列繼續進入下一個循環;

  3. 系統被迫停止。

假設執行沒有達到一個特殊狀態,並達到一個「可控制的狀態」或正常停止,那麼機器就會生成結果狀態、保留執行後剩餘的 Gas。

說了這麼多,終於結束了以太坊中最複雜的部分。即使沒有完全理解這部分,也沒關係,除非是從事底層的開發工作,否則並不需要真正理解這些細節。

一個區塊的最終完成

最後,來看看多個交易塊是如何最終完成的。

這裏所說的「最終」可能是指兩種不同的東西,這取決於區塊是新的還是已經存在的。如果是一個新區塊,「最終」指的是挖掘這個區塊所需要的過程。如果是一個現有的區塊,那麼「最終」指的是驗證塊的過程。在這兩種情況下,對於要到「最終」狀態的區塊有以下四個要求。

  1. 驗證(如果是挖礦,就是判定) ommer:每個區塊頭中的每個 ommer 區塊都必須是一個有效的區塊頭,並且在當前區塊的六代以內。

  2. 驗證(如果是挖礦,就是判定)交易:區塊上的 gasUsed 數字必須等於該區塊中所列交易所使用 Gas 的累積。

  3. 申請獎勵(只限於挖礦的情況):受益人地址被授予 5 以太幣,用於開採該區塊。(根據以太坊 EIP-649,這 5 個 ETH 的報酬將很快減少到 3 個)。此外,對於每一個 ommer,當前區塊的受益者將額外獲得當前區塊獎勵的 1/32。最後,ommer 區塊的受益人也可以得到一定數額的賠償。

  4. 驗證(如果挖礦,計算一個有效的)狀態和 nonce:確保應用所有交易和由此產生的狀態更改,在區塊獎勵應用於最終交易的結果狀態之後,定義新區塊的狀態。通過檢查這個最終狀態來驗證存儲在區塊頭中的狀態。

挖礦的工作量證明

將區塊難度賦予意義的算法叫做「工作量證明」(PoW)。以太坊的工作量證明算法被稱爲「Ethash」(之前被稱爲 Dagger-Hashimoto)。

該算法的公式如下:

0 基礎以太坊 DAPP 開發啓蒙

這裏 m 是 mixHash、n 是 nonce、Hn 是新區塊的頭(不包括 nonce 和 mixHash 組件)、Hn 是塊頭的 nonce、d 是 DAG (一個大數據集)。

還記得上面談到的在區塊頭中存在的 mixHash 和 nonce 兩個字段嗎?

  • mixHash 是一個哈希,當與 nonce 結合時,可證明這個區塊執行了足夠的計算;

  • nonce 是一個哈希,當與 mixHash 結合時,可證明這個區塊已經執行了足夠的計算

PoW 的功能就是評估這兩個字段。至於如何使用的 PoW 函數來精確計算 mixHash 和 nonce 說起來有點複雜,但從總體上看,它的工作原理是這樣的。

每個區塊都算出一個「seed」,每個「epoch」對應的 seed 都不相同,一個 epoch 相當於 3 萬個區塊的長度。在第一個 epoch,seed 是一系列 32 字節零的哈希。對於後來的每一個 epoch 來說,它都是以前 seed 哈希的哈希。使用 seed,一個節點可以計算一個僞隨機的「緩存」。

這個緩存非常有用,因爲它使之前討論過的「輕節點」成爲可能。輕節點的目的是爲了使某些節點能夠有效地驗證交易,而沒有存儲整個塊環鏈數據集的負擔。一個輕節點可以完全基於這個緩存來驗證交易的有效性,因爲緩存可以重新生成需要驗證的特定區塊。

使用緩存,節點可以生成 DAG 數據集,數據集中的每個項都依賴於緩存中的少量僞隨機選擇(pseudo-randomly-selected)的項。爲了成爲一名礦工,必鬚生成這個完整的數據集;所有客戶和礦工都存儲這個數據集,並且數據集隨時間線性增長。

然後,礦工們可以隨機抽取數據集的片段,然後通過數學函數將它們混合成一個 mixHash。礦工將反覆生成 mixHash,直到輸出低於預期目標的 nonce。當輸出滿足這個要求時,這個 nonce 被認爲是有效的,並且塊可以添加到鏈上。

挖礦作爲一種安全機制

總體而言,PoW 的目的是以一種安全加密方式證明工作量,基於特定的計算量生成某些輸出(即 nonce)。這是因爲除了窮舉所有可能性之外,沒有更好的辦法來找到低於要求閾值的 nonce。重複應用哈希函數的輸出具有均勻分佈,因此我們可以確信,找到這樣一個 nonce 所需的平均時間取決於難度閾值。難度越大,解決問題的時間就越長。這樣,PoW 算法對難度概念賦予了真實的意義,這個概念被用來增強區塊鏈的安全。

那麼,區塊鏈安全又是指什麼?很簡單:就是要創建一個每個人都信任的區塊鏈。正如先前在本文中討論的那樣,如果存在一個以上的鏈,用戶將對其失去信任,因爲他們無法合理地確定哪一個鏈是「有效的」鏈。爲了讓一組用戶接受存儲在塊環鏈上的基本狀態,需要一個大家都相信的且單一規範的區塊鏈。

而這正是 PoW 的作用:它確保一個特定的區塊鏈可以保持規範,使攻擊者難以創建新的區塊,或者覆蓋歷史的某一部分(例如擦除交易或創建虛假的交易),或者對一個分叉進行維護。爲了驗證他們的區塊,攻擊者需要比網絡中的其他任何人都更快地解決 nonce 問題,這樣網絡就會相信他們的鏈條是最重鏈(基於之前提到的 GHOST 協議的原則)。這是基本上不可能的,除非攻擊者擁有超過一半的網絡挖掘能力,因此這種情況被稱爲「51% 攻擊」。

0 基礎以太坊 DAPP 開發啓蒙

挖礦作爲一種財富分配機制

除了確保一個安全的區塊鏈環境,對那些爲了提供這種安全而消耗算力的人,Pow 還是一種分配財富的方式。回想一下,礦工在開採一個區塊時會得到獎勵,其中包括:

  • 「獲勝」區塊獲得的 5 以太幣(不久將改爲 3 以太幣)的獎賞;

  • 該區塊所包括的交易在區塊內消耗的 Gas 成本;

  • 將 ommer 作爲區塊的一部分的額外獎勵。

從長遠來看,爲了確保 PoW 機制在安全和財富分配方面的使用是可持續的,以太坊努力培養它的兩個特性:

  • 讓儘可能多的人能夠接觸到它,換句話說,人們不應該需要專門的硬件來運行算法,這樣做的目的是使財富分配模型儘可能開放,以便任何人都可以根據自身的情況提供計算能力,以換取以太幣。

  • 減少單個節點(或小集)產生不成比例利潤的可能性,任何節點,如果能夠獲得不成比例的利潤,就意味着節點對規範區塊鏈的確定有很大的影響,這會降低網絡的安全性。

在比特幣區塊鏈網絡中,與上述兩個屬性有關的一個問題是,PoW 算法是一個 SHA256 哈希函數。這類函數的弱點在於,它可以通過使用專門的硬件更有效地解決問題,也就是所謂的 ASIC。

爲了解決這一問題,以太坊選擇了將 PoW 算法按順序存儲到內存硬件中。這意味着這個算法是經過設計的,所以計算 nonce 需要大量的內存和帶寬。大量的內存需求使得計算機很難同時使用它的內存來同時發現多個 nonce,而且高帶寬的要求使得即使是超級計算機也很難同時發現多個 nonce。這減少了集中化的風險,也爲正在進行驗證的節點創建了一個更公平的機制。

需要注意的是,以太坊正在從 PoW 機制過渡到 PoS 機制,這又是另一個話題了,希望可以在今後的文章中探討。

結束語

終於到底了,這篇文章是不是有很多東西需要消化?

如果真的對以太坊感興趣,建議可以多讀幾次。我也是親自閱讀了以太坊的白皮書和代碼,然後才搞清楚以太坊要做的究竟是什麼。還是那句話,你無需理解文章的每一個細節,只要力求對整理原理有把握就很不錯了。

0 基礎以太坊 DAPP 開發啓蒙

微信公衆號全都是空氣

今日留言

你 (準備) 在以太坊上開發啥?

如果你是一名以太坊生態的 DAPP 開發者,歡迎介紹你的 DAPP,如果你想學以太坊 DAPP 開發也可以留言,入選到精選留言的朋友,會拉你進入開發 DAPP 在以太坊,我驕傲微信羣,一起繼續給 V 神打 Call。