用Java爲Hyperledger Fabric(超級帳本)開發區塊鏈鏈代碼智能合約之編寫鏈代碼程序

編寫第一個 Java 鏈代碼程序

在上一節中,您已經熟悉瞭如何構建、運行、部署和調用鏈代碼,但還沒有編寫任何 Java 代碼。html

在本節中,將會使用 Eclipse IDE、一個用於 Eclipse 的 Gradle 插件,以及一個名爲 ChaincodeTutorial 的 Java 鏈代碼框架項目,編寫第一個 Java 鏈代碼程序。您將從我爲此教程建立的 GitHub 存儲庫中獲取框架代碼,將該代碼導入 Eclipse 中,添加代碼來讓鏈代碼智慧合同按要求生效,而後在 Eclipse IDE 內使用 Gradle 構建該代碼。java

您將執行的步驟以下:git

  • 安裝適用於 Eclipse 的 Gradle Buildship 插件。
  • 從 GitHub 克隆 ChaincodeTutorial 項目。
  • 將該項目導入 Eclipse 中。
  • 探索該鏈代碼框架項目。
  • 編寫 Java 鏈代碼。
  • 構建 Java 鏈代碼。 完成本節後,您的鏈代碼就能夠在本地區塊鏈網絡上運行了。

1.安裝適用於 Eclipse 的 Gradle Buildship 插件

您使用本身喜歡的任何 IDE,但本教程中的說明是針對 Eclipse 的。備註:Buildship Gradle 插件有助於將 Gradle 與 Eclipse 集成,但仍然須要將 Gradle 安裝在計算機上。github

若是您一直在按照教程進行操做,那麼您應該已經將 Gradle 安裝在計算機上;若是還沒有安裝它,請當即安裝。請參閱 「安裝構建軟件」 部分,瞭解如何將 Gradle 安裝在計算機上。 Eclipse Marketplace:Gradle Buildship 插件apache

Buildship Gradle Integration 下,單擊 Install 按鈕並按照提示進行操做。單擊 Finish 後,將安裝適用於 Eclipse 的 Buildship Gradle 插件,並且會要求您重啓 Eclipse。json

從新打開 Eclipse 後,Gradle 應該已經與 Eclipse IDE 全面集成。您如今已準備好從 GItHub 克隆 ChaincodeTutorial 存儲庫。網絡

從 GitHub 克隆 ChaincodeTutorial 項目

配置 Eclipse IDE 和 Gradle集成後,將從 GitHub 克隆 ChaincodeTutorial 代碼並將其導入 Eclipse 中。打開一個命令提示符或終端窗口,導航到 $GOPATH 並執行如下命令:架構

git clone https://github.com/makotogo/ChaincodeTutorial.git

命令輸出應相似於:app

$ export GOPATH=/Users/sperry/home/mychaincode
$ cd $GOPATH
$ git clone https://github.com/makotogo/ChaincodeTutorial.git
Cloning 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 0
Receiving 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)。

Eclipse Import Wizard:Gradle Project

單擊 Next。在嚮導中隨後出現的對話框中(參見圖 10),瀏覽到 $GOPATH/ChaincodeTutorial,而後單擊 Finish 導入該項目。 Eclipse Import Wizard:Gradle Project(項目的 root 目錄)

完成項目導入後,確保選擇了 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 客戶端(第 七、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 任務的樹結構。成功完成的任務在旁邊會用一個複選標記進行指示。

Eclipse:Gradle Executions 視圖

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.
 */
@Override
public 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.
 */
@Override
protected 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).
 */
@Override
protected 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.
 */
@Override
protected 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。)

Eclipse:Show View:Gradle Tasks 視圖

Gradle Tasks 視圖打開後,展開 ChaincodeTutorial > build 節點,選擇 buildclean。(參見圖 13。)

Eclipse:Gradle Tasks 視圖

右鍵單擊 buildclean,而後選擇 Run Gradle Tasks(Gradle 將肯定運行它們的正確順序)。您的 Gradle Executions 視圖應該顯示一個乾淨的構建版本,如圖 14 所示,其中每項的旁邊僅有一個複選標記。 Eclipse:Gradle Executions 視圖:乾淨構建

完成構建後,$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/ChaincodeTutorial
Feb 28, 2017 4:18:16 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnection
INFO: Inside newPeerCLientConnection
Feb 28, 2017 4:18:16 PM io.grpc.internal.TransportSet$1 call
INFO: Created transport io.grpc.netty.NettyClientTransport@10bf86d3(/127.0.0.1:7051) for /127.0.0.1:7051
Feb 28, 2017 4:18:21 PM io.grpc.internal.TransportSet$TransportListener transportReady
INFO: Transport io.grpc.netty.NettyClientTransport@10bf86d3(/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 所示)並提交該請求。

SoapUI:ChaincodeLog Deploy 請求

若是沒有使用來自 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。)

SoapUI:ChaincodeLog Log 請求

若是沒有使用來自 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。) SoapUI:ChaincodeLog Query 請求

若是沒有使用來自 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/ChaincodeTutorial
Feb 28, 2017 4:18:16 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnection
INFO: Inside newPeerCLientConnection
Feb 28, 2017 4:18:16 PM io.grpc.internal.TransportSet$1 call
INFO: Created transport io.grpc.netty.NettyClientTransport@10bf86d3(/127.0.0.1:7051) for /127.0.0.1:7051
Feb 28, 2017 4:18:21 PM io.grpc.internal.TransportSet$TransportListener transportReady
INFO: Transport io.grpc.netty.NettyClientTransport@10bf86d3(/127.0.0.1:7051) for /127.0.0.1:7051 is ready
Feb 28, 2017 4:34:52 PM com.makotojava.learn.blockchain.chaincode.AbstractChaincode run
INFO: Greetings from run(): function -> init | args -> [KEY-1, Chaincode Initialized]
Feb 28, 2017 4:34:52 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleLog
INFO: *** Storing log message (K,V) -> (ChaincodeLogSmartContract-CLSC-KEY-1,Chaincode Initialized) ***
Feb 28, 2017 4:50:27 PM com.makotojava.learn.blockchain.chaincode.AbstractChaincode run
INFO: 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 handleLog
INFO: *** 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 run
INFO: Greetings from run(): function -> query | args -> [KEY-1, KEY-2]
Feb 28, 2017 5:02:13 PM com.makotojava.learn.blockchain.chaincode.solution.ChaincodeLog handleQuery
INFO: *** 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 handleQuery
INFO: *** 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 架構

致謝

很是感謝杜婧細心評審本文,提供建設性意見並進行校訂。

若是你但願高效的學習以太坊DApp開發,能夠訪問匯智網提供的最熱門在線互動教程:

其餘更多內容也能夠訪問這個以太坊博客

做者: J Steven Perry 設置開發環境

相關文章
相關標籤/搜索