Hyperledger Fabric鏈碼之二


    上篇文章中我們介紹了鏈碼的概念,本文中我們將介紹Fabric下鏈碼的編寫和測試。我們會通過一個簡單例子的方式來闡述鏈碼API的使用。

鏈碼API

    每一個鏈碼程序都必須實現一個接口Chaincode Interface, 這些方法用來響應接受到的交易。特別的,當鏈碼接收到``Instantiate``和``upgrade``類型的交易時會調用``Init``方法,執行一些需要的初始化,包括應用狀態的初始化。當鏈碼接收到``Invoke``類型的交易時候會調用``Invoke``方法來處理交易提議。

    鏈碼中調用的其他接口“shim” APIs,用來訪問和修改賬本,以及調用其他鏈碼操作。

在本文中,我們通過一個簡單的資產管理的鏈碼應用來展示這些APIs的使用。

簡單資產鏈碼

    我們的應用是一個簡單的鏈碼,用來在賬本上創建資產(key-value健值對)。

選擇代碼目錄位置

    如果沒有使用Go做過開發,應該首先確定系統中已經安裝和配置了Golang。然后為鏈碼應用程序創建一個目錄,我們使用如下的命令進行創建:

mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc

現在,我們創建源文件

touch sacc.go

編寫鏈碼

    現在我們來編寫一個具體的鏈碼。每個鏈碼都實現了``Chaincode Interface``,主要是``Init``和``Invoke``函數。因此在編寫程序時,我們首先要導入shim接口,以及其他一些包,,比如:``peer protobuf``包。然后,我們添加一個struct  ``SimpleAsset``作為鏈碼shim函數的接收器(這塊不懂,請補習go語言基本知識)。

代碼如下:

.. code:: go

    package main

    import (
        "fmt"

        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/protos/peer"
    )

    // SimpleAsset implements a simple chaincode to manage an asset
    type SimpleAsset struct {
    }

初始化鏈碼

接下來,我們要實現``Init``函數。

.. code:: go

//Init在鏈碼初始化的時候調用,用來初始化一些數據
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

  }

注意:鏈碼升級也會調用這個函數。當進行鏈碼升級的時候,確保新的鏈碼對``Init``進行了合適的修改。特別的,如果沒有遷移或者升級不需要進行一些初始化,那么可以提供一個空的``Init``。

然后,我們通過`ChaincodeStubInterface.GetStringArgs`來獲取調用``Init``的參數列表。在我們的例子中,我們期望得到一個健值對。

代碼如下:

.. code:: go

    // Init is called during chaincode instantiation to initialize any
    // data. Note that chaincode upgrade also calls this function to reset
    // or to migrate data, so be careful to avoid a scenario where you
    // inadvertently clobber your ledger's data!
    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")
      }
    }

 在代碼中我們簡單檢查了參數數量,然后我們將初始的狀態存入賬本中。我們調用``ChaincodeStubInterface.PutState``並傳入健值參數。假設程序執行正常,最后我們返回一個``peer.Response``來表明初始化成功。

.. code:: go

  // Init is called during chaincode instantiation to initialize any
  // data. Note that chaincode upgrade also calls this function to reset
  // or to migrate data, so be careful to avoid a scenario where you
  // inadvertently clobber your ledger's data!
  func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // Get the args from the transaction proposal
    args := stub.GetStringArgs()
    if len(args) != 2 {
      return shim.Error("Incorrect arguments. Expecting a key and a value")
    }

    // Set up any variables or assets here by calling stub.PutState()

    // We store the key and the value on the 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)
  }

調用鏈碼

首先我們添加``Invoke``函數

.. code:: go

    // Invoke is called per transaction on the chaincode. Each transaction is
    // either a 'get' or a 'set' on the asset created by Init function. The 'set'
    // method may create a new asset by specifying a new key-value pair.
    func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

    }

像``Init``一樣,我們通過``ChaincodeStubInterface``來獲取參數。``Invoke`` 的函數參數是鏈碼應用程序調用時候的名字。在我們的例子中,我們的應用程序只有兩個函數: "set"和“get”,用來設置資產和資產狀態的查詢。

1、我們首先調用“ChaincodeStubInterface.GetFunctionAndParameters"來獲取函數名字和參數。

2、驗證函數參數是否為“set”或者“get”,調用鏈碼應用相關函數,返回成功或者失敗的響應(通過“shim.Sucess"或者”shim.Error"函數)。

.. code:: go

    // Invoke is called per transaction on the chaincode. Each transaction is
    // either a 'get' or a 'set' on the asset created by Init function. The Set
    // method may create a new asset by specifying a new key-value pair.
    func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
        // Extract the function and args from the transaction proposal
        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 the result as success payload
        return shim.Success([]byte(result))
    }

實現鏈碼應用

前面提到,我們的鏈碼應用程序實現了兩個函數,可以通過”Invoke“函數來進行調用。下面是實現方法,分別調用了”ChaincodeStubInterface“的“PutState”和“GetState”接口。

.. code:: go

    // Set stores the asset (both key and value) on the ledger. If the key exists,
    // it will override the value with the new one
    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
    }

    // Get returns the value of the specified asset key
    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"函數。

.. code:: go

    package main

    import (
        "fmt"

        "github.com/hyperledger/fabric/core/chaincode/shim"
        "github.com/hyperledger/fabric/protos/peer"
    )

    // SimpleAsset implements a simple chaincode to manage an asset
    type SimpleAsset struct {
    }

    // Init is called during chaincode instantiation to initialize any
    // data. Note that chaincode upgrade also calls this function to reset
    // or to migrate data.
    func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
        // Get the args from the transaction proposal
        args := stub.GetStringArgs()
        if len(args) != 2 {
            return shim.Error("Incorrect arguments. Expecting a key and a value")
        }

        // Set up any variables or assets here by calling stub.PutState()

        // We store the key and the value on the 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)
    }

    // Invoke is called per transaction on the chaincode. Each transaction is
    // either a 'get' or a 'set' on the asset created by Init function. The Set
    // method may create a new asset by specifying a new key-value pair.
    func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
        // Extract the function and args from the transaction proposal
        fn, args := stub.GetFunctionAndParameters()

        var result string
        var err error
        if fn == "set" {
            result, err = set(stub, args)
        } else { // assume 'get' even if fn is nil
            result, err = get(stub, args)
        }
        if err != nil {
            return shim.Error(err.Error())
        }

        // Return the result as success payload
        return shim.Success([]byte(result))
    }

    // Set stores the asset (both key and value) on the ledger. If the key exists,
    // it will override the value with the new one
    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
    }

    // Get returns the value of the specified asset key
    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 function starts up the chaincode in the container during instantiate
    func main() {
        if err := shim.Start(new(SimpleAsset)); err != nil {
            fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
        }
    }

鏈碼編譯

現在讓我們編譯剛才編寫的鏈碼。

go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
go build --tags nopkcs11

假設編譯過程中沒有錯誤,接下來我們對鏈碼進行測試。

使用dev模式進行測試

正常情況下,鏈碼由節點來啟動和維護。然而在“dev“模式下,鏈碼由用戶來編譯和啟動,這種模式便於快速的編碼、編譯和調試鏈碼。

我們使用預先生成的orderer和channel artifacts來啟動”dev“模式下的測試網絡。之后,用戶可以快速的進行鏈碼的編譯和調用。

安裝超級賬本Fabric-samples

如何你還未安裝過samples,首先需要安裝。

git clone https://github.com/hyperledger/fabric-samples.git

進入fabric-samples的”chaincode-docker-devmode“目錄

.. code:: bash

  cd chaincode-docker-devmode

下載docker鏡像

在”dev“模式下,我們需要4個docker鏡像。如果已經clone了”fabric-samples", 執行’download-platfrom-specific-binaries', 會下載需要的鏡像。下載成功后,執行’docker images‘,會顯示已經下載好的鏡像如下:

  docker images
  REPOSITORY                     TAG                                  IMAGE ID            CREATED             SIZE
  hyperledger/fabric-tools       latest                               e09f38f8928d        4 hours ago         1.32 GB
  hyperledger/fabric-tools       x86_64-1.0.0                         e09f38f8928d        4 hours ago         1.32 GB
  hyperledger/fabric-orderer     latest                               0df93ba35a25        4 hours ago         179 MB
  hyperledger/fabric-orderer     x86_64-1.0.0                         0df93ba35a25        4 hours ago         179 MB
  hyperledger/fabric-peer        latest                               533aec3f5a01        4 hours ago         182 MB
  hyperledger/fabric-peer        x86_64-1.0.0                         533aec3f5a01        4 hours ago         182 MB
  hyperledger/fabric-ccenv       latest                               4b70698a71d3        4 hours ago         1.29 GB
  hyperledger/fabric-ccenv       x86_64-1.0.0                         4b70698a71d3        4 hours ago         1.29 GB

現在打開3個終端,切換到`chaincode-docker-devmode`。

終端1 - 啟動網絡

    docker-compose -f docker-compose-simple.yaml up

執行上面命令后會啟動fabric網路,包括一個`SingleSampleMSPSolo`配置的orderer和一個`dev`模式的peer。同時還會啟動另外兩個容器 — 一個是鏈碼環境容器,另一個CLI用來跟鏈碼進行交互,同時內嵌了創建和加入通道的命令,因此我們可以立刻進行合約的調用操作。

終端2 — 編譯&啟動鏈碼

docker exec -it chaincode bash

執行后進入到鏈碼容器中,

root@d2629980e76b:/opt/gopath/src/chaincode#
現在,編譯你的鏈碼: cd sacc go build 然后運行鏈碼: CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc

現在鏈碼在peer節點上啟動,鏈碼日志表明鏈碼已經成功注冊到peer上。注意,在這個階段鏈碼還沒有與通道進行關聯,由接下來的`instantiate`來完成。

終端3 — 使用鏈碼

盡管你使用的是`--peer-chaincodedev`模式,你仍然需要安裝鏈碼以便於生命周期系統鏈碼能夠通過正常的檢查,以后該模式下可能會移除這個檢查。

我們使用CLI容器來調用鏈碼。

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`。 我們會看到a的值已經改為`20`

 peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc

測試新的鏈碼

我們默認僅加載了`sacc`。 然而,我們可以很容的測試其他的鏈碼,通過將鏈碼添加到`chaincode`子目錄下並重新啟動你的網絡。此時在鏈碼容器中就可以使用這些新添加的鏈碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM