以太坊的工作原理 程式篇
這篇文章主要講解以太坊的基本原理,對技術感興趣的朋友可以看看。
原文地址:How does Ethereum work, anyway?
簡介
不管你們知不知道以太坊(Ethereum blockchain)是什麼,但是你們大概都聽說過以太坊。最近在新聞裡出現過很多次,包括一些專業雜誌的封面,但是如果你們對以太坊到底是什麼沒有一個基本的瞭解的話,看這些文章就會感覺跟看天書一樣。 所以,什麼是以太坊?本質上,就是一個儲存數字交易永久記錄的公共資料庫。重要的是,這個資料庫不需要任何中央權威機構來維持和保護它。相反的它以一個“無信任”的交易系統來執行—一個個體在不需要信任任何第三方或對方的情況下進行點對點交易的架構。
依然感到很困惑?這就是這篇文章存在的理由。我的目標是在技術層面來解釋以太坊的工作原理,但是不會出現很複雜的數學問題或看起來很可怕的公式。即使你不是一個程式設計師,我希望你看完之後最起碼對技術有個更好的認識。如果有些部分技術性太強不好理解,這是非常正常的,真的沒有必要完全理解每一個小細節。我建議只要巨集觀的理解一下事物就行了。
這篇文章中的很多議點都是以太坊黃皮書中討論過的概念的細分。我新增了我自己的解釋和圖表使理解以太坊更加簡單一點。那些足夠勇敢的人可以挑戰一下技術,去閱讀一下以太坊的黃皮書。
好了, 讓我們開始吧!
區塊鏈定義
區塊鏈就是一個具有共享狀態的密碼性安全交易的單機(cryptographically secure transactional singleton machine with shared-state)。[1]這有點長,是吧?讓我們將它分開來看:
- “密碼性安全(Cryptographically secure)”是指用一個很難被解開的複雜數學機制演算法來保證數字貨幣生產的安全性。將它想象成類似於防火牆的這種。它們使得欺騙系統近乎是一個不可能的事情(比如:構造一筆假的交易,消除一筆交易等等)。
- “交易的單機(Transactional singleton machine)”是指只有一個權威的機器例項為系統中產生的交易負責任。換句話說,只有一個全球真相是大家所相信的。
- “具有共享狀態(With shared-state)”是指在這臺機器上儲存的狀態是共享的,對每個人都是開放的。
以太坊實現了區塊鏈的這個範例。
以太坊模型說明
以太坊的本質就是一個基於交易的狀態機(transaction-based state machine)。在電腦科學中,一個 狀態機 是指可以讀取一系列的輸入,然後根據這些輸入,會轉換成一個新的狀態出來的東西。
根據以太坊的狀態機,我們從創世紀狀態(genesis state)開始。這差不多類似於一片空白的石板,在網路中還沒有任何交易的產生狀態。當交易被執行後,這個創世紀狀態就會轉變成最終狀態。在任何時刻,這個最終狀態都代表著以太坊當前的狀態。
以太坊的狀態有百萬個交易。這些交易都被“組團”到一個區塊中。一個區塊包含了一系列的交易,每個區塊都與它的前一個區塊連結起來。
為了讓一個狀態轉換成下一個狀態,交易必須是有效的。為了讓一個交易被認為是有效的,它必須要經過一個驗證過程,此過程也就是挖礦。挖礦就是一組節點(即電腦)用它們的計算資源來建立一個包含有效交易的區塊出來。
任何在網路上宣稱自己是礦工的節點都可以嘗試建立和驗證區塊。世界各地的很多礦工都在同一時間建立和驗證區塊。每個礦工在提交一個區塊到區塊鏈上的時候都會提供一個數學機制的“證明”,這個證明就像一個保證:如果這個證明存在,那麼這個區塊一定是有效的。
為了讓一個區塊新增到主鏈上,一個礦工必須要比其他礦工更快的提供出這個“證明”。通過礦工提供的一個數學機制的“證明”來證實每個區塊的過程稱之為工作量證明(proof of work)。
證實了一個新區塊的礦工都會被獎勵一定價值的獎賞。獎賞是什麼?以太坊使用一種內在數字代幣—以太幣(Ether)作為獎賞。每次礦工證明了一個新區塊,那麼就會產生一個新的以太幣並被獎勵給礦工。
你也許會在想:什麼能確保每個人都只在區塊的同一條鏈上呢?我們怎麼能確定不會存在一部分礦工建立一個他們自己的鏈呢?
前面,我們定義了區塊鏈就是一個具有共享狀態的交易單機。使用這個定義,我們可以知道正確的當前狀態是一個全球真相,所有人都必須要接受它。擁有多個狀態(或多個鏈)會摧毀這個系統,因為它在哪個是正確狀態的問題上不可能得到統一結果。如果鏈分叉了,你有可能在一條鏈上擁有10個幣,一條鏈上擁有20個幣,另一條鏈上擁有40個幣。在這種場景下,是沒有辦法確定哪個鏈才是最”有效的“。
不論什麼時候只要多個路徑產生了,一個”分叉“就會出現。我們通常都想避免分叉,因為它們會破壞系統,強制人們去選擇哪條鏈是他們相信的鏈。
為了確定哪個路徑才是最有效的以及防止多條鏈的產生,以太坊使用了一個叫做“GHOST協議(GHOST protocol.)”的數學機制。
GHOST = Greedy Heaviest Observed Subtree
簡單來說,GHOST協議就是讓我們必須選擇一個在其上完成計算最多的路徑。一個方法確定路徑就是使用最近一個區塊(葉子區塊)的區塊號,區塊號代表著當前路徑上總的區塊數(不包含創世紀區塊)。區塊號越大,路徑就會越長,就說明越多的挖礦算力被消耗在此路徑上以達到葉子區塊。使用這種推理就可以允許我們贊同當前狀態的權威版本。
現在你大概對區塊鏈是什麼有個理性的認識,讓我們在再深入了地解一下以太坊系統主要組成部分:
- 賬戶(accounts)
- 狀態(state)
- 損耗和費用(gas and fees)
- 交易(transactions)
- 區塊(blocks)
- 交易執行(transaction execution)
- 挖礦(mining)
- 工作量證明(proof of work)
在開始之前需要注意的是:每當我說某某的hash, 我指的都是KECCAK-256 hash, 以太坊就是使用這個hash演算法。
賬戶
以太坊的全域性“共享狀態”是有很多小物件(賬戶)來組成的,這些賬戶可以通過訊息傳遞架構來與對方進行互動。每個賬戶都有一個與之關聯的狀態(state)和一個20位元組的地址(address)。在以太坊中一個地址是160位的識別符號,用來識別賬戶的。
這是兩種型別的賬戶:
- 外部擁有的賬戶,被私鑰控制且沒有任何程式碼與之關聯
- 合約賬戶,被它們的合約程式碼控制且有程式碼與之關聯
外部擁有賬戶與合約賬戶的比較
理解外部擁有賬戶和合約賬戶的基本區別是很重要的。一個外部擁有賬戶可以通過建立和用自己的私鑰來對交易進行簽名,來傳送訊息給另一個外部擁有賬戶或合約賬戶。在兩個外部擁有賬戶之間傳送的訊息只是一個簡單的價值轉移。但是從外部擁有賬戶到合約賬戶的訊息會啟用合約賬戶的程式碼,允許它執行各種動作。(比如轉移代幣,寫入內部儲存,挖出一個新代幣,執行一些運算,建立一個新的合約等等)。
不像外部擁有賬戶,合約賬戶不可以自己發起一個交易。相反,合約賬戶只有在接收到一個交易之後(從一個外部擁有賬戶或另一個合約賬戶接),為了響應此交易而觸發一個交易。我們將會在“交易和訊息”部分來了解關於合約與合約之間的通訊。
因此,在以太坊上任何的動作,總是被外部控制賬戶觸發的交易所發動的。
賬戶狀態
賬戶狀態有四個組成部分,不論賬戶型別是什麼,都存在這四個組成部分:
- nonce:如果賬戶是一個外部擁有賬戶,nonce代表從此賬戶地址傳送的交易序號。如果賬戶是一個合約賬戶,nonce代表此賬戶建立的合約序號
- balance: 此地址擁有Wei的數量。1Ether=10^18Wei
- storageRoot: Merkle Patricia樹的根節點Hash值(我們後面在解釋Merkle樹)。Merkle樹會將此賬戶儲存內容的Hash值進行編碼,預設是空值
- codeHash:此賬戶EVM(以太坊虛擬機器,後面細說)程式碼的hash值。對於合約賬戶,就是被Hash的程式碼並作為codeHash儲存。對於外部擁有賬戶,codeHash域是一個空字串的Hash值
世界狀態
好了,我們知道了以太坊的全域性狀態就是由賬戶地址和賬戶狀態的一個對映組成。這個對映被儲存在一個叫做Merkle Patricia樹的資料結構中
Merkle Tree(也被叫做Merkle trie)是一種由一系列節點組成的二叉樹,這些節點包括:
- 在樹底的包含了源資料的大量葉子節點
- 一系列的中間的節點,這些節點是兩個子節點的Hash值
- 一個根節點,同樣是兩個子節點的Hash值,代表著整棵樹
樹底的資料是通過分開我們想要儲存到chunks的資料產生的,然後將chunks分成buckets,再然後再獲取每個bucket的hash值並一直重複直到最後只剩下一個Hash:根Hash。
這棵樹要求存在裡面的值(value)都有一個對應的key。從樹的根節點開始,key會告訴你順著哪個子節點可以獲得對應的值,這個值存在葉子節點。在以太坊中,key/value是地址和與地址相關聯的賬戶之間狀態的對映,包括每個賬戶的balance, nonce, codeHash和storageRoot(storageRoot自己就是一顆樹)。
同樣的樹結構也用來儲存交易和收據。更具體的說,每個塊都有一個頭(header),儲存了三個不同Merkle trie結構的根節點的Hash,包括:
- 狀態樹
- 交易樹
- 收據樹
在Merkle tries中儲存所有資訊的高效性在以太坊中的“輕客戶端”和“輕節點”相當的有用。記住區塊鏈就是一群節點來維持的。廣泛的說,有兩種節點型別:全節點和輕節點。
全節點通過下載整條鏈來進行同步,從創世紀塊到當前塊,執行其中包含的所有交易。通常,礦工會儲存全節點,因為他們在挖礦過程中需要全節點。也有可能下載一個全節點而不用執行所有的交易。無論如何,一個全節點包含了整個鏈。
不過除非一個節點需要執行所有的交易或輕鬆訪問歷史資料,不然沒必要儲存整條鏈。這就是輕節點概念的來源。比起下載和儲存整個鏈以及執行其中所有的交易,輕節點僅僅下載鏈的頭,從創世紀塊到當前塊的頭,不執行任何的交易或檢索任何相關聯的狀態。由於輕節點可以訪問塊的頭,而頭中包含了3個tries的Hash,所有輕節點依然可以很容易生成和接收關於交易、事件、餘額等可驗證的答案。
這個可以行的通是因為在Merkle樹中hash值是向上傳播的—如果一個惡意使用者試圖用一個假交易來交換Merkle樹底的交易,這個會改變它上面節點的hash值,而它上面節點的值的改變也會導致上上一個節點Hash值的改變,以此類推,一直到樹的根節點。
任何節點想要驗證一些資料都可以通過Merkle證明來進行驗證,Merkle 證明的組成:
- 一塊需要驗證的資料
- 樹的根節點Hash
- 一個“分支”(從 chunk到根這個路徑上所有的hash值)
任何可以讀取證明的人都可以驗證分支的hash是連貫的,因此給出的塊在樹中實際的位置就是在此處。
總之,使用Merkle Patricia樹的好處就是該結構的根節點加密取決於儲存在樹中的資料,而且根據點的hash還可以作為該資料的安全標識。由於塊的頭包含了狀態、交易、收據樹的根hash,所有任何節點都可以驗證以太坊的一小部分狀態而不用儲存整個狀態,這整個狀態的的大小可能是非常大的。
Gas和費用
在以太坊中一個比較重要的概念就是費用(fees),由以太坊網路上的交易而產生的每一次計算,都會產生費用—沒有免費的午餐。這個費用是以稱之為”gas”的來支付。
gas就是用來衡量在一個具體計算中要求的費用單位。gas price就是你願意在每個gas上花費Ether的數量,以“gwei”進行衡量。“Wei”是Ether的最小單位,1Ether表示10^18Wei. 1gwei是1,000,000,000 Wei。
對每個交易,傳送者設定gas limit和gas price。gas limit和gas price就代表著傳送者願意為執行交易支付的Wei的最大值。
例如,假設傳送者設定gas limit為50,000,gas price為20gwei。這就表示傳送者願意最多支付50,000*20gwei = 1,000,000,000,000,000 Wei = 0.001 Ether來執行此交易。
記住gas limit代表使用者願意花費在gas上的錢的最大值。如果在他們的賬戶餘額中有足夠的Ether來支付這個最大值費用,那麼就沒問題。在交易結束時任何未使用的gas都會被返回給傳送者,以原始費率兌換。
在傳送者沒有提供足夠的gas來執行交易,那麼交易執行就會出現“gas不足”然後被認為是無效的。在這種情況下,交易處理就會被終止以及所有已改變的狀態將會被恢復,最後我們就又回到了交易之前的狀態—完完全全的之前狀態就像這筆交易從來沒有發生。因為機器在耗盡gas之前還是為計算做出了努力,
所以理論上,將不會有任何的gas被返回給傳送者。
這些gas的錢到底去了哪裡?傳送者在gas上花費的所有錢都傳送給了“受益人”地址,通常情況下就是礦工的地址。因為礦工為了計算和驗證交易做出了努力,所以礦工接收gas的費用作為獎勵。
通常,傳送者願意支付更高的gas price,礦工從這筆交易總就能獲得更多的價值。因此,礦工也就更加願意選擇這筆交易。這樣的話,礦工可以自由的選擇一筆交易自己願意驗證或忽略。為了引導傳送者應該設定gas price為多少,礦工可以選擇建議一個最小的gas值他們願意執行一個交易。
儲存也有費用
gas不僅僅是用來支付計算這一步的費用,而且也用來支付儲存的費用。儲存的總費用與所使用的32位位元組的最小倍數成比例。
儲存費用有一些比較細微的方面。比如,由於增加了的儲存增加了所有節點上的以太坊狀態資料庫的大小,所以激勵保持資料儲存量小。為了這個原因,如果一個交易的執行有一步是清除一個儲存實體,那麼為執行這個操作的費用就會被放棄,並且由於釋放儲存空間的退款就會被返回給傳送者。
費用的作用是什麼?
以太坊可以運作的一個重要方面就是每個網路執行的操作同時也被全節點所影響。然而,計算的操作在以太坊虛擬機器上是非常昂貴的。因此,以太坊智慧合約最好是用來執行最簡單的任務,比如執行一個簡單的業務邏輯或者驗證簽名和其他密碼物件,而不是用於複雜的操作,比如檔案儲存,電子郵件,或機器學習,這些會給網路造成壓力。施加費用防止使用者使網路超負荷。
以太坊是一個圖靈完備語言(短而言之,圖靈機器就是一個可以模擬任何電腦演算法的機器。對於圖靈機器不太熟悉的人可以看看這個 和這個 )。這就允許有迴圈,並使以太坊受到停機問題 的影響,這個問題讓你無法確定程式是否無限制的執行。如果沒有費用的話,惡意的執行者通過執行一個包含無限迴圈的交易就可以很容易的讓網路癱瘓而不會產生任何反響。因此,費用保護網路不受蓄意攻擊。
你也許會想,“為什麼我們還需要為儲存付費?”其實就像計算一樣,以太坊網路上的儲存是整個網路都必須要負擔的成本。
交易和訊息
之前說過以太坊是一個基於交易的狀態機。換句話說,在兩個不同賬戶之間發生的交易才讓以太坊全球狀態從一個狀態轉換成另一個狀態。
最基本的概念,一個交易就是被外部擁有賬戶生成的加密簽名的一段指令,序列化,然後提交給區塊鏈。
有兩種型別的交易:訊息通訊和合約建立(也就是交易產生一個新的以太坊合約)。
不管什麼型別的交易,都包含:
- nonce:傳送者傳送交易數的計數
- gasPrice:傳送者願意支付執行交易所需的每個gas的Wei數量
- gasLimit:傳送者願意為執行交易支付gas數量的最大值。這個數量被設定之後在任何計算完成之前就會被提前扣掉
- to:接收者的地址。在合約建立交易中,合約賬戶的地址還沒有存在,所以值先空著
- value:從傳送者轉移到接收者的Wei數量。在合約建立交易中,value作為新建合約賬戶的開始餘額
- v,r,s:用於產生標識交易發生著的簽名
- init(只有在合約建立交易中存在):用來初始化新合約賬戶的EVM程式碼片段。init值會執行一次,然後就會被丟棄。當init第一次執行的時候,它返回一個賬戶程式碼體,也就是永久與合約賬戶關聯的一段程式碼。
- data(可選域,只有在訊息通訊中存在):訊息通話中的輸入資料(也就是引數)。例如,如果智慧合約就是一個域名註冊服務,那麼呼叫合約可能就會期待輸入域例如域名和IP地址
在“賬戶”這個章節中我們學到交易—訊息通訊和合約建立交易兩者都總是被外部擁有賬戶觸發並提交到區塊鏈的。換種思維思考就是,交易是外部世界和以太坊內部狀態的橋樑。
但是這也並不代表一個合約與另一個合約無法通訊。在以太坊狀態全域性範圍內的合約可以與在相同範圍內的合約進行通訊。他們是通過“訊息”或者“內部交易”進行通訊的。我們可以認為訊息或內部交易類似於交易,不過與交易有著最大的不同點—它們不是由外部擁有賬戶產生的。相反,他們是被合約產生的。它們是虛擬物件,與交易不同,沒有被序列化而且只存在與以太坊執行環境。
當一個合約傳送一個內部交易給另一個合約,存在於接收者合約賬戶相關聯的程式碼就會被執行。
一個重要需要注意的事情是內部交易或者訊息不包含gasLimit。因為gas limit是由原始交易的外部建立者決定的(也就是外部擁有賬戶)。外部擁有賬戶設定的gas limit必須要高到足夠將交易完成,包括由於此交易而長生的任何”子執行”,例如合約到合約的訊息。如果,在一個交易或者資訊鏈中,其中一個訊息執行使gas已不足,那麼這個訊息的執行會被還原,包括任何被此執行觸發的子訊息。不過,父執行沒必要被還原。
區塊']
所有的交易都被組成一個”塊”。一個區塊鏈包含了一系列這樣的鏈在一起區塊。
在以太坊中,一個區塊包含:
- 區塊頭
- 關於包含在此區塊中交易集的資訊
- 與當前塊的ommers相關的一系列其他區塊頭
Ommers解釋
“ommer”到底是什麼? ommer就是一個區塊的父區塊與當前區塊父區塊的父區塊是相同的。讓我們快速瞭解一下ommers是用來幹嘛的,並且為什麼一個區塊需要為ommers包含區塊頭。
由於以太坊的構造,它的區塊生產時間(大概15秒左右)比其他的區塊鏈例如Bitcoin(大概10分鐘左右)要快很多。這使得交易的處理更快。但是,更短的區塊生產時間的一個缺點就是:更多的競爭區塊會被礦工發現。這些競爭區塊同樣也被稱為“孤區塊”(也就是被挖出來但是不會被新增到主鏈上的區塊)。
Ommers的目的就是為了幫助獎勵礦工納入這些孤區塊。礦工包含的ommers必須是有效的,也就是ommers必須在父區塊的第6個子區塊之內或更小範圍內。在第6個子區塊之後,陳舊的孤區塊將不會再被引用(因為包含老舊的交易會使事情變得複雜一點)。
Ommer區塊會收到比全區塊少一點的獎勵。不管怎樣,依然存在激勵來讓礦工們納入孤區塊並能從中獲得一些報酬。
區塊頭
讓我們再回到區塊的問題上。我們前面提到每個區塊都有一個“區塊頭”,但這究竟是什麼?
區塊頭是一個區塊的一部分,包含了:
- parentHash:父區塊頭的Hash值(這也是使得區塊變成區塊鏈的原因)
- ommerHash:當前區塊ommers列表的Hash值
- beneficiary:接收挖此區塊費用的賬戶地址
- stateRoot:狀態樹根節點的Hash值(回憶一下我們之前所說的儲存在頭中的狀態樹以及它使得輕客戶端認證任何關於狀態的事情都變得非常簡單)
- transactionsRoot:包含此區塊所列的所有交易的樹的根節點Hash值
- receiptsRoot:包含此區塊所列的所有交易收據的樹的根節點Hash值
- logsBloom:由日誌資訊組成的一個Bloom過濾器 (資料結構)
- difficulty: 此區塊的難度級別
- number:當前區塊的計數(創世紀塊的區塊序號為0,對於每個後續區塊,區塊序號都增加1)
- gasLimit:每個區塊的當前gas limit
- gasUsed: 此區塊中交易所用的總gas量
- timestamp:此區塊成立時的unix的時間戳
- extraData:與此區塊相關的附加資料
- mixHash:一個Hash值,當與nonce組合時,證明此區塊已經執行了足夠的計算
- nonce:一個Hash值,當與mixHash組合時,證明此區塊已經執行了足夠的計算
注意每個區塊是如何包含三個樹結構的,三個樹結構分別對應:
- 狀態(stateRoot)
- 交易(transactionsRoot)
- 收據(receiptsRoot)
這三個樹結構就是我們前面討論的Merkle Patricia樹。
另外,上面描述的有幾個術語值得說明一下,下面來看一下。
日誌
以太坊允許日誌可以跟蹤各種交易和資訊。一個合約可以通過定義“事件”來顯示的生成日誌。
一個日誌的實體包含:
- 記錄器的賬戶地址
- 代表本次交易執行的各種事件的一系列主題以及與這些事件相關的任何資料
日誌被儲存在bloom過濾器 中,過濾器高效的儲存了無盡的日誌資料。
交易收據
自於被包含在交易收據中的日誌資訊儲存在頭中。就像你在商店買東西時收到的收據一樣,以太坊為每筆交易都產生一個收據。像你期望的那樣,每個收據包含關於交易的特定資訊。這些收據包含著:
- 區塊序號
- 區塊Hash
- 交易Hash
- 當前交易使用了的gas
- 在當前交易執行完之後當前塊使用的累計gas
- 執行當前交易時建立的日誌
- 等等
區塊難度
區塊的難度是被用來在驗證區塊時加強一致性。創世紀區塊的難度是131,072,有一個特殊的公式用來計算之後的每個塊的難度。如果某個區塊比前一個區塊驗證的更快,以太坊協議就會增加區塊的難度。
區塊的難度影響nonce,它是在挖礦時必須要使用proof-of-work演算法來計算的一個hash值。
區塊難度和nonce之間的關係用數學形式表達就是:
Hd代表的是難度。
找到符合難度閾值的nonce唯一方法就是使用proof-of-work演算法來列舉所有的可能性。找到解決方案預期時間與難度成正比—難度越高,找到nonce就越困難,因此驗證一個區塊也就越難,這又相應地增加了驗證新塊所需的時間。所以,通過調整區塊難度,協議可以調整驗證區塊所需的時間。
另一方面,如果驗證時間變的越來越慢,協議就會降低難度。這樣的話,驗證時間自我調節以保持恆定的速率—平均每15s一個塊。
交易執行
我們已經到了以太坊協議最複雜的部分:交易的執行。假設你傳送了一筆交易給以太坊網路處理,將以太坊狀態轉換成包含你的交易這個過程到底發生了什麼?
首先,為了可以被執行所有的交易必須都要符合最基礎的一系列要求,包括:
- 交易必須是正確格式化的RLP。”RLP”代表Recursive Length Prefix,它是一種資料格式,用來編碼二進位制資料巢狀陣列。以太坊就是使用RLP格式序列化物件。
- 有效的交易簽名。
- 有效的交易序號。回憶一下賬戶中的nonce就是從此賬戶傳送出去交易的計數。如果有效,那麼交易序號一定等於傳送賬戶中的nonce。
- 交易的gas limit 一定要等於或者大於交易使用的intrinsic gas,intrinsic gas包括:
——-1.執行交易預訂費用為21,000gas
——-2.隨交易傳送的資料的gas費用(每位元組資料或程式碼為0的費用為4gas,每個非零位元組的資料或程式碼費用為68gas)
——-3.如果交易是合約建立交易,還需要額外的32,000gas - 傳送賬戶餘額必須有足夠的Ether來支付”前期”gas費用。前期gas費用的計算比較簡單:首先,交易的gas limit乘以交易的gas價格得到最大的gas費用。然後,這個最大gas費用被加到從傳送方傳送給接收方的總值。
如何交易符合上面所說的所有要求,那麼我們進行下面步驟。
第一步,我們從傳送者的餘額中扣除執行的前期費用,併為當前交易將傳送者賬戶中的nonce增加1。此時,我們可以計算剩餘的gas,將交易的總gas減去使用的intrinsic gas。
第二步,開始執行交易。在交易執行的整個過程中,以太坊保持跟蹤“子狀態”。子狀態是記錄在交易中生成的資訊的一種方式,當交易完成時會立即需要這些資訊。具體來說,它包含:
- 自毀集:在交易完成之後會被丟棄的賬戶集(如果存在的話)
- 日誌系列:虛擬機器的程式碼執行的歸檔和可檢索的檢查點
- 退款餘額:交易完成之後需要退還給傳送賬戶的總額。回憶一下我們之前提到的以太坊中的儲存需要付費,傳送者要是清理了記憶體就會有退款。以太坊使用退款計數進行跟蹤退款餘額。退款計數從0開始並且每當合約刪除了一些儲存中的東西都會進行增加。
第三步,交易所需的各種計算開始被處理。
當交易所需的步驟全部處理完成,並假設沒有無效狀態,通過確定退還給傳送者的未使用的gas量,最終的狀態也被確定。除了未使用的gas,傳送者還會得到上面所說的“退款餘額”中退還的一些津貼。
一旦傳送者得到退款之後:
- gas的Ether就會礦工
- 交易使用的gas會被新增到區塊的gas計數中(計數一直記錄當前區塊中所有交易使用的gas總量,這對於驗證區塊時是非常有用的)
- 所有在自毀集中的賬戶(如果存在的話)都會被刪除
最後,我們就有了一個新的狀態以及交易建立的一系列日誌。
現在我們已經介紹了交易執行的基本知識,讓我們再看看合約建立交易和訊息通訊的一些區別。
合約建立(Contract creation)
回憶一下在以太坊中,有兩種賬戶型別:合約賬戶和外部擁有賬戶。當我們說一個交易是“合約建立”,是指交易的目的是建立一個新的合約賬戶。
為了建立一個新的合約賬戶,我們使用一個特殊的公式來宣告新賬戶的地址。然後我們使用下面的方法來初始化一個賬戶:
- 設定nonce為0
- 如果傳送者通過交易傳送了一定量的Ether作為value,那麼設定賬戶的餘額為value
- 將儲存設定為0
- 設定合約的codeHash為一個空字串的Hash值
一旦我們完成了賬戶的初始化,使用交易傳送過來的init code(檢視”交易和資訊”章節來複習一下init code),實際上就創造了一個賬戶。init code的執行過程是各種各樣的。取決於合約的構造器,可能是更新賬戶的儲存,也可能是建立另一個合約賬戶,或者發起另一個訊息通訊等等。
當初始化合約的程式碼被執行之後,會使用gas。交易不允許使用的gas超過剩餘gas。如果它使用的gas超過剩餘gas,那麼就會發生gas不足異(OOG)常並退出。如果一個交易由於gas不足異常而退出,那麼狀態會立刻恢復到交易前的一個點。傳送者也不會獲得在gas用完之前所花費的gas。
不過,如果傳送者隨著交易傳送了Ether,即使合約建立失敗Ether也會被退回來。
如果初始化程式碼成功的執行完成,最後的合約建立的花費會被支付。這些是儲存成本,與建立的合約程式碼大小成正比(再一次,沒有免費的午餐)。如果沒有足夠的剩餘gas來支付最後的花費,那麼交易就會再次宣佈gas不足異常並中斷退出。
如果所有的都正常進行沒有任何異常出現,那麼任何剩餘的未使用gas都會被退回給原始的交易傳送者,現在改變的狀態才被允許永久儲存。
訊息通訊(Message calls)
訊息通訊的執行與合約建立比較類似,只不過有一點點區別。
由於沒有新賬戶被建立,所以訊息通訊的執行不包含任何的init code。不過,它可以包含輸入資料,如果交易傳送者提供了此資料的話。一旦執行,訊息通訊同樣會有一個額外的元件來包含輸出資料,如果後續執行需要此資料的話就元件就會被使用。
就像合約建立一樣,如果訊息通訊執行退出是因為gas不足或交易無效(例如棧溢位,無效跳轉目的地或無效指令),那麼已使用的gas是不會被退回給原始觸發者的。相反,所有剩餘的未使用gas也會被消耗掉,並且狀態會被立刻重置為餘額轉移之前的那個點。
沒有任何方法停止或恢復交易的執行而不讓系統消耗你提供的所有gas,直到最新的以太坊更新。例如,假設你編寫了一個合約,當呼叫者沒有授權來執行這些交易的時候丟擲一個錯誤。在以太坊的前一個版本中,剩餘的gas也會被消耗掉,並且沒有任何gas退回給傳送者。但是拜占庭更新包括了一個新的“恢復”程式碼,允許合約停止執行並且恢復狀態改變而不消耗剩餘的gas,此程式碼還擁有返回交易失敗原因的能力。如果一個交易是由於恢復而退出,那麼未使用的gas就會被返回給傳送者。
執行模式
到目前為止,我們瞭解了從開始到結束執行的交易必須經歷的一系列的步驟。現在,我們來看看交易究竟是如何在虛擬機器(VM)中執行的。
協議實際操作交易處理的部分是以太坊自己的虛擬機器,稱之為以太坊虛擬機器(EVM)。
像之前定義的那樣,EVM是圖靈完備虛擬機器器。EVM存在而典型圖靈完備機器不存在的唯一限制就是EVM本質上是被gas束縛。因此,可以完成的計算總量本質上是被提供的gas總量限制的。
此外,EVM具有基於堆疊的架構。堆疊機器 就是使用後進先出來儲存臨時值的計算機。
EVM中每個堆疊項的大小為256位,堆疊有一個最大的大小,為1024位。
EVM有記憶體,專案按照可定址位元組陣列來儲存。記憶體是易失性的,也就是資料是不持久的。
EVM也有一個儲存器。不像記憶體,儲存器是非易失性的,並作為系統狀態的一部分進行維護。EVM分開儲存程式程式碼,在虛擬ROM 中只能通過特殊指令來訪問。這樣的話,EVM就與典型的馮·諾依曼架構 不同,此架構將程式的程式碼儲存在記憶體或儲存器中。
EVM同樣有屬於它自己的語言:“EVM位元組碼”,當一個程式設計師比如你或我寫一個在以太坊上執行的智慧合約時,我們通常都是用高階語言例如Solidity來編寫程式碼。然後我們可以將它編譯成EVM可以理解的EVM位元組碼。
好了,現在來說執行。
在執行特定的計算之前,處理器會確定下面所說的資訊是有效和是否可獲取:
- 系統狀態
- 用於計算的剩餘gas
- 擁有執行程式碼的賬戶地址
- 原始觸發此次執行的交易傳送者的地址
- 觸發程式碼執行的賬戶地址(可能與原始傳送者不同)
- 觸發此次執行的交易gas價格
- 此次執行的輸入資料
- Value(單位為Wei)作為當前執行的一部分傳遞給該賬戶
- 待執行的機器碼
- 當前區塊的區塊頭
- 當前訊息通訊或合約建立堆疊的深度
執行剛開始時,記憶體和堆疊都是空的,程式計數器為0。
1 | PC: 0 STACK: [] MEM: [], STORAGE: {} |
然後EVM開始遞迴的執行交易,為每個迴圈計算系統狀態和機器狀態。系統狀態也就是以太坊的全域性狀態(global state)。機器狀態包含:
- 可獲取的gas
- 程式計數器
- 記憶體的內容
- 記憶體中字的活躍數
- 堆疊的內容
堆疊中的項從系列的最左邊被刪除或者新增。
每個迴圈,剩餘的gas都會被減少相應的量,程式計數器也會增加。
在每個迴圈的結束,都有三種可能性:
- 機器到達異常狀態(例如 gas不足,無效指令,堆疊項不足,堆疊項會溢位1024,無效的JUMP/JUMPI目的地等等)因此停止,並丟棄任何的更改
- 進入後續處理下一個迴圈
- 機器到達了受控停止(到達執行過程的終點)
假設執行沒有遇到異常狀態,達到一個“可控的”或正常的停止,機器就會產生一個合成狀態,執行之後的剩餘gas、產生的子狀態、以及組合輸出。
呼。我們終於過了一遍以太坊最難的部分了。如果你不能完全理解這個部分,也沒關係。除非你在理解非常深層次的東西,否則你真的沒有必要去理解執行的每個細節。
一個塊是如何完成的?
最後,讓我們看看一個包含許多交易的塊是如何完成的。
當我們說“完成”,取決於此塊是新的還是已存在的,可以指兩個不同的事情。如果是個新塊,就是指挖這個塊所需的處理。如果是已存在的塊,就是指驗證此塊的處理。不論哪種情況,一個塊的“完成”都有4個要求:
1)驗證(或者,如果是挖礦的話,就是確定)ommers
在區塊頭中的每個ommer都必須是有效的頭並且必須在當前塊的6代之內
2)驗證(或者,如果是挖礦的話,就是確定)交易
區塊中的gasUsed數量必須與區塊中所列交易使用的累積gas量相等。(回憶一下,當執行一個交易的時候,我們會跟蹤區塊的gas計數器,也就跟蹤了區塊中所有交易使用的gas總數量)
3)申請獎勵(只有挖礦時)
受益人的地址會因為挖礦而獲得5Ether(在以太坊EIP-649 提案中,5ETH很快將會被減少為3ETH)。另外,對於每個ommer,當前塊的受益人會獲得額外的1/32當前塊獎勵金的獎勵。最近,每個ommer區塊的受益人能夠得到一定量的獎勵(有個特殊公式可以進行計算)。
4)校驗(或者,如果是挖礦的話,就是計算一個有效的)狀態和nonce
確保所有的交易和改變的結果狀態都被應用了,然後在區塊獎勵被應用於最終交易結果狀態之後定義一個新塊為狀態。通過檢查最終狀態與儲存在頭中的狀態樹來進行驗證。
工作量證明挖礦
在“區塊”這個章節簡短的說明了一下區塊難度這個概念。給予區塊難度意義的演算法叫做工作量證明(PoW)。
以太坊的工作量證明演算法稱之為“Ethash” (之前叫做Dagger-Hashimoto)。
演算法正式定義為:
m代表的是mixHash,n代表的是nonce,Hn代表的是新區塊的頭(不包含需要計算的nonce和mixHash),Hn是區塊頭的nonce,d是DAG ,就是一個大資料集。
在”區塊”章節,我們討論了存在於區塊頭中的多項。其中兩項叫做mixHash和nonce。也許你會回憶起:
- mixHash:一個Hash值,當與nonce組合時,證明此區塊已經執行了足夠的計算
- nonce:一個Hash值,當與mixHash組合時,證明此區塊已經執行了足夠的計算
PoW函式就是用來估算這兩項的。
mixHash和nonce到底是如何使用PoW函式來計算出來的有點複雜,如果深入瞭解的話,我們可以另寫一篇文章來講解了。但是在一個高層面上,它大致就是這樣計算的:
會為每個區塊計算一個”種子”。每個“時期”的種子都不一樣,每個時期是30,000個區塊長度。對於第一時期,種子就是32位0的hash值。對於後續的每個時期,種子就是前一個種子hash值的hash值。使用這個種子,節點可以計算一個偽隨機“快取”。
這個快取是非常有用的,因為它可以使“輕節點”的概念變成現實,輕節點概念在這篇文章的前面討論過。輕節點的目的就是讓某個節點有能力高效的校驗交易而用不著儲存整個區塊鏈的資料集。一個輕節點可以僅基於快取來校驗一個交易的有效性,因為快取可以重新生成需要校驗的特定塊。
使用這個快取,節點可以生成DAG“資料集”,資料集中的每項取決於快取中少量偽隨機選擇項。為了成為礦工,你需要要生成全資料集,所有全客戶端和礦工都儲存這個資料集,並且這個資料集隨著時間線性增長。
然後礦工可以隨機抽取資料集中的部分並將它們放入一個數學函式中Hash出一個”mixHash”。礦工會重複生成mixHash直到輸出的值小於想要的目標值nonce。當輸出的值符合這個條件的時候,nonce就被認為是有效的,然後區塊就被新增到鏈中。
挖礦作為安全機制
總的來說,PoW的目的就是以加密安全的方式證明生成的一些輸出(也就是nonce)是經過了一定量的計算的。因為除了列舉所有的可能性,沒有更好的其他方法來找到一個低於要求閾值的nonce。重複應用Hash函式的輸出均勻分佈,所以我們可以確保,在平均值上,找到滿足要求的nonce所需時間取決於難度閾值。難度係數越大,所需時間越長。這樣的話,PoW演算法就給予難度這個概念的意義了:用來加強區塊鏈的安全。
我們所說的區塊鏈的安全又是什麼意思?這非常簡單:我們想要創造一個每個人都信任的區塊鏈。像我們之前在這篇文章中討論的那樣,如果存在超過1條以上的鏈,使用者的信任就會消失,因為他們沒有能力合理的確認哪條鏈才是“有效的”。為了讓一群使用者接受儲存在區塊鏈中的潛在狀態,我們需要有一群人信任的一個權威區塊鏈。
這完完全全就是Pow演算法所做的事情:它確保特定的區塊鏈直到未來都一直保持著權威性,讓攻擊者創造一個新區塊來重寫某個歷史部分(例如清除一個交易或者建立一個假的交易)或者保持一個分叉變得非常困難。為了首先讓他們的區塊被驗證,攻擊者需要總是比網路上的其他人要更快的解決掉nonce問題,這樣網路就會相信他們的鏈是最重的鏈(基於我們之前提到的GHOST協議原則)。除非攻擊者擁有超過一半的網路挖礦能力(這種場景也被稱為大多數51%攻擊 ),要不然這基本上是不可能的。
挖礦作為財富分配機制
除了提供一個安全的區塊鏈,PoW同樣也是分配財富給那些為提供這個安全而花費自己計算力的人的一種方法。回憶一下,一個礦工挖出一個區塊的時候會獲得獎勵,包括:
- 為“獲勝”區塊提供的5 ether靜態區塊獎勵(馬上就會變成3 ether )
- 區塊中的交易在區塊內所消耗的gas
- 納入ommers作為區塊的一部分的額外獎勵
為了保證PoW共識演算法機制對安全和財富分配的使用是長期可持續的,以太坊努力灌輸這兩個特性:
- 儘可能的讓更多的人可訪問。換句話說,人們不需要特殊的或者與眾不同的硬體來執行這個演算法。這樣做的目的是為了讓財富分配模式變的儘可能的開放,以便任何人都可以提供一些算力而獲得Ether作為回報。
- 降低任何單個節點(或小組)能夠創造與其不成比例的利潤可能性。任何可以創造不成比例的利潤的節點擁有比較大的影響力來決定權威區塊鏈。這是件麻煩的事情,因為這降低了網路的安全性。
在區塊鏈網路中,一個與上面兩個特性有關的一個問題是PoW演算法是一個SHA256雜湊函式。這種函式的缺點就是它使用特殊的硬體(也被稱之為ASCIs)可以更加快速高效的解決nonce問題。
為了減輕這個問題,以太坊選擇讓PoW演算法(Ethhash) 提高記憶體級別難度。意思是此演算法被設計為計算出要求的nonce需要大量的記憶體和頻寬。大量記憶體的需求讓電腦平行的使用記憶體同時計算多個nonce變得極其困難,高頻寬的需求讓即使是超級電腦同時計算多個nonce也變得十分艱難。這種方式降低了中心化的風險,併為正在進行驗證的幾點提供了更加公平的競爭環境。
有一件值得注意的事情是以太坊正在從PoW共識機制漸漸轉換為一個叫做“權益證明(PoS)”的共識演算法。這就是一個比較野心的話題了,我們希望可以在未來的文章中探索這個話題。
總結
呼! 你終於堅持到最後了。我希望如此?
這篇文章中有很多的地方需要消化。如果需要你閱讀好幾遍才能理解怎麼回事,這完全正常。我個人重複閱讀了好幾次以太坊黃皮書,白皮書,以及程式碼的不同部分才漸漸明白是怎麼回事。
無論如何,我希望你覺得這篇文章對你有幫助。如果你發現了任何的錯誤或失誤,我很樂意你給我寫個私人訊息或者直接在評論區評論(我保證我會檢視所有評論)。
記住,我是個人類(對,這是真的),我會犯錯誤。為了社群的利益,我花時間免費寫了這篇文章。所以請你在反饋時不要帶著沒必要的攻擊性,儘量是建設性的反饋。
原文:https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369作者:Preethi Kasireddy
翻譯:lilymoana
相關文章
- 以太坊之工作流程
- 以太坊分片技術原理 - vitalik
- 以太坊的程式設計介面程式設計
- 以太坊Solidity程式語言開發框架————11、工作流Solid框架
- 如何學習以太坊的程式碼
- 以太坊智慧合約開發第二篇:理解以太坊相關概念
- 以太坊連載(一):以太坊是什麼?
- 以太坊是什麼?以太坊交易可靠嗎?
- 以太坊官方 Token 程式碼詳解
- 以太坊原始碼分析(37)eth以太坊協議分析原始碼協議
- 以太坊原始碼分析(18)以太坊交易執行分析原始碼
- 以太坊學習筆記————1、以太坊是什麼?筆記
- 以太坊學習筆記————7、以太坊賬戶管理筆記
- 以太坊簡介
- 以太坊連載(六):以太坊客戶端的選擇與安裝客戶端
- 以太坊原始碼分析(52)以太坊fast sync演算法原始碼AST演算法
- 開發者的以太坊入門指南 | Jeth 以太坊系列線下活動
- 以太坊原始碼分析(54)以太坊隨機數生成方式原始碼隨機
- 以太坊原始碼分析(3)以太坊交易手續費明細原始碼
- 【以太坊剖析】以太坊虛擬機器(EVM)之基本定義虛擬機
- 以太坊原始碼分析(5)accounts程式碼分析原始碼
- 50行程式碼寫就以太坊支付行程
- 以太坊及區塊鏈 程式資源整合區塊鏈
- 以太坊概率微支付
- 以太坊Geth安裝
- 6.2 以太坊應用
- 什麼是以太坊
- [譯] 什麼是以太坊?以太坊初學者手把手教程
- 以太坊學習筆記————4、以太坊發展歷史回顧筆記
- 以太坊學習筆記————5、以太坊社群、基金會、貢獻者筆記
- 以太坊Solidity程式語言開發框架————5、移植Solid框架
- 以太坊中的全域性屬性
- 以太坊的POS共識機制
- 以太坊的擴容方案介紹
- 以太坊原始碼分析(15)node包建立多重協議以太坊節點原始碼協議
- 以太坊學習筆記————2、如何使用文件以及以太坊路線圖筆記
- [以太坊] OpenZeppelin 使用筆記筆記
- 以太坊開發計劃