Hyperledger Fabric Chaincode智能合约

前言

之前Hyperledger Fabric 1.1区块链的环境搭建完成了,接下来要为项目开发智能合约。那么先编写一份简单的智能合约吧。

什么是chaincode

chaincode代码通常处理由网络成员同意的业务逻辑,因此它相当于“智能合约”。可以调用chaincode代码来更新或查询提案交易中的分类帐。考虑到适当的许可,chaincode代码可以调用另一个chaincode代码,无论是在同一频道还是在不同的频道中,以访问其状态。请注意,如果被调用的chaincode代码与调用chaincode代码位于不同的通道上,则只允许读取查询。也就是说,不同渠道上的被调用的chaincode代码只是一个查询,它在随后的提交阶段不参与状态验证检查。
上面是官方文档给出的解释,就我个人理解chaincode就是智能合约,它可以来控制交易的逻辑和查询相应的结果。

chaincode API

每一个chaincode必须要实现:Chaincode interface
chaincode “shim”API中的另一个接口是:ChaincodeStubInterface(笔者用的是这个API)
chaincode开发支持的语言:go、node.js
每个chaincode必须实现的方法:Init、Invoke
Init方法是用来执行任何必要的初始化,包括应用程序的初始化。
Invoke方法是用来响应各种invoke事务的。
此次笔者给出的chaincode实现了两个公司之间商品交易,以及对交易数据的查询。

开始chaincode的编写

由于笔者对node.js不是很熟悉,所以此次用的是go语言进行开发的。

1.指定chaincode的位置

创建目录并进入
mkdir -p $GOPATH/src/fcc && cd $GOPATH/src/fcc
创建源文件
vi simpleChainCode.go

2.依赖包的导入

import (
"fmt"
"encoding/json"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
"strconv"
)
var logger = shim.NewLogger("sampleChianCode")
//定义SimpleChainCode结构
type SimpleChainCode struct{
}

3.定义交易的结构体

//定义交易结构,具有5个属性。结构标记由encodingjson库使用
type SimpleAsset struct{
StartCompanyID int `json:"StartCompanyID"` //物品所属公司ID
AssetID int `json:"AssetID"` //交易物品ID
Money int `json:"Money"` //交易金额
EndCompanyID int `json:"EndCompanyID"`//物品归属转移后公司ID
TimeStamp string `json:"TimeStamp"` //交易时间
}

4.初始化chaincode(Init方法)

由于笔者没有想初始化任何数据,所以这里直接返回nil,只进行了系统的默认初始化操作。后面也会提供初始化数据的代码,可以看一下有什么不同之处。

func (t *SimpleChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response{
logger.Info("####### simpleChainCode init #######")
return shim.Success(nil)
}

初始化数据的Init方法:
/*func (t *SimpleChainCode) Init(stub shim.ChaincodeStubInterface) peer.Response{
logger.Info("####### simpleChainCode init #######")
var simpleAsset SimpleAsset
var err error
var DataIndex string
// 获取请求的参数
args := stub.GetStringArgs()
DataIndex = "0"
simpleAsset.StartCompanyID = 1
simpleAsset.AssetID = 1
simpleAsset.Money = 100
simpleAsset.EndCompanyID = 1
simpleAsset.TimeStamp = "1526547532"
// 通过调用stub.PutState()方法写入初始化数据
// 存储数据到账本中
jsonAsBytes, _ := json.Marshal(simpleAsset)//将simpleAsset结构体转换为字节数组
err = stub.PutState(DataIndex,jsonAsBytes)//写入数据到区块链网络
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}*/

5.调用chaincode(Invoke方法)

这里笔者只写了两个调用chaincode的方法,set、query。如果有其他的需要调用的话,可以参考这两个方法自己去实现。

func (t *SimpleChainCode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
logger.Info("####### simpleChainCode Invoke #######")
function, args := stub.GetFunctionAndParameters()
if function == "set"{
//写入交易数据到分布式账本
return t.set(stub,args)
}
if function =="query" {
//查询分布式账本的交易数据
return t.query(stub,args)
}
logger.Errorf("Unknown action, check the first argument, must be one of 'set', 'query'. But got: %v", args[0])
return shim.Error(fmt.Sprintf("Unknown action, check the first argument, must be one of 'set', 'query'. But got: %v", args[0]))
}

set方法:
func (t *SimpleChainCode) set(stub shim.ChaincodeStubInterface, args []string) peer.Response{
//参数接收变量
var simpleAsset SimpleAsset
var err error
var DataIndex string
if len(args) != 6 {
return shim.Error("Incorrect number of arguments. Expecting 6, function followed by DateIndex,StartCompanyID,AssetID,Money,EndCompanyID,TimeStamp")
}
//交易编号(必须非空唯一)
DataIndex = args[0]
if DataIndex == ""{
return shim.Error("DataIndex can not be empty")
}
simpleAsset.StartCompanyID, err = strconv.Atoi(args[1])
if err != nil {
return shim.Error("Expecting integer value for StartCompanyID")
}
simpleAsset.AssetID, err = strconv.Atoi(args[2])
if err != nil {
return shim.Error("Expecting integer value for AssetID")
}
simpleAsset.Money,err = strconv.Atoi(args[3])
if err != nil {
return shim.Error("Expecting integer value for Money")
}
simpleAsset.EndCompanyID,err = strconv.Atoi(args[4])
if err != nil {
return shim.Error("Expecting integer value for EndCompany")
}
simpleAsset.TimeStamp = args[5]
if simpleAsset.TimeStamp == ""{
return shim.Error("TimeStamp can not be empty")
}
//解析好的数据写入到log和区块
logger.Infof("DataIndex = %s, StartCompanyID = %d, AssetID = %d, Money = %d, EndCompanyID = %d,TimeStamp = %s\n",
DataIndex, simpleAsset.StartCompanyID, simpleAsset.AssetID, simpleAsset.Money, simpleAsset.EndCompanyID, simpleAsset.TimeStamp)
jsonAsBytes, _ := json.Marshal(simpleAsset)//将simpleAsset结构体转换为字节数组
err = stub.PutState(DataIndex,jsonAsBytes)//写入数据到区块链网络
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}

query方法:
func (t *SimpleChainCode) query (stub shim.ChaincodeStubInterface, args []string) peer.Response{
var simpleAsset SimpleAsset
var err error
var DataIndex string
if len(args) != 1{
return shim.Error("Incorrect number of arguments. Expecting 1, function followed by DateIndex")
}
DataIndex = args[0]//这个DataIndex在set中被指定非空唯一,如果不唯一的话查询会默认查询最新set的那条数据。
if DataIndex == "" {
return shim.Error("DataIndex can not be empty")
}
//从区块链网络中读取数据
jsonBytes, err := stub.GetState(DataIndex)
if err != nil {
jsonResp := "{\"ERROR\":\"Failed to get state for " + DataIndex + "\"}"
return shim.Error(jsonResp)
}
if jsonBytes == nil {
jsonResp := "{\"Error\":\"Nil value for " + DataIndex + "\"}"
return shim.Error(jsonResp)
}
//查看查询之后的字节数组是否可以转换成simplerAsset结构体,返回正确与否代表数据是否有错误
err = json.Unmarshal(jsonBytes, &simpleAsset)
if err != nil {
return shim.Error("DECODE ERROR")
}
jsonResp := "{\"Key\":\"" + DataIndex + "\",\"Value\":\"" + string(jsonBytes) + "\"}"
logger.Infof("Query Response:%s\n", jsonResp)
return shim.Success(jsonBytes)
}

最后当然少不了main方法:
func main(){
err := shim.Start(new(SimpleChainCode))
if err != nil {
logger.Errorf("Error starting Simple chaincode: %s", err)
}
}

6.建立chaincode

go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
go build --tags nopkcs11

配置基础网络

此处需要安装一下fabric-samples来运行我们的基础网络

git clone -b master https://github.com/hyperledger/fabric-samples.git
cd fabric-samples
git checkout {TAG}

为确保样本与您在下面下载的Fabric二进制文件的版本兼容,此TAG必须和Fabric版本相匹配,例如v1.1.0。
curl -sSL https://goo.gl/6wtTN5 | bash -s 1.1.0
获取并执行这个脚本,可能会因为墙的原因没法下载,可以去github上找到这个脚本bootstrap.sh地址,记得在当前目录执行这个脚本哦。可以生成我们需要的一些工具,生成目录bin
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hyperledger/fabric-tools latest b7bfddf508bc About an hour ago 1.46GB
hyperledger/fabric-tools x86_64-1.1.0 b7bfddf508bc About an hour ago 1.46GB
hyperledger/fabric-orderer latest ce0c810df36a About an hour ago 180MB
hyperledger/fabric-orderer x86_64-1.1.0 ce0c810df36a About an hour ago 180MB
hyperledger/fabric-peer latest b023f9be0771 About an hour ago 187MB
hyperledger/fabric-peer x86_64-1.1.0 b023f9be0771 About an hour ago 187MB
hyperledger/fabric-javaenv latest 82098abb1a17 About an hour ago 1.52GB
hyperledger/fabric-javaenv x86_64-1.1.0 82098abb1a17 About an hour ago 1.52GB
hyperledger/fabric-ccenv latest c8b4909d8d46 About an hour ago 1.39GB
hyperledger/fabric-ccenv x86_64-1.1.0 c8b4909d8d46 About an hour ago 1.39GB
系统里要有上述的镜像文件。执行了上边的二进制文件bootstrap.sh后,镜像可能跟上述有所不同,但是要确保上述镜像存在。

启动网络并安装智能合约

此过程我们需要开启三个terminal,并进入到chaincode-docker-devmode目录下。

Terminal 1启动网络

docker-compose -f docker-compose-simple.yaml up
上面的内容以SingleSampleMSPSoloorderer配置文件启动网络,并以“dev模式”启动对等体。它还启动了两个额外的容器 - 一个用于chaincode环境,一个用于与chaincode交互的CLI。创建和加入通道的命令被嵌入到CLI容器中,因此我们可以立即跳转到链式代码调用。

Terminal 2建立并启动链码

首先将$GOPATH/src/fcc复制到chaincode目录下。

docker exec -it chaincode bash
进入chaincode容器,你会看到下面的结果:
root@d2629980e76b:/opt/gopath/src/chaincode#
现在让我们来编译chaincode:
cd fcc
go build
现在来运行智能合约
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=mycc:0 ./fcc
上述命令没有与任何channel建立连接,所以接下来让我们去第三个终端看看该怎么使用chaincode

Terminal 3使用链码

Hyperledger Fabric 1.1官方文档提示尽管我们在–peer-chaincodedev模式下,但是仍然需要我们自己来安装链码,以便系统链码的生命周期可以正常的进行检查,不过,在将来可能会删除此要求。

docker exec -it cli bash
进入cli容器
peer chaincode install -p chaincodedev/chaincode/fcc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -C myc -c '{"Args":["a","10"]}'
安装chaincode并执行初始化,由于笔者此次没有对数据进行赋值初始化,所以 -c ‘{“Args”:[“a”,”10”]}’ 里面的数据没有任何意义,只是为了符合初始化的参数要求。

让我们调用set方法,公司ID为1与公司ID为2进行一次购买物品ID为1,价格为100的交易:
peer chaincode invoke -n mycc -c '{"args":["set","1","1","2","100","1","1526547532"]}' -C myc
接下来调用query方法:
peer chaincode query -n mycc -c '{"Args":["query","1"]}' -C myc
会返回:
Query Result:{“StartCompanyID”:1,”AssetID”:2,”Money”:100,”EndCompanyID”:1,”TimeStamp”:”1526547532”}
2018-05-25 07:21:48.005 UTC [main] main -> INFO 008 Exiting…..

至此我们的智能合约的安装和运行测试已经结束,我们可以在此基础上进行更深层次的智能合约开发。

其他区块链网络的智能合约部署

我们可以轻松测试不同的chaincode,方法是将它们添加到chaincode子目录并重新启动我们的网络。然后便可以在容器中访问我们的chaincode了。

你可能感兴趣的:(Hyperledger,Fabric)