最近跟着 HyperLedger 中文文档在学习ChainCode开发的时候,对官方文档的文章结构很是不理解。首先叫我们编写一个简单资产管理的 chainCode,最后调试的时候,竟然是调试官方提供的版本。所以决定把这与部分重新整理一下。讲解一下怎样编写,调试自己的ChainCode。全文的结构如下:
前言
目前编写ChainCode的时候,并没有没有 IDE 来供我们使用。因为我们的ChainCode是运行在区块链的节点上。这一点和编写后端程序有点相似,比如你用 Java 写一个后端程序,调试的时候,你需要开启一个 Tomcat 服务器,才能够进行调试。由于 java 后台开发技术比较成熟,很多的很好的框架已经可以帮助你处理这些环境配置,服务器开启等,让你专注于业务逻辑的开发,比如 SpringBoot 框架。
但是,目前在ChainCode的编写调试过程中,并没有这样子的框架。我们需要自己搭建好 Fabric 网络,自己手动编写,编译并调试ChainCode。为了方便我们的调试,这里,我们首先准备好 ChainCode 的开发环境。
1.开发环境的准备
我们这里的开发环境,是建立在官方 fabric-sample 项目提供的网络基础之上。具体步骤如下:
- 下载 fabric-sample 源码
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples
这个项目的目录如下:
这里,很多目录是 HyperLedger 官方提供的小 demo。介绍两个主要的目录。
第一个是 chaincode.进入这个目录,我们可以看到一下几个文件,这些文件都是不同项目中要用到的 ChainCode。
abac chaincode_example02 fabcar hyperledger marbles02 sacc
比如,fabCar 目录里面,就是我们之前 FabCar 项目中智能合约存储的地方。
在这里为我们接下来的项目建立一个文件夹:
mkdir mychaincode
接下来,我们将会在这个目录中编写我们的 ChainCode。
第二个是 chaincode-docker-devmode 目录。在这个目录中,我们可以借助构建自带区块链样例网络时已经预先生成好的 order 和 channel 来启动“开发者模式”。这样,用户可以立即编译 chaincode 并调用函数。
接下来,我们将会在这个目录中调试我们编写的 ChainCode。包括编译,安装,实例化等等。
2.编写 ChainCode
首先,我们进入刚刚创建的 mychaincode 文件夹:
cd ~/fabric-samples/chaincode/mychaincode
新建一个 mychaincode.go 文件,并编写相关的内容。关于 ChainCode 如何编写,前文已经介绍过了,这里不在赘述。直接贴上代码:
//这里是一个简单资产 ChainCode 的编码。关于源码的解读,写在了注释里面。
// This is my first ChainCode
/*
* 引入相关的包
*/
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
// 定义一个简单资产结构体
type SimpleAsset struct {
}
//为 SimpleAsset 结构体实现 Init 接口函数。这个函数会在 ChainCode 初始化,或者升级的时候调用。
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")
}
//通过 stub.PutState() 来讲数据写入到账本中。
// 将 key-value 键值对写入到账本中。
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 接口函数。
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// 从交易提案中获取要调用的功能函数名。
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 功能函数,用于将数据写入到账本中。
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 功能函数,用于查询账本中函数的值。
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 函数,用于在实例化过程中,在容器中启动 ChainCode。
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
3.调试 ChainCode
在 Fabric 中,调用 chainCode 有两种方式,一种是通过 SDK 编写应用程序来调用,比如《HyperLedger 实战-编写一个区块链应用》中的 fabcar 项目中,通过 nodejs 程序调用 ChainCode。还有一种方式,就是使用 cli 命令来调用 ChainCode。由于这篇文章中,我们用 cli 命令行来调用 ChainCode。
调试 ChainCode 主要分为三个步骤:
- 启动 Fabric 网络。
- 编译安装 chainCode。
- 调用 ChainCode。
这三个步骤,需要我们在开发者模式下完成,也就是进入 chaincode-docker-devmode 目录下面打开三个独立的终端。
- 1 号终端
docker-compose -f docker-compose-simple.yaml up
上述指令启动了一个带有 SingleSampleMSPSoloorderer profile 的网络,并将节点在“开发者模式”下启动。它还启动了另外两个容器:一个包含 chaincode 运行环境;另一个是 CLI 命令行,可与 chaincode 进行交互。创建并加入 channel(管道)的指令内嵌于 CLI 容器中。
- 2 号终端
docker exec -it chaincode bash
执行完上述指令是进入了 cli 容器,您应该会看到如下内容:
root@b46549c6be95:/opt/gopath/src/chaincode#
ls 可以看到如下目录
abac chaincode_example02 fabcar hyperledger marbles02 mychaincode sacc
进入我们的目录 mychaincode,可以看到我们编写的 chainCode 源码:
下面编译我们的 chaincode:
go build
运行 ls 命令,可以看到,多了一个可执行的 chainCode 文件。
现在运行 chaincode:
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./mychaincode
Chaincode 被 peer 节点启动,chaincode 日志表明 peer 节点成功注册。
注意:
现阶段 chaincode 还没有与任何 channel 关联。这会在接下来使用 instantiate 指令后实现.
CORE_PEER_ADDRESS=peer:7052。
在中文文档中,参数为 7051 是不对的,应该改为 7052。
- 3 号终端
即便处于 peer-chaincodedev 模式,安装 chaincode 这一步仍必不可少,这样生命周期系统 chaincode 才能正常进行检查。
下面我们将进入 CLI 容器进行 chaincode 调用。
docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/mychaincode -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc
在 CLI 内部会为 mychaincode 创建 SignedChaincodeDeploymentSpec,并将其发送到本地 peer 节点。这些节点会调用 LSCC 上的 Install 方法。上述的-p 选项指明 chaincode 的路径,其必须在用户的 GOPATH 目录下(比如$GOPATH/src/sacc)。
完整的命令选项详见:HyperLedger 官方文档 CLI 部分。
现在我们执行一次将 a 的值设为 20 的调用:
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc
最后查询 a 的值,我们会看到 20。
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
值此,我们实现了 mychaincode 从编写到调试的一系列操作。可以通过将不同的 chaincode 添加到 chaincode 子目录下重启网络来轻松地测试它们。重启它们将可在 chaincode 容器中被访问。
4.玄学级 Bug
在虚拟机环境下,操作终端 2 使用如下命令运行 chainCode 的时候:
现在运行 chaincode:
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0./mychaincode
出现了如下错误:
2018-04-11 03:24:16.466 UTC [shim] userChaincodeStreamGetter -> ERRO 003 Error trying to connect to local peer: context deadline exceeded
但是在电脑上的 Ubuntu 上启动 ChainCode,一切正常。到现在我都不理解这是为什么!!!只能理解为玄学级别的 Bug 了(生无可恋脸)。
总结
这篇文章主要是根据官方文档加上自己的理解整理而成。关于玄学级 Bug,目前还没有发现解决的办法。今后若是发现了,再回来填坑~
参考链接:
HyperLedger-Fabric 文档-ChainCode 开发手册