6.1 專案介紹

xiaohuanglv發表於2018-08-24

6.1.1 專案背景

區塊鏈技術是建立信任機制的技術,常常被認為是自網際網路誕生以來最具顛覆性的技術。然而自從比特幣誕生後,一直以 來都沒有很好的開發平臺,想要藉助於區塊鏈技術開發更多的應用還是具有相當難度的,直接使用比特幣的架構來開發則很複雜繁瑣。事實上,比特幣僅僅被設計為 一個加密數字貨幣系統,只能算是區塊鏈技術的一個應用,雖然也具備一些指令程式解析能力,但只是非常基礎的堆疊指令,無法用來實現更廣闊的業務需求。以太 坊是目前使用最廣泛的支援完備應用開發的公有區塊鏈系統,本章我們就來介紹一下在該系統中應用的開發與部署方式。

與比特幣相比,以太坊屬於區塊鏈2.0的範疇,是為了解決比特幣網路的一些問題而重新設計的一個區塊鏈系統。人們 發現比特幣的設計只適合加密數字貨幣場景,不具備圖靈完備性,也缺乏儲存實時狀態的賬戶概念,以及存在PoW機制帶來的效率和資源浪費的問題。最關鍵的問 題是,在商業環境下,需要有高效的共識機制、具有圖靈完備性、支援智慧合約等多應用場景,以太坊在這種情況下應運而生。那麼,以太坊被設計為一個什麼樣的 系統呢?首先它是一個通用的全球性區塊鏈,也就是說它屬於公有鏈,這一點與比特幣是一樣的,並且可以用來管理金融和非金融型別的應用,同時以太坊也是一個 平臺和程式語言,包括數字貨幣以太幣(Ether)以及用來構建和釋出分散式應用的以太指令碼,也就是智慧合約程式語言。

image.png

如圖所示,這就是以太坊與比特幣最大的一個區別,也因為提供了一個功能更強大的合約程式設計環境,使得使用者可以在以太坊上 編寫智慧合約應用程式,直接將區塊鏈技術的發展帶入到2.0時代。通過智慧合約的設計開發,可以實現各種商業與非商業環境下的複雜邏輯,如眾籌系統、數字 貨幣、融資租賃資產管理、多重簽名的安全賬戶、供應鏈的追蹤監控等。通過智慧合約的應用,可以將傳統的軟體系統鏈化,發揮出更強大的管理能力。理論上,我 們可以在以太坊上實現一個比特幣系統,而且實現過程相當簡單,只需要編寫一個符合比特幣邏輯的智慧合約就可以了。在這方面,以太坊平臺相當於隱藏了底層技 術的複雜性而讓應用開發者更多地專注在應用邏輯及商業邏輯上。

以太坊的發展歷史並不長,2013年年末,Vitalik Buterin(社群一般尊稱他為V神),一位俄羅斯90後釋出了以太坊的初版白皮書,專案就此啟動了。之後的專案開展進度非常快,僅僅半年多時間就釋出 了5個版本的概念驗證,充分體現了極客技術團隊的效率和實力。大概是為了致敬比特幣,團隊開發所需費用直接接受的是比特幣投資。值得一提的是,在開發過程 中,以太坊設計了一個特有的叔區塊的概念。我們知道在比特幣中,一旦某個礦工挖礦成功,那麼系統獎勵的比特幣就都是那個礦工的,其他礦工一無所獲,而以太 坊中將沒有挖礦成功的礦工產生的廢區塊也納入了獎勵範疇,根據一定規則發放獎勵。直至2015年7月,官方團隊釋出了正式的以太坊網路,一片新的天地就此 開啟。

以太坊在國內社群的發展也是如火如荼,為了方便國內使用者更加方便快捷地同步以太坊區塊資料,EthFans(國內最大的以太坊中文技術社群,網址為http://ethfans.org)發起了星火節點計劃。類似於比特幣的種子節點,星火節點的資訊會被打包到節點檔案中,讓社群成員自由下載,通過使用節點檔案,本地執行的以太坊客戶端可以連線到更多超級節點,大大加快了區塊同步速度。我們看一下星火節點的瀏覽頁面(頁面的網址是https://stats.ethfans.org):

image.png

頁面上列出了目前的星火節點名稱,同時也顯示了最新的區塊高度、平均網路雜湊速率等以太坊網路指標資訊。

6.1.2 以太坊組成

以太坊的模組結構與比特幣其實並沒有本質的差別,還是那些物件,如區塊鏈賬本、共識機制、核心節點、P2P網路、 可程式設計邏輯等,雖然很多細節(如區塊的結構、資料編碼方式、交易事務結構等)都有差別,但本質的特點是智慧合約的全面實現,支援了全新的合約程式語言,以 及為了執行合約增加了一個以太坊虛擬機器。因此我們在理解以太坊的時候,基本上可以參照比特幣的結構思路。如果說比特幣是利用區塊鏈技術開發的專用計算器, 那麼以太坊就是利用區塊鏈技術開發的通用計算機,簡單地說,以太坊=區塊鏈+智慧合約,開發者在以太坊上可以開發任意的應用,實現任意的智慧合約。從平臺 的角度來講,以太坊類似於蘋果的應用商店;從技術角度來講,以太坊類似於一個區塊鏈作業系統。

我們來看一下以太坊的組成結構:

image.png

上圖簡易地描繪了以太坊的模組結構。可以發現,正是以太坊虛擬機器與智慧合約層擴充套件了外部應用程式在區塊鏈技術上的應用 能力。若我們想在以太坊的基礎上實現一個比特幣系統,只要在智慧合約層開發一個與比特幣邏輯一致的合約程式就可以了。當然只要你願意,可以根據愛好或者需 求去實現任何數字貨幣系統,它們都能通過以太坊網路良好地執行。值得注意的是,以太坊中的智慧合約是執行在虛擬機器上的,也就是通常說的 EVM(Ethereum Virtual Machine,以太坊虛擬機器)。這是一個智慧合約的沙盒,合約儲存在以太坊的區塊鏈上,並被編譯為以太坊虛擬機器位元組碼,通過虛擬機器來執行智慧合約。由於 這個中間層的存在,以太坊也實現了多種語言的合約程式碼編譯,網路中的每個以太坊節點執行EVM實現並執行相同的指令。

可能有些讀者在這個環節一時不太理解,雖然看結構圖是很簡單,原理也是一目瞭然,可是細細一想,總覺得不夠通透。 如果說以太坊靠實現一個智慧合約就能實現比特幣,那豈不是說比特幣就是一份合約?讓我們來理一下這裡的思路。首先比特幣系統肯定不只是一份合約程式,只能 說比特幣的交易事務就是一份合約,比特幣系統擁有自己的區塊鏈賬本、共識機制、挖礦系統等,這些基礎結構都為一件事服務,就是執行比特幣的智慧合約:位元 幣交易事務。我們知道比特幣之所以被稱為可程式設計加密數字貨幣,就是因為其交易事務的結構中擁有鎖定指令碼和解鎖指令碼兩段指令程式。從技術上來講,比特幣系統 就是通過執行交易事務中的鎖定指令碼和解鎖指令碼完成了比特幣的發行和轉賬交易,也就是說比特幣中的一切機制都是為了這一固定功能的合約而執行存在的。那麼現 在以太坊來了,大家覺得偌大一個系統,就只能執行一種智慧合約,實在是太約束了。如果把鎖定指令碼和解鎖指令碼的程式設計能力加強,把交易事務的結構再擴充套件一下, 使智慧合約的能力不只是實現一個數字貨幣的轉賬交易,那就開啟了另一片天地。不管是什麼功能的合約,站在技術角度來講,無非就是通過執行一組程式改變了一 些值。我們不但可以實現數字貨幣,還可以實現眾籌合約、擔保合約、融資租賃合約、期貨合約以及各種其他金融與非金融的訂單合約,所有這些合約的執行都會被 以太坊打包進區塊,這樣就實現了基於區塊鏈的全功能智慧合約。如果說比特幣是二維世界的話,那麼以太坊就是三維世界,可以實現無數個不同的二維世界。

現在讓我們來更加具體地瞭解下以太坊,畢竟再怎麼神奇強大,總歸也就是一套軟體,我們就來認識下以太坊具體的軟體元件。

以太坊的原始碼是維護在GitHub上的,通過連結https://github.com/ethereum可以檢視,在這個原始碼官網我們可以看到以太坊擁有好多個專案,不像比特幣只有一個Bitcoin,一目瞭然。我們先看一下以太坊的核心客戶端:

image.png

可以看到,以太坊有兩種語言版本的核心客戶端:一個是Go語言版本,這也是官方首推的版本;另外一個是C++語言的版 本。兩種版本的功能和使用是一樣的,只不過用不同的語言實現,對於想要深入瞭解原始碼的讀者,可以根據自己的語言偏好去下載對應的原始碼。除了核心客戶端外, 以太坊還提供了一系列其他獨立使用的工具,比如新的實驗性的合約程式語言Viper、Solidity,以太坊的JavaScript呼叫庫 Web3.js,以太坊官方錢包等。截至2017年7月,GitHub官網上已經放了100多個各類功能的工具專案,我們整理一些常用的進行說明:

1)go-ethereum。官方的Go語言客戶端,客戶端檔案是geth。這是使用最廣泛的客戶端,類似於位元 幣的中本聰核心客戶端,可用於挖礦、組建私有鏈、管理賬號、部署智慧合約等。但是注意不能編譯智慧合約(1.6之前的版本還是內建編譯模組的,1.6之後 就獨立出去了)。該客戶端可以作為一個獨立程式執行,也可以作為一個庫檔案嵌入其他的Go、Android和iOS專案中,它沒有介面,是一個命令列程 序。

2)cpp-ethereum。與第一個一樣,只不過是用C++實現的。

3)EIP。EIP描述以太坊平臺標準,包含核心協議說明、客戶端API以及合約標準等。

4)Mist客戶端。Mist目前主要是錢包客戶端,未來定義為一個DAPP市場交易客戶端,類似於蘋果市場。實 際上Ethereum Wallet可以看作配置在MistBrowser上的一個應用,因此通常也叫Mist/Ethereum Wallet。Mist一般是配合go-ethereum或者cpp-ethereum執行的,如果在Mist啟動的時候沒有執行一個命令列的 ethereum客戶端,則Mist將啟動區塊鏈資料同步(使用繫結的客戶端,通常預設是geth,因此注意了,Mist是會攜帶核心客戶端的)。如果想 要Mist執行在一個私有網路,只要在Mist啟動前先啟動節點(也就是geth)即可,Mist可以通過IPC連線到私有鏈。

5)Solidity專案。Solidity使用C++開發,客戶端檔案為solc,跨平臺,使用命令列介面。solc實際上是一個基本的編譯平臺,Solidity是以太坊智慧合約的程式語言。

6)browse-solidity專案。browse-solidity是智慧合約瀏覽器版本的開發環境,可以直接在瀏覽器中進行開發、除錯、編譯。

7)Remix。Remix是智慧合約(以太坊稱為DAPP)的開發IDE,採用圖形化介面,可以支援智慧合約 (DAPP)的編寫、除錯、部署,是目前最主流的以太坊智慧合約開發平臺。之前還有個Mix專案,不過已經不再繼續維護了,Remix現在可以與 browser solidity整合在一起使用了。

8)pyethereum專案。pyethereum是用Python語言編寫的以太坊客戶端。

9)ethereumj專案。ethereumj是用Java語言編寫的以太坊客戶端,與前面Go語言編寫的客戶 端geth的功能完全相同。實際上,以太坊的相關客戶端遠不止這些,在GitHub站點上也能看到很多,這與Bitcoin不一樣,因為以太坊是要打造一 個生態。

6.1.3 關鍵概念

以太坊在開發時著重設計了虛擬機器和智慧合約相關的規範,這是以太坊的主要特點,然而作為一個開闢了區塊鏈2.0智慧合約時代的新平臺,其特點以及改善之處遠不止這些,在本節中,我們對以太坊中的一些關鍵概念做一些闡述。

1.狀態

狀態的概念是在以太坊白皮書中提出的,我們先來擷取以太坊白皮書中提及狀態的幾段文字描述:

以太坊的目標就是提供一個帶有內建的成熟的圖靈完備語言的區塊鏈,用這種語言可以建立合約來編碼任意狀態轉換功能。

從技術角度講,比特幣賬本可以被認為是一個狀態轉換系統,該系統包括所有現存的比特幣所有權狀態和“狀態轉換函式”。狀態轉換函式以當前狀態和交易為輸入,輸出新的狀態。

在標準的銀行系統中,狀態就是一個資產負債表,一個從A賬戶向B賬戶轉賬X美元的請求是一筆交易,狀態轉換函式將從A賬戶中減去X美元,向B賬戶增加X美元。如果A賬戶的餘額小於X美元,狀態轉換函式就會返回錯誤提示。

比特幣系統的“狀態”是所有已經被挖出的、沒有花費的比特幣(技術上稱為“未花費的交易輸出”,unspent transaction outputs或UTXO)的集合。

一筆交易包括一個或多個輸入和一個或多個輸出。每個輸入包含一個對現有UTXO的引用和由與所有者地址相對應的私鑰建立的密碼學簽名,每個輸出包含一個新的加入到狀態中的UTXO。

看到這裡,不知道大家對狀態的概念是否有一些感覺了。實際上以太坊是站在一個更高的維度來看待區塊鏈賬本中的資料 變化。如果不發生任何交易事務,那相當於賬本就是靜態的,就好像是一個化學容器,裡面有各種原材料,一旦發生了化學反應,不管是什麼樣的反應過程,反應結 束後,容器中的狀態肯定不一樣。對於區塊鏈賬本,這裡的變化可以是指一筆轉賬,也可以是合約的某個規則被啟用等,總之就是資料動了,以太坊中將變化的過程 稱為狀態轉換函式,如下圖所示:

image.png

通過這樣一種更高層面的抽象,就使以太坊的設計具備了實現任意智慧合約的基礎,這裡的狀態資料可以是任何形式的(包括比特幣那種UTXO的機制),狀態函式也可以是任何過程的,只要符合業務需求即可,沒有任何限制。

在以太坊的每一個區塊頭,都包含了指向三棵樹的指標,分別是:狀態樹、交易樹、收據樹。交易樹指標就類似於比特幣 區塊頭中的梅克爾樹根,交易樹是用來代表區塊中發生的所有交易歷史的;狀態樹代表訪問區塊後的整個狀態;收據樹代表每筆交易對應的收據,所謂的收據是指每 一筆交易影響的資料條,或者說是每一筆交易影響的結果。這些都是針對比特幣中單一的梅克爾交易樹的增強,通過狀態樹可以很方便地獲得類似賬戶存在與否、賬 戶餘額、訂單狀態這樣的結果,而不用只依靠交易事務去追溯。

我們看一下以太坊中狀態樹的示意圖:

image.png

如圖所示,在狀態樹中儲存了整個系統的狀態資料,如賬戶餘額、合約儲存、合約程式碼以及賬戶隨機數等資料。我們在玩遊戲的時候,有個功能叫存檔,可以把當時的各項遊戲資料都記錄下來,狀態樹在功能效果上與此類似。

再來看一下以太坊原始碼中,是如何在區塊頭中定義這個狀態樹根雜湊的:


// 定義以太坊區塊鏈中的區域頭結構
type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
    Coinbase    common.Address `json:"miner"            gencodec:"required"`
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
    Number      *big.Int       `json:"number"           gencodec:"required"`
    GasLimit    *big.Int       `json:"gasLimit"         gencodec:"required"`
    GasUsed     *big.Int       `json:"gasUsed"          gencodec:"required"`
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`
    Extra       []byte         `json:"extraData"        gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}

這段程式碼可以在以太坊原始碼中的go-ethereum/core/types/block.go檔案中找到,這是 一個自定義結構型別,定義了以太坊中的區塊頭結構,可以看到:其中有個屬性Root,是common.Hash型別,說明這是一個雜湊值,並且是 stateRoot(狀態樹根雜湊)。除了這些,我們同樣能看到有TxHash和ReceiptHash,分別對應了交易樹根雜湊和收據樹根雜湊。

2.賬戶

在以太坊系統中,狀態是由被稱為“賬戶”的物件和在兩個賬戶之間轉移價值和資訊的狀態轉換構成的,每個賬戶有一個 20位元組的地址,這個其實就跟銀行賬戶差不多意思,在比特幣中是沒有賬戶這個概念的,或者說比特幣中只有狀態轉換的過程歷史。這裡我們再來對比一下位元 幣,假設Alice既使用比特幣也使用以太坊,並且初次使用,之前沒有餘額,那麼Alice在兩者中的賬本資訊大概是這樣的:

image.png

可以看到,以太坊中由於具備賬戶的概念,可以直接獲得當前的餘額,這個餘額相當於Alice資產當前的狀態,而比特幣中只有流水賬,要獲得當前餘額,只能通過計算獲得。

我們來具體瞭解一下以太坊中的賬戶,既然是賬戶就應有賬戶結構,通常包含下面4個部分。

1)隨機數,用於確定每筆交易只能被處理一次的計數器,實際上就是每個賬戶的交易計數,用以防止重放攻擊,當一個賬戶傳送一筆交易時,根據已經傳送的交易數來累加這個數字,比如賬戶傳送了5個交易,則賬戶隨機數是5。

2)賬戶目前的以太幣餘額。

3)賬戶的儲存(預設為空)。

4)賬戶的合約程式碼(只有合約賬戶才有,否則為空)。

這些其實就是以太坊原始碼中的定義,我們常常說要按圖索驥,尋蹤覓跡,任何一個定義,一個邏輯,我們都要看看它的來源到底是什麼樣的,我們來看看賬戶在以太坊原始碼中的定義描述:


// 以太坊中的賬戶物件結構定義
// 這些資料物件會儲存在以太坊中的梅克爾樹中
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

可以看到,賬戶結構的定義中與上述的四項屬性一一對應,原始碼中就是這麼定義的,其中的Root也就是所謂的賬戶存 儲空間,是一個根雜湊值,指向的是一棵patricia trie(帕夏爾字首樹),關於patricia trie的概念在下面會有介紹,總之就是一種儲存結構,類似梅克爾樹,但更復雜一些。

以太坊中的賬戶是區分型別的。

(1)外部賬戶

外部所有賬戶,術語叫EOA,全稱是Externally Owned Account,這個就是一般賬戶的概念。外部所有賬戶是由一對金鑰定義的,一個私鑰一個公鑰,公鑰的後20位作為地址,這個跟比特幣中的公私鑰以及錢包 地址類似。外部所有賬戶是沒有程式碼的,但是可以通過建立和簽名一筆交易從一個外部賬戶傳送訊息到合約賬戶,通過傳遞一些引數,比如EOA的地址、合約的地 址,以及資料(包括合約裡的方法以及傳遞的引數),使用ABI(Application Binary Interface)作為傳遞資料的編碼和解碼的標準。

(2)合約賬戶

合約賬戶是一種特殊的可程式設計賬戶,合約賬戶可以執行圖靈完備的計算任務,也可以在合約賬戶之間傳遞訊息,合約儲存 在以太坊的區塊鏈上,並被編譯為以太坊虛擬機器位元組碼,合約賬戶也是有地址的,不過與外部所有賬戶不同,不是根據公鑰來獲得的,而是通過合約建立者的地址和 該地址發出過的交易數量計算得到。

我們可以看到,外部所有賬戶在以太坊中就相當於一把鑰匙,合約賬戶則相當於一個機關,一旦被外部所有賬戶確認啟用,機關就啟動了。

3.交易

以太坊中的交易,也就是狀態一節中所說的轉換過程。通常提到交易,大家都會習慣性地認為是轉賬交易這種意思,在以 太坊中交易的概念是比較廣義的,因為以太坊並不僅僅支援轉賬交易這樣的合約功能,它的定義如下:在以太坊中是指簽名的資料包,這個資料包中儲存了從外部賬 戶傳送的訊息。所謂的交易就是一個訊息,這個訊息被髮送者簽名了,如果類比一下比特幣的話,可以發現比特幣中的交易也在這個範疇內,在比特幣中也是通過轉 賬發起者簽名了一個UTXO資料然後傳送出去,只不過比特幣中只能傳送這種固定格式的訊息,原始碼中寫死了。

我們來看一下以太坊中的交易格式是什麼,先來看下原始碼中的定義:


type Transaction struct {
   data txdata
   // caches
   hash atomic.Value
   size atomic.Value
   from atomic.Value
}


在這個定義中,最主要的就是data欄位,這是一個命名為txdata的結構型別欄位,代表了真正的交易資料結構,其餘三個都是緩衝欄位,我們來看一下txdata的定義:


type txdata struct {
   AccountNonce uint64          `json:"nonce"    gencodec:"required"`
   Price        *big.Int        `json:"gasPrice" gencodec:"required"`
   GasLimit     *big.Int        `json:"gas"      gencodec:"required"`
   Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
   Amount       *big.Int        `json:"value"    gencodec:"required"`
   Payload      []byte          `json:"input"    gencodec:"required"`

   // Signature values
   V *big.Int `json:"v" gencodec:"required"`
   R *big.Int `json:"r" gencodec:"required"`
   S *big.Int `json:"s" gencodec:"required"`

   // This is only used when marshaling to JSON.
   Hash *common.Hash `json:"hash" rlp:"-"`
}


以下是一些說明。

1)AccountNonce:表明交易的傳送者已傳送過的交易數,與賬戶結構中定義的隨機數對應。

2)Price與GasLimit:這是以太坊中特有的概念,用來抵抗拒絕服務攻擊。為了防止在程式碼中出現意外或 有意無限迴圈或其他計算浪費,每個交易都需要設定一個限制,以限制它的計算總步驟,說白了就是讓交易的執行帶上成本,每進行一次交易都要支付一定的手續 費,GasLimit是交易執行所需的計算量,Price是單價,兩者的乘積就是所需的手續費,交易在執行過程中如果實際所需的消耗超出了設定的Gas限 制就會出錯回滾,如果在範圍內則執行完畢後退還多餘的部分。

3)Recipient:接收方的地址。

4)Amount:傳送的以太幣金額,單位是wei。

5)Payload:交易攜帶的資料,根據不同的交易型別有不同的用法。

6)V、R、S:交易的簽名資料。

可能有些讀者會有疑問,通過這個交易結構,怎麼看出是誰發出的呢,為什麼只有接收方的地址卻沒有發出方的地址呢?那是因為傳送者的地址可以通過簽名獲得。

我們提到了不同的交易型別,那麼在以太坊中都有哪些不同的交易型別呢?接下來我們就一一說明一下,為了讓差別一目 瞭然,我們通過Web3.js的呼叫格式來說明。Web3.js是一個JavaScript庫,可以通過RPC呼叫與本地節點通訊,實際上就是一個外部應 用程式用來呼叫以太坊核心節點功能的一個呼叫庫。

(1)轉賬交易

以太坊本身內建支援了以太幣,因此這裡說的轉賬就是指從一個賬戶往另一個賬戶轉賬傳送以太幣,我們知道要轉賬,一般來說得要有傳送方、接收方、轉賬金額。指令格式如下:


web3.eth.sendTransaction({from:"",to:"",value:});


from後面是傳送方的賬戶地址,to後面是接收方的地址,value後面是轉賬金額。

(2)合約建立交易

有讀者可能會感到奇怪,建立一份合約怎麼也是交易,又沒有向誰轉賬,我們再次重申一下以太坊中交易的定義:在以太坊中,交易是指簽名的資料包。不過,合約在建立的時候是需要消耗以太坊的,從這個層面來看,也算是一種傳統的交易吧。我們來看一下指令格式:


web3.eth.sendTransaction({from: "",data: ""});


from後面是合約建立者的地址,data後面是合約程式的二進位制編碼,這個還是容易理解的。

(3)合約執行交易

合約一旦部署完成後,就可以呼叫合約中的方法,也就是執行合約,在以太坊中執行合約也屬於一種交易,我們來看一下合約執行交易的指令格式:


web3.eth.sendTransaction({from: "",to: "",data: ""});


from後面是合約呼叫者的地址,to後面是合約的地址,data後面是合約中具體的呼叫方法以及傳入的引數。實際上,轉賬交易也屬於一種合約執行交易,只不過以太幣是以太坊內建的數字貨幣,對於以太幣的合約處理是系統直接自動完成的,不再需要指定一個合約。

以上就是以太坊中的3種交易型別,在後續的章節中會有具體的操作示例,現在我們只要有個基本瞭解就行了。使用過比特幣的朋友都知道,比特幣是有很多計量單位的,從最小的“聰”到最大的“BTC”,那麼以太坊中涉及以太幣的交易計量單位有哪些呢,我們來說明一下。

以太幣(Ether幣)的最小單位是wei,類似於比特幣中的最小單位是聰,然後每1000個遞進一個單位,如下所示:

·kwei=1000wei

·mwei=1000kwei

·gwei=1000mwei

·szabo=1000gwei

·finney=1000szabo

·ether=1000finney

通過以上的換算關係,我們可以發現,1ether=1000000000000000000wei,足有18個 0,可別看眼花了,我們使用命令列工具訪問以太坊節點時,預設的以太幣計量單位是wei,如果是圖形介面的錢包客戶端,則一般是ether,大家在使用 時,一定要看清楚計量單位。

交易資料在以太坊區塊中也是有棵樹的,在上述介紹狀態時,我們看過區塊頭的資料結構,其中就有一個交易樹根雜湊,交易樹的概念與比特幣中的梅克爾樹是一個意思,只不過儲存的結構與編碼方式有些差別。

image.png

4.收據

收據這個概念也是以太坊中特有的,字面的意思是指每條交易執行所影響的資料條,在以太坊的區塊頭中儲存有收據樹的根雜湊值,也就是說在每個區塊中,收據和交易以及狀態一樣,都是一棵樹,那麼收據中到底是些什麼呢?我們還是看一下原始碼中怎麼定義的:


// 收據物件描述的是交易事務產生的結果
type Receipt struct {
   // Consensus fields
   PostState         []byte   `json:"root"`
   CumulativeGasUsed *big.Int `json:"cumulativeGasUsed" gencodec:"required"`
   Bloom             Bloom    `json:"logsBloom"         gencodec:"required"`
   Logs              []*Log   `json:"logs"              gencodec:"required"`

   // Implementation fields (don’t reorder!)
   TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
   ContractAddress common.Address `json:"contractAddress"`
   GasUsed         *big.Int       `json:"gasUsed" gencodec:"required"`
}


乍看之下有點不明所以,不過既然是指交易執行後的影響結果,那就跟交易有關,交易執行後會影響狀態的變更,會消耗Gas,我們來看一看結構定義中的主要屬性。

1)PostState:這是狀態樹的根雜湊,不過不是直接儲存的雜湊值,而是轉換為位元組碼儲存,通過這個欄位使得通過收據可以直接訪問到狀態資料。

2)CumulativeGasUsed:累計的Gas消耗,包含關聯的本條交易以及之前的交易所消耗的Gas之和,或者說是指所在區塊的Gas消耗之和。

3)TxHash:交易事務的雜湊值。

4)ContractAddress:合約地址,如果是普通的轉賬交易則為空。

5)GasUsed:本條交易消耗的Gas。

我們可以看到,收據實際上是一個資料的統計記錄,記錄了交易執行後的特徵資料,那麼,這個資料保留下來有什麼用 呢?主要還是方便取得某些統計資料,比如我們建立了一個眾籌合約,大家可以往合約地址轉賬,如果我們想要檢視過去20天內這個合約地址的眾籌情況,通過收 據是很容易查詢得到的。有讀者可能會問,這樣的查詢就算沒有收據這種資料的存在也是可以得到的,為什麼還要冗餘這個資料呢?是的,技術上來說,收據確實不 是必需的,我們也發現,比起比特幣,以太坊支援了3種梅克爾樹:交易樹、狀態樹和收據樹。其目的無非就是為了方便進行各種資料查詢,提高賬本資料在各種需 求之下的統計查詢效率。

不知道大家對於收據樹有沒有一種特別的感覺,如果與比特幣相比,我們發現,特別像比特幣中的UTXO,在比特幣 中,只有一個UTXO賬戶模型,當然嚴格來說比特幣是沒有賬戶的,只不過在這裡我們做一個類比。非要說比特幣中有沒有賬戶的話,UTXO資料就是比特幣中 的賬戶模型,在每一次的交易執行後剩下的就是一個UTXO的結果,以太坊中的收據與這個很相像,它也是交易執行後的一個結果,而且收據與交易是關聯對應 的,這與UTXO的輸出對應輸入也是異曲同工的,就個人的技術傾向,實際上UTXO這種模型是非常可靠的,基本上不會發生資料不一致問題,讀者可以反覆體 會一下。

5.RLP編碼

RLP(recursive length prefix),直譯過來叫“遞迴長度字首”,相當拗口的一個名詞,相信第一次看見這個稱呼的讀者肯定很迷茫,總之這是一種資料編碼方式。這種編碼方式在 以太坊中使用很普遍,是以太坊中物件序列化的主要方式,在區塊、交易、賬戶狀態等地方都有使用,比如交易資料從一個節點傳送到另一個節點時,要被編譯為一 種特別的資料結構,這種結構稱為trie樹(也叫字首樹),然後根據這棵字首樹計算出一個根雜湊(上述介紹的狀態樹、交易樹、收據樹都是這種方式),而這 棵樹中的每一個資料項都會使用RLP的方式編碼。關於trie樹的細節稍後再詳談,這裡不再贅述。

既然是一種編碼方式,那就好描述了,我們知道計算機中的資料在本質上都是二進位制碼,而編碼方式就是一種約定的規則,將二進位制資料通過某種格式要求進行組裝,以便於資料傳輸的編碼與解碼,接下來我們就來了解下RLP的編碼規則,看看它這個拗口的名字到底是怎麼來的。

(1)單位元組資料編碼

對於單位元組資料,如果表示的值的範圍是[0x00,0x7f],則它的RLP編碼就是本身,這個範圍的資料其實就 是ASCII編碼,不過要注意的是,這裡說的是單位元組,如果一個資料的值雖然屬於ASCII編碼的值的範圍,但卻不是單位元組的,那就不符合這個規則了,而 要使用下面的規則。

(2)字串長度是0~55位元組

RLP編碼包含一個單位元組的字首,後面跟著字串本身,這個字首的值是0x80加上字串的位元組長度。由於被編碼 的字串最大的位元組長度是55=0x37,因此單位元組字首的最大值是0x80+0x37=0xb7,即編碼的第一個位元組的取值範圍是 [0x80,0xb7]。這個很好理解,就是第一個位元組是“0x80+字串位元組長度”作為字首,至此我們就理解了RLP中長度字首的意思,至於RLP中 的R(也就是遞迴)是什麼意思,我們接著往下看。

(3)字串長度大於55位元組

它的RLP編碼包含一個單位元組的字首,後面跟著字串的長度,再跟著字串本身。這個字首的值是0xb7加上字元 串長度的二進位制形式的位元組長度,說得有點繞,舉個例子就明白了,例如一個字串的長度是1024,它的二進位制形式是10000000000,這個二進位制形 式的長度是2個位元組,所以字首應該是0xb7+2=0xb9,字串長度1024=0x400,因此整個RLP編碼應該是\xb9\x04\x00再跟上 字串本身。編碼的第一個位元組即字首的取值範圍是[0xb8,0xbf],因為字串長度二進位制形式最少是1個位元組,因此最小值是 0xb7+1=0xb8,字串長度二進位制最大是8個位元組,因此最大值是0xb7+8=0xbf。

注意在這種情況下,字串前面是一個單位元組的字首以及字串的長度,多了一個字串長度也要跟著。單位元組的字首是 指0xb7加上字串長度的二進位制形式的(如上述字串長度是1024位元組,則1024的二進位制形式為10000000000,長度是2個位元組,所以是 0xb7+2=0xb9,後面再跟上字串的長度,1024位元組長度的16進位制是0x400)。

以上都是對於字串的編碼,接下來我們來看看列表的編碼,難度稍微增加些,其實列表編碼就是在上述的編碼基礎上進行的,列表中包含不止一個字串,每個字串的編碼方式都是一樣的,只不過在整體編碼上有些差別。

(4)列表總長度為0~55位元組

列表的總長度是指它包含的項的數量加上它包含的各項的長度之和,它的RLP編碼包含一個單位元組的字首,後面跟著列表中各元素項的RLP編碼,這個字首的值是0xc0加上列表的總長度。編碼的第一個位元組的取值範圍是[0xc0,0xf7]。

(5)列表總長度大於55位元組

RLP編碼包含一個單位元組的字首,後面跟著列表的長度,再跟著列表中各元素項的RLP編碼,這個字首的值是0xf7加上列表總長度的二進位制形式的位元組長度。編碼的第一個位元組的取值範圍是[0xf8,0xff]。

通過列表的編碼規則,我們可以看到這裡有遞迴的影子,除了字首以外,其中的編碼都是不斷地重複單個字串的編碼方式對每一個列表項進行編碼,這就是遞迴字首編碼的稱呼來源。

(6)示例

·字串:"dog"=[0x83,'d','o','g']

·列表:["cat","dog"]=[0xc8,0x83,'c','a','t',0x83,'d','o','g']

·空字串:""=[0x80]

·空列表:=[0xc0]

·整數:15('\x0f')=[0x0f]

讀者可以根據對規則的理解,嘗試編寫一個RLP編碼程式,體驗一下這種編碼的特點。

6.梅克爾–帕特里夏樹

我們知道,在比特幣系統中有一個梅克爾樹(Merkle Tree)的概念,在每一個區塊頭都有一個梅克爾根,實際上就是一個區塊中交易雜湊樹的根雜湊值,而以太坊中也有類似的結構,通過上述章節的學習,我們知 道在以太坊的區塊頭中有3個根雜湊,分別是狀態樹、交易樹和收據樹的根雜湊,對應著各自的樹結構,那麼這些樹結構與比特幣中的梅克爾樹有什麼差別?嚴格來 說,比特幣中的梅克爾樹叫二叉梅克爾樹,以太坊中的則是梅克爾–帕特里夏樹(有時也稱為帕夏爾樹),是一種更加複雜的結構,英文全稱為Merkle Patricia Tree,就是梅克爾樹與帕特里夏樹(以下以其英文名Patricia Tree稱呼)的結合。

(1)Patricia Tree

我們來了解一下它的概念,以及在以太坊中到底如何應用。以交易資料為例,當交易資料從一個節點傳送到另一個節點的 時候,必須被編譯為一個特別的資料結構,稱為trie(字首樹),然後計算生成一個根雜湊。值得注意的是,這個trie中的每一個項都使用RLP編碼(這 就是RLP編碼的一個應用場合了)。注意,在P2P網路上傳輸的交易是一個簡單的列表,它們被組裝成一個叫作trie樹的特殊資料結構來計算根雜湊,這意 味著交易列表在本地以trie樹的形式儲存,傳送給客戶端的時候序列化成列表。實際上,在以太坊中,使用的是一種特殊的trie結構,也就是 Patricia Tree。這下我們明白了,比特幣中是將交易資料組裝成一棵二叉樹然後計算根雜湊,而以太坊中則是組裝成一棵Patricia Tree然後計算根雜湊。因此我們只要理解什麼叫Patricia Tree就可以了,梅克爾雜湊的計算沒什麼特別的。

大家在平時看一些資料的時候,看到以太坊關於Patricia Tree的介紹時,常常會看到trie這個名字,一會兒是Patricia Tree,一會兒是Patricia Trie等,讓人不明所以,我們先把這些名詞稱呼理一理。Patricia Tree也稱為Patricia Trie、radix tree或者crit bit tree,是基於trie tree的一種結構,trie tree是一種單詞查詢樹結構,我們看下示例圖:

image.png

trie中每個節點儲存單個字元,我們可以看到,but與big兩個單詞共享了同一個字首b,通過這種方式可以節約儲存空間,用通常的陣列或者key- value鍵值對的方式都不能很好地節約儲存空間,trie的這種方式還為檢索資料帶來了便利,只要定位一個字首,所有具有同一個字首的資料都在一起了。 然而,我們說Patricia Tree是基於trie tree的一種結構,但並不相同,在trie tree中通常每個節點只儲存單個字元,而Patricia Tree的每個節點可以儲存字串或者說二進位制串,這樣就使得Patricia Tree可以儲存更為一般化的資料,而不只是一個單詞字元,如下圖所示:

image.png

在這樣的樹結構中,每個節點中通常儲存一個key-value鍵值對資料,key用來儲存索引,是用來搜尋定位 的,value則是節點中具體的業務資料,key-value是典型的字典資料結構,因此這種結構也稱為字典樹,顧名思義,就是方便用來像查字典一樣檢索 資料的結構。實際上我們日常生活中經常會用到這樣的結構,拋開這些技術上的概念,比如我們在查新華字典的時候,通過拼音來查字,比如“海”字,我們會先翻 到“h”開頭的目錄,可以發現有很多“h”開頭的字,接著往下查“ha”開頭的,還是有很多,最後查到“hai”,定位到“海”這個字了,這其實就是字首 索引樹的應用,所以說很多看起來複雜的技術,在生活中其實都有在運用,不但藝術來源於生活,技術也是來源於生活的。

好了,到這裡就可以結束了嗎?答案是:沒有!很不幸,以太坊中的樹結構在這個基礎上還要複雜不少,理解起來頗費周折。我們來看看以太坊中的Merkle Patricia Tree具體是哪種結構。

我們知道在一棵樹結構中,無論樹的結構有什麼特別的,總歸就是一個個的節點,事實上,以太坊中對節點還進行了不同型別的劃分,分為空節點、葉子節點、擴充套件節點、分支節點,我們先不管這些節點分別有什麼作用,只要知道節點中是key-value格式的資料儲存格式就行了。


Node=(Key,Value)


這些節點資料會被儲存在一個叫LevelDB的本地資料庫中,LevelDB是Google實現的一種非常高效的鍵值儲存資料庫。那麼怎麼儲存呢?既然是鍵值儲存,那就是有一個key,有一個value,value就是節點的RLP編碼,key則是RLP編碼的雜湊值。


value=RLP(Node)
key=sha3(value)


如上所示,儲存到LevelDB中的value是節點資料的RLP編碼,而key則是這個RLP編碼的雜湊值,以 太坊中使用了SHA3演算法計算了雜湊值,SHA3是第三代sha雜湊計算演算法。以太坊網路中的核心客戶端會不斷地同步更新這個資料庫以保持與網路中的其他 客戶端資料同步,我們看一下原始碼中的定義:


type SyncResult struct {
   Hash common.Hash  
   Data []byte      
}


SyncResult是定義用來同步儲存在LevelDB中的key-value資料的,顯而易見,這裡定義了兩個欄位型別:一個是樹節點的雜湊值,一個是樹節點中包含的資料,而樹節點的雜湊值是通過對樹節點的包含資料進行雜湊計算得來的。

(2)節點型別

至止,我們知道了以太坊中Merkle Patricia Tree的節點是儲存在本地的LevelDB資料庫中的,接下來解析一下組成這棵樹的節點分別是什麼結構,剛才提到節點是有不同的型別的,那麼分別有哪些型別?

1)空節點:表示空的意思,value中是一個空串;

2)葉子節點:表示為[key,value]的一個鍵值對,其中value是資料項的RLP編碼,key是key資料的一種特殊的十六進位制編碼,葉子節點用來儲存業務資料,葉子節點下面不再有子節點。

image.png

3)擴充套件節點:也是[key,value]的一個鍵值對,但是這裡的value是指向其他節點的雜湊值。什麼雜湊值呢?就是上面所說的儲存在 LevelDB中的節點雜湊值,通過這個雜湊值可以直接定位到某一個節點,也就是說擴充套件節點相當於一個指標節點。另外,擴充套件節點的key也被編碼為一個特 殊的十六進位制編碼。我們看以下示意圖,圖中的葉子節點Bob只是一個假設稱呼,可以看到擴充套件節點的value部分實際上儲存的是另外的節點的雜湊值,通過 這樣的對應關係,可以使用擴充套件節點連線到另外一個節點。

image.png

4)分支節點:葉子節點是真正儲存業務資料的,並且葉子節點不再有子節點(要不怎麼叫葉子呢),擴充套件節點是用來指向其他節點的。Merkle Patricia Tree作為一種字首樹,主要特點是依靠共享的字首來提高樹結構的處理效能,那麼這個字首就很重要了,對於擴充套件節點和葉子節點來說,節點的key就是起到 字首的作用。通過上面的瞭解,我們知道葉子節點和擴充套件節點的key都會被編碼為一種十六進位制的格式,先不細究到底是什麼樣的格式,有一點我們知道,既然是 十六進位制的資料,那編碼字元的範圍就是0~F。如果需要一個節點的key能夠包含所有這些字元的範圍,則需要一個長度為16的列表,再加上一個 value,這樣的節點型別稱為分支節點,所以分支節點是一個長度為17的列表,我們看下分支節點的示例樣式:

image.png

5)樹結構示例:比起比特幣中的梅克爾樹,以太坊中的設計複雜了很多。當然,由於比特幣中僅支援轉賬交易合約,需要構造的梅克爾樹也就是一棵二叉雜湊樹, 自然是簡單很多,以太坊中支援更廣泛的智慧合約,也增加了更多的概念,如上所述的賬戶、狀態、收據等,資料種類複雜許多,為了能夠更有效地進行增刪改查操 作,並且讓樹的結構更加平衡有效,因此設計出了許多有趣的結構,我們來看下這些節點型別組合起來會是怎樣一個效果:

image.png

如上圖所示,我們可以看到,一個根節點指向了一個分支節點,而分支節點又為下面兩個葉子節點和一個擴充套件節點提供了3個 字首(分別是2、8、14),擴充套件節點又指向了一個葉子節點。當然,根據實際的資料構造出的樹會更加複雜,分支節點會有多個,擴充套件節點也會有多個,葉子節 點更是會有很多。

我們看一下“葉子節點1”,它的key是什麼?是“01234”。(什麼?圖中標記的不是34嗎?)我們先來看下 這個“01234”是怎麼來的,首先從根節點的“01”開始,然後經過了分支節點的“2”,再到達自己的“34”,連起來就是“01234”,那麼“葉子 節點1”中的“34”是什麼呢?這個其實是“葉子節點1”中key的尾綴部分,在帕特里夏樹中就是依靠這樣的字首路徑索引來定位到目標節點的。除了“葉子 節點1”,其餘的“葉子節點2”、“葉子節點3”以及“擴充套件節點”也是同樣的索引邏輯。

大家觀察這棵樹的結構,可以發現這些節點型別的存在,就是要通過共享字首的方式來充分提高存取效率,而且樹的結構比較緊湊均衡,下圖為各節點的key列表:

image.png

(3)十六進位制字首


事情到這裡似乎可以結束了,然而以太坊中的帕特里夏樹還有一個特徵,這個特徵很有意思,如我們在前面看到的,分支 節點的結構很特殊,是一個長度為17的列表,很容易判斷出來,但是擴充套件節點和葉子節點的長度都是2(節點的型別判斷在下一節中有詳細描述),那麼對於都是 具備(key,value)特徵的葉子節點和擴充套件節點,怎麼去區分呢?很簡單,那就是在這兩種節點的key部分增加一個字首,一個十六進位制字元的字首,通 過這個字首字元用來表示節點是葉子還是擴充套件,除了用來判斷型別外,還順便用來編碼表示key長度的奇偶性,具體如下:

1)十六進位制長度的字元使用4位二進位制表示,也就是半個位元組,在這個4位二進位制碼中,最低位用來表示key長度的奇偶性,第二低位用來表示是否終止(1表示終止,也就是葉子節點,0表示擴充套件節點)。

2)這個半位元組的字元由如下4種編碼組成:

image.png

我們來看以下示例圖:

image.png

增加這個特殊的十六進位制字首並不是屬於節點key的一部分,而僅僅是在構建樹結構的時候附加上去的,我們知道整棵樹表示的資料在網路中傳遞的時候就是一個列表資料,而樹結構是以太坊客戶端接收到資料後另行構造出來的。

(4)節點型別判斷

補充一點,上述那些節點型別在以太坊中是怎麼判斷的呢?我們來看段ethereumjs中的原始碼片段(ethereumjs是以太坊的JavaScript模擬專案,其中實現的邏輯與以太坊是一致的,方便測試使用)。

image.png

可以看到,就是通過一個簡單的長度來判斷的,長度是17的就是分支節點,因為分支節點是一個包含17個字元的列表;長 度是2,可能是葉子節點也可能是擴充套件節點,因為這兩個節點都是(key,value)的組合,也就是包含2個元素的列表。對於葉子節點和擴充套件節點的區分, 就是根據那個特殊的十六進位制字首,邏輯原理上面已經介紹過,不再贅述。

7.燃料

在以太坊中這個概念稱為Gas,可以理解為在以太坊平臺上執行程式需要付出的成本或手續費。在比特幣中也有類似的 概念,我們在轉賬一筆比特幣的時候,為了鼓勵礦工儘快將我們的交易打包,會設定一定的手續費。以太坊中只不過是擴充套件了這個概念,在以太坊中建立合約、執行 合約等操作都需要支付費用,這個費用的目的也並不只是用來激勵礦工,還能約束以太坊中合約的執行復雜度。我們知道以太坊中支援的合約程式語言是圖靈完備 的,不像比特幣只能進行一些簡單的壓棧出棧等操作。如果在以太坊中編寫一個步驟很複雜,甚至是一個惡意的死迴圈合約,該怎麼來對這樣的任性行為做一個約束 呢?那就是Gas的作用了,Gas是通過以太坊中合約的執行計算量來決定的,這個計算量可以簡單地認為是算力資源的消耗,比如執行一次SHA3雜湊計算會 消耗20個Gas,執行一次普通的轉賬交易會需要21000個Gas,諸如此類,在以太坊中只要是會消耗計算資源的步驟都有個標價。

站在技術和經濟的角度來看,通過Gas機制,可以鼓勵大家編寫更為緊湊高效的合約,避免死迴圈計算的執行步驟,根 據Gas單價設定打包優先順序順序等,這是一種自動化的約束機制,以太坊作為一種公有區塊鏈系統,在去中心自治的前提下,通過一個簡單的Gas,讓程式碼的執 行具備了成本,從而使得以太坊網路不再是一個簡單的軟體網路系統,而且是一個具有金融管控能力的系統。Gas並不等於以太幣,這裡有個公式需要說明一下,以太幣總額=消耗的Gas×Gas單價,Gas單價是可以自己設定的,以太坊客戶端一般會設定一個預設 的Gas單價(0.05e12wei)以方便使用。在有些操作過程中,比如通過以太幣來購買某個投資代幣,為了搶奪到優先的打包順序,往往會設定一個較高 的Gas單價。當某個賬戶在發起一個合約操作時,如果執行過程需要的Gas大於賬戶的餘額,則執行過程會被中斷回滾。

來源:我是碼農,轉載請保留出處和連結!

本文連結:http://www.54manong.com/?id=82

'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })();
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();

相關文章