本篇博客主要记录使用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 总结
目前还有很多问题没有解决,未来会继续对本篇博客中的代码进行精简。