本篇博客主要记录使用Fabric-Sdk-Go@1.0.0操作FabricV2.2.10网络的整体代码及错误记录。涉及创建通道、安装及实例化链码、链码执行等过程。

写在最前

前序博客已经介绍了使用命令的方式在Fabric网络上创建通道以及部署执行链码的整个过程。除了这种方式之外,Hyperledger Fabric官方还提供了以SDK的方式来实现该流程的整个方法,即Fabric-Sdk-Go(也有针对其他语言的sdk)。本篇博客主要介绍Fabric-Sdk-Go的使用。

基本环境

本机操作系统为MAC OS系统,Fabric网络运行在虚拟机中。Fabric的具体运行环境为: 操作系统:Ubuntu 16.04 Fabric版本:V2.2.10

1 GoLand连接虚拟机

1.1 安装SSH服务

因为后续主要在GoLand上编写代码,所以开始之前需要让Goland能够使用ssh方式连接上Fabric网络所在的虚拟机环境。这就两个系统上(本例中即为:Mac OS和虚拟机系统)安装ssh服务,并且要保证ssh服务处于开启状态。具体安装及设置方法这里不赘述。

1.2 在MAC系统上连接虚拟机

当两个系统都安装SSH服务之后,可以尝试在MAC的命令行中输入如下命令检测是否可以连接到虚拟机系统上。具体如下:

ssh sherry@172.16.8.129

其中@前的部分为Ubuntu系统的用户名。@后面的ip地址可以在Ubuntu系统上通过ifconfig命令进行查询,具体如下: Tips:虚拟机中的Ubuntu系统的IP地址不是固定不变的(虽然很少发生改变)。 如果两个系统上的SSH都已经正常启动,在会在MAC上的命令行终端看到如下执行结果:

1.3 GoLand连接虚拟机系统

使用Goland连接Ubuntu系统所在的虚拟机环境,只需要完成以下几个设置。

在Goland中,打开Tools->Deployment->configuration进入Deployment界面,这里要新建一个SFTP部署。具体如下: connecton 其中ssh configuration的配置界面如下,其中Username这里要需要Ubuntu系统的用户名: 在Goland中配置设置文件映射目录。这里的Deployment path即为Ubuntu系统上Fabric网络的相对路径地址(根路径为/home/sherry)。而Local path即为Deployment path中的文件在本地MAC系统上的映射地址。图中显示的是Fabric中的测试网络test-network所在的目录映射到了Mac系统的Fabric_GOPATH目录上。 接着启动ssh连接。具体如下: 之后便可以在Goland中操作Ubuntu系统了。具体如下: 接着为了能在GoLand中修改链码及编写代码。先将Fabric网络中的文件上传到本地系统上。具体如下: Tips:在文件同步时可能会出错,可以先在Ubuntu系统上更改Fabric网络文件的权限,再重新进行同步。

到目前为止,基本的环境已经配置好了,接下来则开始使用Fabric-Sdk-Go@1.0.0来完成创建通道、部署链码及链码执行的整个过程。

2 使用Fabri-SDK-GO操作网络

Fabric2.5.4中可以直接创建应用通道,但这种通道创建方法只能生成.block文件,而无法生成.tx形式的通道配置交易文件。目前的Fabric-Sdk-Go@1.0.0版本中无法通过.block文件来创建通道,因此这里使用Fabric V2.2.10并沿用传统的通道创建方法:先创建系统通道再创建应用通道。

2.1 准备工作

在Goland界面上使用Fabric-SDK-Go操作Fabric创建通道、部署链码之前,需要完成一些准备工作。具体包括以下:

2.1.1 搭建Fabric网络

这里要注意,Fabric-Sdk-Go不能完成Fabric网络的创建,所以还是要在Ubuntu系统上来执行操作。在Ubuntu系统上搭建finance网络,具体搭建过程可以参考博客:https://blog.csdn.net/yeshang_lady/article/details/135556094 搭建完成之后,会在channel-artifacts目录下会生成三个文件:channel.tx、Org1MSPanchors.tx和Org2MSPanchors.tx。注意形成这三个文件之后,重启docker容器。

2.1.2 创建链码目录

在Ubuntu系统上完成链码的创建后续会减少很多麻烦。在finance_network目录下创建chaincode目录。该目录下的文件包括以下: Tips:在使用Fabric-Sdk-Go打包链码的时候,链码文件中一定要包含go.mod、go.sum和Vendor目录,否则在安装链码的时候可能会提示: rpc error:code=DeadlineExceeded desc = context deadline exceeded. 关于上述文件有以下几点需要说明:

其中assetTransfer.go的代码可以在博客中找到:https://blog.csdn.net/yeshang_lady/article/details/134801201这里关于go.mod文件,使用go mod init命令生成go.mod文件后,将其内容修改成如下:

module finance_network/chaincode

go 1.21

require (

github.com/golang/protobuf v1.3.2

github.com/hyperledger/fabric-chaincode-go v0.0.0-20210718160520-38d29fabecb9

github.com/hyperledger/fabric-contract-api-go v1.1.1

github.com/hyperledger/fabric-protos-go v0.0.0-20201028172056-a3136dde2354

github.com/stretchr/testify v1.5.1

)

如果不做这个修改,在部署链码时可能产生如下错误: could not build chaincode: docker build failed: docker image build failed: docker build failed: Error returned from build: 2 "google.golang.org/protobuf/internal/pragma google.golang.org/protobuf/internal/detrand

2.1.3 配置hosts文件

想要在Mac上使用Fabric-Sdk-Go操作finance网络,需要在Mac的hosts文件上添加以下信息(所有的peer节点和orderer节点都要添加):

#172.16.8.129为Ubuntu系统的ip地址

172.16.8.129 peer0.org1.finance.com

172.16.8.129 peer1.org1.finance.com

172.16.8.129 peer0.org2.finance.com

172.16.8.129 orderer.finance.com

如果未修改hosts文件,后续在添加节点到应用通道中可能会出现以下错误:SendProposal failed: Transaction processing for endorser [peer1.org1.finance.com:8051]: Endorser Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection on target [peer1.org1.finance.com:8051]: waiting for connection failed: context deadline exceeded. 补充一点,如果直接在Ubuntu系统内使用Fabric-Sdk-Go运行下面的代码的时候,其hosts文件中添加的信息为:

127.0.0.1 peer0.org1.finance.com

127.0.0.1 peer1.org1.finance.com

127.0.0.1 peer0.org2.finance.com

127.0.0.1 orderer.finance.com

2.2 创建Fabric-Sdk-Go代码目录

2.2.1 相关Go包的设置

在Ubuntu系统上创建financ_network创建目录userCode用来保存与Fabric-Sdk-Go相关的配置。在这个目录下需要生成go.mod、go.sum和vendor目录。命令如下:

go mod init

vim go.mod #这里要修改go.mod文件,将go语言版本修改为1.21

go get https://github.com/hyperledger/fabric-sdk-go

go get github.com/hyperledger/fabric-contract-api-go v1.2.2

GO111MODULE=on go mod vendor

接着可以到Mac系统上的GoLand上进行后续操作。后续如果需要其他包可以使用go mod tidy命令来下载。这里重点说一下Fabric-Sdk-Go@1.0.0。虽然可以使用go get命令下载,但这种方式下Fabric-Sdk-Go包里的内容会有遗失,在后续过程会遇到如下错误: 为了保证Fabric-sdk-go的顺利执行,仅使用go get命令下载Fabric-Sdk-Go还不够,否则执行后续代码时会面临如下错误: ..\vendor\github.com\hyperledger\fabric-sdk-go\internal\github.com\hyperledger\fabric\discovery\client\api.go:47:38: undefined: discovery.ChaincodeCall 这时,需要手动使用git clone命令将完整的Fabric-Sdk-Go包里的内容下载到vendor/github.com/hyperledger目录下。具体如下:

git clone https://github.com/hyperledger/fabric-sdk-go.git

即: Tip:go.mod、go.sum和vendor这三个文件也可以在Mac系统上生成,然后使用ssh下载到Fabric_GOPATH目录下,这两种方法在引用后文中的sdkInit包的写法上不同。

2.2.2 映射文件

修改Goland的ssh部署。将finance网络所在的Ubuntu目录finance_network映射到GoLand的Fabric_GOPATH目录上,并将finance网络的所有文件下载到Fabric_GOPATH目录上。完整的目录结构如下:

2.3 Fabric-Sdk-Go的使用

2.3.1 设置config.yaml文件

使用Fabric-Sdk-Go需要一个基础配置文件config.yaml(Fabric-Sdk-Go中提供了一些参考样例),本例中该文件中的内容如下:

# Copyright SecureKey Technologies Inc. All Rights Reserved.

# SPDX-License-Identifier: Apache-2.0

version: 1.0.0

client:

organization: Org1 #此应用程序的所有者

logging:

level: info

cryptoconfig:

path: ${GOPATH}/src/Fabric_GOPATH/organizations

credentialStore:

path: "/tmp/state-store"

cryptoStore:

path: /tmp/msp

BCCSP:

security:

enabled: true

default:

provider: "SW"

hashAlgorithm: "SHA2"

softVerify: true

level: 256

tlsCerts:

systemCertPool: true

client:

key:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/users/Admin@org1.finance.com/tls/client.key

cert:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/users/Admin@org1.finance.com/tls/client.crt

channels:

mychannel:

peers:

peer0.org1.finance.com:

endorsingPeer: true

chaincodeQuery: true

ledgerQuery: true

eventSource: true

peer1.org1.finance.com:

endorsingPeer: true

chaincodeQuery: true

ledgerQuery: true

eventSource: true

peer0.org2.finance.com:

endorsingPeer: true

chaincodeQuery: true

ledgerQuery: true

eventSource: true

policies:

queryChannelConfig:

minResponses: 1

maxTargets: 1

retryOpts:

attempts: 5

initialBackoff: 500ms

maxBackoff: 5s

backoffFactor: 2.0

selection:

SortingStrategy: BlockHeightPriority

Balancer: RoundRobin

BlockHeightLagThreshold: 5

eventService:

resolverStrategy: MinBlockHeight

balancer: RoundRobin

blockHeightLagThreshold: 4

reconnectBlockHeightLagThreshold: 8

peerMonitorPeriod: 6s

organizations:

Org1:

mspid: Org1MSP

cryptoPath: peerOrganizations/org1.finance.com/users/{username}@org1.finance.com/msp

peers:

- peer0.org1.finance.com

- peer1.org1.finance.com

certificateAuthorities:

- ca.org1.finance.com

Org2:

mspid: Org2MSP

cryptoPath: peerOrganizations/org2.finance.com/users/{username}@org2.finance.com/msp

peers:

- peer0.org2.finance.com

certificateAuthorities:

- ca.org2.finance.com

ordererorg:

mspID: OrdererMSP

cryptoPath: ordererOrganizations/finance.com/users/{username}@finance.com/msp

orderers:

_default:

grpcOptions:

keep-alive-time: 0s

keep-alive-timeout: 20s

keep-alive-permit: false

fail-fast: false

allow-insecure: false

orderer.finance.com:

url: grpcs://orderer.finance.com:7050

grpcOptions:

ssl-target-name-override: orderer.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/ordererOrganizations/finance.com/tlsca/tlsca.finance.com-cert.pem

peers:

-defaults:

grpcOptions:

keep-alive-time: 0s

keep-alive-timeout: 20s

keep-alive-permit: false

fail-fast: false

allow-insecure: false

peer0.org1.finance.com:

url: peer0.org1.finance.com:7051

grpcOptions:

ssl-target-name-override: peer0.org1.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/tlsca/tlsca.org1.finance.com-cert.pem

peer1.org1.finance.com:

url: peer1.org1.finance.com:8051

grpcOptions:

ssl-target-name-override: peer1.org1.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/tlsca/tlsca.org1.finance.com-cert.pem

peer0.org2.finance.com:

url: peer0.org2.finance.com:9051

grpcOptions:

ssl-target-name-override: peer0.org2.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org2.finance.com/tlsca/tlsca.org2.finance.com-cert.pem

certificateAuthorities:

ca.org1.finance.com:

url: https://ca.org1.finance.com:7054

grpcOptions:

ssl-target-name-override: ca.org1.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/tlsca/tlsca.org1.finance.com-cert.pem

client:

key:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/users/Admin@org1.finance.com/tls/client.key

cert:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org1.finance.com/users/Admin@org1.finance.com/tls/client.crt

registrar:

enrollId: admin

enrollSecret: adminpw

caName: ca.org1.finance.com

ca.org2.finance.com:

url: https://ca.org2.finance.com:9054

grpcOptions:

ssl-target-name-override: ca.org2.finance.com

tlsCACerts:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org2.finance.com/tlsca/tlsca.org2.finance.com-cert.pem

client:

key:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org2.finance.com/users/Admin@org2.finance.com/tls/client.key

cert:

path: ${GOPATH}/src/Fabric_GOPATH/organizations/peerOrganizations/org2.finance.com/users/Admin@org2.finance.com/tls/client.crt

registrar:

enrollId: admin

enrollSecret: adminpw

caName: ca.org2.finance.com

2.3.2 main函数

先介绍userCode/main.go函数的写法,具体如下:

package main

import (

"finance_network/userCode/sdkInit"

"fmt"

)

const (

configFile = "config.yaml"

initialized = false

)

func main() {

sdk, err := sdkInit.SetupSDK(configFile, initialized)

if err != nil {

fmt.Println(err.Error())

}

defer sdk.Close()

err = sdkInit.CreateAndJoinChannel(sdk)

if err != nil {

fmt.Println(err.Error())

}

err = sdkInit.InstallAndInstantiateCC(sdk)

if err != nil {

fmt.Println(err.Error())

}

err = sdkInit.InitAndQueryCC(sdk)

if err != nil {

fmt.Println(err.Error())

}

}

各个函数的主要作用如下:

sdkInit.SetupSDK: 创建实例化Fabric SDK;sdkInit.CreateAndJoinChannel: 创建通道并将节点加入到通道中;sdkInit.InstallAndInstantiateCC:安装和实例化链码;sdkInit.InitAndQueryCC: 初始化账本并调用链码函数;

上述代码都在userCode/sdkInit/start.go文件中,下面将依次介绍start.go中的各个函数代码,并将可能遇到的bug问题记录在下。

2.3.3 start.go的公共变量

先介绍userCode/sdkInit/start.go文件中的公共变量及import部分如下。

package sdkInit

import (

"bytes"

"fmt"

mb "github.com/hyperledger/fabric-protos-go/msp"

pb "github.com/hyperledger/fabric-protos-go/peer"

"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"

mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"

"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"

contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"

"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"

"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"

contextImpl "github.com/hyperledger/fabric-sdk-go/pkg/context"

"github.com/hyperledger/fabric-sdk-go/pkg/core/config"

lcpackager "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/lifecycle"

"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"

"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl"

"os"

"strconv"

)

const (

org1 = "Org1"

org2 = "Org2"

ordererAdminUser = "Admin"

ordererOrgName = "ordererorg"

ordererPoint = "orderer.finance.com"

org1AdminUser = "Admin"

org2AdminUser = "Admin"

org1User = "User1"

)

var (

sdk *fabsdk.FabricSDK

org1MspClient *mspclient.Client

org2MspClient *mspclient.Client

org1Peers []fab.Peer

org2Peers []fab.Peer

)

type multiorgContext struct {

ordererClientContext contextAPI.ClientProvider

org1AdminClientContext contextAPI.ClientProvider

org2AdminClientContext contextAPI.ClientProvider

org1ResMgmt *resmgmt.Client

org2ResMgmt *resmgmt.Client

ccName string

ccVersion string

channelID string

sequence int64

}

var mc multiorgContext

2.3.4 实例化Fabric SDK

start.go中SetupSDK函数的写法如下:

func SetupSDK(configFile string, initialized bool) (*fabsdk.FabricSDK, error) {

if initialized {

return nil, fmt.Errorf("Fabric SDK已经实例化.")

}

sdk, err := fabsdk.New(config.FromFile(configFile))

if err != nil {

return nil, fmt.Errorf("实例化Fabric SDK失败: %v", err)

}

fmt.Println("Fabric SDK初始化成功.")

return sdk, nil

}

2.3.5 创建通道并将peer、orderer节点添加节点上

start.go中CreateAndJoinChannel函数的代码如下:

func CreateAndJoinChannel(sdk *fabsdk.FabricSDK) error {

mc = multiorgContext{

ordererClientContext: sdk.Context(fabsdk.WithUser(ordererAdminUser), fabsdk.WithOrg(ordererOrgName)),

org1AdminClientContext: sdk.Context(fabsdk.WithUser(org1AdminUser), fabsdk.WithOrg(org1)),

org2AdminClientContext: sdk.Context(fabsdk.WithUser(org2AdminUser), fabsdk.WithOrg(org2)),

channelID: "mychannel",

ccName: "basic3",

ccVersion: "1",

sequence: 1,

}

org1MspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(org1))

if err != nil {

return fmt.Errorf("failed to create org1MspClient: %v", err)

}

org2MspClient, err := mspclient.New(sdk.Context(), mspclient.WithOrg(org2))

if err != nil {

return fmt.Errorf("failed to create org2MspClient:%v", err)

}

if mc.ordererClientContext == nil || mc.org1AdminClientContext == nil || mc.org2AdminClientContext == nil {

return fmt.Errorf("根据指定的组织名称与管理员创建资源管理客户端Context失败.")

}

org1RMgmt, err := resmgmt.New(mc.org1AdminClientContext)

if err != nil {

return fmt.Errorf("组织Org1的资源管理客户端创建失败.")

}

mc.org1ResMgmt = org1RMgmt

org2RMgmt, err := resmgmt.New(mc.org2AdminClientContext)

if err != nil {

return fmt.Errorf("组织Org2的资源管理客户端创建失败.")

}

mc.org2ResMgmt = org2RMgmt

org1AdminUser, err := org1MspClient.GetSigningIdentity(org1AdminUser)

if err != nil {

return fmt.Errorf("failed to get org1AdminUser, err : %s", err)

}

org2AdminUser, err := org2MspClient.GetSigningIdentity(org2AdminUser)

if err != nil {

return fmt.Errorf("failed to get org2AdminUser, err : %s", err)

}

chMgmtClient, err := resmgmt.New(mc.ordererClientContext)

if err != nil {

return fmt.Errorf("创建通道管理客户端失败.")

}

req := resmgmt.SaveChannelRequest{ChannelID: mc.channelID,

ChannelConfigPath: os.Getenv("GOPATH") + "/src/Fabric_GOPATH/channel-artifacts/channel.tx",

SigningIdentities: []msp.SigningIdentity{org1AdminUser, org2AdminUser}}

_, err = chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts),

resmgmt.WithOrdererEndpoint(ordererPoint))

if err != nil {

return fmt.Errorf(err.Error())

}

chMgmtClient, err = resmgmt.New(mc.org1AdminClientContext)

if err != nil {

return fmt.Errorf("failed to get a new channel management client for org1Admin")

}

req = resmgmt.SaveChannelRequest{ChannelID: mc.channelID,

ChannelConfigPath: os.Getenv("GOPATH") + "/src/Fabric_GOPATH/channel-artifacts/Org1MSPanchors.tx",

SigningIdentities: []msp.SigningIdentity{org1AdminUser}}

_, err = chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts),

resmgmt.WithOrdererEndpoint(ordererPoint))

if err != nil {

return fmt.Errorf(err.Error())

}

chMgmtClient, err = resmgmt.New(mc.org2AdminClientContext)

if err != nil {

return fmt.Errorf("failed to get a new channel management client for org2Admin")

}

req = resmgmt.SaveChannelRequest{ChannelID: mc.channelID,

ChannelConfigPath: os.Getenv("GOPATH") + "/src/Fabric_GOPATH/channel-artifacts/Org2MSPanchors.tx",

SigningIdentities: []msp.SigningIdentity{org2AdminUser}}

_, err = chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts),

resmgmt.WithOrdererEndpoint(ordererPoint))

if err != nil {

return fmt.Errorf(err.Error())

}

fmt.Println("应用通道创建完成.")

err = mc.org1ResMgmt.JoinChannel(mc.channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts),

resmgmt.WithOrdererEndpoint(ordererPoint))

if err != nil {

fmt.Println(err.Error())

return fmt.Errorf("组织Org1的peer节点加入通道失败.")

}

err = mc.org2ResMgmt.JoinChannel(mc.channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts),

resmgmt.WithOrdererEndpoint(ordererPoint))

if err != nil {

return fmt.Errorf("组织Org2的peer节点加入通道失败.")

}

fmt.Println("Peer节点加入通道完成.")

return nil

}

代码执行完之后可以在Ubuntu系统中看到peer0.org1.finance.com(设置环境变量的过程不再赘述)已经加入到相关的通道mychannel中了: 如果在创建通道过程中提示如下错误:create channel failed: create channel failed: SendEnvelope failed: calling orderer ‘orderer.finance.com:7050’ failed: Orderer Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection on target [orderer.finance.com:7050]: connection is in TRANSIENT_FAILURE.此时需要将organizations目录下的所有文件重新生成,重走一遍finance网络搭建过程。

2.3.5 部署及安装链码

start.go中InstallAndInstantiateCC函数的代码如下:

func InstallAndInstantiateCC(sdk *fabsdk.FabricSDK) error {

// 打包链码

label := mc.ccName + "_" + mc.ccVersion

desc := &lcpackager.Descriptor{

Path: os.Getenv("GOPATH") + "/src/Fabric_GOPATH/chaincode",

Type: pb.ChaincodeSpec_GOLANG,

Label: label,

}

ccPkg, err := lcpackager.NewCCPackage(desc)

if err != nil {

return fmt.Errorf("链码打包失败.")

}

packageID := lcpackager.ComputePackageID(label, ccPkg)

//获取Peer节点

var ctxProvider contextAPI.ClientProvider

ctxProvider = mc.org1AdminClientContext

ctx, err := contextImpl.NewLocal(ctxProvider)

if err != nil {

return fmt.Errorf("Org1客户端创建失败.")

}

org1Peers, err = ctx.LocalDiscoveryService().GetPeers()

ctxProvider = mc.org2AdminClientContext

ctx, err = contextImpl.NewLocal(ctxProvider)

if err != nil {

return fmt.Errorf("Org2客户端创建失败.")

}

org2Peers, err = ctx.LocalDiscoveryService().GetPeers()

// 安装链码

err = installCC(label, ccPkg)

if err != nil {

return fmt.Errorf("链码安装失败:%v", err)

} else {

fmt.Println("链码安装成功.")

}

// Approve cc

err = approveCC(packageID)

if err != nil {

return fmt.Errorf(err.Error())

} else {

fmt.Println("组织Org1和Org2链码批准成功.")

}

// Check commit readiness

err = commitCC()

if err != nil {

return fmt.Errorf(err.Error())

} else {

fmt.Println("链码提交完成.")

}

return nil

}

func installCC(label string, ccPkg []byte) error {

//安装链码、获取已安装的的链码包及来链码

installCCReq := resmgmt.LifecycleInstallCCRequest{

Label: label,

Package: ccPkg,

}

packageID := lcpackager.ComputePackageID(installCCReq.Label, installCCReq.Package)

fmt.Println("链码打包成功.链码ID:", packageID)

//安装链码-Org1

if !checkInstalled(packageID, org1Peers[0], mc.org1ResMgmt) {

resOrg1, err := mc.org1ResMgmt.LifecycleInstallCC(installCCReq,

resmgmt.WithTargets(org1Peers...), resmgmt.WithRetry(retry.DefaultResMgmtOpts))

if err != nil {

return fmt.Errorf(err.Error())

}

if resOrg1[0].PackageID != packageID {

return fmt.Errorf(err.Error())

}

}

resp1, err := mc.org1ResMgmt.LifecycleGetInstalledCCPackage(packageID, resmgmt.WithTargets(org1Peers[0]))

if err != nil {

return fmt.Errorf("组织Org1的Peer0节点获取已安装链码失败: %v", err)

}

if !bytes.Equal(resp1, ccPkg) {

return fmt.Errorf("组织Org1的Peer0节点获取的链码内容与新链码不一致.")

}

//安装链码-Org2

if !checkInstalled(packageID, org2Peers[0], mc.org2ResMgmt) {

resOrg2, err := mc.org2ResMgmt.LifecycleInstallCC(installCCReq,

resmgmt.WithTargets(org2Peers...), resmgmt.WithRetry(retry.DefaultResMgmtOpts))

if err != nil {

return fmt.Errorf(err.Error())

}

if resOrg2[0].PackageID != packageID {

return fmt.Errorf(err.Error())

}

}

resp2, err := mc.org2ResMgmt.LifecycleGetInstalledCCPackage(packageID, resmgmt.WithTargets(org2Peers[0]))

if err != nil {

return fmt.Errorf("组织Org2的Peer0节点获取已安装链码失败: %v", err)

}

if !bytes.Equal(resp2, ccPkg) {

return fmt.Errorf("组织Org2的Peer0节点获取的链码内容与新链码不一致.")

}

return nil

}

func checkInstalled(packageID string, peer fab.Peer, client *resmgmt.Client) bool {

flag := false

resp1, err := client.LifecycleQueryInstalledCC(resmgmt.WithTargets(peer))

if err != nil {

fmt.Println(err.Error())

return flag

}

for _, t := range resp1 {

if t.PackageID == packageID {

flag = true

}

}

return flag

}

func approveCC(packageID string) error {

ccPolicy := policydsl.SignedByNOutOfGivenRole(2, mb.MSPRole_MEMBER, []string{"Org1MSP", "Org2MSP"})

approveCCReq := resmgmt.LifecycleApproveCCRequest{

Name: mc.ccName,

Version: mc.ccVersion,

PackageID: packageID,

Sequence: mc.sequence,

EndorsementPlugin: "escc",

ValidationPlugin: "vscc",

SignaturePolicy: ccPolicy,

InitRequired: true,

}

_, err := mc.org1ResMgmt.LifecycleApproveCC(mc.channelID, approveCCReq, resmgmt.WithTargets(org1Peers...),

resmgmt.WithOrdererEndpoint(ordererPoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))

if err != nil {

return fmt.Errorf(err.Error())

}

_, err = mc.org2ResMgmt.LifecycleApproveCC(mc.channelID, approveCCReq, resmgmt.WithTargets(org2Peers...),

resmgmt.WithOrdererEndpoint(ordererPoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))

if err != nil {

return fmt.Errorf(err.Error())

}

queryApprovedCCReq := resmgmt.LifecycleQueryApprovedCCRequest{

Name: mc.ccName,

Sequence: mc.sequence,

}

clientPeers := map[*resmgmt.Client][]fab.Peer{mc.org1ResMgmt: org1Peers, mc.org2ResMgmt: org2Peers}

for orgClient, peers := range clientPeers {

for _, p := range peers {

_, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(

func() (interface{}, error) {

resp1, err := orgClient.LifecycleQueryApprovedCC(mc.channelID, queryApprovedCCReq, resmgmt.WithTargets(p))

if err != nil {

return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryApprovedCC returned error: %v", err), nil)

}

return resp1, err

},

)

if err != nil {

return fmt.Errorf(err.Error())

}

}

}

return nil

}

func commitCC() error {

ccPolicy := policydsl.SignedByNOutOfGivenRole(2, mb.MSPRole_MEMBER, []string{"Org1MSP", "Org2MSP"})

req := resmgmt.LifecycleCheckCCCommitReadinessRequest{

Name: mc.ccName,

Version: mc.ccVersion,

EndorsementPlugin: "escc",

ValidationPlugin: "vscc",

SignaturePolicy: ccPolicy,

Sequence: mc.sequence,

InitRequired: true,

}

clientPeers := map[*resmgmt.Client][]fab.Peer{mc.org1ResMgmt: org1Peers, mc.org2ResMgmt: org2Peers}

for orgClient, peers := range clientPeers {

for _, p := range peers {

_, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(

func() (interface{}, error) {

resp1, err := orgClient.LifecycleCheckCCCommitReadiness(mc.channelID, req, resmgmt.WithTargets(p))

fmt.Printf("LifecycleCheckCCCommitReadiness cc = %v, = %v\n", mc.ccName, resp1)

if err != nil {

return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned error: %v", err), nil)

}

flag := true

for _, r := range resp1.Approvals {

flag = flag && r

}

if !flag {

return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned : %v", resp1), nil)

}

return resp1, err

},

)

if err != nil {

return fmt.Errorf(err.Error())

}

}

}

ccPolicy = policydsl.SignedByNOutOfGivenRole(2, mb.MSPRole_MEMBER, []string{"Org1MSP", "Org2MSP"})

reqCC := resmgmt.LifecycleCommitCCRequest{

Name: mc.ccName,

Version: mc.ccVersion,

Sequence: mc.sequence,

EndorsementPlugin: "escc",

ValidationPlugin: "vscc",

SignaturePolicy: ccPolicy,

InitRequired: true,

}

_, err := mc.org1ResMgmt.LifecycleCommitCC(mc.channelID, reqCC,

resmgmt.WithOrdererEndpoint(ordererPoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))

if err != nil {

return fmt.Errorf(err.Error())

}

reqQCC := resmgmt.LifecycleQueryCommittedCCRequest{

Name: mc.ccName,

}

for orgClient, peers := range clientPeers {

for _, p := range peers {

_, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(

func() (interface{}, error) {

resp1, err := orgClient.LifecycleQueryCommittedCC(mc.channelID, reqQCC, resmgmt.WithTargets(p))

if err != nil {

return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned error: %v", err), nil)

}

flag := false

for _, r := range resp1 {

if r.Name == mc.ccName && r.Sequence == mc.sequence {

flag = true

break

}

}

if !flag {

return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned : %v", resp1), nil)

}

return resp1, err

},

)

if err != nil {

return fmt.Errorf(err.Error())

}

}

}

return nil

}

关于部署链码时有一点要注意:每次运行该段代码之后,**mc.ccVersion**要修改,否则生成的变量packageID不会发生改变,那么后续执行提交链码时会遇到如下错误:Event Server Status Code: (10) ENDORSEMENT_POLICY_FAILURE. Description: instantiateOrUpgradeCC failed。具体如下: 链码部署完之后就可以在Ubuntu系统中的docker命令查看链码容器,具体如下:

2.3.6 调用链码

start.go中InitAndQueryCC函数的代码如下:

func InitAndQueryCC(sdk *fabsdk.FabricSDK) error {

//prepare channel client context using client context

clientChannelContext := sdk.ChannelContext(mc.channelID, fabsdk.WithUser(org1User), fabsdk.WithOrg(org1))

// Channel client is used to query and execute transactions (Org1 is default org)

client, err := channel.New(clientChannelContext)

if err != nil {

return fmt.Errorf("Failed to create new channel client: %s", err)

}

var args [][]byte

// init

_, err = client.Execute(channel.Request{ChaincodeID: mc.ccName, Fcn: "InitLedger", Args: args, IsInit: true},

channel.WithRetry(retry.DefaultChannelOpts))

if err != nil {

return fmt.Errorf("Failed to init: %s", err)

}

req := channel.Request{

ChaincodeID: mc.ccName,

Fcn: "GetAllAssets",

}

_, err = retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(

func() (interface{}, error) {

resp, err := client.Query(req, channel.WithRetry(retry.DefaultChannelOpts))

if err != nil {

return nil, fmt.Errorf("链码执行失败.")

}

// Verify that transaction changed block state

fmt.Println(strconv.Atoi(string(resp.Payload)))

return nil, nil

},

)

if err != nil {

return fmt.Errorf("失败.")

}

return nil

}

2.3.7 代码运行结果

main.go函数的执行结果如下:

3 总结

目前还有很多问题没有解决,未来会继续对本篇博客中的代码进行精简。

参考资料

https://blog.csdn.net/lakersssss24/article/details/125645713

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: