標籤:資訊 智能合約 背景 分享 object rom ext gtest _id
編寫第一個 Java 鏈代碼程式
在上一節中,您已經熟悉了如何構建、運行、部署和調用鏈代碼,但尚未編寫任何 Java 代碼。
在本節中,將會使用 Eclipse IDE、一個用於 Eclipse 的 Gradle 外掛程式,以及一個名為 ChaincodeTutorial 的 Java 鏈代碼架構項目,編寫第一個 Java 鏈代碼程式。您將從我為此教程建立的 GitHub 存放庫中擷取架構代碼,將該代碼匯入 Eclipse 中,添加代碼來讓鏈代碼智慧合約按要求生效,然後在 Eclipse IDE 內使用 Gradle 構建該代碼。
您將執行的步驟如下:
- 安裝適用於 Eclipse 的 Gradle Buildship 外掛程式。
- 從 GitHub 複製 ChaincodeTutorial 項目。
- 將該項目匯入 Eclipse 中。
- 探索該鏈代碼架構項目。
- 編寫 Java 鏈代碼。
- 構建 Java 鏈代碼。
完成本節後,您的鏈代碼就可以在本地區塊鏈網路上運行了。
1.安裝適用於 Eclipse 的 Gradle Buildship 外掛程式
您使用自己喜歡的任何 IDE,但本教程中的說明是針對 Eclipse 的。備忘:Buildship Gradle 外掛程式有助於將 Gradle 與 Eclipse 整合,但仍然需要將 Gradle 安裝在電腦上。
如果您一直在按照教程進行操作,那麼您應該已經將 Gradle 安裝在電腦上;如果尚未安裝它,請立即安裝。請參閱 “安裝構建軟體” 部分,瞭解如何將 Gradle 安裝在電腦上。
在 Buildship Gradle Integration 下,單擊 Install 按鈕並按照提示進行操作。單擊 Finish 後,將安裝適用於 Eclipse 的 Buildship Gradle 外掛程式,而且會要求您重啟 Eclipse。
重新開啟 Eclipse 後,Gradle 應該已經與 Eclipse IDE 全面整合。您現在已準備好從 GItHub 複製 ChaincodeTutorial 存放庫。
從 GitHub 複製 ChaincodeTutorial 項目
配置 Eclipse IDE 和 Gradle整合後,將從 GitHub 複製 ChaincodeTutorial 代碼並將其匯入 Eclipse 中。開啟一個命令提示字元或終端視窗,導航到 $GOPATH 並執行以下命令:
git clone https://github.com/makotogo/ChaincodeTutorial.git
命令輸出應類似於:
$ export GOPATH=/Users/sperry/home/mychaincode$ cd $GOPATH$ git clone https://github.com/makotogo/ChaincodeTutorial.gitCloning into ‘ChaincodeTutorial‘...remote: Counting objects: 133, done.remote: Compressing objects: 100% (90/90), done.remote: Total 133 (delta 16), reused 118 (delta 1), pack-reused 0Receiving objects: 100% (133/133), 9.39 MiB | 1.95 MiB/s, done.Resolving deltas: 100% (16/16), done.$ cd ChaincodeTutorial$ pwd/Users/sperry/home/mychaincode/ChaincodeTutorial
此命令將 Blockchain ChaincodeTutorial 存放庫從 GitHub 複製到 $GOPATH。它包含一個 Java 鏈代碼架構項目,您可以在本地區塊鏈網路中構建、運行和測試它。
但在執行所有這些操作之前,需要將該代碼匯入 Eclipse 中。
3.將該項目匯入 Eclipse 中
在 Eclipse 中,轉到 File > Import...> Gradle > Existing Gradle Project。這會開啟一個嚮導對話方塊(參見圖 9)。
單擊 Next。在嚮導中隨後出現的對話方塊中(參見圖 10),瀏覽到 $GOPATH/ChaincodeTutorial,然後單擊 Finish 匯入該項目。
完成項目匯入後,確保選擇了 Java Perspective,您剛匯入的 ChaincodeTutorial 項目會顯示在 Project Explorer 視圖中。
將代碼匯入 Eclipse 工作區後,就可以編寫鏈代碼了。
4.探索該鏈代碼架構項目
在本節中,將探索該鏈代碼項目,以便理解在編寫任何 Java 代碼前它應該如何運行。
作為開發人員,我們喜歡編寫代碼,所以我不想讓您失去編寫 Java 代碼的機會。但是,項目設定可能很複雜,我不想讓這些設定阻礙實現本教程的主要目的。為此,我提供了您所需的大部分代碼。
首先讓我們快速查看一下基類 AbstractChaincode,它位於 com.makotojava.learn.blockchain.chaincode 包中,如清單 1 所示。
清單 1. AbstractChaincode 類
package com.makotojava.learn.blockchain.chaincode; import java.util.Arrays; import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.hyperledger.java.shim.ChaincodeBase;import org.hyperledger.java.shim.ChaincodeStub; public abstract class AbstractChaincode extends ChaincodeBase { private static final Log log = LogFactory.getLog(AbstractChaincode.class); public static final String FUNCTION_INIT = "init"; public static final String FUNCTION_QUERY = "query"; protected abstract String handleInit(ChaincodeStub stub, String[] args); protected abstract String handleQuery(ChaincodeStub stub, String[] args); protected abstract String handleOther(ChaincodeStub stub, String function, String[] args); @Override public String run(ChaincodeStub stub, String function, String[] args) { String ret; log.info("Greetings from run(): function -> " + function + " | args -> " + Arrays.toString(args)); switch (function) { case FUNCTION_INIT: ret = handleInit(stub, args); break; case FUNCTION_QUERY: ret = handleQuery(stub, args); default: ret = handleOther(stub, function, args); break; } return ret; } @Override public String query(ChaincodeStub stub, String function, String[] args) { return handleQuery(stub, args); } }
我想指出的第一點是,AbstractChaincode 是 ChaincodeBase 的子類,後者來自該結構的 shim 用戶端(第 7、10 行)。
第 17-19 行顯示了需要在 ChaincodeLog 類(AbstractChaincode 的子類)中實現的方法,這些方法分別用於實現初始化、賬本查詢和日誌功能。
第 22-36 行顯示了 ChaincodeBase 類(來自鏈代碼 shim 用戶端)的 run() 方法,我們可以在其中查看調用了哪個函數,以及該調用應委託給哪個處理函數。該類是可擴充的,因為 init 和 query 以外的其他任何函數(比如 log 函數)都由 handleOther() 處理,所以您還必須實現它。
現在開啟 com.makotojava.learn.blockchain.chaincode 包中的 ChaincodeLog 類。
我只提供了一個架構供您填充 — 也就是說,我僅提供了編譯它所需的代碼。您需要編寫剩餘代碼。您應該執行 JUnit 測試,然後會看到測試失敗(因為還未編寫實現)和失敗的原因。換句話說,可以使用 JUnit 測試作為指導來正確地實現代碼。
現在,如果感覺難以理解,不要擔心;我在 com.makotojava.learn.blockchain.chaincode.solution 中提供瞭解決方案,以防您遇到阻礙(或者想根據參考來協助完成實現)。
編寫 Java 鏈代碼
首先介紹一下在 ChaincodeLog 中實現鏈代碼方法需要瞭解的一些背景。Java 鏈代碼通過 ChaincodeStub 類與 Hyperledger Fabric 架構進行通訊,另外需要記住,賬本是區塊鏈技術的透明性方面的核心。讓智能合約(責任性)發揮其作用的是賬本的狀態,而鏈代碼是通過 ChaincodeStub 來評估賬本的狀態。通過訪問賬本狀態,可以實現一個智能合約(也即鏈代碼)。
ChaincodeStub 上有許多方法可用於在賬本的目前狀態中儲存、檢索和刪除資料項目,但本教程僅討論兩個方法,它們用於儲存和檢索賬本狀態:
putState(String key, String value)— 將指定的狀態值儲存在賬本中,該值被相應映射到指定的鍵。
getState()— 擷取與指定鍵關聯的狀態值,並以字串形式返回它。
為本教程編寫代碼時,只需在賬本中儲存或檢索狀態值,就會使用 putState() 或 getState() 函數。ChaincodeLog 類僅在賬本中儲存和檢索值來實現其智能合約,所以實現這些方法只需知道該值即可。更複雜的鏈代碼將使用 ChaincodeStub 中的其他一些方法(但這些方法不屬於本教程的介紹範疇)。
我非常喜歡測試驅動開發 (TDD),所以按照 TDD 的方式,我首先編寫單元測試。繼續運行它們,並觀察它們的失敗過程。在這之後,編寫符合規範的代碼,直到單元測試得到通過。單元測試的工作是確保能夠獲得預期的行為,通過研究單元測試,您將獲得實現這些方法所需的足夠資訊。
但是,我還在每個方法頂部編寫了 javadoc 注釋,這可能有所協助(以防您不熟悉 TDD 或 JUnit)。在學完本節的內容後,在 JUnit 測試中的代碼與架構 ChaincodeLog 中的 javadoc 注釋之間,你應該知道有實現鏈代碼所需的所有資訊。
從 Project Explorer 視圖(在 Java 透視圖中),導航到 ChaincodeLogTest 類,按右鍵它並選擇 Run As > Gradle Test。在它運行時,您會看到 11 所示的結果,其中顯示了啟動並執行所有 Gradle 任務的樹結構。成功完成的任務在旁邊會用一個核取記號進行指示。
Gradle Executions 選項卡中的驚嘆號表示與失敗的單元測試對應的 Gradle 任務(跟我們期望的一樣,所有 4 個單元測試都失敗了)。
由於我們編寫 JUnit 測試案例的方式,每個測試方法對應於 ChaincodeLog 中的一個方法,您需要在本教程中正確實現它們。
實現 getChaincodeID()
首先,需要實現 getChaincodeID()。它的合約要求返回鏈代碼的唯一識別碼。我在 ChaincodeLog 類的頂部定義了一個名為 CHAINCODE_ID 的常量,您會用到它。可以自由更改它的值,但是,如果要更改 getChaincodeID() 返回的鏈代碼 ID,請確保它在您的網路中是唯一的,而且不要忘記更改 JSON 訊息的 ChaincodeID.name 屬性。
/** * Returns the unique chaincode ID for this chaincode program. */@Overridepublic String getChaincodeID() { return null;// ADD YOUR CODE HERE}
練習:完成 getChaincodeID() 方法。如果需要一個參考,請參見 com.makotojava.learn.blockchain.chaincode.solution 包。
實現 handleInit()
接下來將實現 handleInit() 方法。它的合約要求處理鏈代碼程式的初始化,在本例中,這意味著它將向賬本添加一條(由調用方指定的)訊息,並在調用成功時將該訊息返回給調用方。
/** * Handles initializing this chaincode program. * * Caller expects this method to: * * 1. Use args[0] as the key for logging. * 2. Use args[1] as the log message. * 3. Return the logged message. */@Overrideprotected String handleInit(ChaincodeStub stub, String[] args) { return null;// ADD YOUR CODE HERE}
練習:完成 handieInit() 方法。如果需要一個參考,請參見 com.makotojava.learn.blockchain.chaincode.solution 包。
實現 handleQuery()
接下來將實現 handleQuery() 方法。它的合約要求查詢賬本,為此,它會擷取指定的鍵,在賬本中查詢與這個(這些)鍵匹配的值,然後將該(這些)值返回給調用方。如果指定了多個鍵,應該使用逗號分隔返回的值。
/** * Handles querying the ledger. * * Caller expects this method to: * * 1. Use args[0] as the key for ledger query. * 2. Return the ledger value matching the specified key * (which should be the message that was logged using that key). */@Overrideprotected String handleQuery(ChaincodeStub stub, String[] args) { return null;// ADD YOUR CODE HERE}
確保編寫了代碼來輸出查詢調用的結果,以便可以在控制台輸出中查看結果(如果想瞭解我是如何做的,請參閱解決方案)。
練習:完成 handleQuery() 方法。如果需要一個參考,請參見 com.makotojava.learn.blockchain.chaincode.solution 包。
實現 handleOther()
最後需要實現 handleOther() 方法,它的合約要求處理其他訊息(這是完全開放的,但正因如此它才是可擴充的)。您將在這裡實現 log 函數,它的合約要求將調用方指定的一條訊息添加到賬本中,並在調用成功時將該訊息返回給調用方。這看起來與 init 函數中發生的事非常相似,所以或許您可以在該實現中利用此函數。
/** * Handles other methods applied to the ledger. * Currently, that functionality is limited to these functions: * - log * * Caller expects this method to: * Use args[0] as the key for logging. * Use args[1] as the log message. * Return the logged message. */@Overrideprotected String handleOther(ChaincodeStub stub, String function, String[] args) { // TODO Auto-generated method stub return null;// ADD YOUR CODE HERE}
練習:完成 handleOther() 方法。如果需要一個參考,請參見 com.makotojava.learn.blockchain.chaincode.solution 包。
如果您為前面的每個練習編寫的代碼滿足本節(以及代碼注釋中)為它們設定的要求,JUnit 測試應該都能通過,而且將鏈代碼部署在本地區塊鏈網路中並運行時,它們應該能夠正常工作。
請記住,如果遇到阻礙,我提供了一個解決方案(但是在查看解決方案之前,您必須自行實現這些方法)。
構建 Java 鏈代碼
現在您已編寫 Java 鏈代碼且通過了所有 JUnit 測試,是時候使用 Eclipse 和用於 Eclipse 的 Gradle Buildship 外掛程式構建鏈代碼了。通過轉到 Window > Show View > Other... 調出 Gradle Tasks 視圖,然後搜尋 gradle,選擇 Gradle Tasks,並單擊 OK。(參見圖 12。)
Gradle Tasks 視圖開啟後,展開 ChaincodeTutorial > build 節點,選擇 build 和 clean。(參見圖 13。)
按右鍵 build 和 clean,然後選擇 Run Gradle Tasks(Gradle 將確定運行它們的正確順序)。您的 Gradle Executions 視圖應該顯示一個乾淨的構建版本, 14 所示,其中每項的旁邊僅有一個核取記號。
完成構建後,$GOPATH/ChaincodeTutorial 目錄(您之前已從 GitHub 將程式碼複製品到這裡)下有一個子目錄 build/distributions,它包含您的鏈代碼(這應該看起來很熟悉,因為本教程前面的 hello 樣本中已經這麼做過)。
構建 Java 鏈代碼後,就可以在本地區塊鏈網路中部署和運行它,並在它之上調用交易。
部署並運行 Java 鏈代碼
在本節中,將會啟動並註冊您的鏈代碼,部署它,並通過 Hyperledger Fabric REST 介面在鏈代碼之上調用交易,就像本教程前面對 hello 樣本所做的一樣。確保本地區塊鏈正在運行(如想溫習一下相關內容,請參閱 “啟動區塊鏈網路” 部分)。
您將執行以下步驟:
- 註冊 Java 鏈代碼。
- 部署 Java 鏈代碼。
- 在 Java 鏈代碼上調用交易。
1.註冊 Java 鏈代碼
您需要提取 build/distributions/ChaincodeTutorial.zip 檔案並運行鏈代碼指令碼,就像本教程前面運行 hello 樣本時一樣(參見 “註冊樣本” 部分)。
運行 ChaincodeTutorial 指令碼時,輸出應如下所示:
$ ./ChaincodeTutorial/bin/ChaincodeTutorialFeb 28, 2017 4:18:16 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnectionINFO: Inside newPeerCLientConnectionFeb 28, 2017 4:18:16 PM io.grpc.internal.TransportSet$1 callINFO: Created transport [email protected](/127.0.0.1:7051) for /127.0.0.1:7051Feb 28, 2017 4:18:21 PM io.grpc.internal.TransportSet$TransportListener transportReadyINFO: Transport [email protected](/127.0.0.1:7051) for /127.0.0.1:7051 is ready
現在您的 Java 鏈代碼已向本地區塊鏈網路註冊,您已準備好部署和測試鏈代碼了。
2.部署 Java 鏈代碼
就像對 hello 樣本鏈代碼執行的操作一樣,將會使用該結構的 REST 介面部署 Java 鏈代碼,並在它之上調用交易。
開啟 SoapUI。如果願意的話,可以自行建立一個新 REST 項目和它的所有請求,或者可以匯入我包含在之前複製的 GitHub 項目中的 SoapUI REST 項目。該 SoapUI 項目位於 $GOPATH/ChaincodeTutorial 目錄中。
要部署鏈代碼,可以導航到 ChaincodeLog Deploy 請求( 15 所示)並提交該請求。
如果沒有使用來自 GitHub 的 SoapUI 項目(或者使用不同的 HTTP 用戶端),那麼應該提交的 JSON 請求如下所示:
{"jsonrpc": "2.0", "method": "deploy", "params": { "type": 4, "chaincodeID":{ "name": "ChaincodeLogSmartContract" }, "ctorMsg": { "args": ["init", "KEY-1", "Chaincode Initialized"] } }, "id": 1}
提交請求。如果請求被成功處理,您會獲得以下 JSON 響應:
{ "jsonrpc": "2.0", "result": { "status": "OK", "message": "ChaincodeLogSmartContract" }, "id": 1}
現在您的鏈代碼已部署並準備好運行。
3.在 Java 鏈代碼上調用交易
部署並初始化 Java 鏈代碼後,就可以在它之上調用交易了。在本節中,將會調用 log 和 query 函數作為交易。
要調用 log 函數,可以開啟 ChaincodeLog Log 請求並提交它。(參見圖 16。)
如果沒有使用來自 GitHub 的 SoapUI 項目(或者使用不同的 HTTP 用戶端),那麼應該提交的 JSON 請求如下所示:
{"jsonrpc": "2.0", "method": "invoke", "params": { "type": 1, "chaincodeID":{ "name": "ChaincodeLogSmartContract" }, "CtorMsg": { "args": ["log", "KEY-2", "This is a log message."] } }, "id": 2}
如果請求被成功處理,您會獲得以下 JSON 響應:
{ "jsonrpc": "2.0", "result": { "status": "OK", "message": "a6f7a4fc-2980-4d95-9ec2-114dd9d0e4a5" }, "id": 2}
要調用 query 函數,可以開啟 ChaincodeLog Query 請求並提交它。(參見圖 17。)
如果沒有使用來自 GitHub 的 SoapUI 項目(或者使用不同的 HTTP 用戶端),那麼應該提交的 JSON 請求如下所示:
{"jsonrpc": "2.0", "method": "invoke", "params": { "type": 1, "chaincodeID":{ "name": "ChaincodeLogSmartContract" }, "ctorMsg": { "args": ["query", "KEY-1", "KEY-2"] } }, "id": 3}
如果請求被成功處理,您會獲得以下 JSON 響應:
{ "jsonrpc": "2.0", "result": { "status": "OK", "message": "84cbe0e2-a83e-4edf-9ce9-71ae7289d390" }, "id": 3}
解決方案代碼的終端視窗輸出類似於:
$ ./ChaincodeTutorial/bin/ChaincodeTutorialFeb 28, 2017 4:18:16 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnectionINFO: Inside newPeerCLientConnectionFeb 28, 2017 4:18:16 PM io.grpc.internal.TransportSet$1 callINFO: Created transport [email protected](/127.0.0.1:7051) for /127.0.0.1:7051Feb 28, 2017 4:18:21 PM io.grpc.internal.TransportSet$TransportListener transportReadyINFO: Transport [email protected](/127.0.0.1:7051) for /127.0.0.1:7051 is readyFeb 28, 2017 4:34:52 PM com.makotojava.learn.blockchain.chaincode.AbstractChaincode runINFO: Greetings from run(): function -> init | args -> [KEY-1, Chaincode Initialized]Feb 28, 2017 4:34:52 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleLogINFO: *** Storing log message (K,V) -> (ChaincodeLogSmartContract-CLSC-KEY-1,Chaincode Initialized) ***Feb 28, 2017 4:50:27 PM com.makotojava.learn.blockchain.chaincode.AbstractChaincode runINFO: Greetings from run(): function -> log | args -> [KEY-2, This is a log message.]Feb 28, 2017 4:50:27 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleLogINFO: *** Storing log message (K,V) -> (ChaincodeLogSmartContract-CLSC-KEY-2,This is a log message.) ***Feb 28, 2017 5:02:13 PM com.makotojava.learn.blockchain.chaincode.AbstractChaincode runINFO: Greetings from run(): function -> query | args -> [KEY-1, KEY-2]Feb 28, 2017 5:02:13 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleQueryINFO: *** Query: For key ‘ChaincodeLogSmartContract-CLSC-KEY-1, value is ‘Chaincode Initialized‘ ***Feb 28, 2017 5:02:13 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleQueryINFO: *** Query: For key ‘ChaincodeLogSmartContract-CLSC-KEY-2, value is ‘This is a log message.‘ ***
恭喜您!您已向未來邁出了第一步。
鼓勵您執行以下操作:修改 ChaincodeTutorial 項目,向它添加方法,更改實現,等等。您也可以自由地編寫鏈代碼。祝您好運,編碼愉快!
結束語
本教程簡要概述了區塊鏈技術和智能合約(實現為鏈代碼程式),以及最新的區塊鏈技術的發展形勢。
我們介紹了設定 Java 鏈代碼開發環境的步驟,包括需要安裝的軟體,如何定義和運行本地區塊鏈網路,以及如何部署來自 GitHub 中的 Hyperledger Fabric 項目的一個 Java 鏈程式碼範例程式並在它之上調用交易。
您學習了如何使用 Eclipse、JUnit 和 Gradle 編寫和構建第一個 Java 鏈代碼程式,然後部署該 Java 鏈代碼程式並在它之上調用交易。
您親自查看了區塊鏈技術和智能合約,隨著區塊鏈技術發展日漸成熟和市場規模逐漸擴大,您會掌握更多的技巧來編寫更複雜的 Java 鏈代碼。
那麼您接下來會怎麼做?
後續行動
以下建議可協助您在目前所學知識的基礎上繼續進行研究:
深入研究 Hyperledger Fabric 架構
致謝
非常感謝杜婧細心評審本文,提供建設性意見並進行校正。
順便分享兩個教程:
1.區塊鏈新手以太坊DApp入門實戰
2.區塊鏈進階以太坊電商平台實戰
J Steven Perry
用Java為Hyperledger Fabric(超級賬本)編寫區塊鏈智能合約鏈代碼