fabric-sdk-go的使用——基于自己部署的节点(二)

作者: AlexTan
CSDN: http://blog.csdn.net/alextan_
Github: https://github.com/AlexTan-b-z
e-mail: alextanbz@gmail.com
操作系统:Ubuntu18.04

接着上一篇:Fabric1.4 Raft单机部署详解(一)

四、编写链码

创建chaincode文件夹:

cd ..
mkdir chaincode
cd chaincode

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

创建文件scoreStruct.go:

/*
 * @Author: AlexTan
 * @GIthub: https://github.com/AlexTan-b-z
 * @Date: 2020-08-04 17:44:15
 * @LastEditors: AlexTan
 * @LastEditTime: 2020-08-12 15:10:41
 */
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
}

5.2 编写链码

创建文件main.go:

/*
 * @Author: AlexTan
 * @GIthub: https://github.com/AlexTan-b-z
 * @Date: 2020-08-04 20:35:47
 * @LastEditors: AlexTan
 * @LastEditTime: 2020-08-12 22:18:49
 */
package main

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

/*
func stringToByte(args string) []byte{
	var as []byte
	for _, a := range args {
		as = append(as, []byte(a)...)
	}
	return as
}*/

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)
	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的状态(值)是什么。

2. 为什么要用CouchDB?

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

5.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"}

因为链码中有通过NameGrade两个字段来进行查询的函数。

五、SDK配置

创建配置目录:

cd .. # 回到主目录
mkdir sdkConfig

创建org1的配置文件org1_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: 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

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

创建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

创建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,用于链码安装,实例化链码操作:

package cli

import (
	"log"
	"net/http"
	"strings"
	"encoding/json"

	"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/fab/ccpackager/gopackager"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/cauthdsl"
	"github.com/hyperledger/fabric-protos-go/common"
	"github.com/pkg/errors"

	"scoreDapp/model"
)

// InstallCC install chaincode for target peer
func (c *Client) InstallCC(v string, peer string) error {
	targetPeer := resmgmt.WithTargetEndpoints(peer)

	// pack the chaincode
	ccPkg, err := gopackager.NewCCPackage(c.CCPath, c.CCGoPath)
	if err != nil {
		return errors.WithMessage(err, "pack chaincode error")
	}

	// new request of installing chaincode
	req := resmgmt.InstallCCRequest{
		Name:    c.CCID,
		Path:    c.CCPath,
		Version: v,
		Package: ccPkg,
	}

	resps, err := c.rc.InstallCC(req, targetPeer)
	if err != nil {
		return errors.WithMessage(err, "installCC error")
	}

	// check other errors
	var errs []error
	for _, resp := range resps {
		log.Printf("Install  response status: %v", resp.Status)
		if resp.Status != http.StatusOK {
			errs = append(errs, errors.New(resp.Info))
		}
		if resp.Info == "already installed" {
			log.Printf("Chaincode %s already installed on peer: %s.\n",
				c.CCID+"-"+v, resp.Target)
			return nil
		}
	}

	if len(errs) > 0 {
		log.Printf("InstallCC errors: %v", errs)
		return errors.WithMessage(errs[0], "installCC first error")
	}
	return nil
}

func (c *Client) InstantiateCC(v string, peer string) (fab.TransactionID,
	error) {
	// endorser policy
	org1OrOrg2 := "OR('Org1MSP.member','Org2MSP.member')"
	ccPolicy, err := c.genPolicy(org1OrOrg2)
	if err != nil {
		return "", errors.WithMessage(err, "gen policy from string error")
	}

	// new request
	// Attention: args should include `init` for Request not
	// have a method term to call init
	args := packArgs([]string{"init"})
	req := resmgmt.InstantiateCCRequest{
		Name:    c.CCID,
		Path:    c.CCPath,
		Version: v,
		Args:    args,
		Policy:  ccPolicy,
	}

	// send request and handle response
	reqPeers := resmgmt.WithTargetEndpoints(peer)
	resp, err := c.rc.InstantiateCC(c.ChannelID, req, reqPeers)
	if err != nil {
		if strings.Contains(err.Error(), "already exists") {
			return "", nil
		}
		return "", errors.WithMessage(err, "instantiate chaincode error")
	}

	log.Printf("Instantitate chaincode tx: %s", resp.TransactionID)
	return resp.TransactionID, nil
}

func (c *Client) genPolicy(p string) (*common.SignaturePolicyEnvelope, error) {
	// TODO bug, this any leads to endorser invalid
	if p == "ANY" {
		return cauthdsl.SignedByAnyMember([]string{c.OrgName}), nil
	}
	return cauthdsl.FromString(p)
}

func (c *Client) InvokeCCadd(peers []string, sco model.Score) (fab.TransactionID, error) {
	//将sco序列化成字节数组
	sco_b, err := json.Marshal(sco)
	if err != nil {
		log.Printf("指定的Score对象序列化时发生错误: %v", err)
		return "", err
	}

	// new channel request for invoke
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "addScore",
		Args:        [][]byte{sco_b},
	}

	// send request and handle response
	// peers is needed
	reqPeers := channel.WithTargetEndpoints(peers...)
	resp, err := c.cc.Execute(req, reqPeers)
	log.Printf("Invoke chaincode response:\n"+
		"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
		resp.TransactionID,
		resp.TxValidationCode,
		resp.ChaincodeStatus)
	if err != nil {
		return "", errors.WithMessage(err, "invoke chaincode error")
	}

	return resp.TransactionID, nil
}

func (c *Client) QueryCCByNameAndGrade(peer, name string, grade string) error {
	// new channel request for query
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "queryScoreByNameAndGrade",
		Args:        packArgs([]string{name, grade}),
	}

	// send request and handle response
	reqPeers := channel.WithTargetEndpoints(peer)
	resp, err := c.cc.Query(req, reqPeers)
	if err != nil {
		return errors.WithMessage(err, "query chaincode error")
	}

	log.Printf("Query chaincode tx response:\ntx: %s\nresult: %v\n\n",
		resp.TransactionID,
		string(resp.Payload))
	return nil
}

func (c *Client) QueryCCByID(peer, StuID string) error {
	// new channel request for query
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "queryScoreDetailByStuID",
		Args:        packArgs([]string{StuID}),
	}

	// send request and handle response
	reqPeers := channel.WithTargetEndpoints(peer)
	resp, err := c.cc.Query(req, reqPeers)
	if err != nil {
		return errors.WithMessage(err, "query chaincode error")
	}

	log.Printf("Query chaincode tx response:\ntx: %s\nresult: %v\n\n",
		resp.TransactionID,
		string(resp.Payload))
	return nil
}

func (c *Client) UpdateCCScore(peers []string, sco model.Score) (fab.TransactionID, error) {
	//将sco序列化成字节数组
	sco_b, err := json.Marshal(sco)
	if err != nil {
		log.Printf("指定的Score对象序列化时发生错误: %v", err)
		return "", err
	}

	// new channel request for invoke
	req := channel.Request{
		ChaincodeID: "mycc",
		Fcn:         "updateScore",
		Args:        [][]byte{sco_b},
	}

	// send request and handle response
	// peers is needed
	reqPeers := channel.WithTargetEndpoints(peers...)
	resp, err := c.cc.Execute(req, reqPeers)
	log.Printf("Invoke chaincode response:\n"+
		"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
		resp.TransactionID,
		resp.TxValidationCode,
		resp.ChaincodeStatus)
	if err != nil {
		return "", errors.WithMessage(err, "invoke chaincode error")
	}

	return resp.TransactionID, nil
}

func (c *Client) InvokeCCDelete(peers []string, stuID string) (fab.TransactionID, error) {
	log.Println("Invoke delete")
	// new channel request for invoke
	req := channel.Request{
		ChaincodeID: c.CCID,
		Fcn:         "delScore",
		Args:        packArgs([]string{stuID}),
	}

	// send request and handle response
	// peers is needed
	reqPeers := channel.WithTargetEndpoints(peers...)
	resp, err := c.cc.Execute(req, reqPeers)
	log.Printf("Invoke chaincode delete response:\n"+
		"id: %v\nvalidate: %v\nchaincode status: %v\n\n",
		resp.TransactionID,
		resp.TxValidationCode,
		resp.ChaincodeStatus)
	if err != nil {
		return "", errors.WithMessage(err, "invoke chaincode error")
	}

	return resp.TransactionID, nil
}

func (c *Client) UpgradeCC(v string, peer string) error {
	// endorser policy
	org1AndOrg2 := "AND('Org1MSP.member','Org2MSP.member')"
	ccPolicy, err := c.genPolicy(org1AndOrg2)
	if err != nil {
		return errors.WithMessage(err, "gen policy from string error")
	}

	// new request
	// Attention: args should include `init` for Request not
	// have a method term to call init
	// Reset a b's value to test the upgrade
	args := packArgs([]string{"init"})
	req := resmgmt.UpgradeCCRequest{
		Name:    c.CCID,
		Path:    c.CCPath,
		Version: v,
		Args:    args,
		Policy:  ccPolicy,
	}

	// send request and handle response
	reqPeers := resmgmt.WithTargetEndpoints(peer)
	resp, err := c.rc.UpgradeCC(c.ChannelID, req, reqPeers)
	if err != nil {
		return errors.WithMessage(err, "instantiate chaincode error")
	}

	log.Printf("Instantitate chaincode tx: %s", resp.TransactionID)
	return nil
}

func (c *Client) QueryCCInfo(v string, peer string) {

}

func (c *Client) Close() {
	c.SDK.Close()
}

func packArgs(paras []string) [][]byte {
	var args [][]byte
	for _, k := range paras {
		args = append(args, []byte(k))
	}
	return args
}

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

回到项目根目录

cd .. 
mkdir samples
cd samples
mkdir chaincode

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

创建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.gosamples/event/main.go即可完成测试。

其中包括以下操作:

  • 创建通道
  • org1org2(所有节点)加入通道
  • 在指定节点安装链码
  • 实例化链码(配置背书策略)
  • 调用链码
  • 更新链码背书策略
  • 调用新的链码(新的背书策略)

清理环境

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

docker-compose -f docker-compose-local.yaml down -v

完整文件已发github: fabric-sdk-go-demo, 欢迎Star

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值