Fabric:使用GoLand+Fabric-SDK-Go操作Fabric网络(附代码)

本篇博客主要记录使用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.
关于上述文件有以下几点需要说明:

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.modgo.sumvendor目录。命令如下:

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.goSetupSDK函数的写法如下:

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.goCreateAndJoinChannel函数的代码如下:

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.goInstallAndInstantiateCC函数的代码如下:

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.goInitAndQueryCC函数的代码如下:

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 总结

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

参考资料

  1. https://blog.csdn.net/lakersssss24/article/details/125645713
  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值