Hyperledger系列(十三)開發Chaincode
Chaincode簡介
chaincode通常處理由網路成員贊同的業務邏輯,因此它類似於“智慧合約”。 可以呼叫chaincode來更新或查詢提案交易中的ledger。
如果有適當的許可,chaincode可以呼叫另一個chaincode,以訪問其狀態,無論是在同一個Channel還是在不同的Channel中。 請注意,如果被呼叫的chaincode與呼叫chaincode位於不同的通道上,則只允許讀取查詢。
Chaincode API
每個chaincode程式必須實現Chaincode interface
,其方法被呼叫以迴應收到的交易。
特別是當Chaincode接收例項化或升級transaction時,將呼叫Init方法,以便Chaincode可以執行任何必要的初始化,包括應用程式狀態的初始化。 呼叫Invoke方法是為了響應接收呼叫transaction來處理transaction提議。
chaincode “shim”API中的另一個介面是ChaincodeStubInterface
,用於訪問和修改ledger,並在chaincode之間進行呼叫。
在本文中,我們將通過實現一個管理“資產”的簡單chaincode應用程式來演示如何使用這些API。
簡單的資產Chaincode
我們的應用程式是一個基本樣本chaincode,用於在ledger上建立資產(key-value對)。
選擇程式碼的位置
現在,需要為chaincode應用程式建立一個目錄作為$GOPATH/src/
的子目錄。
為了簡單起見,我們使用下面的命令:
mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc
現在,建立將用程式碼填充的原始檔:
touch sacc.go
Housekeeping
首先,我們從一些Housekeeping開始。與每個chaincode一樣,它實現了Chaincode介面,即Init
和Invoke
函式。 因此,讓我們新增go import語句以獲取chaincode的必要依賴關係。 我們將匯入chaincode shim包和peer protobuf包。 接下來,讓我們新增一個結構SimpleAsset
作為Chaincode shim函式的接收器。
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // SimpleAsset 實現管理asset的一個簡單chaincode type SimpleAsset struct { }
Initializing the Chaincode
下面,我們實現 Init
函式.
// chaincode 例項化的時候,呼叫Init來初始化資料
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
}
請注意,chaincode升級也會呼叫此函式。在編寫將升級現有chaincode的程式碼時,請確保適當地修改Init
函式。 特別是,如果沒有“migration”(遷移),或沒有任何東西需要作為升級的一部分進行初始化時,請提供一個空的“Init”
方法。
接下來,我們將使用ChaincodeStubInterface.GetStringArgs
函式檢索Init
呼叫的引數,並檢查其有效性。 在我們的例子中,我們期待一個key-value對。
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 從 transaction proposal 獲取引數
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
}
接下來,已經確定該呼叫是有效的,我們將把初始狀態儲存在ledger中。 要做到這一點,我們將呼叫ChaincodeStubInterface.PutState
,並將key和value作為引數傳入。 假設一切順利,會返回一個指示初始化成功的peer.Response
物件。
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// 從 transaction proposal 獲取引數
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
// 通過呼叫 stub.PutState(),設定任何 variables或assets
// 我們把 key 和 value 儲存在ledger上
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
Invoking the Chaincode
首先,我們加入Invoke
函式的signature(簽名)。
//每個transaction都呼叫`Invoke`。每個 transactio是'get'
//或者 'set' asset 。 'set'方法可以,
//通過提供一個新的key-value對,建立一個新的asset。
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
}
與上面的Init
函式一樣,我們需要從ChaincodeStubInterface
中提取引數。 Invoke
函式的引數,將是要呼叫的chaincode應用程式函式的名稱。
在我們的例子中,我們的應用程式只有兩個函式:set
和get
,它們允許設定資產的value或檢索當前狀態。 我們首先呼叫ChaincodeStubInterface.GetFunctionAndParameters
來提取該chaincode應用函式的函式名稱和引數。
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 從transaction proposal中抽取 function和 args
fn, args := stub.GetFunctionAndParameters()
}
接下來,我們將驗證函式名稱是set
還是get
,並呼叫這些chaincode應用函式,通過shim返回相應的響應。shim.Success或shim.Error函式將響應序列化為gRPC protobuf訊息。
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 從transaction proposal中抽取 function和 args
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else {
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte(result))
}
實現Chaincode應用程式
如前所述,我們的chaincode應用程式實現了,兩個可以通過Invoke
函式呼叫的函式。 現在我們來實現這些功能。
請注意,正如我們上面提到的,為了訪問ledger的狀態,我們將利用chaincode shim API
的ChaincodeStubInterface.PutState
和ChaincodeStubInterface.GetState
函式。
// 在ledger上,儲存 asset (both key and value),如果key 存在,會用新的value覆蓋.
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
// 獲取指定asset key的value
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
完整程式碼
最後, 加入 main
函式, 它呼叫 shim.Start
函式。
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
type SimpleAsset struct {
}
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else {
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
return shim.Success([]byte(result))
}
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
// main函式,啟動container中的chaincode(在例項化的時候)。
main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
Building Chaincode
現在讓我們編譯chaincode。
go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
go build --tags nopkcs11
假設沒有錯誤,可以繼續下一步,測試chaincode。
使用Dev模式測試
通常,chaincodes由peer啟動和維護。 然而,在“Dev模式”下,鏈chaincodes由使用者構建並啟動。 在快速code/build/run/debug週期迭代的開發階段,此模式非常有用。
我們通過利用,為樣例dev網路,預先生成的orderer和channel artifacts,來開始“Dev模式”。 因此,使用者可以立即跳到編輯chaincode和呼叫的過程中。
安裝 Hyperledger Fabric Samples
如果之前沒有做過,請安裝Hyperledger Fabric Samples。
到fabric-samples
目錄的chaincode-docker-devmode
目錄下:
cd chaincode-docker-devmode
下載 Docker images
我們需要四個Docker映象,才能使“dev模式”按照提供的docker compose script執行。
如果您安裝了fabric-samples
repo clone,並按照說明下載平臺特定的二進位制檔案,那麼你應該,已經在本地安裝了必要的Docker映象。
使用docker images
命令,檢視本地的Docker Registry。應該如下“
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hyperledger/fabric-tools latest b7bfddf508bc About an hour ago 1.46GB
hyperledger/fabric-tools x86_64-1.1.0 b7bfddf508bc About an hour ago 1.46GB
hyperledger/fabric-orderer latest ce0c810df36a About an hour ago 180MB
hyperledger/fabric-orderer x86_64-1.1.0 ce0c810df36a About an hour ago 180MB
hyperledger/fabric-peer latest b023f9be0771 About an hour ago 187MB
hyperledger/fabric-peer x86_64-1.1.0 b023f9be0771 About an hour ago 187MB
hyperledger/fabric-javaenv latest 82098abb1a17 About an hour ago 1.52GB
hyperledger/fabric-javaenv x86_64-1.1.0 82098abb1a17 About an hour ago 1.52GB
hyperledger/fabric-ccenv latest c8b4909d8d46 About an hour ago 1.39GB
hyperledger/fabric-ccenv x86_64-1.1.0 c8b4909d8d46 About an hour ago 1.39GB
現在開啟3個終端,並切換到chaincode-docker-devmode
目錄
Terminal 1 - Start the network
docker-compose -f docker-compose-simple.yaml up
上面的命令使用SingleSampleMSPSolo
orderer profile啟動網路,並以“dev模式”啟動peer。
它還啟動了兩個額外的容器 - 一個用於chaincode環境,一個用於與chaincode互動的CLI。 建立和加入Channel的命令被嵌入到CLI容器中,因此我們可以立即跳轉到chaincode的呼叫。
Terminal 2 - Build & start the chaincode
docker exec -it chaincode bash
應該看到如下資訊:
[email protected]:/opt/gopath/src/chaincode#
現在編譯你的chaincode:
cd sacc
go build
現在執行chaincode:
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
chaincode,以peer和chaincode日誌開始,表示與peer註冊成功。 請注意,在此階段chaincode不與任何Channel關聯。與Channel的關聯,是在後續步驟中使用例項化命令完成的。
Terminal 3 - Use the chaincode
儘管你在 --peer-chaincodedev
模式中, 也需要install chaincode,這樣 life-cycle system chaincode 可以正常通過它的檢查。 當在 --peer-chaincodedev
模式中,這個需求可以在後續的版本中被移除掉。
啟動 CLI container 來進行呼叫docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc
現在發起一個invoke
把 “a” 的值改成“20”。
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc
再查詢一下”a“的值
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
Chaincode加密
在某些情況下,加密與key相關的value(全部加密或者部分加密)可能是有用的。例如,如果一個人的社會安全號碼,或地址正在寫入ledger,那麼你可能不希望這些資料以明文形式出現。 Chaincode加密,通過利用實體擴充套件實現,該BCCSP包裝,執行諸如加密和橢圓曲線數字簽名的加密操作。例如,為了加密,Chaincode的呼叫者通過transient欄位傳遞加密金鑰。然後可以將相同的金鑰用於隨後的查詢操作,從而允許對加密的狀態值進行適當的解密。
有關更多資訊和示例,請參閱fabric/examples
目錄中的Encc
示例。特別注意utils.go
幫手程式。此實用工具載入Chaincode shim API和實體擴充套件,並構建樣本加密Chaincode,隨後利用新類函式(例如,encryptAndPutState&getStateAndDecrypt)。因此,chaincode現在可以結合Get和Put的基本shim API(填充API)以及Encrypt和Decrypt的附加功能。
管理用Go編寫的chaincode的外部依賴關係
如果你的chaincode需要非Go標準庫提供的軟體包,則需要將這些軟體包包含在您的chaincode中。 有許多工具可用於管理這些依賴關係。 以下演示如何使用govendor:
govendor init
govendor add + external //新增所有外部包,或者
govendor add github.com/external/pkg //新增特定的外部軟體包
這將外部依賴關係匯入本地vendor目錄。這樣 peer chaincode包和peer chaincode安裝操作,將會包含與chaincode包相關的程式碼。