Chaincode的開發環境和Hello world
使用GO lang對Chaincode進行開發
# 安裝Golang的SDK
go get github.com/hyperledger/fabric-sdk-go
# 安裝shim包
go get -u github.com/hyperledger/fabric/core/chaincode/shim
啟動鏈碼必須通過調用shim包中的Start函數實現,而Start函數被調用時需要傳遞一個類型為Chaincode的參數,這個Chaincode參數是一個接口類型,該接口中有兩個重要的函數——Init和Invoke。
type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}
- Init: 在鏈碼實例化或升級時被調用,完成初始化數據的工作。
- Invoke: 在更新或查詢提案事務中的分類賬本數據狀態時被調用,因此響應調用或查詢的業務實現邏輯都需要在此函數中編寫實現。
在實際開發中,開發人員可以自行定義一個結構體,然后重寫Chaincode接口的兩個函數,並將兩個函數指定為自定義結構體的成員方法
必要結構
package main
// 引入必要的包
import (
"fmt"
// shim包提供了鏈碼與賬本交互的中間層
// 鏈碼通過shim.ChaincodeStub提供的相應函數來讀取和修改賬本的狀態
"github.com/hyperledger/fabric/core/chiancode/shim"
// 鏈碼被調用執行之后通過peer包中的Response來封裝執行結果的響應信息
"github.com/hyperledger/fabric/protos/peer"
)
// 聲明一個結構體
type SimpleChaincode struct {
}
// 為結構體添加Init函數
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 在該方法中實現鏈碼初始化或升級時的處理邏輯
// 編寫時可靈活使用stub中的API
}
// 為結構體添加Init函數
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 該方法中實現鏈碼運行中被調用或查詢時的處理邏輯
// 編寫時可靈活使用stub中的API
}
// 主函數,需要調用shim.Start()方法
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Print("Error starting Simple chaincode: %s", err)
}
}
shim包中提供的API
參數解析API
- GetArgs()[][]byte: 返回調用鏈碼時在交易提案中指定提供的被調用函數及參數列表
- GetArgsSlice() ([]byte, error): 返回調用鏈碼時在交易提案中指定提供的參數列表
- GetFunctionAndParameters() (function string, Params[]string): 返回調用鏈碼時在交易提案中指定提供的被調用函數名稱及其參數列表
- GetStringArgs()[]string: 返回調用鏈碼時指定提供的參數列表
賬本數據狀態操作API
- GetState(key string) ([]byte, error): 根據指定的key查詢相應的數據狀態
- PutState(key string, value[]byte) error: 根據指定的key,將對應的value保存在分類賬本中
- DelState(key string) error: 根據指定的key將對應的數據狀態刪除
- GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error): 根據指定的開始key及結束key,查詢范圍內的所有數據狀態。注意結束key對應的數據狀態不包含在返回的結果集中。
- GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error): 根據指定的key查詢所有的歷史紀錄信息。
- CreateCompositeKey(objectType string, attributes[]string) (string, error): 創建一個復合鍵。
- SplitCompositeKey(compositeKey string) (string, []string, error): 對指定的復合鍵進行分割。
- GetQueryResult(query string) (StateQueryIteratorInterface, error): 對(支持富查詢功能的)狀態數據庫進行富查詢
交易信息API
- GetTxID() string: 返回交易提案中指定的交易ID
- GetChannelID() string: 返回交易提案中指定的通道ID
- GetTxTimestamp() (*timestamp.Timestamp, error): 返回交易創建的時間戳,這個時間戳時Peer接收到交易的具體時間
- GetBinding() ([]byte, error): 返回交易的綁定信息,如一些臨時信息,以避免重復性攻擊
- GetSignedProposal() (*pb.SignedProposal, error): 返回與交易提案相關的簽名身份信息
- GetCreator() ([]byte, error): 返回該交易提交者的身份信息
- GetTransient() (map[string][]byte, error): 返回交易中不會被寫至賬本中的一些臨時信息
事件處理API
- SetEvent(name string, payload[]byte) error: 設置事件,包括事件名稱及內容
對PrivateData操作的API
- GetPrivateData(collection string , key string) ([]byte, error): 根據指定的key,從指定的私有數據集中查詢對應的私有數據
- PutPrivateData(collection string, key string, value[]byte) error: 將指定的key與value保存到私有數據集中
- DelPrivateData(collection string, key string) error: 根據指定的key從私有數據集中刪除相應的數據
- GetPrivateDataByRange(collection string, startKey, endKey string) (State-QueryIteratorInterface, error): 根據指定的開始key與結束key查詢范圍(不包含結束key)內的私有數據
- GetPrivateDataByPartialCompositeKey(collection string, objectType string, keys[]string) (StateQueryIteratorInterface, error): 根據給定的部分組合鍵的集合,查詢給定的私有狀態
- GetPrivateDataQueryResult(collection string, query string) (StateQueryIteratorInterface, error): 根據指定的查詢字符串執行富查詢
鏈碼開發 Hello world
先進入fabric-samples/chaincode目錄下創建一個hello的目錄
創建鏈碼文件:vim hello.go
// hello.go
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type HelloChaincode struct{}
// 實例化/升級鏈碼時被自動調用
// -c '{"Args": ["Hello", "World"]}'
func (t *HelloChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Println("開始實例化鏈碼......")
// 獲取參數
// args := stub.GetStringArgs()
_, args := stub.GetFunctionAndParameters()
// 判斷參數長度是否為2個
if len(args) != 2 {
return shim.Error("指定了錯誤的參數個數")
}
fmt.Println("保存數據......")
// 通過調用PutState函數將數據保存在賬本中
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error("保存數據時發生錯誤")
}
fmt.Println("實例化鏈碼成功")
return shim.Success(nil)
}
// 對賬本數據進行操作時被自動調用(query, invoke)
func (t *HelloChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 獲取調用鏈碼時傳遞的參數內容(包括要調用的函數名及參數)
fun, args := stub.GetFunctionAndParameters()
// 客戶意圖
if fun == "query" {
return query(stub, args)
}
return shim.Error("非法操作, 指定功能不能實現")
}
func query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
// 檢查傳遞的參數是否為1
if len(args) != 1 {
return shim.Error("指定的參數錯誤, 必須且只能指定相應的Key")
}
// 根據指定的Key調用GetState方法查詢數據
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("根據指定的 " + args[0] + " 查詢數據時發生錯誤")
}
if result == nil {
return shim.Error("根據指定的 " + args[0] + " 沒有查詢到相應的數據")
}
// 返回查詢結果
return shim.Success(result)
}
func main() {
err := shim.Start(new(HelloChaincode))
if err != nil {
fmt.Printf("chaincode start failed: %v", err)
}
}
鏈碼測試
先進入github.com/hyperledger/fabric-samples/chaincode-docker-devmode
目錄,啟動鏈碼的Dev開發測試模式
先清空一下docker的環境,使用上一篇文章中的清空命令
設置下docker-compose-simple.yaml
文件,將yaml文件的tag改成自己使用鏡像tag,所有用到的image都改一下,不然又要從docker-hub拉取鏡像了
services:
orderer:
container_name: orderer
image: hyperledger/fabric-orderer:1.4 # 就是這里
environment:
- FABRIC_LOGGING_SPEC=debug
- ORDERER_GENERAL_LISTENADDRESS=orderer
- ORDERER_GENERAL_GENESISMETHOD=file
- ORDERER_GENERAL_GENESISFILE=orderer.block
- ORDERER_GENERAL_LOCALMSPID=DEFAULT
- ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp
- GRPC_TRACE=all=true,
- GRPC_VERBOSITY=debug
working_dir: /opt/gopath/src/github.com/hyperledger/fabric
command: orderer
volumes:
- ./msp:/etc/hyperledger/msp
- ./orderer.block:/etc/hyperledger/fabric/orderer.block
ports:
- 7050:7050
啟動docker容器
docker-compose -f docker-compose-simple.yaml up -d
docker ps
看一下環境的狀態
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
073c62bb15d5 hyperledger/fabric-tools:1.4 "/bin/bash -c ./scri…" 6 seconds ago Up 3 seconds cli
6e78e11d6b1f hyperledger/fabric-ccenv:1.4 "/bin/sh -c 'sleep 6…" 6 seconds ago Up 2 seconds chaincode
eeb2dd0e8509 hyperledger/fabric-peer:1.4 "peer node start --p…" 7 seconds ago Up 5 seconds 0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp peer
c9cdcf50b042 hyperledger/fabric-orderer:1.4 "orderer" 8 seconds ago Up 6 seconds 0.0.0.0:7050->7050/tcp orderer
這樣就正常啟動容器了
- Chaincode容器,用於鏈碼環境
- CLI容器,用於與鏈碼進行交互
接着進入chaincode容器
docker exec -it chaincode bash
# 進入chaincode容器后, 進入存有chaincode的目錄編譯鏈碼
cd hello
go build
啟動鏈碼:
CORE_PEER_ADDRESS=peer:7052
CORE_CHAINCODE_ID_NAME=hellocc:0 ./hello
- CORE_PEER_ADDRESS: 用於指定Peer
- CORE_CHAINCODE_ID_NAME: 用於注冊到Peer的鏈碼
- hellocc: 指定鏈碼名稱
- 0: 指定鏈碼初始版本號
- ./hello: 指定鏈碼文件
CORE_PEER_ADDRESS=peer:7052CORE_PEER_ADDRESS=peer:7052中的7052端口指的是什么,為什么不是7051?
7052是用於指定鏈碼的專用監聽地址及端口號,而7051是Peer節點監聽的網絡端口
正常啟動,顯示如下內容:
root@6e78e11d6b1f:/opt/gopath/src/chaincode/hello# CORE_CHAINCODE_ID_NAME=hellocc:0 ./hello
2020-02-25 16:51:41.309 UTC [shim] setupChaincodeLogging -> INFO 001 Chaincode log level not provided; defaulting to: INFO
2020-02-25 16:51:41.310 UTC [shim] setupChaincodeLogging -> INFO 002 Chaincode (build level: ) starting up ...
新開一個terminal,進入cli容器:
docker exec -it cli bash
# 安裝鏈碼時指定的鏈碼名稱與版本號必須與在之前chaincode容器中注冊的鏈碼名稱及版本號相同
peer chaincode install -p chaincodedev/chiancode/hello -n hellocc -v 0
實例化鏈碼:
peer chaincode instantiate -n hellocc -v 0 -c '{"Args":["init","Hello","World"]}' -C myc
調用鏈碼:
peer chaincode query -n hellocc -c '{"Args":["query","Hello"]}' -C myc
最后返回World
,調用成功