fabirc链码开发及在开发模式下的调试

一 前言

Fabric Chaincode是智能合约在Fabric上的实现方式,是与Fabric区块链交互的唯一渠道,也是生成Transaction的唯一来源。
开发语言:go、java
本文中选用go
Fabric节点运行模式有两种:

  • 一般模式
    Chaincode运行在docker容器里,这种方式开发调试过程非常繁杂:部署—调试—修改—创建docker镜像—部署—···
  • 开发模式
    相对容易:部署—调试—修改—部署—···

本文中采用开发模式调试链码。

二 开发环境

需要fabric-samples源码
下载好后进入到chaincode目录,新建一个mychaincode文件夹,接下来,我们将会在这个目录中编写我们的ChainCode。
还有一个chaincode-docker-devmode目录。在这个目录中,我们可以借助构建自带区块链样例网络时已经预先生成好的order和channel来启动“开发者模式”。这样,用户可以立即编译chaincode并调用函数。
接下来,我们将会在这个目录中调试我们编写的ChainCode。包括编译,安装,实例化等等。

三 编写Chaincode

在mychaincode目录下新建一个go文件,编写相应内容,给出一个建立链路合约(仿照官方转账示例)的示例代码如下:

package main

import (
    "fmt"
    "encoding/json"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    pb "github.com/hyperledger/fabric/protos/peer"
    "time"
)

// 合约状态
const (
    BillInfo_State_Publish = "Publish"
)


// 账单数据结构
type Bill struct {
    BillId string `json:"bill_id"`
    PurchaseUserCode string `json:"purchase_user_code"`
    BidingStartTime string `json:"biding_start_time"`
    BidingEndTime string `json:"biding_end_time"`
    ContractStartTime string `json:"contract_start_time"`
    ContractEndTime string `json:"contract_end_time"`
    LinkStart string `json:"link_start"`
    LinkEnd string `json:"link_end"`
    State string `json:"state"`
}

//链码返回结构
type chaincodeRet struct {
    Code int // 0 success otherwise 1
    Des  string //description
}

// 根据返回码和描述返回序列号后的字节数组
func getRetByte(code int,des string) []byte {
    var r chaincodeRet
    r.Code = code
    r.Des = des

    b,err := json.Marshal(r)

    if err!=nil {
        fmt.Println("marshal Ret failed")
        return nil
    }
    return b
}

// 根据返回码和描述返回序列号后的字符串
func getRetString(code int,des string) string {
    var r chaincodeRet
    r.Code = code
    r.Des = des

    b,err := json.Marshal(r)

    if err!=nil {
        fmt.Println("marshal Ret failed")
        return ""
    }
    return string(b[:])
}

//初始化默认
func (a *Bill) Init(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success(nil)
}

//链码Invoke接口
func (a *Bill) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    function,args := stub.GetFunctionAndParameters()
    //invoke
    if function == "issue" {
        //发布合约(招标)
        return a.issue(stub, args)
    }

    if function == "query" {
        // the old "Query" is now implemtned in invoke
        return a.query(stub, args)
    }


    return shim.Error("ChainnovaChaincode Unkown method!")
}

//保存合约
func (a *Bill) putBill(stub shim.ChaincodeStubInterface, bill Bill) ([]byte, bool) {

    byte,err := json.Marshal(bill)
    if err!=nil {
        return nil, false
    }

    err = stub.PutState(bill.BillId, byte)
    if err!=nil {
        return nil, false
    }
    return byte, true
}

//根据合约号取出合约
func (a *Bill) getBill(stub shim.ChaincodeStubInterface, bill_No string) (Bill, bool) {

    var bill Bill
    key := bill_No
    b,err := stub.GetState(key)
    if b!=nil {
        return bill, false
    }

    err = json.Unmarshal(b,&bill)
    if err!=nil {
        return bill, false
    }
    return bill, true
}

//发布合约
func (a *Bill) issue(stub shim.ChaincodeStubInterface, args []string) pb.Response {
    if len(args)!=9 {
        return shim.Error("ChainnovaChaincode Invoke issue args != 9")
    }

    bill := Bill{BillId:args[0],PurchaseUserCode:args[1],BidingStartTime:args[2],BidingEndTime:args[3],ContractStartTime:args[4],ContractEndTime:args[5],LinkStart:args[6],LinkEnd:args[7],State:args[8]}

    //根据合约ID查找合约ID是否已存在
    _, existbl :=a.getBill(stub,bill.BillId)
    if existbl {
        return shim.Error("ChainnovaChaincode Invoke issue failed : the billNo has exist")
    }
    if bill.BillId == "" {
        bill.BillId = fmt.Sprintf("%d",time.Now().UnixNano())
    }

    //更改合约信息和状态并保存合约:合约状态设为新发布
    bill.State = BillInfo_State_Publish

    //保存票据
    _, bl := a.putBill(stub, bill)
    if !bl {
        return shim.Error("ChainnovaChaincode Invoke issue putdata failed!")
    }

    return shim.Success(nil)
}

//查询合约
func (a *Bill) query(stub shim.ChaincodeStubInterface, args []string) pb.Response {

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    billAsBytes, _ := stub.GetState(args[0])
    return shim.Success(billAsBytes)
}


func main() {
    if err := shim.Start(new(Bill)); err != nil {
        fmt.Printf("Error starting Bill chaincode: %s", err)
    }
}

四 调试Chaincode

调试ChainCode主要分为三个步骤:

  • 启动Fabric网络
  • 编译安装chainCode
  • 调用ChainCode

这三个步骤,需要进入chaincode-docker-devmode目录下面打开三个独立的终端分别完成。

第一个终端

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

上述指令启动了一个带有SingleSampleMSPSoloorderer profile的网络,并将peer和orderer节点在“开发者模式”下启动。它还启动了两个额外的容器:一个包含chaincode运行环境;另一个是CLI命令行,可与chaincode进行交互。创建并加入channel(管道)的指令内嵌于CLI容器中。

第二个终端

进入容器:

docker exec -it chaincode bash

在容器中ls可以看到mychaincode文件夹,cd进入后可以看到我们编写的源码。
编译chaincode:

go build

如果没有报错,则可以看到多了一个可执行的chaincode文件。
(注:编译这一步也可以在容器外做,即链码编写完之后直接在目录中执行go build)
运行chaincode:

CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./mychaincode

Chaincode被peer节点启动,chaincode日志表明peer节点成功注册。

第三个终端

下面我们将进入CLI容器进行chaincode在链上的安装和初始化:

docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/mychaincode -n mycc -v 0
peer chaincode instantiate -n mycc -v0 -c'{"Args":[]}' -C myc

这里写图片描述
因为链码功能为新建合约,所以初始化的时候参数为空。
在CLI内部会为mychaincode创建SignedChaincodeDeploymentSpec,并将其发送到本地peer节点。这些节点会调用LSCC上的Install方法。上述的-p选项指明chaincode的路径,其必须在用户的GOPATH目录下(比如$GOPATH/src/sacc)。
现在我们执行一次invoke调用,新建一条从北京到纽约的链路:

peer chaincode invoke -n mycc -v 0 -c '{"Args":["issue","lc1522512000","CMCC","1525104000","1525881600","1527782400","1559318400","beijing","new york","new"]}' -C myc

最后查询该链路,我们会看到链路的内容。

peer chaincode invoke -n mycc -v 0 -c '{"Args":["query","lc1522512000"]}' -C myc

退出

Terminal2和Terminal3用exit命令退出docker。

Terminal 1直接ctrl+c退出,然后再执行

docker-compose -f docker-compose-simple.yaml down

一定要确保容器清理干净。

调试过程

当测试时出错时,如果每次都关闭网络环境退出容器后重新修改代码并编译,过于麻烦。一个简单做法是第一个终端不关,在第二个终端中退出容器,然后进入mychaincode文件夹修改链码并编译后重新进入容器,甚至可以在容器内安装vim并修改编译链码,完成之后在第三个终端中继续调用即可。

参考:

区块链和HyperLedger系列(IBM微讲堂)——区块链第四讲
官方文档
《深度探索区块链——hyperledger技术与应用》

猜你喜欢

转载自blog.csdn.net/vivian_ll/article/details/80339943