天天看點

hyperledger fabric chaincode 開發

chaincode是由go語言寫的,實作了定義的接口。其他語言例如JAVA也是支援的。通過application的transaction,chaincode可以初始化并管理Ledger狀态。

本文基于一個用于在Ledger上建立資産(鍵值對)的簡單案例來講述如何在fabric開發智能合約代碼

1.1 首選需要确定Go的環境被安裝以及被合适的配置。

為了簡單起見,我們使用以下指令:

mkdir -p $GOPATH/src/chaincode-example && cd $GOPATH/src/chaincode-example

接下來,我們建立源碼檔案

touch chaincode-example.go

1.2 Housekeeping

每一個chaincode都實作了Chaincode接口特别是Init以及Invoke函數。是以,我們引入

package main

import (

    "fmt"

    "github.com/hyperledger/fabric/core/chaincode/shim"

    "github.com/hyperledger/fabric/protos/peer"

)

1.3 初始化chaincode

接下來,我們事先Init函數

// Init is called during chaincode instantiation to initialize any data.

func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

}

接下來,我們調用ChaincodeStubInterface.GetStringArgs方法擷取Init中需要的參數,并檢查參數的有效性。我們期望擷取的參數是一個鍵值對。

// 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")

  }

}

接下來,由于我們的調用是有效的,我們将會在Ledger上存儲初始狀态。為了實作狀态的存儲,我們将會調用ChaincodeStubInterface.PutState方法,并把鍵值作為參數進行輸入。假設一切都工作正常,則會傳回一個peer.Response對象,表面初始化成功。

// 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)

}

1.4 調用chaincode

首先,添加Invoke函數簽名

// 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函數的參數就是需要調用chaincode應用的名稱。在我們的例子中,我們的應用有兩個簡單的函數set與get,允許asset的值被設定,同時允許擷取現在的狀态。我們首先調用ChaincodeStubInterface.GetFunctionAndParameters用來擷取chaincode應用的函數名稱與參數。

// 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()

}

接着,我們設定set與get函數名稱,并調用這些chaincode應用函數,通過shim傳回一個合适的響應。Error函數将會把一個響應序列化成gRPC protobuf消息。

// 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))

}

1.5 實作chaincode應用

我們的chaincode應用實作了兩個函數,能夠通過Invoke進行調用。接下來實作這些函數。就像我們上面所提到的,我使用chaincode shim API的ChaincodeStubInterface.PutState與ChaincodeStubInterface.GetState來通路access的狀态。

// 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

}

1. 6 合并上述代碼

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)

    }

}

1.7 測試

我們利用CLI容器去調用這些方法

docker exec -it cli bash

peer chaincode install -p chaincodedev/chaincode/chaincode-example -n mycc -v 1.0 

peer chaincode instantiate  --tls true

--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -n mycc -v 1.0 -c '{"Args":["a","10"]}' -C mychannel

接下來改變a的值為20.

peer chaincode invoke 

--tls true

--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

-n mycc -c '{"Args":["set", "a", "20"]}' -C mychannel

最後查詢

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

注意:目前基于搭建的容器環境 隻能添加chaincode_example字首的自定義合約代碼

繼續閱讀