Ethereum - 利用 web3j 開發以太坊相關應用教學
在開始介紹之前,建議如果對 Ethereum Blockchain 還不太熟悉的話,可以先去看一下相關文獻。我如果有空在補 Ethereum Blockchain 本身架構的介紹,區塊鏈本身的底層技術其實不是很好理解,現在大部分的區塊鏈應用比較著重在 dapp 的設計,也就是透過 Smart Contract 來建立出一個分散式應用程式,藉此打破以前中心化應用程式的架構,我也是新手,純紀錄分享走過的坑。
介紹 web3j 程式庫
web3j 是由一個 Java 撰寫的程式庫,主要是用來提供處理以太坊智慧合約及以太坊網路上的節點,簡單來說它是一個 Ethereum Client Library,方便透過該程式庫去與以太坊進行溝通。web3j 除了提供原生 Java 的程式庫也提供可在 android 上進行使用。
安裝 web3j CLI
這個是 web3j 提供 CLI 程式庫,這個是方便我們在 CLI 有一些便捷的指令,其中最重要的是可以在 CLI 中將我們 Smart Contract 編譯出來的.abi、.bin 檔案轉成 Java Class 進行對應,這樣當我們操作區塊鏈上的智能合約就能用 Java Class 對應智能合約進行操作。
在安裝之前要先確保電腦上有 JDK Version8 + 環境,若糾結 JDK 環境要安裝哪個可參考:JDK 安裝教學介紹
Linux 安裝方式
輸入以下指令:
1 | curl -L get.web3j.io | sh |
當安裝完的時候,它會提醒要在輸入以下指令:
1 | source $HOME/.web3j/source.sh |
意思就是幫你把 web3j CLI 的環境變數放置進去,這樣每次在 CLI 使用的時候就不用重新設定環境變數。
接著就能檢查是否安裝成功:
1 | web3j version |
會出現 logo 及版本資訊。
Windows 安裝方式
開啟 Windows 內建的 PowerShell,並輸入以下指令:
1 | Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/web3j/web3j-installer/master/installer.ps1')) |
當下載完成後就會幫你設定好環境變數,因此打開 windows cmd,輸入以下指令就能檢查是否安裝成功:
1 | web3j version |
一樣會出現 logo 及版本資訊。
web3j 基本操作介紹
安裝 web3j
首先 web3j 提供兩種方式安裝:
-
maven
1
2
3
4
5<dependency>
<groupId>org.web3j</groupId>
<artifactId>core</artifactId>
<version>4.5.12</version>
</dependency> -
gradle
1
compile ('org.web3j:core:4.5.12')
這邊的示範採用 Gradle Project~
設定 Ganache 環境
根據我寫的這篇文章內容,先把 Ganache 環境架起來:
Ganache - 快速開發 Ethereum-Blockchain 的工具這樣的做法因為我們要透過 web3j 連接到 Ganache 架起來的節點。
web3j 連接 Ganache
同步連接
直接上程式碼:
1 | public class Main { |
先說當執行這段程式碼,雖然程式可以跑,但是你會發現執行結果出現一個紅色錯誤:
1 | SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". |
這個我的理解是 web3j 內建有用到 slf4j 的一個 log library,這個錯誤是在提醒你,該程式庫沒有被實作,因此你要在額外添加一個 library:
1 | compile ('org.slf4j:slf4j-nop:1.7.25') |
而 maven 就是依照其格式同樣加入該 library 即可。
接著講解上面的程式碼:
- 透過 HttpService 實例化我們要連接的網址,這個網址就是對應於 Ganache 上面的 RPC Server 的網址。
- 此外,該程式碼是同步化的作法,也就是它會一直 block 住,直到拿到 response,因此會採用 send ()
- 這邊就是進行連線然後拿取 web3ClientVersion 而已。
輸出結果:
1 | EthereumJS TestRPC/v2.8.0/ethereum-js |
實際上這個就是 Ganache 底層是透過這個來建立起 RPC 節點的。
異步連接
1 | public class Main { |
這邊是使用異步連接,所以你會發現是使用 sendAsync (),當執行這段程式會發現一樣會出現上面的輸出結果,而如果今天是一個耗時的操作是不會 block 住的會直接繼續往下執行其它程式碼。
開啟 Remix 編輯 Smart Contract
這邊突然跳到這邊,是因為要介紹如何透過 web3j 去佈署 smart contract 及連接 smart contract。這邊就需要先寫一些 solidity。
這邊講一下,Remix 是一個線上 IDE,就是一個 web 網頁可以編輯 solidity code。
1 | pragma solidity ^0.4.25; |
這邊直接拿 web3j 官方提供的範例 XD,這邊不多加講解 smart contract code 在做甚麼,因為我覺得過於簡單,不熟悉語法可以再去參考官方文檔。
編譯 solidity
- 直接透過 remix 編譯
- 在電腦上安裝 solc 編譯器,在電腦上自己手動編譯。
我個人是推薦第一種,因為 remix 寫 code 比較方便所以一併編譯會比較理想。
透過 remix 編譯 solidity 可以在編譯頁面看到:
假設該 smart contract 叫做 Greeter.sol。在左邊可以看到編譯按鈕,當編譯完成後看到左下方的 ABI 跟 Bytecode,可以各自複製,這時候需要將這兩個複製的內容個創建兩個檔案:
- Greeter.abi
- Greeter.bin
因為等等要透過 web3j CLI 來針對這兩個檔案產生 smart contract 對應的 Java Class。
web3 CLI 轉換 smart contract
將 Greeter.abi、Greeter.bin 放置專案根目錄下,並執行以下指令:
1 | web3j solidity generate -b ./Greeter.bin -a ./Greeter.abi -o ./src/main/java -p kenny.home |
- -b:Greeter.bin 的檔案路徑
- -a:Greeter.abi 的檔案路徑
- -o:產生出來對應的 Java 檔案要放置在哪
- -p:你的專案 package name
簡單來說你產生出來的 java 檔案會放在 - o 路徑下的 - p 下。
產生出來的 Java 檔案如下:
1 | public class Greeter extends Contract { |
恩… 非常的龐大,基本上你不太會需要去看裡面的程式碼實作,而是知道怎麼呼叫這 class 裡面的 method 就可以了,因為它就是對應於 smart contract 定義的函式,當你呼叫這邊,就是幫你呼叫 smart contract 的函式。所以其實要理解的是你 smart contract 函式怎麼實作的~
web3j 佈署 smart contract
1 | public class Main { |
-
首先要知道部署 smart contract 是需要用到帳戶資訊的,由於我們這邊是採用 Ganache 架設環境,因此這邊是直接拿帳戶的私鑰當字串建立 Credentials 物件。
-
如果不是用 Ganache,也可以用以下的方式:
1
Credentials credentials = WalletUtils.loadCredentials("password", "/path/to/walletfile");
-
然後需要建立 ContractGasProvider 物件,因為佈署 smart contract 需要設定 Gas Price 跟 Gas Limit,這邊直接拿 Ganache 提供的預設值的數字。
-
接著透過剛剛產生出來的 Greeter Class,建立該物件,事實上就是對應於 smart contract,利用 deploy (),進行佈署,而 provider 後面的參數是代表可以填入 smart contract 建構子的值。
-
透過 getContractAddress () 可以取得合約位址,如果佈署成功就會成功取到。
但是執行這段程式碼可能會得到以下錯誤:
1 | Caused by: java.lang.RuntimeException: Error processing transaction request: VM Exception while processing transaction: stack underflow |
這個原因是因為透過 web3 CLI 產生的 Java 檔案裏面有一些錯誤。這可能是 web3j 潛在的 bug。
首先看 Greeter.java 裡面的一個 Binary 變數:
1 | private static final String BINARY = "{\r\n" |
對就是這麼長,正確的話應該裡面只會有 object 裡面的值,因此改成:
1 | private static final String BINARY = "608060405234801561001057600080fd5b506040516106c23803806106c283398101806040528101908080518201929190505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060019080519060200190610089929190610090565b5050610135565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100d157805160ff19168380011785556100ff565b828001600101855582156100ff579182015b828111156100fe5782518255916020019190600101906100e3565b5b50905061010c9190610110565b5090565b61013291905b8082111561012e576000816000905550600101610116565b5090565b90565b61057e806101446000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806341c0e1b51461005c5780634ac0d66e14610073578063cfae3217146100dc575b600080fd5b34801561006857600080fd5b5061007161016c565b005b34801561007f57600080fd5b506100da600480360381019080803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091929192905050506101fd565b005b3480156100e857600080fd5b506100f161040b565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610131578082015181840152602081019050610116565b50505050905090810190601f16801561015e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156101fb576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b565b806040518082805190602001908083835b602083101515610233578051825260208201915060208101905060208303925061020e565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020600160405180828054600181600116156101000203166002900480156102bd5780601f1061029b5761010080835404028352918201916102bd565b820191906000526020600020905b8154815290600101906020018083116102a9575b505091505060405180910390207f047dcd1aa8b77b0b943642129c767533eeacd700c7c1eab092b8ce05d2b2faf560018460405180806020018060200183810383528581815460018160011615610100020316600290048152602001915080546001816001161561010002031660029004801561037b5780601f106103505761010080835404028352916020019161037b565b820191906000526020600020905b81548152906001019060200180831161035e57829003601f168201915b5050838103825284818151815260200191508051906020019080838360005b838110156103b557808201518184015260208101905061039a565b50505050905090810190601f1680156103e25780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a380600190805190602001906104079291906104ad565b5050565b606060018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104a35780601f10610478576101008083540402835291602001916104a3565b820191906000526020600020905b81548152906001019060200180831161048657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106104ee57805160ff191683800117855561051c565b8280016001018555821561051c579182015b8281111561051b578251825591602001919060010190610500565b5b509050610529919061052d565b5090565b61054f91905b8082111561054b576000816000905550600101610533565b5090565b905600a165627a7a72305820c9ef43bfc7be6a77def404c505dcd9ae4f88a22c2a5b9ecaa581c675a2993b700029"; |
這個錯誤也有可能是因為透過 remix 編譯出來的問題,因為如果在電腦上透過 solc 編譯器進行編譯後,在用 web3 CLI 轉換不會遇到這種錯誤。
因此如果透過 remix 的話要記得修改 BINARY 變數裡面的值。
修改好後,再度執行程式碼:
1 | 0x03bf993df7c8811d00efe210ff582544ff245465 |
就會修出合約位址,可以去 Ganache 那邊去進行確認是否正確:
可以看到位址是一樣的~
web3j 載入 smart contract 並呼叫相關函式
1 | public class Main { |
由於剛剛成功佈署上去了,因此之後要獲取該 smart contract,就透過 load () 即可,裡面要放置 smart contract 的 address。
再來就可以呼叫 contract 裡面的函式,裡面有個 greet (),可以獲取一個字串值,記得要加 send (),代表發送 request。還記得前面建構仔我們是放 "test" 字串,因此理論上輸出結果會是這個字串。
我們在加入這兩行程式碼:
1 | TransactionReceipt transactionReceipt = contract.newGreeting("Hello World").send(); |
由於只是讀取 smart contract 的值並不會有交易產生,而如果要有交易產生,可以呼叫 newGreeting (),這個是去修改剛剛輸出 test 值的變數。而產生的 TransactionReceipt 物件,事實上就是該交易的相關紀錄。
因此再度執行該程式碼,輸出結果應為如下:
1 | test |
web3j 監聽 smart contract 的事件
1 | public class Main { |
由於在實際的需求時常會有監聽 smart contract 的操作,因此這邊特別提出來講,可以透過 EthFilter 物件來達成。
也就是建立該物件需要三種參數:
- 從哪個區塊開始監聽
- 監聽到哪個區塊
- smart contract address
1 | contract.modifiedEventFlowable(filter).subscribe(log -> System.out.println(log.newGreeting)); |
這段程式碼這是開啟異步監聽,也就是執行到這裡並不會被 block 住,而是繼續往下執行程式碼,直到如果 smart contract 對應的事件發生,也就是針對 greeting 的值修改,就會監聽到,一旦監聽到這邊的操作就是會輸出結果。
當執行該程式碼後,此刻你有兩種做法測試:
- 寫另外一個 Java 專案,並且是拿來提交交易
- 使用 remix 去做以上的事情
由於方便,我們可以直接透過 remix 去操作:
畫面轉到這邊,可以看到 remix 這邊可以 load smart contract 等操作,其實就等同於在 web3j 的操作一樣。
在左邊 At Address 填入合約位址,就可以成功 load 進來,因此在左下方看到該 smart contract 的函式,透過點 greet 及可讀取該便數的值,並顯示在下面如 string: text。
因此我們可以在 newGreeting 填入新的值,一旦提交此操作便會形成一個交易並觸動事件,照理說你的 Java 程式就會監聽到該操作,並且將值輸出。
假設將值填入 Hello World。
基本上,輸出結果會輸出 test 跟 Hello World。
首先要知道以下幾件事情:
- 之所以會秀 test,是因為我們是監聽從第 0 塊到最後一塊,因此前面的交易紀錄會被輸出
- 你會發現 Hello World 並不是及時輸出,而是需要等待幾秒才會輸出,沒錯這是因為我們是用 HttpService 去連接而不是用 webSocket 的機制,無法讓 RPC Server 主動 push 給我們知道,HttpService 是透過輪詢 (polling) 的方式去得知,所以其資源耗用多也不即時。
web3j 連接 webSocket
1 | public class Main { |
其實改成 websocket 也是很簡單的,就是要加上前面的一些程式碼,初始化 web3j 物件則改成丟進去 WebSocketService 物件。而 webSocketService.connect () 要記得加上,否則會出錯。
這邊主要是提供兩種 push 回來的資訊:
- Block Head
- Transaction log
但關於 websocket web3j 提供的功能在文檔寫得不甚清楚,因為好像是新出的功能,需要在鑽研一番~~
總結
基本上如果你是要開發 Ethereum Blockchain 的相關應用,透過 web3j 提供的功能大部分都能做到了,不過因為官方文檔也沒有寫得很清楚,需要多多去研究。
基本除了 web3j 的選擇,還有 web3 (JavaScript)、Golang (Ethereum Client) 原生。
下篇文章帶來如何運用在 quorum 區塊鏈上,因為有另外的程式庫替 web3j 添加了 quorum 的特性,但其實其它使用方法就如同這篇文章操作一樣。quorum 最特別的就在於可以發送私人交易給對方~才是最值得探討的。
最後最後!請聽我一言!
如果你還沒有註冊 Like Coin,你可以在文章最下方看到 Like 的按鈕,點下去後即可申請帳號,透過申請帳號後可以幫我的文章按下 Like,而 Like 最多可以點五次,而你不用付出任何一塊錢,就能給我寫這篇文章的最大的回饋!