編者按:本文系 以太坊佈道師 王仕軍講師,在由掘金技術社群主辦,以太坊社群基金會、以太坊愛好者與 ConsenSys 協辦的《開發者的以太坊入門指南 | Jeth 第一期 - 北京場》 活動上的分享整理。Jeth 圍繞以太坊技術開發主題的系列線下活動。每期 Jeth 會邀請以太坊開發領域的優秀技術團隊和工程師線上下分享技術乾貨。旨在為開發者提供線下技術交流互動機會,幫助開發者成長。
王仕軍老師本次分享視訊回放(B站)
分享整理傳送門
詳解 ERC20 代幣及眾籌 - 熊麗兵 | Jeth 第一期
王仕軍 是掘金專欄和掘金小冊作者,他著作的《區塊鏈開發入門:從 0 到 1 構建基於以太坊智慧合約的 ICO DApp》銷量近千本;他創辦和維護的微信公眾號“前端週刊”目前有2600+訂閱數;此外他還是 async/await、styled-components 高質量技術視訊教程的作者。
先跟大家介紹下我的背景,我是從去年五月開始學習區塊鏈的,在場的同學可能學習區塊鏈時間有比我還長的,所以我跟大家是在同一條起跑線上。我是做前端出身,對 JavaScript 非常熟悉,今天這個分享裡的程式碼除了 Solidity 其他的都是 JS 寫的。如果做這開發時間比較長,會了解到語言間的差別其實沒有那麼大,外界有說 Solidity 跟 JS 很像,但實際上 Solidity 語言創造的時候借鑑了 Golang、 Python 和 Javascript。我所要介紹的內容就是在我接觸以太坊開發的這一段時間內,所積累出來的,怎麼把所有區塊鏈有關的點串起來做成一個可以用的應用,在座的可能看了不少資料,但是真正動手做東西的可能很少,接下來我就正式開始我的分享。
在講具體的事情之前我們站在軟體開發這的角度來看一下區塊鏈在電腦科學裡面,或者說區塊鏈的技術棧構成到底是什麼樣的,區塊鏈最底層依賴於 OS,接下來算是基礎設施層,這裡面有 TCP/IP 協議和密碼學。2017年 - 2018年期間爆發的是區塊鏈協議這一層,包括分散式、共識演算法,其中共識演算法現在搞的人非常多,但是實際上在分散式裡面已經有很成熟的研究。在應用這一層現在最成功的應該當屬於以太坊,它引入是智慧合約的概念以及DApp。最上面的是表現層,這個是現在所有的網際網路產品都具有的東西,比如說你做了一個網站,一個H5頁面,甚至是做了一個APP,甚至是給開發者提供一些API或者是命令列的介面。憑我自己對這個行業的觀察,我覺得不光是前端工程師,所有做應用的工程師可以介入的點就是應用層和表現層,只不過對前端工程師來說做表現層有天然的優勢。
接下來是具體我要分享的內容:
- 要做出一個完整的區塊鏈 DApp 你需要掌握的最少必要知識;
- 怎麼準備開發環境,開發環境需要有什麼必要的條件;
- 如何在我們熟悉的開發環境裡面把智慧合約整合進來;
- 怎麼做一個簡單的DApp並且讓他上線給人們去使用。
最少必要知識
相信大家都聽過賬戶、錢包、區塊、區塊鏈等沒名詞,但是怎麼把這些東西串在一起,真正去琢磨、研究的人可能不多。首先回顧一下Howard 老師分享的區塊、和區塊鏈的結構。每個區塊鏈平臺裡面都會有帳戶的機制,最成功的區塊鏈應用大家應該都知道,叫做比特幣,比特幣可以認為是去中心化的銀行,那不同的帳戶之間會有轉帳交易,一段時間之內所有的轉帳交易彙集在一起形成一個區塊,區塊的資料結構上面也有介紹,我就不展開講了。
隨著時間的推移多個不同的區塊通過一些特定的方式連起來,就形成了區塊鏈,區塊鏈上每一個區塊可以認為是編號,這個編號就是塊高,那不同的鏈產生一個區塊所需要的時間是不一樣的,那這個時間是出塊時間,現在比特幣大概在10分鐘左右,而以太坊需要10多秒。
至於以太坊,它本質是個分散式網路,所有的訊息包括轉帳、合約部署、以及合約介面呼叫都要通過一個節點,節點接收到訊息然後把他廣播給網路中的所有節點,然後當交易被打包之後出來的區塊同樣也會被廣播給網路的所有節點。
那怎麼跟以太坊的網路互動呢?拿現在我們比較熟悉的微信小程式舉例,開發者可以通過微信小程式提供的特定框架、小程式的管理後臺去創造小程式,普通使用者可以在微信 APP 裡面使用小程式。騰訊的伺服器他本身是中心化的,只有騰訊自己去維護;對應到以太坊的網路裡面,社群給開發者提供的工具很多,可以用 web3.js、web3j、web3.swift,也可用 etherscan,這些工具或者語言包通過某一個節點作為入口與以太坊網路互動,使用者通過瀏覽 DApp 或者錢包來和網路互動。
以太坊的帳戶和比特幣的帳戶最本質的結構是很類似的,包含了三個要素:私鑰、公鑰和地址。以太坊除了主網之外然後還有三個測試網,它的主網我們可以理解為傳統軟體開發環境裡面的線上環境,Rinkeby、Kovan 和 Ropsten 是三個測試網路,這是三個測試環境是大家都可以公用的,後面的實戰案例會用到。
說完帳戶,另外一個大家很熟悉的概念是區塊鏈錢包,那比如說是像 imToken 或者是 Bitpie,還有後面我們要介紹的 Metamask,錢包和帳戶之間有什麼關係呢。我們用這個我們現在所熟知的金融系統裡面的錢包和帳戶來類比一下,就比如說是我在招商銀行和建設銀行開了兩三個帳戶,那我有一個錢包裝在我兜裡面的,我實際上持有5張銀行卡,對於區塊鏈裡面我安裝了一個位元派錢包,這個錢包裡面有以太坊的帳戶和比特幣的帳戶,以太坊的帳戶我可以有很多個,比特幣的帳戶也可以有很多個。
智慧合約本質上是一個被程式碼控制的帳戶,這個帳戶本身和你在錢包裡面所擁有的帳戶是相同的,不同的是你所擁有的帳戶的私鑰掌握在你的手裡,智慧合約的則是掌握在合約部署者的手裡。關於智慧合約,可以用我們非常熟悉的物件導向的概念做個類比:我寫了一個類 Class,那我可以生成很多的例項,然後在區塊鏈世界裡面我有一份智慧合約原始碼,可以部署到上面介紹的幾個以太坊網路上面,每部署一次產生的合約例項都是不一樣的,是完全不同的帳戶,這個應該是很多做 DApp 開發的工程師迷惑的地方,也是智慧合約不能升級的原因。現在也有一些比較 “Trick” 的方法可以完成合約的熱更新,剛興趣的同學可以自己去搜。
智慧合約的原始碼大多數情況下是用 Solidity 編寫的,合約帳戶和普通帳戶之間的關係用上面這張圖說明一下,開發者通過向以太坊網路傳送一個交易建立合約例項,拿到合約例項地址,有了合約例項地址之後普通使用者可以和合約互動。比如說是 EOS 眾籌時,實際上是在以太坊上釋出了一個智慧合約,每天都有人蔘與進去,參與時把代幣取回來的時候就是在調對應的合約。合約和合約之間也是可以互動的,合約帳戶是被稱為內部帳戶,而普通使用者帳戶通常被稱為外部帳戶。
開發環境準備
接下來是第二個主題 —— 開發環境的準備。需要在以太坊這個區塊鏈上做開發,要有什麼條件才可以開始呢?只有兩個關鍵點:因為它是P2P網路,交易、合約部署都需要節點,就是說你需要有一個節點,然後任何活動都需要有帳戶,即使說你調一個不花錢的合約方法也是需要帳戶的。
具體點來說,第一個必要條件是測試網路節點。有好多種方法,第一個自己跑一個節點,稱其為私鏈可能不夠準確,那這個方法實施或者是理解的成本對於不做底層的同學稍微高一點,不建議做應用層的同學採用;如果說想把控制權掌握在自己手裡的話,那還是非常建議做這個事情。
本地開發除錯可以使用 Ganache,方便地在本地起一個節點來處理交易,還有 Remix,它提供在瀏覽器內部的 JavaScript 測試網路。成本非常低,開啟就可以用,本地測試非常的方便,Ganache 和 Remix Javascript VM 內建了已經解鎖的帳戶,不需要去關心帳戶的私鑰或助記詞。
還可以使用共享測試網路,就是前面提到的 Rinkeby、Ropsten 和 Kovan,使用這幾個作為網路入口的節點,我們不需要自己跑節點。infura.io 則是為廣大開發者提供區塊連結入的服務,不過使用他需要我們有自己註冊、自己管理錢包和帳戶。
這二個必要條件是帳戶和餘額,因為以太坊上的任何操作都需要帳戶才能夠發起,所以我們需要建立錢包和帳戶。我們開發的 DApp 是執行在瀏覽器裡面的,對於 PC 端來說錢包最好是能和瀏覽器無縫整合的。目前社群中有個很好的選擇是 Metamask,它實際上是一個瀏覽器外掛,但是 Metamask 歷史上出了一個安全事故,有人發了個假的 Metamask,有一部分的使用者上當了,因此大家安裝的時候一定要認準狐狸圖示。
另外一點以太坊上的很多交易都是要收費的,這個也是它現在最大的痛點,部分割槽塊鏈專案在解決這個問題,目前我們開發還沒有辦法繞過他。在做DApp 測試的時候我們不需要去花費真金白銀,可以使用不同的測試網提供的 faucet 給測試網的帳戶充值,即把 ETH 充到 Metamask 錢包裡面。以上就是準備開發環境的兩個必要的條件,需要動手做的就是 Metamask、充值還有註冊 infura.io。
智慧合約工作流
接下來是如何做一個完整的應用。對應到傳統的應用開發,先要有一個後端然後有一個前端,我們先介紹後端部分。後端在以太坊上面可以粗暴的用智慧合約代替,複雜應用中所有的資料應該不是全部儲存在以太坊區塊鏈上,有一部分資料是存在傳統的資料庫裡面,這裡我們簡化設定所有資料都存在鏈上。
做智慧合約開發有兩種方式,第一種是通過 Remix 線上 IDE ,這個 IDE 還算好用。另一種方式,對於前端來說,就是用你熟悉的編輯器加上你熟悉的語言去做。
Remix 適合做我們快速的驗證概念和原型,在 Remix中可以快速寫合約程式碼,然後呼叫它的合約介面,測試它的行為,此外還可以測試已有的合約例項,我們可以從以太坊的線上環境和測試環境把合約例項載入到 Remix 裡面然後測試,也可以通過 Remix 把合約部署到任何以太坊網路上面。Remix 還可用來做單步除錯,當你發現合約某一些介面有奇怪問題的時候可以用 Remix 做單步除錯。但是 Remix 有個明顯的缺點,在傳統的軟體開發工作流裡面,通常會有版本管理,在 Remix 裡是做不了的,但在我們自己的工作流裡面是可以的,我們可以用 Git 做版本管理,然後儲存合約編譯部署的結果,最重要的一點就是能把可以自動化的都自動化了,因為自動化之後出錯的可能性會小很多。通常在實際的工作當中或者是說學習過程當中這 Remix 和自動化工作流會結合使用,來回切換使用。
接下來就是實戰 DApp 裡面用到的非常簡單的智慧合約,可以認為這是一個以博彩原型,程式碼非常簡單,合約有兩個屬性,合約的管理員,以及誰參與了博彩活動。建構函式作用是設定合約管理員,然後參與抽獎/下注介面,再然後是隨機數函式,在以太坊區塊鏈裡面沒有非常好的隨機數的方法,合約部署到以太坊上之後隨機數的程式碼別人是可以看到的,它能夠知道你種子是怎麼來的,然後很容易被操控。pickWinner 是開獎介面,裡面呼叫了隨機數的函式,然後把整個獎池裡面的錢都轉給贏家。合約本身是不收任何手續費的,只不過是在呼叫介面的時候收手續費。modifier 是做了安全的限制,開獎的介面只能是合約的管理員才可以呼叫。安全、許可權也是智慧合約要重點考慮的問題,最近兩個月爆發出來的合約的漏洞非常多,有一部分安全限制,還有一部分是溢位,值得大家關注。
合約在 Remix 裡面的工作流,簡單給大家演示下,在 Remix 裡選 JavaScript VM,它是 Remix 提供的跑在瀏覽器記憶體裡的一個測試網路,它的響應速度非常快,選擇 JavaScript VM 之後預設這有幾個帳戶,裡面的餘額是 100 ETH,點選 Deploy 把合約部署一下,可以看到很快合約例項就有了,例項介面中紅色的是合約介面,藍色是合約屬性。然後我們怎麼去下注呢?
可以看到下注並不需要提供引數,下注金額需要在發起交易的地方填寫,用合約管理員下注一次,現在玩家有一個人,下面換一個帳戶,再下一次注,就是模擬兩個人,這裡就變成了兩個人。用第二個帳戶去調開獎介面時候直接報錯了,那是因為我們的合約程式碼裡面強制了必須是管理員才可以呼叫,失敗的例證就是沒有人拿到獎池裡面的錢,把帳戶切回去重新調開獎介面,可以看到第一個人拿到了獎池裡面所有的錢,這個是非常簡單的 Remix 合約工作流演示。
那怎麼把這個 Remix 裡面做的事情自動化?接下來我介紹一下怎麼在 Node.js 裡面做智慧合約的工作流。我先介紹兩個工具,第一個是把智慧合約的原始碼編譯,編譯會產生位元組碼 ByteCode,這個是部署到測試網路時用的;以及介面宣告 ABI,通過 ABI 實際業務程式碼就能知道這個合約到底暴露了哪些介面,每個介面接收引數的型別和數量。
接下來工作流裡做的事情就是圍繞這個圖展開的,可以看到這裡面用到了 web3.js,可以把 web3.js 理解為應用層的程式碼通向以太坊網路的一個橋樑。它作為橋樑的方式是可以使用很多不同的外掛,在 web3 裡面叫 Provider,我在瀏覽器當中執行時,Metamask也提供了一個外掛;在本地的話,Ganache-cli 提供了一個外掛;如果只想呼叫 infura.io 提供的入口節點,那可以通過 HTTP Provider。
我們要做的第一步把智慧合約的原始碼變成可以部署到以太坊網路上的 ByteCode 以及我們應用層程式碼可以使用的 ABI。編譯指令碼的程式碼也很簡單,我們可以把檔案讀出來,準備一個結果儲存目錄,然後呼叫 solc 的 compile,接下來會處理編譯結構裡面的錯誤,最後把編譯結果寫到檔案系統裡面。
合約構建完之後怎麼通過 web3 測試它?直接使用 Ganache-cli 就行了,我們在測試合約程式碼之前,要初始化 web3 的例項,直接使用 Ganache的 Provider 外掛。測試裡面我們會先建立一個隔離的測試環境,即跑每個測試的時候我們會重新部署合約,然後呼叫這個新合約上的方法,或者是嘗試修改他的狀態,最後對他的狀態做斷言。
接下來是部署合約,在建立 Contract 的時候我們要把 ABI 傳進去,在 Deploy 裡把它的 ByteCode 傳進去,可以看到我們做任何事情都需要有帳戶,gas 是我們願意為這個操作最多支付多少的手續費。關於 gas 有兩個引數調節,一個是 gas limited,另一個是 gas pressed,感興趣的同學可以自行去研究。
再來看一個抽獎合約完整的流程測試,先呼叫下注介面,用得還是解鎖出來帳戶裡面第一個,然後取出來合約的玩家,確認玩家是我們下注過的人,接下來檢查帳戶 initialBalance 和賬戶抽獎完成之後的餘額狀態。在這個地方呼叫了開獎介面,開獎之後對於部署合約的帳戶的餘額做了一個斷言,最後是我們斷言中每次開獎之後這個合約裡面的玩家要被清空。
合約部署所需要做的事情跟合約自動化測試時做的事情有很多相似的地方,不過部署的網路不是 Ganache-cli 提供的本地網路,而是 Rinkaby 測試網路,這裡用到了一個外掛,我們可以提供一個錢包的助記詞,以及一個網路入口的節點,它通過 HTTP 的方式給節點傳送訊息進行交易。接下來做的事情跟每次隔離測試環境類似,先解鎖帳戶,然後部署合約。因為我們是部署在真實的測試網路上,這個過程通常花費的時間比較長。每次部署完之後我們會有一個合約帳戶的地址,我們可以通過這個地址跟合約互動。
最後是把整個流程串起來的過程,部署之前必須要重新編譯,並且確保所有單元測試在最新的合約上是可以完全通過的。
DAPP構建和部署
合約部署完之後,我們在以太坊的區塊鏈上已經有一個我們可以直接與他互動的後端了,那接下來我們需要寫的就是做這個應用層的程式碼和後端的互動,以及給DApp加上介面。
從技術視角來看這個DApp的本質,我們這沒有討論商業和其他方面的東西,就是 DApp 可以理解為就是小白使用者可以使用的,可以和前端資料互動、讀取的介面。它的形態可以是很多種,可以是 Web、App,也可以是桌面軟體。如果是非常簡單的 DApp 你可以做成以區塊鏈為後端的單頁應用。
接下來我們做一個非常簡單的抽獎或者是博彩的 DApp,可以使用 create-react-app,使得我們的技術棧變得非常簡單。還有一個是 web3.js,合約工作流裡面用的比較多,在 DApp 裡面用 web3.js 是跟 ABI 打交道。如果開發完,整個合約加上 DApp 的目錄結構如同上圖的,編譯部署指令碼和測試指令碼分別放在對應的目錄下面。
抽獎 Dapp 可以說是相當簡單,甚至相當簡陋,我們可以把介面對應到合約的原始碼上面,現在獎池的金額有 1 個 ETH ,有 1 人蔘與,但這個 Balance 屬性在之前的合約程式碼中沒有提到,實際上合約例項是一個帳戶,那在任何的一個帳戶上面都是有餘額的,共有 1 個人參與抽獎,有兩個按鈕,一個是下注,呼叫的的合約介面是 participate;還有就是開獎,對應的介面是 pickWinner。
下面我們來看一下大概的關鍵程式碼實現。第一個就是使用 web3 把我們的橋樑建起來,這裡面我們假設使用這個 DApp 的使用者安裝了 Metamask,第二個關鍵的地方是我們新建這個合約的時候不像部署和編譯指令碼,傳入了一個地址,這個不是完全新建的合約例項,而是從這個地址把這個合約載入進來。
DApp 和智慧合約關鍵的互動就是兩點,一個是讀取合約資料,還有一個就是提交資料。那可以看到這裡面我們在介面上顯示三個屬性,一個是管理員,一個是玩家的數量還有一個是合約裡面獎池的金額,也就是合約例項他的帳戶餘額,這裡面調的三個介面都是非同步的。
渲染合約資料因為是 React 語法也比較簡單,DApp 開發的同學一定要注意單位之間的相互轉換,我們展示給使用者的是ETH。
修改合約資料有一個是觸發點在下方,給這個按鈕綁 onClick,裡面做的事情實際上是調取了合約的方法然後把元件狀態做了一些變更。在提交交易的時候那我們在元件上設定 loading 狀態,loading 狀態下按鈕是不能重新點選的,然後調 participate,如果安裝了 Metamask,傳給合約的值要填寫進去,等我們這個操作完成後,我們會把這個頁面重新整理一下,合約資料狀態會被重新載入。
真正要做出使用者友好的 DApp,需要在這個基礎上做很多的事情,比如說點選按鈕並安裝了這個 Metamask 的之後,使用者的螢幕上馬上就彈出了一個 Metamask 交易確認介面,會使使用者疑惑,我明明在用你這個網站怎麼出來了一個我沒有見過的東西,彈出 Metamask 交易確認框過程當中需要給使用者一些提示,讓使用者不會感覺到意外。
抽獎 DAPP DEMO
抽獎 DApp 流程我已經部署到我在阿里雲上的一臺機器上面,部署的過程跟傳統的 WEB 應用的部署類似,部署前需要對程式碼做構建,構建使用 create-react-app 內建的構建指令碼就可以了,構建產生的是純靜態檔案,然後我們用 express 來啟動極簡的 http 服務,管理服務程式使用 pm2,部署和構建的過程也可以使用 npm script 串起來。
結束語
到這裡我要分享的內容就結束了,區塊鏈領域雖然已經存在了 9 年,但是開發者大量湧入是在最近這一兩年的時間,這個領域裡面有很多的概念的同時,也有比較好的實踐,歡迎大家在微信群裡跟我多多交流,謝謝。