HyperLeger Fabric SDK-go开发使用

git branch -a                // 查看分支,不出意外的话,此时分支应该处于 release-1.4
git checkout release-1.4              // 切换到 release-1.3 版本
git branch -v

git checkout release-1.4 

报错过程:
进入fabric-samples 目录中的first-networkd子目录后,在使用“./byfn.sh —help”时报错:
error getting endorser client for channel: endorser client failed to connect to peer0.org1.example.com:7051: failed to create new connection: context deadline exceeded

报错过程:
进入fabric-samples 目录中的first-networkd子目录后,在使用“./byfn.sh —help”时报错:
error getting endorser client for channel: endorser client failed to connect to peer0.org1.example.com:7051: failed to create new connection: context deadline exceeded

解决方案:
 /etc/resolv.conf 注释掉 options timeout:2 attempts:3 rotate single-request-reopen,再次运行可成功。
cat /etc/resolv.conf 
sudo chmod 777 /etc/resolv.conf 
gedit /etc/resolv.conf 

前言

本教程分为两部分,其中包括Fabric基础环境部署和Fabric-sdk-go的使用部分,涉及到的内容涵盖:

  • 编写crypto-config.yaml文件并生成秘钥文件
  • 编写configtx.yaml文件并生成创世快文件
  • 编写docker-compose.yaml文件,并运行成功
  • 编写链码
  • 为fabric-sdk-go编写配置文件,并调用成功
  • 使用fabric-sdk-go创建通道
  • 使用fabric-sdk-go把org1、org2(所有节点)加入通道
  • 使用fabric-sdk-go在指定节点安装自己编写的链码
  • 使用fabric-sdk-go实例化链码(配置背书策略)
  • 使用fabric-sdk-go调用链码
  • 使用fabric-sdk-go更新链码背书策略
  • 使用fabric-sdk-go调用新的链码(新的背书策略)

一、系统初始化

步骤1

生成秘钥文件

首先创建crypto-config.yaml文件:

../bin/cryptogen generate --config=./crypto-config.yaml

查看目录生成crypto-config文件夹,证书文件存放在此文件夹,用这些文件进行系统初始化

步骤2

生成创世块文件

创建configtx.yaml文件,文件内容如下:

创建系统初始化文件夹:
mkdir channel-artifacts

生成创世快文件:
export FABRIC_CFG_PATH=$PWD
../bin/configtxgen -profile SampleMultiNodeEtcdRaft -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block   byfn-sys-channel  ==代表通道名称

运行成功后可看到 channel-artifacts文件夹下生成了genesis.block文件。

步骤3

创建通道

../bin/configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
运行成功后可看到channel-artifacts文件夹下生成了channel.tx文件 
通道名 mychannel

为通道内的两个组织创建锚节点初始化文件

../bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
../bin/configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP

其中 锚节点 为:

    AnchorPeers:
        # AnchorPeers defines the location of peers which can be used
        # for cross org gossip communication.  Note, this value is only
        # encoded in the genesis block in the Application section context
        - Host: peer0.org1.example.com
          Port: 7051
   AnchorPeers:
        # AnchorPeers defines the location of peers which can be used
        # for cross org gossip communication.  Note, this value is only
        # encoded in the genesis block in the Application section context
        - Host: peer0.org2.example.com
          Port: 9051

运行成功后可看到channel-artifacts文件夹下生成了Org1MSPanchors.txOrg2MSPanchors.tx两个文件。

二、启动节点

步骤一: 准备docker-compose文件

创建base.yaml文件:

创建docker-compose-local.yaml文件:

创建.env文件:

FABRIC_IMG_TAG=latest
SYS_CHANNEL=byfn-sys-channel
NETWORK=local_test

三、 启动容器

创建网络:

docker network create  local_test

docker-compose -f docker-compose-local.yaml up -d

如果运行成功,我们可以用docker ps命令查看运行起来的容器:

四、编写链码

创建chaincode文件夹:

cd ..
mkdir chaincode
cd chaincode

1 声明结构体(即链上存储的内容)

创建文件scoreStruct.go:

package main

// 根据实际需求,字段自行增删,以下只供参考
type Score struct {
	ObjectType	string	`json:"docType"`
	Name	string	`json:"Name"`		// 姓名
	Gender	string	`json:"Gender"`		// 性别
	StuID	string	`json:"StuID"`		// 学生学号
	Grade	string	`json:"Grade"`		// 年级
	Result	string	`json:"Result"`		// 成绩
	Time	string	`json:"Time"`		// 插入该数据的时间
Historys	[]HistoryItem	// 当前Score的历史记录
}

type HistoryItem struct {				// 交易hash,也是标志ID
	TxId	string
	Score	Score
}

2 编写链码

创建文件main.go:

package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"fmt"
	"github.com/hyperledger/fabric/protos/peer"
)

type ScoreChaincode struct {

}

func (t *ScoreChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
	return shim.Success(nil)
}

func (t *ScoreChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
	// 获取用户意图
	fun, args := stub.GetFunctionAndParameters()

	if fun == "addScore"{
		stub.SetEvent("addScore", []byte{})
		return t.addScore(stub, args)		// 添加成绩
	}else if fun == "queryScoreByNameAndGrade" {
		stub.SetEvent("queryScoreByNameAndGrade", []byte{})
		return t.queryScoreByNameAndGrade(stub, args)		// 根据姓名及年级查询成绩,假设不同年级存在同名
	}else if fun == "queryScoreDetailByStuID" {
		stub.SetEvent("queryScoreDetailByStuID", []byte{})
		return t.queryScoreDetailByStuID(stub, args)		// 根据学号查询成绩详细信息(包括历史信息)
	}else if fun == "updateScore" {
		stub.SetEvent("updateScore", []byte{})
		return t.updateScore(stub, args)		// 根据证书编号更新信息
	}else if fun == "delScore"{
		stub.SetEvent("delScore", []byte{})
		return t.delScore(stub, args)	// 根据证书编号删除信息
	}
	
	return shim.Error("指定的函数名称错误")

}

func main(){
	err := shim.Start(new(ScoreChaincode))
	if err != nil{
		fmt.Printf("启动ScoreChaincode时发生错误: %s", err)
	}
}

创建文件scoreCC.go:

package main

import (
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/protos/peer"
	"encoding/json"
	"fmt"
	"bytes"
)

const DOC_TYPE = "ScoreObj"

// 保存sco
// args: Score
func PutScore(stub shim.ChaincodeStubInterface, sco Score) ([]byte, bool) {
    sco.ObjectType = DOC_TYPE

    b, err := json.Marshal(sco) //序列化
    if err != nil {
        return nil, false
    }
    // 保存sco状态
    err = stub.PutState(sco.StuID, b)
    if err != nil {
        return nil, false
    }

    return b, true
}

// 根据学号查询信息
// args: stuID
func GetScoreInfo(stub shim.ChaincodeStubInterface, stuID string) (Score, bool)  {
	var sco Score
	// 根据学号查询信息状态
	b, err := stub.GetState(stuID)
	if err != nil {
		return sco, false
	}

	if b == nil {
		return sco, false
	}
	
	// 对查询到的状态进行反序列化
	err = json.Unmarshal(b, &sco)
	if err != nil {
		return sco, false
	}
	
	// 返回结果
	return sco, true

}

// 根据指定的查询字符串实现富查询
func getEduByQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) {

	resultsIterator, err := stub.GetQueryResult(queryString)
	if err != nil {
		return nil, err
	}
	defer  resultsIterator.Close() //记得结束富查询调用
	
	// buffer is a JSON array containing QueryRecords
	var buffer bytes.Buffer
	
	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
	
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		bArrayMemberAlreadyWritten = true
	}
	
	fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String())
	
	return buffer.Bytes(), nil

}

// 添加信息
// args: scoreObject
// 学生号为 key, Score 为 value
func (t *ScoreChaincode) addScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {

	if len(args) != 1{
		return shim.Error("给定的参数个数不符合要求")
	}
	
	var sco Score
	err := json.Unmarshal([]byte(args[0]), &sco)
	if err != nil {
		return shim.Error("反序列化信息时发生错误")
	}
	
	// 查重: 身份证号码必须唯一
	_, exist := GetScoreInfo(stub, sco.StuID)
	if exist {
		return shim.Error("要添加的学生号已存在")
	}
	
	_, bl := PutScore(stub, sco)
	if !bl {
		return shim.Error("保存信息时发生错误")
	}
	
	return shim.Success([]byte("信息添加成功"))

}

// 根据姓名及年级查询信息
// args: Name, Grade
func (t *ScoreChaincode) queryScoreByNameAndGrade(stub shim.ChaincodeStubInterface, args []string) peer.Response {

	if len(args) != 2 {
		return shim.Error("给定的参数个数不符合要求")
	}
	name := args[0]
	grade := args[1]
	
	// 拼装CouchDB所需要的查询字符串(是标准的一个JSON串)
	// queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"eduObj\", \"CertNo\":\"%s\"}}", CertNo)
	queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"%s\", \"Name\":\"%s\", \"Grade\":\"%s\"}}", DOC_TYPE, name, grade)
	
	// 查询数据
	result, err := getEduByQueryString(stub, queryString)//getEduByQueryString
	if err != nil {
		return shim.Error("根据证书姓名及年级查询信息时发生错误")
	}
	if result == nil {
		return shim.Error("根据指定的姓名及年级没有查询到相关的信息")
	}
	return shim.Success(result)

}

// 根据学号查询详情(溯源)
// args: StuID
func (t *ScoreChaincode) queryScoreDetailByStuID(stub shim.ChaincodeStubInterface, args []string) peer.Response {
	if len(args) != 1 {
		return shim.Error("给定的参数个数不符合要求")
	}

	// 根据学号查询sco状态
	b, err := stub.GetState(args[0])
	if err != nil {
		return shim.Error("根据学号查询信息失败")
	}
	
	if b == nil {
		return shim.Error("根据学号没有查询到相关的信息")
	}
	
	// 对查询到的状态进行反序列化
	var sco Score
	err = json.Unmarshal(b, &sco)
	if err != nil {
		return  shim.Error("反序列化sco信息失败")
	}
	
	// 获取历史变更数据
	iterator, err := stub.GetHistoryForKey(sco.StuID)
	if err != nil {
		return shim.Error("根据指定的学号查询对应的历史变更数据失败")
	}
	defer iterator.Close()
	
	// 迭代处理
	var historys []HistoryItem
	var hisSco Score
	for iterator.HasNext() {
		hisData, err := iterator.Next()
		if err != nil {
			return shim.Error("获取sco的历史变更数据失败")
		}
	
		var historyItem HistoryItem
		historyItem.TxId = hisData.TxId
		json.Unmarshal(hisData.Value, &hisSco)
	
		if hisData.Value == nil {
			var empty Score
			historyItem.Score = empty
		}else {
			historyItem.Score = hisSco
		}
	
		historys = append(historys, historyItem)
	
	}
	
	sco.Historys = historys
	
	// 返回
	result, err := json.Marshal(sco)
	if err != nil {
		return shim.Error("序列化sco信息时发生错误")
	}
	return shim.Success(result)

}

// 根据学号更新信息
// args: Score Object
func (t *ScoreChaincode) updateScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {
	if len(args) != 1{
		return shim.Error("给定的参数个数不符合要求")
	}

	var info Score
	err := json.Unmarshal([]byte(args[0]), &info)
	if err != nil {
		return  shim.Error("反序列化edu信息失败")
	}
	
	// 根据学号查询信息
	result, bl := GetScoreInfo(stub, info.StuID)
	if !bl{
		return shim.Error("根据学号查询信息时发生错误")
	}
	
	result.Name = info.Name
	result.Gender = info.Gender
	result.Grade = info.Grade
	result.Result = info.Result
	result.Time = info.Time
	
	_, bl = PutScore(stub, result)
	if !bl {
		return shim.Error("保存信息信息时发生错误")
	}
	
	return shim.Success([]byte("信息更新成功"))

}

// 根据学号删除信息(暂不对外提供)
// args: StuID
func (t *ScoreChaincode) delScore(stub shim.ChaincodeStubInterface, args []string) peer.Response {
	if len(args) != 1{
		return shim.Error("给定的参数个数不符合要求")
	}

	/*var edu Education
	result, bl := GetEduInfo(stub, info.EntityID)
	err := json.Unmarshal(result, &edu)
	if err != nil {
		return shim.Error("反序列化信息时发生错误")
	}*/
	
	err := stub.DelState(args[0])
	if err != nil {
		return shim.Error("删除信息时发生错误")
	}
	return shim.Success([]byte("信息删除成功"))

}


1.可能大家会疑惑区块链为什么还存在删除操作?

答:Fabric区块中存的是动作:“谁在什么时间对某个数据执行了什么操作”,例如:“张三在2020年8月10日插入了key为xxx,value为xx的数据”。而LevelDB或CouchDB存的是状态数据,及当前时间点某个key的状态(值)是什么。

  1. 为什么要用CouchDB?

**答:简单点讲就是只有用CouchDB才支持富查询等操作,LevelDB是不支持的。 **

3 创建CouchDB索引

mkdir META-INF/statedb/couchdb/indexes
cd META-INF/statedb/couchdb/indexes

创建indexNameGrade.json文件:

{"index":{"fields":["Name","Grade"]},"ddoc":"indexNameGrade", "name":"indexNameGrade","type":"json"}
因为链码中有通过Name和Grade两个字段来进行查询的函数。

五、SDK配置

创建配置目录:

cd .. # 回到主目录
mkdir sdkConfig
12

创建org1的配置文件org1_config.yaml:

特别注意其中的目录配置,在organizations下的cryptoPath里一定要是相对目录,从crypto-config的下一级目录开始,因为在文件的开始就已经配置了crypto-config目录的,不然在创建resmgmt Client的时候会提示user not found。

name: "org1-config"
#

# Copyright SecureKey Technologies Inc. All Rights Reserved.

#

# SPDX-License-Identifier: Apache-2.0

#
#

# The network connection profile provides client applications the information about the target

# blockchain network that are necessary for the applications to interact with it. These are all

# knowledge that must be acquired from out-of-band sources. This file provides such a source.

#

# copied from fabric-sdk-go/test/fixtures/config/config_e2e_pkcs11.yaml

#

# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.

#
version: 1.0.0

#

# The client section used by GO SDK.

#
client:

  # Which organization does this application instance belong to? The value must be the name of an org

  # defined under "organizations"

  organization: Org1
  logging:
    # Develope can using debug to get more information
    #    level: debug
    level: info
  cryptoconfig:
    path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config

  # Some SDKs support pluggable KV stores, the properties under "credentialStore"

  # are implementation specific

  credentialStore:
    # [Optional]. Used by user store. Not needed if all credentials are embedded in configuration
    # and enrollments are performed elswhere.
    path: "/tmp/examplestore"


  # [Optional] BCCSP config for the client. Used by GO SDK.

  BCCSP:
    security:
      enabled: true
      default:
        provider: "SW"
      hashAlgorithm: "SHA2"
      softVerify: true
      level: 256

  tlsCerts:
    # [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false
    systemCertPool: true
    # [Optional]. Client key and cert for TLS handshake with peers and orderers
    client:
      # 使用User1@org1的证书
      keyfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.key
      certfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.cert



################################## General part ##################################


#

# [Optional]. But most apps would have this section so that channel objects can be constructed

# based on the content below. If an app is creating channels, then it likely will not need this

# section.

#
channels:

  # name of the channel

  mychannel:
    # Required. list of orderers designated by the application to use for transactions on this
    # channel. This list can be a result of access control ("org1" can only access "ordererA"), or
    # operational decisions to share loads from applications among the orderers.  The values must
    # be "names" of orgs defined under "organizations/peers"
    # deprecated: not recommended, to override any orderer configuration items, entity matchers should be used.
    #    orderers:
    #      - orderer.example.com

    # 不要缺少当前channel的orderer节点
    orderers:
      - orderer0.example.com
      - orderer1.example.com
      - orderer2.example.com
    
    # Required. list of peers from participating orgs
    peers:
      peer0.org1.example.com:
        # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must
        # have the chaincode installed. The app can also use this property to decide which peers
        # to send the chaincode install request. Default: true
        endorsingPeer: true
    
        # [Optional]. will this peer be sent query proposals? The peer must have the chaincode
        # installed. The app can also use this property to decide which peers to send the
        # chaincode install request. Default: true
        chaincodeQuery: true
    
        # [Optional]. will this peer be sent query proposals that do not require chaincodes, like
        # queryBlock(), queryTransaction(), etc. Default: true
        ledgerQuery: true
    
        # [Optional]. will this peer be the target of the SDK's listener registration? All peers can
        # produce events but the app typically only needs to connect to one to listen to events.
        # Default: true
        eventSource: true
    
      # Add other peers in mychannel for byfn
      peer1.org1.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
    
      peer0.org2.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
    
      peer1.org2.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true
    
    # [Optional]. The application can use these options to perform channel operations like retrieving channel
    # config etc.
    policies:
      #[Optional] options for retrieving channel configuration blocks
      queryChannelConfig:
        #[Optional] min number of success responses (from targets/peers)
        minResponses: 1
        #[Optional] channel config will be retrieved for these number of random targets
        maxTargets: 1
        #[Optional] retry options for query config block
        retryOpts:
          #[Optional] number of retry attempts
          attempts: 5
          #[Optional] the back off interval for the first retry attempt
          initialBackoff: 500ms
          #[Optional] the maximum back off interval for any retry attempt
          maxBackoff: 5s
          #[Optional] he factor by which the initial back off period is exponentially incremented
          backoffFactor: 2.0

#

# list of participating organizations in this network

#
organizations:
  Org1:
    mspid: Org1MSP
    # set msp files path
    cryptoPath: peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp
    # Add peers for org1
    peers:
      - peer0.org1.example.com
      - peer1.org1.example.com

    # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based
    # network. Typically certificates provisioning is done in a separate process outside of the
    # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for
    # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for
    # Fabric-CA servers.
    certificateAuthorities:
      - ca.org1.example.com
    
    #users:
    #  Admin:
    #    cert:
    #      pem: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/tlsca/tlsca.org1.example.com-cert.pem



  # the profile will contain public information about organizations other than the one it belongs to.

  # These are necessary information to make transaction lifecycles work, including MSP IDs and

  # peers with a public URL to send transaction proposals. The file will not contain private

  # information reserved for members of the organization, such as admin key and certificate,

  # fabric-ca registrar enroll ID and secret, etc.

  Org2:
    mspid: Org2MSP
    cryptoPath: peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp

    # Add peers for org2
    peers:
      - peer0.org2.example.com
      - peer1.org2.example.com
    certificateAuthorities:
      - ca.org2.example.com


  # Orderer Org name

  ordererorg:
    # Membership Service Provider ID for this organization
    mspID: OrdererMSP
    cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp
    orderers:
      - orderer0.example.com
      - orderer1.example.com
      - orderer2.example.com


#

# List of orderers to send transaction and channel create/update requests to. For the time

# being only one orderer is needed. If more than one is defined, which one get used by the

# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers.

#
orderers:
  orderer0.example.com:
    # [Optional] Default: Infer from hostname
    url: grpcs://localhost:7050

    # these are standard properties defined by the gRPC library
    # they will be passed in as-is to gRPC client constructor
    grpcOptions:
      ssl-target-name-override: orderer0.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
    
      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    
    tlsCACerts:
      # Certificate location absolute path
      # Replace to orderer cert path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

  orderer1.example.com:
    url: grpcs://localhost:8050
    grpcOptions:
      ssl-target-name-override: orderer1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

  orderer2.example.com:
    url: grpcs://localhost:9050
    grpcOptions:
      ssl-target-name-override: orderer2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

#

# List of peers to send various requests to, including endorsement, query

# and event listener registration.

#
peers:
  peer0.org1.example.com:

    # this URL is used to send endorsement and query requests

    # [Optional] Default: Infer from hostname

    # 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接

    # url: grpcs://peer0.org1.example.com:7051

​    url: grpcs://localhost:7051

    grpcOptions:
      ssl-target-name-override: peer0.org1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
    
      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer1.org1.example.com:
    # this URL is used to send endorsement and query requests
    # [Optional] Default: Infer from hostname
    # 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
    # url: grpcs://peer0.org1.example.com:7051
    url: grpcs://localhost:8051
    grpcOptions:
      ssl-target-name-override: peer1.org1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer0.org2.example.com:
    # Replace the port
    url: grpcs://localhost:9051
    grpcOptions:
      ssl-target-name-override: peer0.org2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

  peer1.org2.example.com:
    # Replace the port
    url: grpcs://localhost:10051
    grpcOptions:
      ssl-target-name-override: peer1.org2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows

# certificate management to be done via REST APIs. Application may choose to use a standard

# Certificate Authority instead of Fabric-CA, in which case this section would not be specified.

#
certificateAuthorities:
  ca.org1.example.com:
    url: http://localhost:7054
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      #client:
      #  key:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
      #  cert:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
    # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
    # needed to enroll and invoke new users.
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org1.example.com

  ca.org2.example.com:
    url: http://localhost:8054
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      #client:
      #  key:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
      #  cert:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
    # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
    # needed to enroll and invoke new users.
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org2.example.com

entitymatchers:
  peer:
    - pattern: (\w*)peer0.org1.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:7051
      ssltargetoverrideurlsubstitutionexp: peer0.org1.example.com
      mappedhost: peer0.org1.example.com

    - pattern: (\w*)peer1.org1.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:8051
      ssltargetoverrideurlsubstitutionexp: peer1.org1.example.com
      mappedhost: peer1.org1.example.com
    
    - pattern: (\w*)peer0.org2.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:9051
      ssltargetoverrideurlsubstitutionexp: peer0.org2.example.com
      mappedhost: peer0.org2.example.com
    
    - pattern: (\w*)peer1.org2.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:10051
      ssltargetoverrideurlsubstitutionexp: peer1.org2.example.com
      mappedhost: peer1.org2.example.com

  orderer:
    - pattern: (\w*)orderer0.example.com(\w*)
      urlsubstitutionexp: localhost:7050
      ssltargetoverrideurlsubstitutionexp: orderer0.example.com
      mappedhost: orderer0.example.com
    - pattern: (\w*)orderer1.example.com(\w*)
      urlsubstitutionexp: localhost:8050
      ssltargetoverrideurlsubstitutionexp: orderer1.example.com
      mappedhost: orderer1.example.com
    - pattern: (\w*)orderer2.example.com(\w*)
      urlsubstitutionexp: localhost:9050
      ssltargetoverrideurlsubstitutionexp: orderer2.example.com
      mappedhost: orderer2.example.com

  certificateAuthorities:
    - pattern: (\w*)ca.org1.example.com(\w*)
      urlSubstitutionExp: http://localhost:7054
      mappedHost: ca.org1.example.com
    

    - pattern: (\w*)ca.org2.example.com(\w*)
      urlSubstitutionExp: http://localhost:8054
      mappedHost: ca.org2.example.com


创建org2_config.yaml的文件:

name: "org1-config"
#
# Copyright SecureKey Technologies Inc. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
#
# The network connection profile provides client applications the information about the target
# blockchain network that are necessary for the applications to interact with it. These are all
# knowledge that must be acquired from out-of-band sources. This file provides such a source.
#

# copied from fabric-sdk-go/test/fixtures/config/config_e2e_pkcs11.yaml

#
# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.
#
version: 1.0.0

#
# The client section used by GO SDK.
#
client:
  # Which organization does this application instance belong to? The value must be the name of an org
  # defined under "organizations"
  organization: Org2
  logging:
    # Develope can using debug to get more information
    #    level: debug
    level: info
  cryptoconfig:
    path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config
  # Some SDKs support pluggable KV stores, the properties under "credentialStore"
  # are implementation specific
  credentialStore:
    # [Optional]. Used by user store. Not needed if all credentials are embedded in configuration
    # and enrollments are performed elswhere.
    path: "/tmp/examplestore"


  # [Optional] BCCSP config for the client. Used by GO SDK.
  BCCSP:
    security:
      enabled: true
      default:
        provider: "SW"
      hashAlgorithm: "SHA2"
      softVerify: true
      level: 256

  tlsCerts:
    # [Optional]. Use system certificate pool when connecting to peers, orderers (for negotiating TLS) Default: false
    systemCertPool: true
    # [Optional]. Client key and cert for TLS handshake with peers and orderers
    client:
      # 使用User1@org2的证书
      keyfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/tls/client.key
      certfile: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/tls/client.cert



################################## General part ##################################


#
# [Optional]. But most apps would have this section so that channel objects can be constructed
# based on the content below. If an app is creating channels, then it likely will not need this
# section.
#
channels:
  # name of the channel
  mychannel:
    # Required. list of orderers designated by the application to use for transactions on this
    # channel. This list can be a result of access control ("org1" can only access "ordererA"), or
    # operational decisions to share loads from applications among the orderers.  The values must
    # be "names" of orgs defined under "organizations/peers"
    # deprecated: not recommended, to override any orderer configuration items, entity matchers should be used.
    #    orderers:
    #      - orderer.example.com

    # 不要缺少当前channel的orderer节点
    orderers:
      - orderer0.example.com
      - orderer1.example.com
      - orderer2.example.com

    # Required. list of peers from participating orgs
    peers:
      peer0.org1.example.com:
        # [Optional]. will this peer be sent transaction proposals for endorsement? The peer must
        # have the chaincode installed. The app can also use this property to decide which peers
        # to send the chaincode install request. Default: true
        endorsingPeer: true

        # [Optional]. will this peer be sent query proposals? The peer must have the chaincode
        # installed. The app can also use this property to decide which peers to send the
        # chaincode install request. Default: true
        chaincodeQuery: true

        # [Optional]. will this peer be sent query proposals that do not require chaincodes, like
        # queryBlock(), queryTransaction(), etc. Default: true
        ledgerQuery: true

        # [Optional]. will this peer be the target of the SDK's listener registration? All peers can
        # produce events but the app typically only needs to connect to one to listen to events.
        # Default: true
        eventSource: true

      # Add other peers in mychannel for byfn
      peer1.org1.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer0.org2.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.org2.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

    # [Optional]. The application can use these options to perform channel operations like retrieving channel
    # config etc.
    policies:
      #[Optional] options for retrieving channel configuration blocks
      queryChannelConfig:
        #[Optional] min number of success responses (from targets/peers)
        minResponses: 1
        #[Optional] channel config will be retrieved for these number of random targets
        maxTargets: 1
        #[Optional] retry options for query config block
        retryOpts:
          #[Optional] number of retry attempts
          attempts: 5
          #[Optional] the back off interval for the first retry attempt
          initialBackoff: 500ms
          #[Optional] the maximum back off interval for any retry attempt
          maxBackoff: 5s
          #[Optional] he factor by which the initial back off period is exponentially incremented
          backoffFactor: 2.0

#
# list of participating organizations in this network
#
organizations:
  Org1:
    mspid: Org1MSP
    # set msp files path
    cryptoPath: peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp
    # Add peers for org1
    peers:
      - peer0.org1.example.com
      - peer1.org1.example.com

    # [Optional]. Certificate Authorities issue certificates for identification purposes in a Fabric based
    # network. Typically certificates provisioning is done in a separate process outside of the
    # runtime network. Fabric-CA is a special certificate authority that provides a REST APIs for
    # dynamic certificate management (enroll, revoke, re-enroll). The following section is only for
    # Fabric-CA servers.
    certificateAuthorities:
      - ca.org1.example.com
    
    #users:
    #  Admin:
    #    cert:
    #      pem: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/tlsca/tlsca.org1.example.com-cert.pem



  # the profile will contain public information about organizations other than the one it belongs to.
  # These are necessary information to make transaction lifecycles work, including MSP IDs and
  # peers with a public URL to send transaction proposals. The file will not contain private
  # information reserved for members of the organization, such as admin key and certificate,
  # fabric-ca registrar enroll ID and secret, etc.
  Org2:
    mspid: Org2MSP
    cryptoPath: peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp

    # Add peers for org2
    peers:
      - peer0.org2.example.com
      - peer1.org2.example.com
    certificateAuthorities:
      - ca.org2.example.com


  # Orderer Org name
  ordererorg:
    # Membership Service Provider ID for this organization
    mspID: OrdererMSP
    cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp
    orderers:
      - orderer0.example.com
      - orderer1.example.com
      - orderer2.example.com


#
# List of orderers to send transaction and channel create/update requests to. For the time
# being only one orderer is needed. If more than one is defined, which one get used by the
# SDK is implementation specific. Consult each SDK's documentation for its handling of orderers.
#
orderers:
  orderer0.example.com:
    # [Optional] Default: Infer from hostname
    url: grpcs://localhost:7050

    # these are standard properties defined by the gRPC library
    # they will be passed in as-is to gRPC client constructor
    grpcOptions:
      ssl-target-name-override: orderer0.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      # Replace to orderer cert path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

  orderer1.example.com:
    url: grpcs://localhost:8050
    grpcOptions:
      ssl-target-name-override: orderer1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

  orderer2.example.com:
    url: grpcs://localhost:9050
    grpcOptions:
      ssl-target-name-override: orderer2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/ordererOrganizations/example.com/orderers/orderer2.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

#
# List of peers to send various requests to, including endorsement, query
# and event listener registration.
#
peers:
  peer0.org1.example.com:
    # this URL is used to send endorsement and query requests
    # [Optional] Default: Infer from hostname
    # 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
    # url: grpcs://peer0.org1.example.com:7051
    url: grpcs://localhost:7051

    grpcOptions:
      ssl-target-name-override: peer0.org1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer1.org1.example.com:
    # this URL is used to send endorsement and query requests
    # [Optional] Default: Infer from hostname
    # 表明使用grpcs协议,设置IP和端口号,使用域名会无法连接
    # url: grpcs://peer0.org1.example.com:7051
    url: grpcs://localhost:8051
    grpcOptions:
      ssl-target-name-override: peer1.org1.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer0.org2.example.com:
    # Replace the port
    url: grpcs://localhost:9051
    grpcOptions:
      ssl-target-name-override: peer0.org2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

  peer1.org2.example.com:
    # Replace the port
    url: grpcs://localhost:10051
    grpcOptions:
      ssl-target-name-override: peer1.org2.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false

      #will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      allow-insecure: false
    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

# Fabric-CA is a special kind of Certificate Authority provided by Hyperledger Fabric which allows
# certificate management to be done via REST APIs. Application may choose to use a standard
# Certificate Authority instead of Fabric-CA, in which case this section would not be specified.
#
certificateAuthorities:
  ca.org1.example.com:
    url: http://localhost:7054
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org1.example.com/ca/ca.org1.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      #client:
      #  key:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
      #  cert:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
    # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
    # needed to enroll and invoke new users.
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org1.example.com
  
  ca.org2.example.com:
    url: http://localhost:8054
    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/fixtures/crypto-config/peerOrganizations/org2.example.com/ca/ca.org2.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      #client:
      #  key:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
      #  cert:
      #    path: /home/alextan/blockchain/fabric/fabric-samples-1.4/raft-local-test/crypto-config/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt
    # Fabric-CA supports dynamic user enrollment via REST APIs. A "root" user, a.k.a registrar, is
    # needed to enroll and invoke new users.
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org2.example.com

entitymatchers:
  peer:
    - pattern: (\w*)peer0.org1.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:7051
      ssltargetoverrideurlsubstitutionexp: peer0.org1.example.com
      mappedhost: peer0.org1.example.com

    - pattern: (\w*)peer1.org1.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:8051
      ssltargetoverrideurlsubstitutionexp: peer1.org1.example.com
      mappedhost: peer1.org1.example.com

    - pattern: (\w*)peer0.org2.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:9051
      ssltargetoverrideurlsubstitutionexp: peer0.org2.example.com
      mappedhost: peer0.org2.example.com

    - pattern: (\w*)peer1.org2.example.com(\w*)
      urlsubstitutionexp: grpcs://localhost:10051
      ssltargetoverrideurlsubstitutionexp: peer1.org2.example.com
      mappedhost: peer1.org2.example.com

  orderer:
    - pattern: (\w*)orderer0.example.com(\w*)
      urlsubstitutionexp: localhost:7050
      ssltargetoverrideurlsubstitutionexp: orderer0.example.com
      mappedhost: orderer0.example.com
    - pattern: (\w*)orderer1.example.com(\w*)
      urlsubstitutionexp: localhost:8050
      ssltargetoverrideurlsubstitutionexp: orderer1.example.com
      mappedhost: orderer1.example.com
    - pattern: (\w*)orderer2.example.com(\w*)
      urlsubstitutionexp: localhost:9050
      ssltargetoverrideurlsubstitutionexp: orderer2.example.com
      mappedhost: orderer2.example.com

  certificateAuthorities:
    - pattern: (\w*)ca.org1.example.com(\w*)
      urlSubstitutionExp: http://localhost:7054
      mappedHost: ca.org1.example.com
    
    - pattern: (\w*)ca.org2.example.com(\w*)
      urlSubstitutionExp: http://localhost:8054
      mappedHost: ca.org2.example.com

六、SDK实现

链码写好后,我们使用Fabric-SDK-Go提供的API来实现对链码的安装及实例化操作。

6.1 创建SDK

创建目录cli

mkdir cli
cd cli
12

创建init.go文件

用于创建channel,加入channel:

/*
 * @Author: AlexTan
 * @GIthub: https://github.com/AlexTan-b-z
 * @Date: 2020-08-12 21:13:19
 * @LastEditors: AlexTan
 * @LastEditTime: 2020-08-12 22:07:26
 */

package cli

import (
	"log"

	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"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/providers/msp"
	mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
)

const (
	org1CfgPath = "../../sdkConfig/org1_config.yaml"
	org2CfgPath = "../../sdkConfig/org2_config.yaml"

	channelConfig = "../../fixtures/channel-artifacts/channel.tx"
	ordererID = "orderer0.example.com"
	ordererOrgName = "ordererorg"
	org1Name = "Org1"
	org2Name = "Org2"
	orgAdmin = "Admin"
	channelID = "mychannel"
)

func CreateChannel(){
	sdk1, err := fabsdk.New(config.FromFile(org1CfgPath))
	if err != nil {
		log.Panicf("failed to create fabric sdk1: %s", err)
	}
	sdk2, err := fabsdk.New(config.FromFile(org2CfgPath))
	if err != nil {
		log.Panicf("failed to create fabric sdk2: %s", err)
	}

	clientContext := sdk1.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(ordererOrgName))
	resMgmtClient, err := resmgmt.New(clientContext)
	if err != nil {
		log.Panicf("failed to create resMgmtClient in createChannel: %s", err)
	}

	mspClient, err := mspclient.New(sdk1.Context(), mspclient.WithOrg(org1Name))
	if err != nil {
		log.Panicf("failed to create msp client: %s", err)
	}
	adminIdentity, err := mspClient.GetSigningIdentity(orgAdmin)
	if err != nil {
		log.Panicf("failed to GetSigningIdentity: %s", err)
	}
	req := resmgmt.SaveChannelRequest{ChannelID: channelID,
		ChannelConfigPath: channelConfig,
		SigningIdentities: []msp.SigningIdentity{adminIdentity}}
		
	_, err = resMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID))
	if err != nil {
		log.Panicf("failed to GetSigningIdentity: %s", err)
	}
	log.Println("created fabric channel")

	// join Channel
	org1Context := sdk1.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(org1Name))
	org2Context := sdk2.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(org2Name))

	org1ResMgmt, err := resmgmt.New(org1Context)
	if err != nil {
		log.Panicf("failed to create org1ResMgmt: %s", err)
	}
	org2ResMgmt, err := resmgmt.New(org2Context)
	if err != nil {
		log.Panicf("failed to create org2ResMgmt: %s", err)
	}
	if err = org1ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID)); err != nil {
		log.Panicf("Org1 peers failed to JoinChannel: %s", err)
	}
	log.Println("org1 joined channel")
	if err = org2ResMgmt.JoinChannel(channelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint(ordererID)); err != nil {
		log.Panicf("Org2 peers failed to JoinChannel: %s", err)
	}
	log.Println("org2 joined channel")
}

创建client.go,用于创建客户端:

/*
 * @Author: AlexTan
 * @GIthub: https://github.com/AlexTan-b-z
 * @Date: 2020-08-08 17:00:23
 * @LastEditors: AlexTan
 * @LastEditTime: 2020-08-12 21:57:18
 */
package cli

import (
	"log"
	"os"

	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)

type Client struct {
	// Fabric network information
	ConfigPath string
	OrgName    string
	OrgAdmin   string
	OrgUser    string

	// sdk clients
	SDK *fabsdk.FabricSDK
	rc  *resmgmt.Client
	cc  *channel.Client

	// for create channel
	// ChannelConfig string
	// OrdererID string
	
	
	// Same for each peer
	ChannelID string
	CCID      string // chaincode ID, eq name
	CCPath    string // chaincode source path, 是GOPATH下的某个目录
	CCGoPath  string // GOPATH used for chaincode
}

func New(cfg, org, admin, user string) *Client {
	c := &Client{
		ConfigPath: cfg,
		OrgName:    org,
		OrgAdmin:   admin,
		OrgUser:    user,
		// ChannelConfig: "../fixtures/channel-artifacts/channel.tx",
		// OrdererID: "orderer0.example.com",
		CCID:      "mycc",
		CCPath:    "chaincode/", // 相对路径是从GOPAHT/src开始的
		CCGoPath:  os.Getenv("GOPATH"),
		ChannelID: "mychannel",

	}

	// create sdk
	sdk, err := fabsdk.New(config.FromFile(c.ConfigPath))
	if err != nil {
		log.Panicf("failed to create fabric sdk: %s", err)
	}
	c.SDK = sdk
	log.Println("Initialized fabric sdk")

	c.rc, c.cc = NewSdkClient(sdk, c.ChannelID, c.OrgName, c.OrgAdmin, c.OrgUser)

	return c
}

// create channel
// func CreateChannel(sdk *fabsdk.FabricSDK, orgName, orgAdmin, channelID, ChannelConfig)

// NewSdkClient create resource client and channel client
func NewSdkClient(sdk *fabsdk.FabricSDK, channelID, orgName, orgAdmin, OrgUser string) (rc *resmgmt.Client, cc *channel.Client) {
	var err error

	// create rc
	rcp := sdk.Context(fabsdk.WithUser(orgAdmin), fabsdk.WithOrg(orgName))
	rc, err = resmgmt.New(rcp)
	if err != nil {
		log.Panicf("failed to create resource client: %s", err)
	}
	log.Println("Initialized resource client")

	// create cc
	ccp := sdk.ChannelContext(channelID, fabsdk.WithUser(OrgUser))
	cc, err = channel.New(ccp)
	if err != nil {
		log.Panicf("failed to create channel client: %s", err)
	}
	log.Println("Initialized channel client")

	return rc, cc
}

// RegisterChaincodeEvent more easy than event client to registering chaincode event.
func (c *Client) RegisterChaincodeEvent(ccid, eventName string) (fab.Registration, <-chan *fab.CCEvent, error) {
	return c.cc.RegisterChaincodeEvent(ccid, eventName)
}

创建chaincode.go

用于链码安装,实例化链码操作:

6.2 创建执行链码相关操作的文件

回到项目根目录

cd .. 
mkdir samples
cd samples
mkdir chaincode
1234

samples/chaincode目录下创建文件main.go,用于运行链码相关操作:

/*
 * @Author: test1Tan
 * @GIthub: https://github.com/test1Tan-b-z
 * @Date: 2020-08-11 21:03:54
 * @LastEditors: AlexTan
 * @LastEditTime: 2020-08-19 20:20:44
 */
package main

import (
	"log"
	"time"

	"github.com/AlexTan-b-z/fabric-sdk-go-demo/cli"
	"github.com/AlexTan-b-z/fabric-sdk-go-demo/model"
)

const (
	org1CfgPath = "../../sdkConfig/org1_config.yaml"
	org2CfgPath = "../../sdkConfig/org2_config.yaml"
)

var (
	peer0Org1 = "peer0.org1.example.com"
	peer0Org2 = "peer0.org2.example.com"
)

func main() {

	// init
	// 第一次运行后记得注释掉
	cli.CreateChannel()

	org1Client := cli.New(org1CfgPath, "Org1", "Admin", "User1")
	org2Client := cli.New(org2CfgPath, "Org2", "Admin", "User1")

	defer org1Client.Close()
	defer org2Client.Close()

	// Install, instantiate, invoke, query
	Phase1(org1Client, org2Client)
	// Install, upgrade, invoke, query
	Phase2(org1Client, org2Client)
}

func Phase1(cli1, cli2 *cli.Client) {
	log.Println("=================== Phase 1 begin ===================")
	defer log.Println("=================== Phase 1 end ===================")

	if err := cli1.InstallCC("v1", peer0Org1); err != nil {
		log.Panicf("Intall chaincode error: %v", err)
	}
	log.Println("Chaincode has been installed on org1's peers")

	if err := cli2.InstallCC("v1", peer0Org2); err != nil {
		log.Panicf("Intall chaincode error: %v", err)
	}
	log.Println("Chaincode has been installed on org2's peers")

	// InstantiateCC chaincode only need once for each channel
	if _, err := cli1.InstantiateCC("v1", peer0Org1); err != nil {
		log.Panicf("Instantiated chaincode error: %v", err)
	}
	log.Println("Chaincode has been instantiated")

	sco1 := model.Score{
		Name: "test1",
		Gender: "男",
		StuID: "123",
		Grade: "2015",
		Result: "100",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}

	sco2 := model.Score{
		Name: "test2",
		Gender: "女",
		StuID: "1234",
		Grade: "2017",
		Result: "100",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}

	if _, err := cli1.InvokeCCadd([]string{peer0Org1}, sco1); err != nil {
		log.Panicf("InvokeCCadd test1 chaincode error: %v", err)
	}
	log.Println("InvokeCCadd test1 chaincode success 1")

	if _, err := cli1.InvokeCCadd([]string{peer0Org1}, sco2); err != nil {
		log.Panicf("InvokeCCadd test2 chaincode error: %v", err)
	}
	log.Println("InvokeCCadd test2 chaincode success 2")

	if err := cli1.QueryCCByNameAndGrade("peer0.org1.example.com", "test1", "2015"); err != nil {
		log.Panicf("QueryCCByNameAndGrade chaincode error: %v", err)
	}
	log.Println("QueryCCByNameAndGrade chaincode success on peer0.org1")

	if err := cli1.QueryCCByID("peer0.org1.example.com", "1234"); err != nil {
		log.Panicf("QueryCCByID chaincode error: %v", err)
	}
	log.Println("QueryCCByID chaincode success on peer0.org1")

	new_sco := model.Score{
		Name: "test1",
		Gender: "男",
		StuID: "123",
		Grade: "2015",
		Result: "99",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}
	if _, err := cli1.UpdateCCScore([]string{peer0Org1}, new_sco); err != nil {
		log.Panicf("updateCCScore chaincode error: %v", err)
	}
	log.Println("updateCCScore chaincode success 1")

	if err := cli1.QueryCCByID("peer0.org1.example.com", "123"); err != nil {
		log.Panicf("QueryCCByID chaincode error: %v", err)
	}
	log.Println("QueryCCByID chaincode success on peer0.org1")

	if _, err := cli1.InvokeCCDelete([]string{"peer0.org1.example.com"}, "123"); err != nil {
		log.Panicf("InvokeCCDelete chaincode error: %v", err)
	}
	log.Println("InvokeCCDelete chaincode success on peer0.org1")
}

func Phase2(cli1, cli2 *cli.Client) {
	log.Println("=================== Phase 2 begin ===================")
	defer log.Println("=================== Phase 2 end ===================")

	v := "v2"

	// Install new version chaincode
	if err := cli1.InstallCC(v, peer0Org1); err != nil {
		log.Panicf("Intall chaincode error: %v", err)
	}
	log.Println("Chaincode has been installed on org1's peers")

	if err := cli2.InstallCC(v, peer0Org2); err != nil {
		log.Panicf("Intall chaincode error: %v", err)
	}
	log.Println("Chaincode has been installed on org2's peers")

	// Upgrade chaincode only need once for each channel
	if err := cli1.UpgradeCC(v, peer0Org1); err != nil {
		log.Panicf("Upgrade chaincode error: %v", err)
	}
	log.Println("Upgrade chaincode success for channel")

	sco1 := model.Score{
		Name: "test3",
		Gender: "男",
		StuID: "12345",
		Grade: "2015",
		Result: "100",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}

	sco2 := model.Score{
		Name: "test4",
		Gender: "女",
		StuID: "123456",
		Grade: "2017",
		Result: "100",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}

	if _, err := cli1.InvokeCCadd([]string{"peer0.org1.example.com", "peer0.org2.example.com"}, sco1); err != nil {
		log.Panicf("InvokeCCadd test3 chaincode error: %v", err)
	}
	log.Println("InvokeCCadd test3 chaincode success 1")

	if _, err := cli1.InvokeCCadd([]string{peer0Org1, "peer0.org2.example.com"}, sco2); err != nil {
		log.Panicf("InvokeCCadd test4 chaincode error: %v", err)
	}
	log.Println("InvokeCCadd test4 chaincode success 2")

	if err := cli1.QueryCCByNameAndGrade("peer0.org2.example.com", "test3", "2015"); err != nil {
		log.Panicf("QueryCCByNameAndGrade chaincode error: %v", err)
	}
	log.Println("QueryCCByNameAndGrade chaincode success on peer0.org2")

	if err := cli1.QueryCCByID("peer0.org2.example.com", "12345"); err != nil {
		log.Panicf("QueryCCByID chaincode error: %v", err)
	}
	log.Println("QueryCCByID chaincode success on peer0.org2")

	new_sco := model.Score{
		Name: "test3",
		Gender: "男",
		StuID: "12345",
		Grade: "2015",
		Result: "99",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}
	if _, err := cli1.UpdateCCScore([]string{peer0Org1, "peer0.org2.example.com"}, new_sco); err != nil {
		log.Panicf("updateCCScore chaincode error: %v", err)
	}
	log.Println("updateCCScore chaincode success 1")

	if err := cli1.QueryCCByID("peer0.org2.example.com", "12345"); err != nil {
		log.Panicf("QueryCCByID chaincode error: %v", err)
	}
	log.Println("QueryCCByID chaincode success on peer0.org2")

	if _, err := cli1.InvokeCCDelete([]string{"peer0.org2.example.com", peer0Org1}, "12345"); err != nil {
		log.Panicf("InvokeCCDelete chaincode error: %v", err)
	}
	log.Println("InvokeCCDelete chaincode success on peer0.org2")
}

6.3 创建事件监听程序

回到前一个目录,创建event文件夹

cd ..
mkdir event
cd event
123

创建main.go,用于测试监听链码事件:

package main

import (
	"encoding/hex"
	"log"
	"time"

	"github.com/hyperledger/fabric-sdk-go/pkg/fab/events/deliverclient/seek"

	"github.com/hyperledger/fabric-sdk-go/pkg/client/event"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
	
	"github.com/AlexTan-b-z/fabric-sdk-go-demo/cli"
	"github.com/AlexTan-b-z/fabric-sdk-go-demo/model"
)

const (
	org1CfgPath = "../../sdkConfig/org1_config.yaml"
	org2CfgPath = "../../sdkConfig/org2_config.yaml"
)

var (
	peer0Org1 = "peer0.org1.example.com"
	peer0Org2 = "peer0.org2.example.com"
)

func main() {
	org1Client := cli.New(org1CfgPath, "Org1", "Admin", "User1")
	org2Client := cli.New(org2CfgPath, "Org2", "Admin", "User1")
	defer org1Client.Close()
	defer org2Client.Close()

	// New event client
	cp := org1Client.SDK.ChannelContext(org1Client.ChannelID, fabsdk.WithUser(org1Client.OrgUser))

	ec, err := event.New(
		cp,
		event.WithBlockEvents(), // 如果没有,会是filtered
		// event.WithBlockNum(1), // 从指定区块获取,需要此参数
		event.WithSeekType(seek.Newest))
	if err != nil {
		log.Printf("Create event client error: %v", err)
	}

	// block event listen
	defer ec.Unregister(blockListener(ec))
	defer ec.Unregister(filteredBlockListener(ec))

	// tx listen
	txIDCh := make(chan string, 100)
	go txListener(ec, txIDCh)

	// chaincode event listen
	defer ec.Unregister(chainCodeEventListener(nil, ec))

	DoChainCode(org1Client, txIDCh)
	close(txIDCh)

	time.Sleep(time.Second * 10)
}

func blockListener(ec *event.Client) fab.Registration {
	// Register monitor block event
	beReg, beCh, err := ec.RegisterBlockEvent()
	if err != nil {
		log.Printf("Register block event error: %v", err)
	}
	log.Println("Registered block event")

	// Receive block event
	go func() {
		for e := range beCh {
			log.Printf("Receive block event:\nSourceURL: %v\nNumber: %v\nHash"+
				": %v\nPreviousHash: %v\n\n",
				e.SourceURL,
				e.Block.Header.Number,
				hex.EncodeToString(e.Block.Header.DataHash),
				hex.EncodeToString(e.Block.Header.PreviousHash))
		}
	}()

	return beReg
}

func filteredBlockListener(ec *event.Client) fab.Registration {
	// Register monitor filtered block event
	fbeReg, fbeCh, err := ec.RegisterFilteredBlockEvent()
	if err != nil {
		log.Printf("Register filtered block event error: %v", err)
	}
	log.Println("Registered filtered block event")

	// Receive filtered block event
	go func() {
		for e := range fbeCh {
			log.Printf("Receive filterd block event:\nNumber: %v\nlen("+
				"transactions): %v\nSourceURL: %v",
				e.FilteredBlock.Number, len(e.FilteredBlock.
					FilteredTransactions), e.SourceURL)

			for i, tx := range e.FilteredBlock.FilteredTransactions {
				log.Printf("tx index %d: type: %v, txid: %v, "+
					"validation code: %v", i,
					tx.Type, tx.Txid,
					tx.TxValidationCode)
			}
			log.Println() // Just go print empty log for easy to read
		}
	}()

	return fbeReg
}

func txListener(ec *event.Client, txIDCh chan string) {
	log.Println("Transaction listener start")
	defer log.Println("Transaction listener exit")

	for id := range txIDCh {
		// Register monitor transaction event
		log.Printf("Register transaction event for: %v", id)
		txReg, txCh, err := ec.RegisterTxStatusEvent(id)
		if err != nil {
			log.Printf("Register transaction event error: %v", err)
			continue
		}
		defer ec.Unregister(txReg)

		// Receive transaction event
		go func() {
			for e := range txCh {
				log.Printf("Receive transaction event: txid: %v, "+
					"validation code: %v, block number: %v",
					e.TxID,
					e.TxValidationCode,
					e.BlockNumber)
			}
		}()
	}
}

func chainCodeEventListener(c *cli.Client, ec *event.Client) fab.Registration {
	eventName := ".*"
	log.Printf("Listen chaincode event: %v", eventName)

	var (
		ccReg   fab.Registration
		eventCh <-chan *fab.CCEvent
		err     error
	)
	if c != nil {
		log.Println("Using client to register chaincode event")
		ccReg, eventCh, err = c.RegisterChaincodeEvent("mycc", eventName)
	} else {
		log.Println("Using event client to register chaincode event")
		ccReg, eventCh, err = ec.RegisterChaincodeEvent("mycc", eventName)
	}
	if err != nil {
		log.Printf("Register chaincode event error: %v", err.Error())
		return nil
	}

	// consume event
	go func() {
		for e := range eventCh {
			log.Printf("Receive cc event, ccid: %v \neventName: %v\n"+
				"payload: %v \ntxid: %v \nblock: %v \nsourceURL: %v\n",
				e.ChaincodeID, e.EventName, string(e.Payload), e.TxID, e.BlockNumber, e.SourceURL)
		}
	}()

	return ccReg
}

// Install、Deploy、Invoke、Query、Upgrade
func DoChainCode(cli1 *cli.Client, txCh chan<- string) {
	var (
		txid fab.TransactionID
		err  error
	)

	// ccVersion := "v1"
	// if err := cli1.InstallCC(ccVersion, peer0Org1); err != nil {
	// 	log.Panicf("Intall chaincode error: %v", err)
	// }
	// log.Println("Chaincode has been installed on org1's peers")
	//
	// // InstantiateCC chaincode only need once for each channel
	// if txid, err = cli1.InstantiateCC(ccVersion, peer0Org1); err != nil {
	// 	log.Panicf("Instantiated chaincode error: %v", err)
	// }
	// if txid != "" {
	// 	txCh <- string(txid)
	// }
	// log.Println("Chaincode has been instantiated")
	
	sco3 := model.Score{
		Name: "eventTest",
		Gender: "男",
		StuID: "888888",
		Grade: "2018",
		Result: "100",
		Time: time.Now().Format("2006-01-02 15:04:05"),
	}
	if txid, err = cli1.InvokeCCadd([]string{peer0Org1, "peer0.org2.example.com"}, sco3); err != nil {
		log.Panicf("InvokeCCadd chaincode error: %v", err)
	}
	log.Println("InvokeCCadd chaincode success 1")
	if txid != "" {
		txCh <- string(txid)
	}
	log.Println("Invoke chaincode success")

	if err = cli1.QueryCCByID("peer0.org1.example.com", "888888"); err != nil {
		log.Panicf("Query chaincode error: %v", err)
	}
	log.Println("Query chaincode success on peer0.org1")

	if txid, err = cli1.InvokeCCDelete([]string{"peer0.org1.example.com", "peer0.org2.example.com"}, "888888"); err != nil {
		log.Panicf("InvokeCCDelete chaincode error: %v", err)
	}
}

七、测试运行

确保已经启动好容器环境后,分别运行samples/chaincode/main.go和samples/event/main.go即可完成测试。

其中包括以下操作:

  • 创建通道
  • 把org1、org2(所有节点)加入通道
  • 在指定节点安装链码
  • 实例化链码(配置背书策略)
  • 调用链码
  • 更新链码背书策略
  • 调用新的链码(新的背书策略)
  • 清理环境

清理环境

要清理运行环境,使用docker-compose将容器停掉并移除即可:

docker-compose -f docker-compose-local.yaml down -v
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值