1. 程式人生 > >Hyperledger系列(十三)開發Chaincode

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介面,即InitInvoke函式。 因此,讓我們新增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應用程式函式的名稱。

在我們的例子中,我們的應用程式只有兩個函式:setget,它們允許設定資產的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 APIChaincodeStubInterface.PutStateChaincodeStubInterface.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

上面的命令使用SingleSampleMSPSoloorderer 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包相關的程式碼。