面向 Java 開發人員的鏈代碼簡介
您或許聽說過區塊鏈,但可能不確定它對 Java? 開發人員有何用。本教程將幫助大家解惑。我將分步展示如何使用 Hyperledger Fabric v0.6 來構建、運行和執行使用 Java 語言編寫的智能合約或鏈代碼。您將安裝一些工具,定義本地區塊鏈網絡,構建并運行一個鏈代碼智能合約。
有關區塊鏈的概述,請參閱 developerWorks 博客中的 “區塊鏈是什么?分布式賬本技術入門”。
前提條件
本教程假設您滿足以下前提條件:
- 聽說過區塊鏈或 Hyperledger Fabric 框架
- 擁有中等水平的 Java 編程知識,以及使用該語言和平臺的經驗
- 熟悉或(最好)精通使用:
- Eclipse IDE
- Docker 和 Docker Compose
- Gradle
- Linux 命令行
- SoapUI 或另一種 HTTP 客戶端軟件,比如 Postman
您還應該能夠在最少的指導下在計算機上安裝軟件。由于篇幅所限,我不會介紹所有必要軟件的詳細安裝說明;為您提供軟件的網站應該提供了安裝說明。
深入介紹本教程之前,我想簡單說說區塊鏈。
區塊鏈基礎
盡管關于區塊鏈存在大量炒作,但它確實物有所值。該技術不僅本身很有趣,而且它是顛覆性的,有潛力徹底變革互聯網上的業務執行方式。
如何變革?讓我們來想想一次成功的業務交易的基本屬性:
- 信任:我們達成了協議,但我能夠真的相信您會遵守它(或尊重我)嗎?
- 透明性:允許查看 “幕后情況”(這既能建立信任,又能減少對信任的需求)。
- 責任性:用來定義確定所有各方是否都認為該協議得以遵守的條件。
兩方或多方之間的任何業務關系的健康程度對應于上述 3 種屬性的不同級別(例如,更信任意味著需要的透明度更少,反之亦然),但其中一些屬性必須存在,否則就會出現問題。
“區塊鏈技術正被快速應用到您身邊的軟件開發項目中。您準備好了嗎?”
區塊鏈有何幫助?首先,通過使用通用框架,業務合作伙伴可以提前建立一個信任網絡。然后,通過使用對所有交易方可見的賬本,區塊鏈提供了透明性。最后,通過采用所有各方的一致意見(使用智能合約或鏈代碼的形式),建立了責任制。
這對 Java 開發人員有何意義?
Hyperledger 社區和 Hyperledger Fabric 的快速發展,意味著區塊鏈技術正快速被應用到您身邊的軟件開發項目中。您準備好了嗎?
區塊鏈技術發展形勢
有時,開發技術可能會妨礙業務問題的解決。本教程的主要目的是展示如何編寫 Java 鏈代碼,所以我選擇了最簡單的開發技術組合來實現此目的。
也就是說,該組合中的組件還有其他選擇。在本教程中,我將使用 Docker 作為網絡容器環境,而另一個選擇是帶 VirtualBox 的 Vagrant。如果從未使用過 Vagrant,您至少應該嘗試一下。
Docker 是一個容器環境,而 Vagrant 使用了虛擬化。在與 VirtualBox 結合使用時,虛擬化環境對計算環境進行了不同程度的控制,這一點備受一些開發人員的青睞(使其成為 fabric 開發人員的理想選擇)。
如果想進一步了解容器化與虛擬化孰優孰劣,請參閱 developerWorks 博客上的 “Docker 是什么?容器對應用程序有哪些好處的簡介”。
如果開發人員只想編寫代碼,而不必擔心容器、虛擬化或任何基礎架構,那么可以選擇 IBM? Bluemix?。盡管 Bluemix 支持運行完整的 IBM 區塊鏈網絡,但它目前還不支持使用 Java 語言開發鏈代碼。預計此情況很快就會發生變化,所以請時刻關注。
如果在您的印象中,區塊鏈技術當時的發展情況非常不穩定,您是對的。但是,這意味著您在恰當的時機(從一開始)就開始接觸區塊鏈和鏈代碼。隨著該技術發展成熟,您早期階段學習該技術的投資將會不斷得到豐厚的回報。
區塊鏈是能夠徹底改變每個人的業務執行方式的顛覆式技術之一。這類技術不僅包括 B2B,還包括 B2C,甚至還有 C2C。這的確是一個非常激動人心的時刻。
讓我們開始吧!
設置開發環境
要運行鏈代碼,首先需要設置開發環境。
完成本節后,您就可以運行一個 Hyperledger Java 鏈代碼示例了,在該示例中,您將在真實鏈代碼上部署和調用交易。然后,我將展示如何(幾乎)從頭編寫一個新鏈代碼程序。
在本節中,您將:
- 設置網絡環境 — 用于運行您的本地區塊鏈網絡。
- 安裝構建軟件 — 用于構建您的鏈代碼。
- 安裝一個 HTTP 客戶端 — 用于在您的鏈代碼上調用交易。
- 啟動區塊鏈網絡。
- 構建 Java shim 客戶端 JAR。
實話說,要編寫鏈代碼,有許多設置工作要做。但是,如果您按照這些說明進行操作并稍微勤奮一點,您的付出將是值得的。
1.設置網絡環境
本教程將使用 Docker 以及來自 Docker Hub 的預構建區塊鏈網絡組件鏡像來運行本地區塊鏈網絡。如果愿意的話,可以從頭構建 fabric(畢竟它是開源的),但在此階段,使用 Docker Hub 中提供的預構建的 Hyperledger Fabric 鏡像更容易一些。
我在介紹中已經提到過,另一個選擇(您可能在 Hyperledger 文檔中看到過)是使用 Vagrant 和 VirtualBox。Vagrant 是 fabric 開發人員的一個不錯選擇,但作為鏈代碼開發人員,與處理 fabric 本身相比,我們更關心鏈代碼的構建、運行和測試。
如果已經安裝 Docker 1.12 版或更高版本,可以跳到下一節(“安裝構建軟件”)。在下面的操作說明中,假設您尚未安裝 Docker(也就是說,您不是從以前的 Docker 版本進行升級)。安裝 Docker 的過程中也會安裝 Docker Compose,該工具用于定義和運行需要多個容器的應用程序,比如本教程中將運行的本地 Hyperledger 區塊鏈網絡。
安裝 Docker
可在這里找到針對 Mac、Windows 和 Linux 的安裝說明:
將 Docker 安裝在 Mac、Windows 和 Linux 上
驗證 Docker 安裝
要測試 Docker 安裝,可打開一個終端窗口(或 Windows 上的命令提示符)并鍵入以下命令:
docker -v
docker-compose -v
您會獲得以下輸出:
$ docker -v
Docker version 1.13.1, build 092cba3
$ docker-compose -v
docker-compose version 1.11.1, build 7c5d5e4
如果想查看 Docker 的實際運行效果,您可運行 hello-world 鏡像,如下所示:
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
78445dd45222: Pull complete
Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7
Status: Downloaded newer image for hello-world:latest
Hello from Docker! This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
2.安裝構建軟件
對于構建系統,Hyperledger Fabric 使用了 Gradle,本教程也將使用它。Gradle 是一個構建自動化系統,它結合使用了指定構建組件的簡單語法與 Apache Ant 和 Apache Maven 的最佳特性,創建了一個容易使用的強大構建系統。如此多開發人員正將他們的項目切換到 Gradle,這不足為奇。請注意,本文使用的是 Gradle 3.3 版本。使用其他版本的 Gradle 可能會導致項目搭建失敗。
可以在 Gradle 主頁 上進一步了解 Gradle(和它的一些知名度高的用戶)。
安裝 Gradle
要安裝 Gradle,請按照下面的說明進行操作:
將 Gradle 安裝在 Mac、Windows 和 Linux 上
驗證 Gradle 安裝
要驗證 Gradle 安裝,可打開一個終端窗口并執行此命令:
gradle -v
您會看到以下輸出:
$ gradle -v
------------------------------------------------------------
Gradle 3.3
------------------------------------------------------------
Build time: 2017-01-03 15:31:04 UTC
Revision: 075893a3d0798c0c1f322899b41ceca82e4e134b
Groovy: 2.4.7
Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM: 1.8.0_102 (Oracle Corporation 25.102-b14)
OS: Mac OS X 10.12.3 x86_64
3.安裝 HTTP 客戶端
接下來安裝 HTTP 客戶端軟件,它允許鏈代碼與 Hyperledger 區塊鏈結構的 REST 接口進行通信。您的瀏覽器可以發出 HTTP GET,但要與 fabric 進行交互,您需要能夠通過 POST 發出消息。這意味著您需要一個 HTTP 客戶端。
我為本教程選擇的 HTTP 客戶端是 SoapUI,它提供了一個強大的、容易使用的、包含許多功能的免費社區版本。
安裝 SoapUI
要安裝 SoapUI,請按照下面的說明進行操作:
為 Mac OS、Windows 和 Linux 安裝 SoapUI
驗證 SoapUI 安裝
要確認 SoapUI 已安裝,可在計算機上啟動該應用程序。在 Mac OS 上,打開 SoapUI 后會顯示 SoapUI Starter Page,如圖 1 所示。
Mac OS X 上的 SoapUI
4.啟動區塊鏈網絡
現在您已安裝開發和測試鏈代碼所需的軟件,是時候啟動本地區塊鏈網絡了。第一步是定義網絡的配置。
首先,創建一個目錄,用它作為鏈代碼開發過程中使用的所有源代碼的 root 目錄。在本教程中,我將使用 ~/home/mychaincode(或 Windows 上的 C:\home\chaincode)。
接下來,將 GOPATH 環境變量設置為此路徑。我們不會編譯任何 Go 代碼,也不會構建 Golang 包或其他二進制文件,但 Golang 術語已融合到 Hyperledger 中,所以熟悉按 Go 語言和 GOPATH 的方式進行思考是一個不錯的主意。
在 Linux 上,執行以下命令:
export GOPATH=~/home/mychaincode
或者在 Windows 上,可以使用以下命令:
SET GOPATH=C:\home\mychaincode
接下來,必須告訴 Docker Compose 如何創建和運行區塊鏈對等網絡。該網絡是使用 YAML 定義的,應該將它命名為 docker-compose.yml。可以將文件命名為其他名稱,但在啟動 Docker Compose 時,必須指定 -f 標志。建議堅持使用默認名稱,也就是 docker-compose.yml。
在 GOPATH 的 root 目錄中創建 docker-compose.yml 文件。粘貼以下內容:
membersrvc:
image: hyperledger/fabric-membersrvc
ports:
- "7054:7054"
command: membersrvc
vp0:
image: hyperledger/fabric-peer:x86_64-0.6.0-preview
ports:
- "7050:7050"
- "7051:7051"
- "7053:7053"
environment:
- CORE_PEER_ADDRESSAUTODETECT=true
- CORE_VM_ENDPOINT=unix:///var/run/docker.sock
- CORE_LOGGING_LEVEL=DEBUG
- CORE_PEER_ID=vp0
- CORE_PEER_PKI_ECA_PADDR=membersrvc:7054
- CORE_PEER_PKI_TCA_PADDR=membersrvc:7054
- CORE_PEER_PKI_TLSCA_PADDR=membersrvc:7054
- CORE_SECURITY_ENABLED=false
- CORE_SECURITY_ENROLLID=test_vp0
- CORE_SECURITY_ENROLLSECRET=MwYpmSRjupbT
links:
- membersrvc
command: sh -c "sleep 5; peer node start --peer-chaincodedev"
這里涉及許多內容,其中大部分內容都不屬于本教程的討論范圍,但我想稍微解釋一下。
- 此文件告訴 Docker Compose 定義兩個服務:
- membersrvc:提供成員服務的成員服務節點,具體來講,它是一個證書簽發機構 (CA),負責處理所有加密解密工作(比如頒發和撤銷證書)。將用于此目的的預構建 Docker 鏡像命名為 hyperledger/fabric-membersrvc。
- vp0:網絡中的單獨驗證對等節點。對于開發目的,我們不需要奢侈地執行對等網絡驗證,只需要一個對等節點即可。將用于此目的的預構建 Docker 鏡像命名為 hyperledger/fabric-peer: x86_64-0.6.0-preview。請注意,由于 Fabric 版本升級,如果省略掉冒號及之后的字符,會導致構建錯誤。此處指定使用 Fabric 0.6 版本。hyperledger/fabric-peer 等同于 hyperledger/fabric-peer:latest。
- 一些環境變量由 vp0 對等節點設置。請注意,CORE_LOGGING_LEVEL 變量被設置為 DEBUG。這會生成大量輸出,這些輸出有時很方便。但是,如果想要更少的輸出,可將該級別更改為 INFO。請參閱 Hyperledger 設置文檔中的 “日志控制” 了解日志級別的更多信息。
可以在 Docker 網站 上找到有關 Docker Compose YML 文件定義的更多信息。
接下來請注意,CORE_SECURITY_ENABLED 的值為 false。這意味著 fabric 不需要您發送任何類型的最終用戶憑證。安全性不屬于本教程的討論范圍,但是如果您有興趣了解更多信息,可以根據您的鏈代碼請求來查閱這篇安全功能說明。
最后提醒一句:對任何這些值的默認設置(尤其是端口值)的任何改動都有可能導致本教程中的示例無法運行。區塊鏈網絡是一組分布式軟件組件,它們需要經過精確協調的通信。強烈建議在理解 fabric 的所有組件如何相互作用之前,不要更改端口值的默認值。
完成區塊鏈的定義后,就可以啟動本地區塊鏈網絡了。為此,請運行 Docker Compose。導航到您的 $GOPATH 并執行此命令:
docker-compose up
您會在終端窗口中獲得以下輸出:
$ docker-compose up
.
.
Pulling membersrvc (hyperledger/fabric-membersrvc:latest)...
latest: Pulling from hyperledger/fabric-membersrvc
.
.
Status: Downloaded newer image for hyperledger/fabric-membersrvc:latest
Pulling vp0 (hyperledger/fabric-peer:latest)...
latest: Pulling from hyperledger/fabric-peer
.
.
Status: Downloaded newer image for hyperledger/fabric-peer:latest
Creating mychaincode_membersrvc_1
Creating mychaincode_vp0_1
Attaching to mychaincode_membersrvc_1, mychaincode_vp0_1
vp0_1 | 19:30:03.773 [logging] LoggingInit -> DEBU 001 Setting default logging level to DEBUG for command 'node'
vp0_1 | 19:30:03.773 [nodeCmd] serve -> INFO 002 Running in chaincode development mode
.
.
.
vp0_1 | 19:30:04.146 [peer] chatWithSomePeers -> DEBU 07c Starting up the first peer of a new network
vp0_1 | 19:30:04.146 [consensus/statetransfer] verifyAndRecoverBlockchain -> DEBU 07d Validating existing blockchain, highest validated block is 0, valid through 0
vp0_1 | 19:30:04.146 [consensus/statetransfer] blockThread -> INFO 07e Validated blockchain to the genesis block
vp0_1 | 19:30:04.146 [consensus/handler] 1 -> DEBU 07f Starting up message thread for consenter
vp0_1 | 19:30:04.146 [nodeCmd] serve -> INFO 080 Starting peer with ID=name:"vp0" , network ID=dev, address=172.17.0.3:7051, rootnodes=, validator=true
vp0_1 | 19:30:04.146 [rest] StartOpenchainRESTServer -> INFO 081 Initializing the REST service on 0.0.0.0:7050, TLS is disabled.
vp0_1 | 19:30:04.147 [peer] ensureConnected -> DEBU 082 Starting Peer reconnect service (touch service), with period = 6s
.
.
此輸出告訴您該網絡在正常運行,已準備好接受鏈代碼注冊請求。
備注:突出顯示的行應該僅在第一次運行區塊鏈網絡時出現,因為 Docker 需要從 Docker Hub 下載鏡像。鏡像下載到計算機后,僅在來自 Docker Hub 的鏡像比您計算機上的鏡像更新時,Docker 才會拉入它們。
現在已準備好構建 Java shim 客戶端 JAR,它允許 Java 語言鏈代碼與 Hyperledger Fabric 框架進行通信。
5.構建 Java shim 客戶端 JAR
在運行鏈代碼示例前,需要從 Hyperledger 的 GitHub 存儲庫獲取最新的源代碼。
首先,需要將 Hyperledger Fabric 克隆到本地機器上,以便構建鏈代碼(備注:這是一項臨時措施;在以后某個時刻,應該能從主要的 Maven 存儲庫訪問 Java shim 客戶端 JAR)。
備注:回想一下,您之前已將 GOPATH 設置為 Linux(或 Mac)上的 ~/home/mychaincode 或 Windows 上的 C:\home\mychaincode。
執行此命令來創建結構構建腳本所期望的目錄結構:
mkdir -p $GOPATH/src/github.com/hyperledger
接下來,導航到已創建的新目錄結構的底部:
cd $GOPATH/src/github.com/hyperledger
您需要從這里獲取 Hyperledger 源代碼,以便構建 Java shim 客戶端 JAR。
可通過兩種方式獲得 Hyperledger 源代碼。
不使用 git:
導航到 Hyperledger GitHub 鏡像,選擇v0.6分支,并單擊 Clone or download 按鈕,然后單擊 Download ZIP(參見圖 2)。一個名為 fabric-master.zip 的 ZIP 文件被下載到您的計算機,您可以將它解壓到 $GOPATH/src/github.com/hyperledger。備注:請確保在解壓該文件時,將 root 目錄的名稱從 fabric-master 更改為 fabric。使用 git:
導航到 $GOPATH/src/github.com/hyperledger,將文本字段中的 URL 復制到 “Clone with HTTPS” 框中(參見圖 2 中的箭頭),然后使用復制的 URL 執行此命令:
git clone https://github.com/hyperledger/fabric.git
或
git clone –b v0.6
https://gerrit.hyperledger.org/r/fabric
您會看到 git 命令返回了以下終端窗口輸出:
$ git clone https://github.com/hyperledger/fabric.git
Cloning into 'fabric'...
remote: Counting objects: 26976, done.
remote: Compressing objects: 100% (406/406), done.
remote: Total 26976 (delta 172), reused 0 (delta 0), pack-reused 26558
Receiving objects: 100% (26976/26976), 43.68 MiB | 4.85 MiB/s, done.
Resolving deltas: 100% (15114/15114), done.
現在您已準備好構建 Java 鏈代碼 shim 客戶端 JAR。導航到 $GOPATH/src/github.com/hyperledger/fabric/core/chaincode/shim/java 并運行以下兩個命令:
gradle -b build.gradle clean
gradle -b build.gradle build
Gradle 構建輸出應如下所示:
$ cd $GOPATH/src/github.com/hyperledger/fabric/core/chaincode/shim/java
$ gradle -b build.gradle clean
Starting a Gradle Daemon (subsequent builds will be faster)
:core:chaincode:shim:java:clean
BUILD SUCCESSFUL
Total time: 5.422 secs
$ gradle -b build.gradle build
:core:chaincode:shim:java:copyProtos UP-TO-DATE
:core:chaincode:shim:java:extractIncludeProto
:core:chaincode:shim:java:extractProto UP-TO-DATE
:core:chaincode:shim:java:generateProto UP-TO-DATE
:core:chaincode:shim:java:compileJava
:core:chaincode:shim:java:processResources
:core:chaincode:shim:java:classes
:core:chaincode:shim:java:jar
:core:chaincode:shim:java:assemble
:core:chaincode:shim:java:extractIncludeTestProto
:core:chaincode:shim:java:extractTestProto UP-TO-DATE
:core:chaincode:shim:java:generateTestProto UP-TO-DATE
:core:chaincode:shim:java:compileTestJava UP-TO-DATE
:core:chaincode:shim:java:processTestResources UP-TO-DATE
:core:chaincode:shim:java:testClasses UP-TO-DATE
:core:chaincode:shim:java:test UP-TO-DATE
:core:chaincode:shim:java:check UP-TO-DATE
:core:chaincode:shim:java:build
:core:chaincode:shim:java:copyToLib
:core:chaincode:shim:java:generatePomFileForMavenJavaPublication
:core:chaincode:shim:java:publishMavenJavaPublicationToMavenLocal
:core:chaincode:shim:java:publishToMavenLocal
BUILD SUCCESSFUL
Total time: 4.521 secs
構建過程中執行的最后一件事是,將 shim 客戶端 JAR 添加到本地 Maven 存儲庫。現在您已準備好構建鏈代碼。除非在未來某個時刻要更新結構源代碼,或者出于某種原因想要再次重新構建 shim 客戶端 JAR,否則不需要再次運行 Java shim 客戶端 JAR。
部署并運行 Java 鏈代碼示例
您已經定義并啟動了本地區塊鏈網絡,而且已構建 Java shim 客戶端 JAR 并安裝到本地 Maven 存儲庫中,現在已準備好在之前下載的 Hyperledger Fabric 附帶的一個 Java 鏈代碼示例上構建、注冊和調用交易。
部署并運行鏈代碼
您將執行以下步驟:
- 使用 Gradle 構建示例。
- 通過運行 Gradle 構建軟件為您創建的腳本,向驗證對等網絡注冊該示例。
- 使用 SoapUI 將示例部署到本地區塊鏈網絡。
- 使用 SoapUI 在示例鏈代碼上調用交易。
1.構建示例
導航到 $GOPATH/src/github.com/hyperledger/fabric/examples/chaincode/java/Example 目錄。
接下來,通過命令行,使用此命令啟動 Gradle 構建軟件:
gradle -b build.gradle build
您會看到以下輸出:
$ cd GOPATH/src/github.com/hyperledger/fabric/examples/chaincode/java/Example
$ gradle -b build.gradle build
Starting a Gradle Daemon (subsequent builds will be faster)
:examples:chaincode:java:Example:compileJava
:examples:chaincode:java:Example:processResources UP-TO-DATE
:examples:chaincode:java:Example:classes
:examples:chaincode:java:Example:jar
:examples:chaincode:java:Example:startScripts
:examples:chaincode:java:Example:distTar
:examples:chaincode:java:Example:distZip
:examples:chaincode:java:Example:assemble
:examples:chaincode:java:Example:compileTestJava UP-TO-DATE
:examples:chaincode:java:Example:processTestResources UP-TO-DATE
:examples:chaincode:java:Example:testClasses UP-TO-DATE
:examples:chaincode:java:Example:test UP-TO-DATE
:examples:chaincode:java:Example:check UP-TO-DATE
:examples:chaincode:java:Example:build
:examples:chaincode:java:Example:copyToLib
BUILD SUCCESSFUL
Total time: 6.935 secs
該構建過程通過兩種形式創建了一個位于目錄 build/distributions 中的獨立發行版:TAR 文件和 ZIP 文件,每個文件都包含運行鏈代碼所需的所有資源,其中包括一個用于驅動鏈代碼的名為 Example 的腳本。
Example 鏈代碼現在已準備好向本地區塊鏈網絡注冊。
2.注冊示例
確保本地區塊鏈網絡正在運行。如果未運行,則需要啟動它。如果需要溫習一下相關內容,請參閱“啟動區塊鏈網絡”部分。
如果您未在 $GOPATH/src/github.com/hyperledger/fabric/examples/chaincode/java/Example 目錄下,請導航到這里。
接下來,將 Example.zip(或 Example.tar)解壓到 build/distributions 目錄中:
$ cd $GOPATH/src/github.com/hyperledger/fabric/examples/chaincode/java/Example
$ cd build/distributions/
$ unzip Example.zip
Archive: Example.zip
inflating: Example/lib/chaincode.jar
inflating: Example/lib/grpc-all-0.13.2.jar
inflating: Example/lib/commons-cli-1.3.1.jar
inflating: Example/lib/shim-client-1.0.jar
inflating: Example/lib/grpc-netty-0.13.2.jar
inflating: Example/lib/grpc-auth-0.13.2.jar
inflating: Example/lib/grpc-protobuf-nano-0.13.2.jar
inflating: Example/lib/grpc-core-0.13.2.jar
inflating: Example/lib/grpc-protobuf-0.13.2.jar
inflating: Example/lib/grpc-okhttp-0.13.2.jar
inflating: Example/lib/grpc-stub-0.13.2.jar
inflating: Example/lib/protobuf-java-3.0.0.jar
inflating: Example/lib/netty-tcnative-boringssl-static-1.1.33.Fork21-osx-x86_64.jar
inflating: Example/lib/netty-codec-http2-4.1.0.CR3.jar
inflating: Example/lib/google-auth-library-oauth2-http-0.3.0.jar
inflating: Example/lib/guava-18.0.jar
inflating: Example/lib/protobuf-javanano-3.0.0-alpha-5.jar
inflating: Example/lib/jsr305-3.0.0.jar
inflating: Example/lib/okio-1.6.0.jar
inflating: Example/lib/okhttp-2.5.0.jar
inflating: Example/lib/netty-codec-http-4.1.0.CR3.jar
inflating: Example/lib/netty-handler-4.1.0.CR3.jar
inflating: Example/lib/google-auth-library-credentials-0.3.0.jar
inflating: Example/lib/google-http-client-1.19.0.jar
inflating: Example/lib/google-http-client-jackson2-1.19.0.jar
inflating: Example/lib/netty-codec-4.1.0.CR3.jar
inflating: Example/lib/netty-buffer-4.1.0.CR3.jar
inflating: Example/lib/netty-transport-4.1.0.CR3.jar
inflating: Example/lib/httpclient-4.0.1.jar
inflating: Example/lib/jackson-core-2.1.3.jar
inflating: Example/lib/netty-common-4.1.0.CR3.jar
inflating: Example/lib/netty-resolver-4.1.0.CR3.jar
inflating: Example/lib/httpcore-4.0.1.jar
inflating: Example/lib/commons-logging-1.1.1.jar
inflating: Example/lib/commons-codec-1.3.jar
inflating: Example/bin/Example
inflating: Example/bin/Example.bat
您可能想知道 “為何有如此多的文件?”該發行版包含(在獨立進程中)單獨運行鏈代碼所需的一切資源,以及所有依賴 JAR 文件。
要注冊鏈代碼示例,可在 build/distributions 文件夾中執行以下腳本:
./Example/bin/Example
這會運行一個獨立進程來向本地區塊鏈網絡注冊鏈代碼示例。您會看到以下終端窗口輸出:
$ ./Example/bin/Example
Hello world! starting [Ljava.lang.String;@7ef20235
Feb 22, 2017 10:05:08 AM example.Example main
INFO: starting
Feb 22, 2017 10:05:08 AM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnection
INFO: Inside newPeerCLientConnection
Feb 22, 2017 10:05:08 AM io.grpc.internal.TransportSet$1 call
INFO: Created transport io.grpc.netty.NettyClientTransport@3dd7b80b(/127.0.0.1:7051) for /127.0.0.1:7051
Feb 22, 2017 10:05:14 AM io.grpc.internal.TransportSet$TransportListener transportReady
INFO: Transport io.grpc.netty.NettyClientTransport@3dd7b80b(/127.0.0.1:7051) for /127.0.0.1:7051 is ready
查看本地區塊鏈網絡的控制臺,您會看到以下輸出行:
.
.
vp0_1 | 16:05:14.048 [chaincode] HandleChaincodeStream -> DEBU 06d Current context deadline = 0001-01-01 00:00:00 +0000 UTC, ok = false
vp0_1 | 16:05:14.065 [chaincode] processStream -> DEBU 06e []Received message REGISTER from shim
vp0_1 | 16:05:14.065 [chaincode] HandleMessage -> DEBU 06f []Handling ChaincodeMessage of type: REGISTER in state created
vp0_1 | 16:05:14.065 [chaincode] beforeRegisterEvent -> DEBU 070 Received REGISTER in state created
vp0_1 | 16:05:14.065 [chaincode] registerHandler -> DEBU 071 registered handler complete for chaincode hello
vp0_1 | 16:05:14.065 [chaincode] beforeRegisterEvent -> DEBU 072 Got REGISTER for chaincodeID = name:"hello" , sending back REGISTERED
.
.
記下注冊日志輸出中的 chaincodeID name(示例中為 hello;如上面 第 8 行 所示)。以后在通過結構的 REST 接口部署 Example 鏈代碼時,JSON 消息中需要使用此信息。
上面的輸出表明 Example 鏈代碼正在運行,而且已向本地區塊鏈驗證對等網絡注冊,并做好了部署準備。
3.部署示例
Hyperledger Fabric 提供了一個用于與該結構交互的 REST Web 服務接口。與 fabric 的第一次交互是部署鏈代碼。確保本地區塊鏈網絡正在運行,然后啟動 SoapUI,單擊 REST 按鈕創建一個新的 REST 項目。您會看到一個類似圖 3 的對話框,在其中輸入用于所有 REST 請求的基礎 URL:
輸入 http://localhost:7050 作為 URL,然后單擊 OK。端口 7050 是 fabric 使用的默認 REST 端口,而且因為區塊鏈網絡是在本地計算機上運行的,所以將使用 localhost 作為主機名。
在 SoapUI 啟動后,可以執行一次快速冒煙測試,以確保它能與本地區塊鏈網絡進行通信。展開剛創建的新的 REST 資源,直到看到 Request 1,然后在 Editor 窗口中打開它。使用 GET 方法,在 resource 下輸入 /chain。確保單擊了 output 選項卡上的 JSON 選項,然后運行請求(通過單擊 arrow 圖標)。執行此請求時,會在 Editor 窗口右側的輸出選項卡中返回當前區塊的哈希值,如圖 4 所示:
如果看到一條類似圖 4 的 JSON 消息(當然您的網絡的 currentBlockHash 值會有所不同),那么您已準備好部署 Example 鏈代碼。
右鍵單擊 REST Project 1 (http://localhost:7050) 下的端點并選擇 New Resource;您會看到一個包含 Resource Path 字段的 “New REST Resource” 對話框(參見圖 5):
輸入 /chaincode 作為 resource path,然后單擊 OK,您會看到 SoapUI Projects 面板中顯示了新資源。打開對此資源的請求(默認情況下該請求名為 Request 1),將方法更改為 POST,并將此 JSON 粘貼到請求編輯器窗口左下角的請求區域:
{
"jsonrpc": "2.0",
"method": "deploy",
"params": {
"type": 1,
"chaincodeID":{
"name": "hello"
},
"CtorMsg": {
"args": [""]
}
},
"id": 1
}
有 3 點需要注意:
第 3 行:method 值必須為 deploy。
第 6-7 行:JSON 消息中的 chaincodeID.name 必須與您在上一節中注冊 Example 鏈代碼時所用的 chaincodeID 匹配(在 Example 鏈代碼中,該值為 hello)。
第 13 行:id 值用于協調請求。本教程不需要過多地考慮它,但要注意的是,在響應中始終會發送回該值(參見下一個清單)。
提交此請求時,JSON 輸出應如下所示:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"message": "hello"
},
"id": 1
}
圖 6 給出了 SoapUI 中的輸出的屏幕截圖。JSON 輸出消息會顯示在輸出選項卡中,該選項卡位于請求編輯器的右側。
終端窗口中的網絡日志輸出應包含以下行:
.
.
vp0_1 | 20:48:39.482 [rest] ProcessChaincode -> INFO 0c4 REST processing chaincode request...
vp0_1 | 20:48:39.482 [rest] processChaincodeDeploy -> INFO 0c5 REST deploying chaincode...
vp0_1 | 20:48:39.483 [devops] Deploy -> DEBU 0c6 Creating deployment transaction (hello)
vp0_1 | 20:48:39.483 [devops] Deploy -> DEBU 0c7 Sending deploy transaction (hello) to validator
vp0_1 | 20:48:39.483 [peer] sendTransactionsToLocalEngine -> DEBU 0c8 Marshalling transaction CHAINCODE_DEPLOY to send to local engine
vp0_1 | 20:48:39.483 [peer] sendTransactionsToLocalEngine -> DEBU 0c9 Sending message CHAIN_TRANSACTION with timestamp seconds:1487796519 nanos:483661510 to local engine
vp0_1 | 20:48:39.483 [consensus/noops] RecvMsg -> DEBU 0ca Handling Message of type: CHAIN_TRANSACTION
vp0_1 | 20:48:39.483 [consensus/noops] broadcastConsensusMsg -> DEBU 0cb Broadcasting CONSENSUS
vp0_1 | 20:48:39.483 [peer] Broadcast -> DEBU 0cc Broadcast took 1.135s
vp0_1 | 20:48:39.483 [consensus/noops] RecvMsg -> DEBU 0cd Sending to channel tx uuid: hello
vp0_1 | 20:48:39.483 [rest] processChaincodeDeploy -> INFO 0ce Successfully deployed chainCode: hello
vp0_1 | 20:48:39.484 [rest] ProcessChaincode -> INFO 0cf REST successfully deploy chaincode: {"jsonrpc":"2.0","result":{"status":"OK","message":"hello"},"id":1}
.
.
第 3-4 行顯示了輸出,表明網絡已收到部署消息,并且該結構正在部署鏈代碼。第 13-14 行表明鏈代碼已成功部署。
在運行鏈代碼的終端窗口中,可以注意到以下輸出:
$ ./build/distributions/Example/bin/Example
Hello world! starting [Ljava.lang.String;@7ef20235
Feb 22, 2017 2:44:43 PM example.Example main
INFO: starting
Feb 22, 2017 2:44:43 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnection
INFO: Inside newPeerCLientConnection
Feb 22, 2017 2:44:43 PM io.grpc.internal.TransportSet$1 call
INFO: Created transport io.grpc.netty.NettyClientTransport@46adccd3(/127.0.0.1:7051) for /127.0.0.1:7051
Feb 22, 2017 2:44:48 PM io.grpc.internal.TransportSet$TransportListener transportReady
INFO: Transport io.grpc.netty.NettyClientTransport@46adccd3(/127.0.0.1:7051) for /127.0.0.1:7051 is ready
Feb 22, 2017 2:48:40 PM example.Example run
INFO: In run, function:
Feb 22, 2017 2:48:40 PM example.Example run
我包含了所有上下文輸出,在向區塊鏈網絡發送部署消息時,您會看到類似第 11-13 行的消息。
4.在示例上調用交易
最后,將會調用 hello 方法,可以看到它會在運行鏈代碼的終端窗口的日志消息中顯示出來。
在 SoapUI 中的 chaincode 資源下,右鍵單擊 Method 1 并選擇 Clone Method。將該方法命名為 Invoke,然后單擊 OK。打開新的 Invoke 方法下的 Request 1,并粘貼到以下 JSON 請求中:
{
"jsonrpc": "2.0",
"method": "invoke",
"params": {
"type": 1,
"chaincodeID":{
"name": "hello"
},
"CtorMsg": {
"args": ["hello"]
}
},
"id": 2
}
運行該請求時,會看到以下 JSON 響應:
{ "jsonrpc":"2.0", "result": { "status":"OK", "message":"1c1811d0-a958-4c58-ab1d-e1df550c18a3" }, "id":2 }
圖 7 給出了 SoapUI 中的輸出的屏幕截圖
網絡日志輸出應包含以下行:
.
.
vp0_1 | 21:44:35.143 [rest] ProcessChaincode -> INFO 555 REST processing chaincode request...
vp0_1 | 21:44:35.143 [rest] processChaincodeInvokeOrQuery -> INFO 556 REST invoke chaincode...
vp0_1 | 21:44:35.143 [devops] invokeOrQuery -> INFO 557 Transaction ID: 1c1811d0-a958-4c58-ab1d-e1df550c18a3
vp0_1 | 21:44:35.143 [devops] createExecTx -> DEBU 558 Creating invocation transaction (1c1811d0-a958-4c58-ab1d-e1df550c18a3)
vp0_1 | 21:44:35.143 [devops] invokeOrQuery -> DEBU 559 Sending invocation transaction (1c1811d0-a958-4c58-ab1d-e1df550c18a3) to validator
vp0_1 | 21:44:35.143 [peer] sendTransactionsToLocalEngine -> DEBU 55a Marshalling transaction CHAINCODE_INVOKE to send to local engine
vp0_1 | 21:44:35.143 [peer] sendTransactionsToLocalEngine -> DEBU 55b Sending message CHAIN_TRANSACTION with timestamp seconds:1487799875 nanos:143438691 to local engine
vp0_1 | 21:44:35.143 [consensus/noops] RecvMsg -> DEBU 55c Handling Message of type: CHAIN_TRANSACTION
vp0_1 | 21:44:35.143 [consensus/noops] broadcastConsensusMsg -> DEBU 55d Broadcasting CONSENSUS
vp0_1 | 21:44:35.143 [peer] Broadcast -> DEBU 55e Broadcast took 1.249s
vp0_1 | 21:44:35.143 [consensus/noops] RecvMsg -> DEBU 55f Sending to channel tx uuid: 1c1811d0-a958-4c58-ab1d-e1df550c18a3
vp0_1 | 21:44:35.143 [rest] processChaincodeInvokeOrQuery -> INFO 560 Successfully submitted invoke transaction with txid (1c1811d0-a958-4c58-ab1d-e1df550c18a3)
vp0_1 | 21:44:35.143 [rest] ProcessChaincode -> INFO 561 REST successfully submitted invoke transaction: {"jsonrpc":"2.0","result":{"status":"OK","message":"1c1811d0-a958-4c58-ab1d-e1df550c18a3"},"id":2}
.
.
鏈代碼日志輸出如下所示:
$ ./build/distributions/Example/bin/Example
Hello world! starting [Ljava.lang.String;@7ef20235
Feb 22, 2017 3:26:57 PM example.Example main
INFO: starting
Feb 22, 2017 3:26:57 PM org.hyperledger.java.shim.ChaincodeBase newPeerClientConnection
INFO: Inside newPeerCLientConnection
Feb 22, 2017 3:26:57 PM io.grpc.internal.TransportSet$1 call
INFO: Created transport io.grpc.netty.NettyClientTransport@765e4953(/127.0.0.1:7051) for /127.0.0.1:7051
Feb 22, 2017 3:27:02 PM io.grpc.internal.TransportSet$TransportListener transportReady
INFO: Transport io.grpc.netty.NettyClientTransport@765e4953(/127.0.0.1:7051) for /127.0.0.1:7051 is ready
Feb 22, 2017 3:27:24 PM example.Example run
INFO: In run, function:
Feb 22, 2017 3:27:24 PM example.Example run
SEVERE: No matching case for function:
Feb 22, 2017 3:30:55 PM example.Example run
INFO: In run, function:hello
hello invoked
我再次給出了所有鏈代碼輸出。您可以看到哪些地方(第 16 行)調用了 hello 函數。
現在您已知道如何在本地區塊鏈網絡上構建、部署和運行 Java 鏈代碼。在下一節中,將會使用 Eclipse IDE(幾乎)從頭編寫一個鏈代碼程序,使用 Gradle 構建該鏈代碼程序,然后使用 SoapUI 體驗它。
編寫第一個 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.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)。
單擊 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.
*/
@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。)
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/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 所示)并提交該請求。
如果沒有使用來自 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/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 鏈代碼。
那么您接下來會怎么做?
后續行動
以下建議可幫助您在目前所學知識的基礎上繼續進行研究:
致謝
非常感謝杜婧細心評審本文,提供建設性意見并進行校正。
如果你希望高效的學習以太坊DApp開發,可以訪問匯智網提供的最熱門在線互動教程:
其他更多內容也可以訪問這個以太坊博客。
原文:
https://www.ibm.com/developerworks/cn/java/j-chaincode-for-java-developers/index.html
作者: J Steven Perry