天天看点

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前缀的自定义合约代码