• 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`子目录下并重新启动你的网络。此时在链码容器中就可以使用这些新添加的链码。

  • 相关阅读:
    charCodeAt() 和charAt()
    去除全角空格
    string字符串js操作
    取小数的常见操作
    js取最大最小值
    js加减法运算多出很多小数点
    js设置div透明度
    setTimeout设置不起作用
    node.js 找不到 xxx 模块解决办法
    servlet 监听器和过滤器入门
  • 原文地址:https://www.cnblogs.com/informatics/p/8051981.html
Copyright © 2020-2023  润新知