MENU

Fabric开发者模式调试ChainCode

前言

上一篇 搭建fabric-v1.0.0 1peer+1orderer网络 描述了跨两台机器的fabric网络的构建,成功执行完上文最后的example_02基本样例后我还有几点要说明以下:首先,当我们如果运行自己的chaincode智能合约时,注意对peer installpeer instantiate通过-n命名链码名称时保证与之前peer install安装的链码名称不同,否则即使peer install了自己的chaincode,但是跑起来还是原来的链码,或者如果之前安装的链码不再使用时,可通过docker rmi [IMAGES_ID]删除之前安装的链码,具体IMAGES_ID通过docker images命令查看。其次,那么既然我要安装自己的链码,如何构建开发者环境去写chaincode呢?这也是我们本文要说的。其实本文大致就是说明如何在本地(LOCAL)搭建一个fabric开发环境去调试和实现chaincode,当然你也可以先仅仅参考 搭建fabric-v1.0.0 1peer+1orderer网络 中仅构建fabric环境这一部分,然后在本地构建开发者模式,当自己的chaincode跑通后再进行后续的 1orderer + 1peer 甚至更多,这里暂不多说,本文后面会补充如何将本地写好的chaincode搭建在多机中(实际上双机搭建后面的example_02替换成自己的chaincode即可,注意修改peer install-p参数所指定的路径参数)。

开发环境的准备

我们这里的开发环境,是建立在官方fabric-samples项目提供的网络基础之上。首先我们$GOPATH/src/github.com/hyperledger/的目录下之前已经构建过fabric环境,即在上述目录下有fabric文件,与fabric文件同级目录下载fabric-samples源码

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

这个项目的目录如下:

fabric-samples
├── balance-transfer
├── basic-network
├── bin
├── chaincode
├── chaincode-docker-devmode
├── fabcar
├── fabric-ca
├── first-network
├── high-throughput
├── LICENSE
├── MAINTAINERS.md
├── README.md
└── scripts

这里很多目录是HyperLedger官方提供的demo。介绍两个主要的目录。第一个是chaincode。进入这个目录,有很多demo文件,但我这边写了自己的智能合约,把其他的demo也删除掉了,在具体跑智能合约的时候这个文件夹会映射在docker的chaincode容器中,那时候可对chaincode进行构建go build,先来看chaincode目录:

chaincode
└── mylogsys

其中mylogsys即存放我的chaincode,这个我后续会讲解,先知道是写好的chaincode即可。这里先为我们接下来的项目建立了这个mylogsys文件夹:

mkdir mylogsys

接下来,我们将会在这个目录中编写我们的ChainCode。

chaincode-docker-devmode目录中,我们可以借助构建自带区块链样例网络时已经预先生成好的order和channel来启动“开发者模式”。这样用户就可以立即编译chaincode并调用函数。我们将会在这个目录中调试我们编写的chaincode。包括编译安装实例化等。

编写ChainCode

首先,我们进入刚刚创建的mylogsys文件夹:

cd ~/fabric-samples/chaincode/mychaincode

新建一个go文件夹和log文件夹,在go文件夹中创建main.go文件,并编写相关的内容;在log文件夹下创建log.txt用于chaincode代码中调用。关于chaincode如何编写,这里不在赘述,fabric1.0主要提供init invoke 接口来编写。直接贴上代码,需要注意synlog功能指定的pathname是相对而言的,对于不同环境可以适当调整

package main

import (
    "bufio"
    "encoding/json"
    "fmt"
    "io"
    "os"
    "time"

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

type logInfo struct {
}

//记录每天发送消息的数量
var messageCount int = 0

//每天发送的小心存放在messageSlice切片中
var messageSlice []map[int]string

//用于时间更新的变量
var timeUpdate string = "19491001"

//Init函数 用于初始化存放抓包信息的日志log 将其以key-value方式载入到peer中
func (log *logInfo) Init(stub shim.ChaincodeStubInterface) peer.Response {
    //初始化时不要求接收任何参数 即'{"Args":[]}'
    _, args := stub.GetFunctionAndParameters()
    if len(args) != 0 {
        return shim.Error("Incorrect number of arguments. Expecting '{\"Args\":[]}'")
    }

    pathname := "/opt/gopath/src/chaincode/mylogsys/log/log.txt"
    //将路径加入至key-value数据库(key = log value = pathname)
    err := stub.PutState("log", []byte(pathname))
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to load log path: %s", pathname))
    }

    return shim.Success([]byte("INIT IS OK!"))
}

//Invoke函数 用于调用chaincode方法 如:
//1. synlog //读取网络包抓取信息
//2. cleanlog //清理网络包信息日志log
//3. write //写自己的日志
//4. read //读自己的日志
//5. delete //清除自己某天的日志
func (log *logInfo) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    function, args := stub.GetFunctionAndParameters()
    if function != "invoke" {
        return shim.Error("Unknown function call. Expecting function \"invoke\"!")
    }
    if len(args) < 1 {
        return shim.Error("Incorrect number of arguments. Expecting at least 2 args such as :'{\"Args\":[\"invoke\", \"func_name\"]}'")
    }

    switch {
    //给value存放日志地址并读取日志 '{"Args":["invoke", "synlog"]}'
    case args[0] == "synlog":
        return log.synLogInfo(stub)

    //对value存放地址处的日志清空 '{"Args":["invoke", "cleanlog"]}'
    case args[0] == "cleanlog":
        return log.cleanLogInfo(stub)

    //记录自己的日志 会自动获取系统时间 '{"Args":["invoke", "write"]}'
    case args[0] == "write":
        return log.writeMyselfDiary(stub, args)

    //查新自己的日志信息 需要输入查询的日期 '{"Args":["invoke", "read", "20190423"]}'
    case args[0] == "read":
        return log.readMyselfDiary(stub, args)

    //删除自己某日的日志信息 '{"Args":["invoke", "delete", "20190423"]}'
    case args[0] == "delete":
        return log.deleteMyselfDiary(stub, args)
    default:
        fmt.Printf("function %s is not exist!\n", args[0])
    }
    return shim.Error("Unknown action!\n")
}

//'{"Args":["invoke", "synlog"]}'
func (log *logInfo) synLogInfo(stub shim.ChaincodeStubInterface) peer.Response {
    pathname, err := stub.GetState("log")
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to get log: %s with error: %s", pathname, err))
    }
    if pathname == nil {
        return shim.Error(fmt.Sprintf("log not found: %s", pathname))
    }

    //读取抓包Log信息
    message := fileRead(string(pathname))

    return shim.Success([]byte(message))
}

//'{"Args":["invoke", "cleanlog"]}'
func (log *logInfo) cleanLogInfo(stub shim.ChaincodeStubInterface) peer.Response {
    pathname, err := stub.GetState("log")
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to get log: %s with error: %s", pathname, err))
    }

    //对抓包Log清空
    message := fileClean(string(pathname))
    return shim.Success([]byte(message))
}

//'{"Args":["invoke", "write", "your log string"]}'
func (log *logInfo) writeMyselfDiary(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var slice map[int]string
    var dairy string

    now := time.Now()
    current := catchCurrentTime(now.Format("2006"), now.Format("01"), now.Format("02"))
    //如果时间不对 则更新时间
    if timeUpdate != current {
        timeUpdate = current
        messageCount = 0
        messageSlice = nil
    }

    //将日志读入切片中
    slice = make(map[int]string)
    // var inputReader *bufio.Reader
    // inputReader = bufio.NewReader(os.Stdin)
    // dairy, err := inputReader.ReadString('\n')
    dairy = args[1]
    // if err != nil {
    // return shim.Error("Failed to call inputReader.readString(\n)")
    // }
    slice[messageCount] = dairy
    messageCount++
    messageSlice = append(messageSlice, slice)

    //将读入切片的日志用JSON序列化
    data, err := json.Marshal(messageSlice)
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to convert your dairy to json, errno :%v", err))
    }

    //将序列化后的日志写入peer
    err = stub.PutState(current, data)
    if err != nil {
        return shim.Error("Failed to insert you dairy!")
    }

    return shim.Success([]byte("DAIRY PUBLISHED!"))
}

//'{"Args":["invoke", "read", "20190422"]}'
func (log *logInfo) readMyselfDiary(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    //获取年月日 来查询自己写的日志
    value, err := stub.GetState(args[1])
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to get your dairy in: %s with error: %s", args[0], err))
    }
    if value == nil {
        return shim.Error(fmt.Sprintf("Dairy not found in: %s", args[0]))
    }

    //申请map切片空间  并将序列化的日志反序列化存入map切片
    var tmp []map[int]string
    err = json.Unmarshal(value, &tmp)

    //打印某年某月某日的日志信息
    fmt.Println(args[1], ":")
    for _, mapstruct := range tmp {
        for _, value := range mapstruct {
            fmt.Println(value)
        }
    }

    return shim.Success([]byte("DAIRY SHOWED!"))
}

//'{"Args":["invoke", "delete", "20190422"]}'
func (log *logInfo) deleteMyselfDiary(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    //删除key-value
    err := stub.DelState(args[1])

    //清空内存切片
    messageSlice = nil
    if err != nil {
        return shim.Error(fmt.Sprintf("Failed to delete your dairy in: %s with error: %s", args[0], err))
    }

    return shim.Success([]byte("DAIRY DELETED!"))
}

func fileRead(pathname string) string {
    //打开path的文件
    fileHandle, err := os.OpenFile(pathname, os.O_RDONLY, 0666)
    if err != nil {
        fmt.Println(err)
        return "LOG READ ERROR!"
    }

    //读文件内容至chaincode上显示
    reader := bufio.NewReader(fileHandle)
    for {
        str, err := reader.ReadString('\n')
        fmt.Printf(str)
        if err == io.EOF {
            break
        }
    }
    fmt.Println()

    return "LOG INFORMATION PRINTED!"
}

func fileClean(pathname string) string {
    //文件清空操作 size置0
    err := os.Truncate(pathname, 0)
    if err != nil {
        return "LOG CLEAN ERROR!"
    }

    return "LOG INFORMATION CLEAN!"
}

//获取当前年月日 并返回组合后的字符串
func catchCurrentTime(year string, month string, day string) string {
    current := year + month + day
    return current
}

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

调试ChainCode

在Fabric中,调用chaincode有两种方式,一种是通过SDK编写应用程序来调用(博主还没试过);还有一种方式就是在docker cli容器中调用chaincode。这篇文章中,我们用方法二来调用chaincode

调试ChainCode主要分为三个步骤:
启动Fabric网络。
编译安装chainCode。
调用ChainCode。

这三个步骤,需要我们在开发者模式下完成,也就是进入chaincode-docker-devmode目录下面打开三个独立的终端。

Terminal 1 - Start the network

进入chaincode-docker-devmode目录,看到docker-compose-simple.yaml并开启网络:

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

该命令会为我们常见fabric网络所需要的四个容器(这里与之前多机网络中的容器数不同,所以chaincode在多机中跑时与开发者模式稍有不同),我们通过docker ps -a查看开启的容器信息:

CONTAINER ID        IMAGE                        COMMAND                  CREATED              STATUS              PORTS                                            NAMES
aee94e5fae69        hyperledger/fabric-tools     "/bin/bash -c ./scri…"   About a minute ago   Up About a minute                                                    cli
1f354fe79475        hyperledger/fabric-ccenv     "/bin/bash -c 'sleep…"   About a minute ago   Up About a minute                                                    chaincode
d960e6624a0c        hyperledger/fabric-peer      "peer node start --p…"   About a minute ago   Up About a minute   0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp   peer
f0aabeaa1a77        hyperledger/fabric-orderer   "orderer"                About a minute ago   Up About a minute   0.0.0.0:7050->7050/tcp

Termial 2 - Build & start the chaincode

首先进入docker chaincode容器:

➜  chenguo docker exec -it chaincode bash

root@1f354fe79475:/opt/gopath/src/chaincode# pwd
/opt/gopath/src/chaincode
root@1f354fe79475:/opt/gopath/src/chaincode# ll
total 12
drwxr-xr-x 3 1000 1000 4096 May 17 02:44 ./
drwxr-xr-x 1 root root 4096 May 17 09:52 ../
drwxrwxr-x 4 1000 1000 4096 May 17 02:59 mylogsys/

# 执行
cd mylogsys/go/
go build main.go

# 运行chaincode 反正fabric1.0中指定端口7051 而非7052 请注意参考链接中写的有问题
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./main

Termial 3 - Use the chaincode

docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/mylogsys/go/ -n mylog -v 0
peer chaincode instantiate -n mylog -v 0 -c '{"Args":[]}' -C myc
peer chaincode invoke -n mylog -c '{"Args":["invoke", "write", "hello world !"]}' -C myc
peer chaincode invoke -n mylog -c '{"Args":["invoke", "read", "20190517"]}'

以上命令执行完后,写入的hello world !将会在二号终端即chaincode容器中打印出来,但是对于我们的1orderer+1peer多机网络只有cli容器进行peer invoke调用,打印结果则需要通过docker查看我们创建的实例peer节点的log日志信息,即就是首先docker ps -a找到我们的容器ID,再通过命令:docker logs -f [CONTAINER_ID]查看日志,其中-f参数表示阻塞等待,如果不加-f则执行查看日志完毕后退出。

我们的chaincode还有个功能是手动在上文中的log.txt文件中写入自己的日志,然后通过以下命令将文件内容读取至二号chaincode容器的终端,(多机网络中即就是docker容器的log中):

peer chaincode invoke -n mylog -c '{"Args":["invoke", "synlog"]}'

我们还可以通过以下命令清除log.txt文件中的内容:

peer chaincode invoke -n mylog -c '{"Args":["invoke", "cleanlog"]}'

说实话我一开始是打算通过一个网卡信息抓取函数将所有的TCP等数据包信息写入到log.txt中,然后我就可以在链上读取网络抓包信息了,但是我的网卡抓取函数需要引用第三方包google/gopacket,当我在fabric网络启动时,我的第三方包就被docker容器隔离在外了,所以代码就找不到头文件,我以为在yaml配置中修改文件的映射,让头文件加入到我的fabric容器中,但是无果,就修改了原来的功能。

至此,以上就是在开发者环境下去调试自己的chaincode,如果我们在本地写好chaincode,可以通过以上方式进行调试运行,不着急具体部署在多机中。写本文和之前的多机网络也算是对自己的毕业设计的一个总结吧,菜鸡还在努力的路上,如上文有误也请务必指出! :)

参考链接:

  1. 超级账本fabric-开发模式(dev mode)
  2. 在开发者模式下调试chaincode
最后编辑于: May 21, 2019
Archives Tip
QR Code for this page
Tipping QR Code