摘要

上周的问题,多次尝试解决不了后,重构了下代码,成功解决了问题,事后分析,应该是有个节点没有成功加入通道,安装链码时,该节点没有安装,但链码执行时,选中的执行背书策略的节点是该节点,从而导致的调用链码错误。解决问题后,又遇到了几个小问题,解决所有问题后,用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))将组织加入通道。

// 输入实例化的`fabricsdk-sdk`、资源管理客户端`sourceClient`、组织名、管理员名、通道ID、通道文件路径、通道组织域名,创建通道,并将组织加入通道。
func CreateChannel(sdk *fabsdk.FabricSDK, sourceClient *resmgmt.Client, info InfoSdk) error {
	// New creates a new Client instance

	mspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(info.OrgName))
	if err != nil {
		return fmt.Errorf("根据指定的 OrgName 创建 Org MSP 客户端实例失败: %v", err)
	}

	//  Returns: signing identity
	adminIdentity, err := mspClient.GetSigningIdentity(info.Admin)
	if err != nil {
		return fmt.Errorf("获取指定id的签名标识失败: %v", err)
	}

	// SaveChannelRequest holds parameters for save channel request
	channelReq := resmgmt.SaveChannelRequest{ChannelID: info.ChannelID, ChannelConfigPath: info.ChannelConfigPath, SigningIdentities: []msp.SigningIdentity{adminIdentity}}
	// save channel response with transaction ID
	_, err = sourceClient.SaveChannel(channelReq, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(info.OrgOrdererName))
	if err != nil {
		return fmt.Errorf("创建应用通道失败: %v", err)
	}

	fmt.Println("通道已成功创建,")

	// allows for peers to join existing channel with optional custom options (specific peers, filtered peers). If peer(s) are not specified in options it will default to all peers that belong to client's MSP.
	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中。

//输入资源管理客户端、查询节点、查询链码名,给定节点上的链码已经存在就返回true,否则返回false
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

//输入资源管理客户端、链码路径、gopath路径、链码ID、链码版本、待安装链码的节点,在指定的组织上安装链码,并指定链码ID、版本。
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)再指定节点实例化链码。

//输入资源管理客户端、链码ID、链码路径、链码版本、初始化Args、背书策略,实例化链码
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)执行调用链码的操作。

//输入通道管理客户端、链码ID、调用链码的函数、待执行的调用指令,返回已经存储的序列化后的数据
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函数

  1. 该函数首先调用FabricNetworkInit()函数,创建sdk、资源管理客户端、创建通道并让组织加入通道、安装并初始化链码。
  2. 用Gin框架搭建路由组,利用CorsMiddle.CorsMiddleware()解决Cors跨域问题。
  3. 访问127.0.0.1:6060/generate/addData并传入待存储数据,会调用函数AddData()将数据存入区块链中。
  4. 访问127.0.0.1:6060/generate/queryData并传入待查询数据的Key值将执行QueryData()函数在区块链中查询数据,并返回查询结果。
func main() {

	//1. init fabric network
	err := FabricNetworkInit()
	defer Manage.Sdk.Close()
	if err != nil {
		fmt.Println(err.Error())
	}
	r := gin.Default()
	r.Use(CorsMiddle.CorsMiddleware())
	//2. insert data
	r.POST("generate/addData", AddData)
	//3. query data
	r.GET("generate/queryData", QueryData)
	r.Run(":6060")
	

}

3.2 FabricNetworkInit()函数

进行链码调用前的初始化操作,如创建Sdk、创建资源管理客户端、创建通道等。

func FabricNetworkInit() error {
	//1. 创建SDK
	Manage.Sdk, _ = SetupSDK(Sdkinfo.ConfigFilePath)
	//2. 创建资源管理客户端。(可用该客户端创建通道,安装链码等操作)
	Manage.Sc, err = SetupResmg(Manage.Sdk, Sdkinfo)
	if err != nil {
		return err
	}
	//3. 查询节点ccinfo.Peer是否已加入通道sdkinfo.ChannelID,没加入则创建通道
	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)
	}

	//4. 查询链码ccinfo.CCID在节点ccinfo.Peer上是否已安装,没安装则安装并初始化
	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)
	}

	//5. 创建通道管理客户端。(可用该客户端调用链码)
	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,
	})

}
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐