WDK学习笔记_基于区块链溯源系统的后端接口开发

Posted 原来如此-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WDK学习笔记_基于区块链溯源系统的后端接口开发相关的知识,希望对你有一定的参考价值。

文章目录

摘要

上周的问题,多次尝试解决不了后,重构了下代码,成功解决了问题,事后分析,应该是有个节点没有成功加入通道,安装链码时,该节点没有安装,但链码执行时,选中的执行背书策略的节点是该节点,从而导致的调用链码错误。解决问题后,又遇到了几个小问题,解决所有问题后,用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.SaveChannelRequestChannelID: info.ChannelID, ChannelConfigPath: info.ChannelConfigPath, SigningIdentities: []msp.SigningIdentityadminIdentity封装创建通道请求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.SaveChannelRequestChannelID: info.ChannelID, ChannelConfigPath: info.ChannelConfigPath, SigningIdentities: []msp.SigningIdentityadminIdentity
	// 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
        
                

摘要

比特币和以太坊都是公链,链中存储的数据任何人都可查询,不适合用来做对隐私有较高要求的企业开发,且其都用了工作量证明机制,能耗成本很高,数据存储效率低(尤其是比特币,大约每10分钟生成一个新块)。Fabric引入了证书颁发机构和通道的概念,使得不被授权的用户无法访问链中数据,保证了数据的隐私性。Fabric还采用背书策略代替了工作量证明机制,在保证数据安全的同时,消除了"挖矿"的能耗,提高了存储效率。本文基于Fabric进行区块链的系统开发,详述了在linux操作系统(Unbuntu)上Fabric环境的搭建、Fabric的基本概念、用Fabric开发区块链系统(未完)。
在总结处细述了Fabric的一个交易提案是如何一步步部署到区块链上的。


一、Fabric环境搭建

1. 官方帮助文档:https://hyperledger-fabric.readthedocs.io/en/release-1.2/ 
2. 安装 cURL( https://curl.haxx.se/download.html ), curl --version 查询版本
3. 安装docker, docker --version 查询版本
4. 安装docker-compose, docker-compose --version 查询版本
5. 安装 go 语言环境, go version 查询版本
6. 安装node.js, node -v 查询版本
7. Python 版本要求为 2.7, python --version 查询版本

1.1 安装docker

  1. 基础软件安装
# 安装基本软件 
$ sudo apt-get update 
$ sudo apt-get install apt-transport-https ca-certificates curl git software- properties-common lrzsz -y
  1. 添加阿里的docker镜像仓库
# 添加阿里的docker镜像仓库 
$ curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository "deb [arch=amd64] https://mirrors.aliyun.com/docker- ce/linux/ubuntu $(lsb_release -cs) stable" 
# 更新软件源 
$ sudo apt-get update
  1. 安装docker
# 安装docker 
$ sudo apt-get install docker-ce -y
# 查看安装的docker版本 
$ docker version 
# 当前用户直接操作docker时, 权限不够, 
需要做下面的第4步操作
  1. 将当前用户添加到docker组
# 将用户加入该 group 内。然后退出并重新登录就生效啦。 $ sudo gpasswd -a ${USER} docker 
# 重启docker服务 
$ systemctl restart docker 
# 当前用户切换到docker群组 
$ newgrp - docker 
$ docker version 

1.2 安装go

下载地址:
https://golang.org/dl/ - 翻墙 https://studygolang.com/dl - 国内镜像源

如果没有进行安装包下载, 可直接使用如下命令(目前最新版本):

# 1. 使用wget工具下载安装包 
$ wget https://dl.google.com/go/go1.11.linux-amd64.tar.gz 
# 2. 解压tar包到/usr/local 
$ sudo tar zxvf go1.11.linux-amd64.tar.gz -C /usr/local 
# 3. 创建Go目录 
$ mkdir $HOME/go 
# 4. 用vi打开~./bashrc,配置环境变量 
$ vim ~/.bashrc 
# 5. 增加下面的环境变量,保存退出 
export GOROOT=/usr/local/go export GOPATH=$HOME/go export PATH=$PATH:$GOROOT/bin:$GOPATH/bin 
# 6. 使环境变量立即生效, 一些命令二选一 
$ source ~/.bashrc $ . ~/.bashrc 
# 7. 检测go是否安装好 
$ go version

1.3 安装Node.js

#安装包
sudo apt-get install nodejs

1.4 部署hyperledger Fabric

  1. 下载并执行fabric的引导脚本bootstrap.sh
# 创建放置的目录,然后进入该目录,用curl下载脚本。 
$ cd ~ # 这里在家目录下创建放置目录 
$ mkdir hyperledger-fabric 
# 创建放置目录 
$ cd hyperledger-fabric

#不翻墙的安装命令
$ curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/master/scripts/bootstrap.sh | bash -s 1.2.0 1.2.0 0.4.10

运行./bash.sh up出现 "Error: error getting endorser client for channel: endorser client failed to connect to peer0 "时。.

二、Fabric的概念

2.1 逻辑架构


身份管理Fabric中的“账户”体系.)

  • 成员服务
    • 注册登录和属性证书:首先用户填写信息(身份信息等)生成证书明文F发送注册请求,证书颁发机构CA对明文进行摘要(进行一次hash运算)得到h1,CA用自己的私钥对h1进行加密得到密文F '。验证证书时,用CA公钥对F '解密得到h2,比较h2是否等于h1。(Hyperledger Fabric注册及CA证书颁发.)

账本管理

  • 区块链:区块链中存储着所有的交易记录。
  • 世界状态:根据区块链中的交易记录得到的数据状态,每当有一个新区块生成时,世界状态也随之更新,每个节点的世界状态都存在其节点的数据库中。

交易管理

  • 客户端发送某一类型提案,根据提案类型对应的背书测略,指定对应的背书节点peer,背书节点peer执行链码,如果成功则生成实际账本。当一定数量的背书节点认可该提案后,该提案通过排序节点广播给所有节点,每个节点验证并执行该提案,从而更新账本。(深入理解Fabric的交易背书)

智能合约

  • 智能合约是用来进行业务逻辑操作的代码,在docker容器中运行,和以太坊相比,Fabric的智能合约和底层账本是分开的,升级链码并不需要将账本迁移到新的区块链上,真正实现了逻辑与数据的分离。(Fabric的智能合约详解)

2.2 Fabric基础概念

(Fabric基本概念—很好的文章)
组织

  • fabric系统是通过组织来划分的,每个组织内都有承担不同功能的peer节点,同时每个组织都有自己对应的fabric-ca服务器,fabric系统中所有的组织共用一个orderer集群。fabric中的组织在现实世界中可以是一个公司、一个企业,或者一个协会。在fabric中,组织是承担着数据信用责任的区块链系统参与方。在设计一个fabric系统时,第一步就是要确定系统的参与方,然后从这些参与者中选出组织(生成对应的组织编号、域名、证书等),然后再确认组织的管理方式。组织的管理方式是指组织在遇到问题时的协作方式(如新组织的加入)。

通道(Channel)

  • fabric的数据存储结构被设计成多账本体系,每个账本在fabric中被称为channel。每个channel中都有一个完全独立的账本。同一个channel中的所有peer节点都保存一份相同的数据。
    通道由成员(组织)、每个成员的锚节点、账本、链码应用程序和排序服务节点定义。网络上的每个交易都是在一个通道上执行的,在该通道上,每一方都必须经过身份验证和授权才能在该通道上进行交易。加入通道的每一个peer都有其自己的身份,由成员服务提供者(MSP)提供。

账本

  • Fabric有一个账本子系统包含两个组件:世界状态和交易日志。世界状态是代表当前数据的状态,交易日志组件存放在数据库中,它记录了所有的交易记录,世界状态是根据交易日志得到的。
    世界状态中有一个属性——版本号,版本号从0开始,每当状态更新时版本号就递增。状态更新时会首先检查版本号,以确保当前状态的版本与背书时的版本一致(避免并发更新)。

节点(peer)
是区块链的通信实体,是一个逻辑概念,不同类型的多个节点可以运行在同一个物理服务器上。节点主要有以下四种:

  1. 客户端节点:客户端必须连接到某一个peer节点或排序服务节点上才能与区块链网络进行通信。客户端向背书节点(endorser)提交交易提案(transaction proposal),当收集到足够背书后,向排序服务节点广播交易提案,进行排序,生成区块。

  2. 普通节点peer:peer节点根据所承担的角色又可以分为记账节点(committer)、背书节点(endorser)、主节点(leader)和锚节点(anchor)。

    1. 记账节点:所有的peer节点都是记账节点(committer),负责验证排序服务节点区块里的交易,维护状态和总账(Ledger)的副本。该节点会定期从orderer节点获取包含交易的区块,在对这些区块进行核发验证之后,会把这些区块加入到区块链中。committer节点无法通过配置文件配置,需要在当前客户端或者命令行发起交易请求的时候手动指定相关的committer节点。记账节点可以有多个。
    2. 背书节点:部分节点还会执行交易并对结果进行签名背书,充当背书节点(endorser)的角色。背书节点是动态的角色,是与具体链码绑定的。每个链码在实例化的时候都会设置背书策略,指定哪些节点对交易背书后交易才是有效的。并且只有应用程序向它发起交易背书请求的时候才是背书节点,其他时候都是普通的记账节点,只负责验证交易并记账。背书节点也无法通过配置文件指定,而是由发起交易请求的客户端指定。背书节点可以有多个。
    3. 锚节点:peer节点还可以是锚节点(anchor peer),锚节点主要负责代表组织和其他组织进行信息交换。每个组织都有一个锚节点,锚节点对于组织来说非常重要,如果锚节点出现问题,当前组织就会与其他组织失去联系。锚节点的配置信息是在configtxgen模块的配置文件configtx.yaml中配置的。锚节点只能有一个。
    4. 主节点:peer节点还可以是主节点(leader peer),能与排序服务节点通信,负责从排序服务节点获取最新的区块并在组织内部同步。主节点在整个组织中只能有一个。
  3. 排序服务节点orderer:接收包含背书签名的交易,对未打包的交易进行排序生成区块,广播给peer节点。排序服务提供的是原子广播,保证同一个链上的节点接收到相同的信息,并且有相同的逻辑顺序。

  4. CA节点:fabric1.0的证书颁发机构,由服务器和客户端组成。CA节点接收客户端的注册申请,返回注册密码用于用户登录,以便获取身份证书。区块链上的所有操作都需要验证用户身份。

区块结构

  • 由三个部分组成,分别是区块头、区块数据和区块元数据。
  1. 区块头包含三个属性(区块号、当前区块哈希、前一个区块的哈希),当一个区块被创建时写入。
  2. 区块数据包含的是排序后的交易列表。当区块被ordering service创建时写入。
  3. 区块元数据包括区块的写入时间,以及区块写入者的证书、公钥和签名。

2.3、Fabric交易流程

前提假设是各节点已经提前颁发好证书,且已正常启动,并加入已经创建好的通道。此流程介绍的是在已经实例化了的链码通道上从发起一个调用交易到最终结账的全过程。

  1. 提交交易提案
    应用程序(客户端节点)构造好交易提案(交易提案中包含本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等)请求后,根据背书策略选择背书节点执行交易提案并进行背书签名。背书节点是链代码中背书策略指定的节点。正常情况下背书节点执行后的结果是一致的,只有背书节点对结果的签名不一样。

  2. 模拟执行提案并进行背书
    背书节点在收到交易提案后会进行一些验证,验证通过后,背书节点会根据当前账本数据模拟执行链码中的业务逻辑并生成读写集(RwSet)。模拟执行时不会更新账本数据。然后背书节点对这些读写集进行签名生成提案响应(proposal response),然后返回给应用程序。

  3. 收集交易的背书(返回模拟执行结果)
    应用程序收到proposal response后会对背书节点的签名进行验证(所有节点接收到任何消息时都需要先验证消息的合法性)。如果链码只进行账本查询操作,应用程序只需要检查查询响应,并不会将交易提交给排序服务节点。如果链码对账本进行了invoke操作,则需要提交交易给排序服务进行账本更新(提交前会判断背书策略是否满足)。

  4. 构造交易请求并发送给排序服务节点
    应用程序接收到所有背书节点的签名后,根据背书签名调用SDK生成交易,并广播给排序服务节点。其中生成交易的过程很简单,只需要确认所有背书节点的执行结果完全一致,再将交易提案、提案响应和背书签名打包生成交易即可。

  5. 排序服务节点对交易进行排序并生成区块

    排序服务节点接收到网络中所有通道发出的交易信息,读取交易信封获取通道名称,按各个通道上交易的接收时间顺序对交易信息进行排序(多通道隔离),生成区块。(在这个过程中,排序服务节点不会关心交易是否正确,只是负责排序和打包。交易的有效性在第7步进行验证)

  6. 排序服务节点广播区块给主节点

    排序服务节点生成区块后会广播给通道上不同组织的主节点。

  7. 记账节点验证区块内容并写入到账本

    所有的peer节点都是记账节点,记录的是节点已加入通道的账本数据。记账节点接收到的排序服务节点生成的区块后,会验证区块交易的有效性,然后提交到本地账本并产生一个生成区块的事件,监听区块事件的应用程序会进行后续的处理。(如果接收的是配置区块,则会更新缓存的配置信息)

  8. 主节点在组织内部同步最新的区块

    如果交易是无效的,也会更新区块,但不会更新世界状态。(区块存储的是操作语句,而世界状态存储的是被处理的数据。

三、基于Fabric的区块链系统开发(未完)

目前开发是准备先做一个简单的系统,在该系统中有一个排序节点,2个组织,每个组织中分别有3个Peer节点和2个用户,系统还未完成,就只展示目前的进度,不详述了。

总结

客户端节点发送交易提案(proposal),根据该类型提案对应的背书策略,指定对应的背书节点,背书节点收到提案请求后,会根据当前账本数据,模拟执行链码中的业务逻辑,生成一个读写集(RwSet),然后背书节点对读写集进行签名生成提案响应(proposal response)并返回给客户端节点,客户端收到一定数量正的提案响应后,便会将该交易发送给排序节点,排序节点收到所有通道发出的交易信息,按其发送时间对其进行排序(多通道隔离),生成区块,将其广播给各个通道的主节点,记账节点对生成的区块中的数据进行校验,然后将其提交到本地账本,更新区块链账本和世界状态。

以上是关于WDK学习笔记_基于区块链溯源系统的后端接口开发的主要内容,如果未能解决你的问题,请参考以下文章

WDK学习笔记第四周_区块链总体简述

WDK学习笔记_docker容器客户端_fabric-go-sdk

基于hyperledger联盟链的汽车轨迹追溯系统 (三)

基于区块链/Hyperledger Fabric的商品交易溯源系统搭建步骤

12. Fabric2.2 区块链农产品溯源系统 - 智能合约开发-2

互融云区块链溯源防伪系统开发,超高并发,全程追溯