摘要
上周的问题,多次尝试解决不了后,重构了下代码,成功解决了问题,事后分析,应该是有个节点没有成功加入通道,安装链码时,该节点没有安装,但链码执行时,选中的执行背书策略的节点是该节点,从而导致的调用链码错误。解决问题后,又遇到了几个小问题,解决所有问题后,用fabric-go-sdk成功实现了调用链码,向区块链账本中插入数据和查询数据的操作。并用Gin框架搭建好了育苗组织基本的后端服务器接口。还需要搭建养殖户组织的后端服务器接口、政府职能组织的后端服务器接口、普通用户组织的后端服务器接口,就基本完成了基于区块链溯源系统的后端服务器开发。
一、fabric-go-sdk各个封装函数的功能介绍
1.1 查询指定节点通道是否已经存在(函数: QuerySavedChannel(…) )
输入资源管理客户端、带查询节点域名、待查询通道,若该通道已存在,则返回true,负责返回false。 该函数是先用resmgmt.WithTargetEndpoints(c.Peer) 创建要查询节点的请求reqPeer,再用sourceClient.QueryChannels(reqPeer) 查询结果保存至数组channelInfo ,再遍历查找待查询的通道ID是否已经存在。
func QuerySavedChannel(sourceClient *resmgmt.Client, info InfoSdk, c InstallCcInfo) (bool, error) {
reqPeer := resmgmt.WithTargetEndpoints(c.Peer)
channelInfo, err := sourceClient.QueryChannels(reqPeer)
if err != nil {
return false, fmt.Errorf("failed to query channel already exists: %v", err)
}
for _, v := range channelInfo.Channels {
if v.ChannelId == info.ChannelID {
return true, nil
}
}
return false, nil
}
1.2 创建并加入通道(函数: CreateChannel(…) )
输入实例化的fabricsdk-sdk 、资源管理客户端sourceClient 、组织名、管理员名、通道ID、通道文件路径、通道组织域名,创建通道,并将组织加入通道。 该函数是通过mspclient.New(sdk.Context(), mspclient.WithOrg(info.OrgName)) 创建MSP客户端mspClient,再用mspClient.GetSigningIdentity(info.Admin) 获取组织管理员身份信息adminIdentity,用resmgmt.SaveChannelRequest{ChannelID: info.ChannelID, ChannelConfigPath: info.ChannelConfigPath, SigningIdentities: []msp.SigningIdentity{adminIdentity}} 封装创建通道请求channelReq,用sourceClient.SaveChannel(channelReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrgOrdererName)) 创建通道,最后用sourceClient.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrgOrdererName)) 将组织加入通道。
func CreateChannel(sdk *fabsdk.FabricSDK, sourceClient *resmgmt.Client, info InfoSdk) error {
mspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(info.OrgName))
if err != nil {
return fmt.Errorf("根据指定的 OrgName 创建 Org MSP 客户端实例失败: %v", err)
}
adminIdentity, err := mspClient.GetSigningIdentity(info.Admin)
if err != nil {
return fmt.Errorf("获取指定id的签名标识失败: %v", err)
}
channelReq := resmgmt.SaveChannelRequest{ChannelID: info.ChannelID, ChannelConfigPath: info.ChannelConfigPath, SigningIdentities: []msp.SigningIdentity{adminIdentity}}
_, err = sourceClient.SaveChannel(channelReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrgOrdererName))
if err != nil {
return fmt.Errorf("创建应用通道失败: %v", err)
}
fmt.Println("通道已成功创建,")
err = sourceClient.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrgOrdererName))
if err != nil {
return fmt.Errorf("Peers加入通道失败: %v", err)
}
fmt.Println("peers 已成功加入通道.")
return nil
}
1.3 查询指定节点的指定链码是否已经存在(函数: QueryInstalledCC(…) )
输入资源管理客户端、查询节点、查询链码名,给定节点上的链码已经存在就返回true,否则返回false。 该函数是用resmgmt.WithTargetEndpoints(c.Peer) 封装查询节点请求得到reqPeers ,再用sourceClient.QueryInstalledChaincodes(reqPeers) 进行查询,将结果保存在queryResult中。
func QueryInstalledCC(sourceClient *resmgmt.Client, c InstallCcInfo) (bool, error) {
reqPeers := resmgmt.WithTargetEndpoints(c.Peer)
queryResult, err := sourceClient.QueryInstalledChaincodes(reqPeers)
if err != nil {
return false, fmt.Errorf("failed to query installed chaincode: %v", err)
}
for _, v := range queryResult.Chaincodes {
if v.Name == c.CCID {
return true, nil
}
}
return false, nil
}
1.4 在指定节点安装链码(函数: InstallChaincode(…) )
输入资源管理客户端、链码路径、gopath路径、链码ID、链码版本、待安装链码的节点,在指定的组织上安装链码,并指定链码ID、版本。 该函数是用gopackager.NewCCPackage(c.CCPath, c.GoPath) 封装链码包得到ccPkg,用resmgmt.WithTargetEndpoints(c.Peer, c.Peer2) 封装节点请求包得到reqPeer ,最后用sourceClient.InstallCC(installReq, reqPeer) ,安装链码。
installReq := resmgmt.InstallCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: c.CCVersion,
Package: ccPkg,
}
封装安装链码的请求包得到installReq
func InstallChaincode(sourceClient *resmgmt.Client, c InstallCcInfo) error {
ccPkg, err := gopackager.NewCCPackage(c.CCPath, c.GoPath)
if err != nil {
return fmt.Errorf("package chaincode failed: %v", err)
}
installReq := resmgmt.InstallCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: c.CCVersion,
Package: ccPkg,
}
reqPeer := resmgmt.WithTargetEndpoints(c.Peer, c.Peer2)
_, err = sourceClient.InstallCC(installReq, reqPeer)
if err != nil {
return fmt.Errorf("Install chaincode failed: %v", c.Peer)
}
fmt.Println("Install chaincode sucessful")
return nil
}
1.5 链码实例化(函数: ChaincodeInit(…) )
输入资源管理客户端、链码ID、链码路径、链码版本、初始化Args、背书策略,实例化链码。 该函数是用ccPolicy, err :=policydsl.FromString(c.Policy) 封装背书策略得到ccPolicy,用
req := resmgmt.InstantiateCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: c.CCVersion,
Args: c.InitArgs,
Policy: ccPolicy,
}
封装实例化链码请求包得到req,再用resmgmt.WithTargetEndpoints(c.Peer, c.Peer2) 封装待安装链码的节点请求包得到reqPeers,最后用sourceClient.InstantiateCC(c.ChannelID, req, reqPeers) 再指定节点实例化链码。
func ChaincodeInit(sourceClient *resmgmt.Client, c InstallCcInfo) error {
ccPolicy, err := policydsl.FromString(c.Policy)
if err != nil {
return fmt.Errorf("create policy failed: %v", err)
}
req := resmgmt.InstantiateCCRequest{
Name: c.CCID,
Path: c.CCPath,
Version: c.CCVersion,
Args: c.InitArgs,
Policy: ccPolicy,
}
reqPeers := resmgmt.WithTargetEndpoints(c.Peer, c.Peer2)
_, err = sourceClient.InstantiateCC(c.ChannelID, req, reqPeers)
if err != nil {
return fmt.Errorf("init chaincode failed: %v", err)
}
fmt.Println("init chaincode sucessful")
return nil
}
1.6 调用链码(添加数据:函数: InvokeCC(…) )
输入通道管理客户端、链码ID、调用链码的函数、待执行的调用指令,返回已经存储的序列化后的数据。 该函数是用
req := channel.Request{
ChaincodeID: c.CCID,
Fcn: c.Fcn,
Args: c.InvokeArgs,
}
封装调用链码请求得到req,再用channel.WithTargetEndpoints(c.Peer) 得到调用通道的请求包reqPeers,最后用cc.Execute(req, reqPeers) 执行调用链码的操作。
func InvokeCC(cc *channel.Client, c InstallCcInfo) ([]byte, error) {
req := channel.Request{
ChaincodeID: c.CCID,
Fcn: c.Fcn,
Args: c.InvokeArgs,
}
reqPeers := channel.WithTargetEndpoints(c.Peer)
result, err := cc.Execute(req, reqPeers)
if err != nil {
return nil, fmt.Errorf("invoke chaincode failed: %v", err)
}
var d GenerateCrop
err = json.Unmarshal(result.Payload, &d)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal in InvokeCC")
}
fmt.Printf("Invoke chaincode sucessful already saved: %v\n", d)
return result.Payload, nil
}
1.7 调用链码(查询操作:函数: QueryCC(…) )
输入通道管理客户端、链码ID、查询数据需要调用的函数、查询指令,返回查询后的数据(序列化的) 该函数先利用
req := channel.Request{
ChaincodeID: c.CCID,
Fcn: c.QueryFcn,
Args: c.QueryArgs,
}
封装链码查询请求包得到req,然后用cc.Query(req) 得到查询到的数据包data。
func QueryCC(cc *channel.Client, c InstallCcInfo) ([]byte, error) {
req := channel.Request{
ChaincodeID: c.CCID,
Fcn: c.QueryFcn,
Args: c.QueryArgs,
}
data, err := cc.Query(req)
if err != nil {
return nil, fmt.Errorf("failed to invoke chaincode: %v", err)
}
return data.Payload, nil
}
二、链码
2.1 向区块链中添加数据
输入序列化后的要被保存的数据,通过stub.PutState()函数将数据保存到区块链账本中。 添加数据主要是利用stub.PutState(data.ID, d) 函数,其中data.ID(string类型)是该条数据的Key值,d是准备存储的数据([]byte)类型,是序列化的数据。
func PutData(stub shim.ChaincodeStubInterface, data GenerateCrop) ([]byte, bool) {
d, err := json.Marshal(data)
if err != nil {
return nil, false
}
err = stub.PutState(data.ID, d)
if err != nil {
return nil, false
}
return d, true
}
func (t *GenerateChaincode) AddData(stub shim.ChaincodeStubInterface, args []string) peer.Response {
var data GenerateCrop
err := json.Unmarshal([]byte(args[0]), &data)
if err != nil {
return shim.Error("data Unmarshal failed")
}
d, e := PutData(stub, data)
if e == false {
return shim.Error("data saved failed")
}
if d == nil {
return shim.Error("failed to marshal data")
}
return shim.Success(d)
}
2.2 查询区块链账本中的数据
输入要查询的数据ID,通过stub.GetState() 查询该ID下最新的数据,再通过stub.GetHistoryForKey() 溯源该ID的所有数据,保存至结构体中,通过JSON.Marshal() 将数据序列化后返回。
func (t *GenerateChaincode) QueryDataByID(stub shim.ChaincodeStubInterface, ID string) peer.Response {
queryResult, err := stub.GetState(ID)
if err != nil {
return shim.Error("failed to query data")
}
if queryResult == nil {
return shim.Error("not found datas")
}
var UnmarResult GenerateCrop
err = json.Unmarshal(queryResult, &UnmarResult)
if err != nil {
return shim.Error("failed to UnmarResult in QueryDataByID")
}
iterator, err := stub.GetHistoryForKey(ID)
if err != nil {
return shim.Error("根据指定的身份证号码查询对应的历史变更数据失败")
}
defer iterator.Close()
var historys []HistoryItem
var hisGenerate GenerateCrop
for iterator.HasNext() {
hisData, err := iterator.Next()
if err != nil {
return shim.Error("获取edu的历史变更数据失败")
}
var historyItem HistoryItem
historyItem.TxId = hisData.TxId
err = json.Unmarshal(hisData.Value, &hisGenerate)
if err != nil {
shim.Error("failed to Unmarshal in QueryDataByID with GetGistoryForKey")
}
if hisData.Value == nil {
var empty GenerateCrop
historyItem.Generate = empty
} else {
historyItem.Generate = hisGenerate
}
historys = append(historys, historyItem)
}
UnmarResult.History = historys
result, err := json.Marshal(UnmarResult)
if err != nil {
return shim.Error("序列化edu信息时发生错误")
}
return shim.Success(result)
}
三、后端服务器的接口
3.1 main函数
- 该函数首先调用FabricNetworkInit()函数,创建sdk、资源管理客户端、创建通道并让组织加入通道、安装并初始化链码。
- 用Gin框架搭建路由组,利用CorsMiddle.CorsMiddleware()解决Cors跨域问题。
- 访问
127.0.0.1:6060/generate/addData 并传入待存储数据,会调用函数AddData()将数据存入区块链中。 - 访问
127.0.0.1:6060/generate/queryData 并传入待查询数据的Key值将执行QueryData()函数在区块链中查询数据,并返回查询结果。
func main() {
err := FabricNetworkInit()
defer Manage.Sdk.Close()
if err != nil {
fmt.Println(err.Error())
}
r := gin.Default()
r.Use(CorsMiddle.CorsMiddleware())
r.POST("generate/addData", AddData)
r.GET("generate/queryData", QueryData)
r.Run(":6060")
}
3.2 FabricNetworkInit()函数
进行链码调用前的初始化操作,如创建Sdk、创建资源管理客户端、创建通道等。
func FabricNetworkInit() error {
Manage.Sdk, _ = SetupSDK(Sdkinfo.ConfigFilePath)
Manage.Sc, err = SetupResmg(Manage.Sdk, Sdkinfo)
if err != nil {
return err
}
queryChannelResult, err := QuerySavedChannel(Manage.Sc, Sdkinfo, Ccinfo)
if err != nil {
return err
}
if !queryChannelResult {
err = CreateChannel(Manage.Sdk, Manage.Sc, Sdkinfo)
if err != nil {
return err
}
} else {
fmt.Printf("channel '%v' already exist\n", Sdkinfo.ChannelID)
}
queryResult, err := QueryInstalledCC(Manage.Sc, Ccinfo)
if err != nil {
fmt.Println(err.Error())
return err
}
if !queryResult {
err = InstallChaincode(Manage.Sc, Ccinfo)
if err != nil {
return fmt.Errorf("install chaincode failed: %v", err)
}
err = ChaincodeInit(Manage.Sc, Ccinfo)
if err != nil {
return fmt.Errorf("init chaincode failed: %v", err)
}
} else {
fmt.Printf("chaincode with name '%v' already exists\n", Ccinfo.CCID)
}
Manage.Cc, err = SetupChannelMg(Manage.Sdk, Ccinfo.ChannelID, Sdkinfo.User, Sdkinfo.OrgName)
if err != nil {
return fmt.Errorf("failed to Create channel client: %v", err)
}
return nil
}
3.3 AddData()函数
该函数接收JSON类型数据,并调用1.6中的函数 (InvokeCC(…) ) 将数据存储至区块链账本中。
func AddData(c *gin.Context) {
chick, err := json.Marshal(ChickData)
if err != nil {
c.JSON(400, gin.H{
"err": err.Error(),
})
}
Ccinfo.InvokeArgs = [][]byte{chick}
_, err = InvokeCC(Manage.Cc, Ccinfo)
if err != nil {
c.JSON(400, gin.H{
"err": err.Error(),
})
}
c.JSON(http.StatusOK, gin.H{
"err": nil,
})
}
3.4 QueryData()函数
该函数接受待查询数据的Key值(ID),调用1.7中的查询链码函数( QueryCC(…) ),查询数据,并将数据返回。
func QueryData(c *gin.Context) {
Ccinfo.QueryArgs = [][]byte{[]byte(ChickData.ID)}
data, err := QueryCC(Manage.Cc, Ccinfo)
if err != nil {
c.JSON(400, gin.H{
"err": err.Error(),
"data": nil,
})
}
var d GenerateCrop
err = json.Unmarshal(data, &d)
if err != nil {
c.JSON(400, gin.H{
"err": err.Error(),
"data": nil,
})
}
c.JSON(http.StatusOK, gin.H{
"err": nil,
"data": d,
})
}
|