fabric-sdk-go

项目地址:https://github.com/sxguan/fabric-go-sdk

一、fabric-sdk-go概述

软件开发工具包(外语首字母缩写:SDK、外语全称:Software Development Kit)一般都是一些软件工程师为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件时的开发工具的集合。软件开发工具包括广义上指辅助开发某一类软件的相关文档、范例和工具的集合。软件开发工具包是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等创建应用软件的开发工具的集合,一般而言SDK即开发 Windows 平台下的应用程序所使用的 SDK。它可以简单的为某个程序设计语言提供应用程序接口 API 的一些文件,但也可能包括能与某种嵌入式系统通讯的复杂的硬件。一般的工具包括用于调试和其他用途的实用工具。SDK 还经常包括示例代码、支持性的技术注解或者其他的为基本参考资料澄清疑点的支持文档。为了鼓励开发者使用其系统或者语言,许多 SDK 是免费提供的。

Fabric的Peer节点和Orderer节点都提供了基于GRPC协议(Google开发的远程过程调用RPC)的接口,通过这些接口可以和Peer节点与Orderer节点进行命令/数据交互,为了简化开发,官方提供了多语言版本的SDK。目前主流支持的已经有Go版本了,列出主流的三个:

  • Fabric Nodejs SDK

  • Fabric Java SDK

  • Fabric Go SDK

Golang是Fabric原生的开发语言,Fabric,Fabric-ca,Chaincode都是采用Golang开发的,所以我们围绕Golang版本的Fabric SDK来介绍。

fabric-go-sdk官方网址为https://github.com/hyperledger/fabric-sdk-go,其目录结构如下:

其中pkg目录是fabric-go-sdk的主要实现,internel目录和third_party目录包含了fabric-go-sdk依赖的一些代码。

pkg目录包含了以下这些软件包:

基本工作流程:

  • 使用配置实例化fabsdk实例。 注意:fabsdk维护高速缓存,因此您应最小化fabsdk本身的实例。

  • 使用fabsdk实例基于用户和组织创建上下文。 注意:通道上下文还需要通道ID。

  • 使用其New函数创建一个客户端实例,并传递上下文。 注意:您需要为每个上下文创建一个新的客户端实例。

  • 使用每个客户提供的功能来创建解决方案。

  • 调用fabsdk.Close()释放资源和缓存。

二、准备网络环境

在这个项目中我们构建一个有一个排序节点以及两个组织每个组织有一个节点的超级账本网络。

1. 准备证书文件

$GOPATH/src目录下创建一个名为fabric-sdk-go的文件夹做为项目根目录,在此目录下创建名为fixtures的文件夹存放我们网络相关配置文件,在文件夹中创建一个名为crypto-config.yaml的文件并编辑如下:

OrdererOrgs:
  - Name: Orderer
    Domain: example.com
    EnableNodeOUs: true
    Specs:
      - Hostname: orderer

PeerOrgs:
  - Name: Org1
    Domain: org1.example.com
    EnableNodeOUs: true
    Template:
      Count: 1
    Users:
      Count: 1
      
  - Name: Org2
    Domain: org2.example.com
    EnableNodeOUs: true
    Template:
      Count: 1
    Users:
      Count: 1

使用以下命令生成证书文件:

cryptogen generate --config=crypto-config.yaml

2. 准备通道文件

fixtures路径下创建一个名为configtx.yaml的文件并编辑如下:

---

Organizations:

    - &OrdererOrg
 
        Name: OrdererOrg
        ID: OrdererMSP
        MSPDir: crypto-config/ordererOrganizations/example.com/msp
        Policies:
            Readers:
                Type: Signature
                Rule: "OR('OrdererMSP.member')"
            Writers:
                Type: Signature
                Rule: "OR('OrdererMSP.member')"
            Admins:
                Type: Signature
                Rule: "OR('OrdererMSP.admin')"
        OrdererEndpoints:
            - orderer.example.com:7050

    - &Org1

        Name: Org1MSP    
        ID: Org1MSP
        MSPDir: crypto-config/peerOrganizations/org1.example.com/msp
        Policies:
            Readers:
                Type: Signature
                Rule: "OR('Org1MSP.member')"
            Writers:
                Type: Signature
                Rule: "OR('Org1MSP.member')"
            Admins:
                Type: Signature
                Rule: "OR('Org1MSP.admin')"
            Endorsement:
                Type: Signature
                Rule: "OR('Org1MSP.member')"

        AnchorPeers:
            - Host: peer0.org1.example.com
              Port: 7051

    - &Org2
        Name: Org2MSP
        ID: Org2MSP
        MSPDir: crypto-config/peerOrganizations/org2.example.com/msp
        Policies:
            Readers:
                Type: Signature
                Rule: "OR('Org2MSP.member')"
            Writers:
                Type: Signature
                Rule: "OR('Org2MSP.member')"
            Admins:
                Type: Signature
                Rule: "OR('Org2MSP.admin')"
            Endorsement:
                Type: Signature
                Rule: "OR('Org2MSP.member')"
        AnchorPeers:
            - Host: peer0.org2.example.com
              Port: 9051

Capabilities: 
    Channel: &ChannelCapabilities    
        V2_0: true 
    Orderer: &OrdererCapabilities
      
        V2_0: true   
    Application: &ApplicationCapabilities      
        V2_0: true

Application: &ApplicationDefaults

    Organizations:
        - *Org1
        - *Org2
    Policies:
        Readers:
            Type: ImplicitMeta
            Rule: "ANY Readers"
        Writers:
            Type: ImplicitMeta
            Rule: "ANY Writers"
        Admins:
            Type: ImplicitMeta
            Rule: "MAJORITY Admins"
        LifecycleEndorsement:
            Type: Signature
            Rule: "OR('Org1MSP.member','Org2MSP.member')"
        Endorsement:
            Type: Signature
            Rule: "OR('Org1MSP.member','Org2MSP.member')"
    Capabilities:
        <<: *ApplicationCapabilities

Orderer: &OrdererDefaults

    OrdererType: solo
    Addresses:
        - orderer.example.com:7050
    BatchTimeout: 2s
    BatchSize:
        MaxMessageCount: 10
        AbsoluteMaxBytes: 99 MB
        PreferredMaxBytes: 512 KB
    Organizations:
    Policies:
        Readers:
            Type: ImplicitMeta
            Rule: "ANY Readers"
        Writers:
            Type: ImplicitMeta
            Rule: "ANY Writers"
        Admins:
            Type: ImplicitMeta
            Rule: "MAJORITY Admins"
        BlockValidation:
            Type: ImplicitMeta
            Rule: "ANY Writers"


Channel: &ChannelDefaults
    Policies:
        Readers:
            Type: ImplicitMeta
            Rule: "ANY Readers"
        Writers:
            Type: ImplicitMeta
            Rule: "ANY Writers"
        Admins:
            Type: ImplicitMeta
            Rule: "MAJORITY Admins"
    Capabilities:
        <<: *ChannelCapabilities

Profiles:
    TwoOrgsOrdererGenesis:
        <<: *ChannelDefaults
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
            Capabilities:
                <<: *OrdererCapabilities
        Consortiums:
            SampleConsortium:
                Organizations:
                    - *Org1
                    - *Org2

    TwoOrgsChannel:
        Consortium: SampleConsortium
        <<: *ChannelDefaults
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2
            Capabilities:
                <<: *ApplicationCapabilities

生成创始块文件,使用以下命令在当前目录下的channel-artifacts目录下得到一个文件genesis.block

configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block -channelID fabric-channel

生成通道文件,使用以下命令在当前目录下的channel-artifacts目录下得到一个文件channel.tx

configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel

生成组织一锚节点更新文件,使用以下命令在当前目录下的channel-artifacts目录下得到一个文件Org1MSPanchors.tx

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

生成组织二锚节点更新文件,使用以下命令在当前目录下的channel-artifacts目录下得到一个文件Org2MSPanchors.tx

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

使用ls命令查看刚刚生成的文件。

ls channel-artifacts
channel.tx  Org1MSPanchors.tx  Org1MSPanchors.tx  genesis.block

3. 准备docker-compose文件

fixtures路径下创建一个名为docker-compose.yaml的文件并编辑如下:

version: '2'

volumes:
  orderer.example.com:
  peer0.org1.example.com:
  peer0.org2.example.com:
  
networks:
  test:

services:

  orderer.example.com:
    container_name: orderer.example.com
    image: hyperledger/fabric-orderer:2.2
    environment:
      - FABRIC_LOGGING_SPEC=DEBUG
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_LISTENPORT=7050
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block
      - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
      - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
      # enabled TLS
      - ORDERER_GENERAL_TLS_ENABLED=true
      - ORDERER_GENERAL_TLS_PRIVATEKEY=/var/hyperledger/orderer/tls/server.key
      - ORDERER_GENERAL_TLS_CERTIFICATE=/var/hyperledger/orderer/tls/server.crt
      - ORDERER_GENERAL_TLS_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
#      - ORDERER_KAFKA_TOPIC_REPLICATIONFACTOR=1
#      - ORDERER_KAFKA_VERBOSE=true
#      - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/var/hyperledger/orderer/tls/server.crt
#      - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/var/hyperledger/orderer/tls/server.key
#      - ORDERER_GENERAL_CLUSTER_ROOTCAS=[/var/hyperledger/orderer/tls/ca.crt]
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    command: orderer
    volumes:
        - ./channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block
        - ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp:/var/hyperledger/orderer/msp
        - ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/:/var/hyperledger/orderer/tls
        - orderer.example.com:/var/hyperledger/production/orderer
    ports:
      - 7050:7050
    networks:
      - test

  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer:2.2
    environment:
      #Generic peer variables
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      # the following setting starts chaincode containers on the same
      # bridge network as the peers
      # https://docs.docker.com/compose/networking/
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fixtures_test
      - FABRIC_LOGGING_SPEC=DEBUG
      #- FABRIC_LOGGING_SPEC=DEBUG
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
      # Peer specific variabes
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_LISTENADDRESS=0.0.0.0:7051
      - CORE_PEER_CHAINCODEADDRESS=peer0.org1.example.com:7052
      - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
      - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.example.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
 
    volumes:
        - /var/run/:/host/var/run/
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/fabric/msp
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls:/etc/hyperledger/fabric/tls
        - peer0.org1.example.com:/var/hyperledger/production
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    ports:
      - 7051:7051
    depends_on:
      - orderer.example.com
      #- couchdb.org1.example.com
    networks:
      - test

  peer0.org2.example.com:
    container_name: peer0.org2.example.com
    image: hyperledger/fabric-peer:2.2
    environment:
      #Generic peer variables
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      # the following setting starts chaincode containers on the same
      # bridge network as the peers
      # https://docs.docker.com/compose/networking/
      - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fixtures_test
      - FABRIC_LOGGING_SPEC=DEBUG
      #- FABRIC_LOGGING_SPEC=DEBUG
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
      # Peer specific variabes
      - CORE_PEER_ID=peer0.org2.example.com
      - CORE_PEER_ADDRESS=peer0.org2.example.com:9051
      - CORE_PEER_LISTENADDRESS=0.0.0.0:9051
      - CORE_PEER_CHAINCODEADDRESS=peer0.org2.example.com:9052
      - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:9052
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:9051
      - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:9051
      - CORE_PEER_LOCALMSPID=Org2MSP
    volumes:
      - /var/run/:/host/var/run/
      - ./crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp:/etc/hyperledger/fabric/msp
      - ./crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls:/etc/hyperledger/fabric/tls
      - peer0.org2.example.com:/var/hyperledger/production
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    ports:
      - 9051:9051
    networks:
      - test


  ca.org1.example.com:
    image: hyperledger/fabric-ca:1.4.9
    container_name: ca.org1.example.com
    environment:
      - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
      - FABRIC_CA_SERVER_CA_NAME=ca.org1.example.com
      - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem
      - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/priv_sk
      - FABRIC_CA_SERVER_TLS_ENABLED=true
      - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem
      - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-config/priv_sk
    ports:
      - 7054:7054
    command: sh -c 'fabric-ca-server start -b admin:adminpw -d'
    volumes:
      - ./crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config
    networks:
      - test

三、配置文件config.yaml

客户端使用sdk与fabric网络交互,需要告诉sdk两类信息:

  • 我是谁:即当前客户端的信息,包含所属组织、密钥和证书文件的路径等, 这是每个客户端专用的信息。

  • 对方是谁:即fabric网络结构的信息,channel、org、orderer和peer等 的怎么组合起当前fabric网络的,这些结构信息应当与configytx.yaml中是一致的。这是通用配置,每个客户端都可以拿来使用。另外,这部分信息并不需要是完整fabric网络信息,如果当前客户端只和部分节点交互,那配置文件中只需要包含所使用到的网络信息。

下面是详细的配置文件用法:

1. 声明

#
# 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.
# 网络连接配置文件为客户端应用程序提供目标区块链网络的信息,这是应用程序与之交互所必须的。
# 这些都是必须从带外源获得的信息。该文件提供了这样的源代码。

#
# Schema version of the content. Used by the SDK to apply the corresponding parsing rules.
# 内容的模式版本。SDK用于应用相应的解析规则。
version: 1.0.0

2. 客户端部分

#
# The client section used by GO SDK.
# 用于GO SDK的客户端部分。
client:

  # Which organization does this application instance belong to? The value must be the name of an org
  # defined under "organizations"
  # 这个应用程序实例属于哪个组织?这个值必须是定义在"organizations"下的一个组织的名称。
  
  organization: org1

  logging:
    level: info

  # Global configuration for peer, event service and orderer timeouts
  # if this this section is omitted, then default values will be used (same values as below)
  # 对等节点,事件服务和排序节点超时的全局配置。
  # 如果这个部分省略,将会使用默认值(默认值如下所示)。
#  peer:
#    timeout:
#      connection: 10s
#      response: 180s
#      discovery:
#        # Expiry period for discovery service greylist filter
#        # The channel client will greylist peers that are found to be offline
#        # to prevent re-selecting them in subsequent retries.
#        # This interval will define how long a peer is greylisted
		 # 发现服务灰名单过滤器的过期时间
		 # 通道客户端需要将掉线的对等节点列为灰名单以阻止重复选择并重试。
		 # 这个间隔时间将定义多长时间将对等节点列为灰名单。
		 
#        greylistExpiry: 10s

    # the below timeouts are commented out to use the default values that are found in
    # "pkg/fab/endpointconfig.go"
    # the client is free to override the default values by uncommenting and resetting
    # the values as they see fit in their config file
    # 下面的超时时间被注释掉以使用默认值,默认值可以在"pkg/fab/endpointconfig.go"中找到
    # 客户端可以根据配置文件的需要取消注释并重新设置这些值,从而覆盖默认值
#    timeout:
#      connection: 15s
#      registrationResponse: 15s
#  orderer:
#    timeout:
#      connection: 15s
#      response: 15s
#  global:
#    timeout:
#      query: 180s
#      execute: 180s
#      resmgmt: 180s
#    cache:
#      connectionIdle: 30s
#      eventServiceIdle: 2m
#      channelConfig: 30m
#      channelMembership: 30s
#      discovery: 10s
#      selection: 10m

  # Root of the MSP directories with keys and certs.
  # 根MSP的keys和certs路径
  cryptoconfig:
    path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}

  # Some SDKs support pluggable KV stores, the properties under "credentialStore"
  # are implementation specific
  # 一些SDK支持可插拔的KV存储,"credentialStore"下的属性是具体实现
  credentialStore:
    # [Optional]. Used by user store. Not needed if all credentials are embedded in configuration
    # and enrollments are performed elsewhere.
    # [可选的] 用于用户存储。如果所有凭证都嵌入到配置中,并且在其他地方执行登记,则不需要。
    path: "/tmp/state-store"

    # [Optional]. Specific to the CryptoSuite implementation used by GO SDK. Software-based implementations
    # requiring a key store. PKCS#11 based implementations does not.
    # [可选的] 特定于GO SDK使用的CryptoSuite实现。基于软件的实现需要密钥存储区。基于PKCS#11的实现则不用。
    cryptoStore:
      # Specific to the underlying KeyValueStore that backs the crypto key store.
      # 特定于支持加密密钥存储的基础KeyValueStore。
      
      path: /tmp/msp

  # [Optional] BCCSP config for the client. Used by GO SDK.
  # [可选的] 客户端的BCCSP配置。用于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
    # [可选的] 当连接到对等节点、排序节点(用于协商TLS)时使用系统证书池。默认值:false
    
    systemCertPool: true

    # [Optional]. Client key and cert for TLS handshake with peers and orderers
    # [可选的] 与对等节点和排序节点进行TLS握手的客户端密钥和证书
    client:
      key:
        path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
      cert:
        path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.crt

#
# [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.
# [可选的] 但是大多数应用程序都会有此部分,以便可以根据以下内容构造通道对象。
# 如果某个应用正在创建频道,则可能不需要此部分。

3. 通道部分

#
# [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:

  # Default channel is used if channel configuration is missing or if defined channel configuration is missing info
  # If channel doesn't define peers then peers from default channel will be used
  # If channel doesn't define orderes then orderes from default channel will be used
  # If channel doesn't define policies then policies from default channel will be used.
  # Also, if channel defines policies and some policy info is missing than that missing info will be filled from default channel.
  # 如果缺少通道配置或定义的通道配置缺少信息,则使用默认通道配置
  # 如果通道未定义对等节点,则将使用默认通道中的对等节点
  # 如果通道未定义排序节点,则将使用默认通道的排序节点
  # 如果通道未定义策略,则将使用默认通道中的策略。
  # 此外,如果通道定义了策略,并且缺少某些策略信息,那么该缺失信息将从默认通道中填充。
  _default:

    # [Optional]. 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
        # 这个对等节点是否会被发送交易建议以供背书?对等端必须安装了chaincode。
        # 应用程序也可以使用这个属性来决定哪个对等节点发送chaincode安装请求。默认值: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
        # 这个对等节点会被发送查询建议吗?对等端必须安装了chaincode。
        # 应用程序也可以使用这个属性来决定哪个对等节点发送chaincode安装请求。默认值:true
        chaincodeQuery: true

        # [Optional]. will this peer be sent query proposals that do not require chaincodes, like
        # queryBlock(), queryTransaction(), etc. Default: true
        # 这个对等节点是否会被发送不需要链码的查询建议,如queryBlock(), queryTransaction()等。默认值: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
        # 这个对等节点会成为SDK侦听器注册的目标吗?所有对等节点都可以产生事件,但应用程序通常只需要连接到一个对等节点就可以监听事件。
		# 默认:真
        eventSource: true

    # [Optional]. The application can use these options to perform channel operations like retrieving channel
    # config etc.
    # 应用程序可以使用这些选项来执行通道操作,如检索通道配置等。
    policies:
      #[Optional] options for retrieving discovery info
      # 检索发现信息的选项
      discovery:
        #[Optional] discovery info will be retrieved for these number of random targets
        maxTargets: 2
        #[Optional] retry options for retriving discovery info
        # 发现信息将为这些数目的随机目标检索
        retryOpts:
          #[Optional] number of retry attempts
          # 重试次数
          attempts: 4
          #[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
      #[Optional] options for selection service
      # 选择服务选项
      selection:
        #[Optional] endorser selection sorting strategy. Possible values: [BlockHeightPriority,Balanced]
        # 背书者选择排序策略
        SortingStrategy: BlockHeightPriority
        #[Optional] load-balancer type. Possible values: [RoundRobin,Random]
        # 负载平衡器类型
        Balancer: RoundRobin
        #[Optional] the number of blocks from the highest block number of a group of peers that a peer
        #can lag behind and still be considered to be up-to-date. These peers will be sorted using the
        #given Balancer. If a peer's block height falls behind this threshold then it will be demoted
        #to a lower priority list of peers which will be sorted according to block height.
        #Note: This property only applies to BlockHeightPriority sorter.
        # 在一组对等节点中,一个对等节点可以落后但仍然被认为是最新的最大块数。
        # 这些对等节点将使用给定的平衡器进行排序。
        # 如果一个对等节点的块高度低于这个阈值,那么它将被降级到一个较低优先级的对等节点列表中,该列表将根据块高度进行排序。
		# 注意:此属性仅适用于BlockHeightPriority排序器。
        BlockHeightLagThreshold: 5
      #[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

      #[Optional] options for the event service
      # 事件服务的选项
      eventService:
        # [Optional] resolverStrategy specifies the peer resolver strategy to use when connecting to a peer
        # resolverStrategy指定连接到对等节点时使用的对等解析器策略
        # Possible values: [PreferOrg (default), MinBlockHeight, Balanced]
        #
        # PreferOrg:
        #   Determines which peers are suitable based on block height lag threshold, although will prefer the peers in the
        #   current org (as long as their block height is above a configured threshold). If none of the peers from the current org
        #   are suitable then a peer from another org is chosen.
        # 根据块高度滞后阈值确定哪些对等节点是合适的,尽管会更喜欢当前org中的对等节点(只要它们的块高度高于配置的阈值)。
        # 如果当前组织的对等节点中没有一个是合适的,则选择另一个组织的对等节点。
        # MinBlockHeight:
        #   Chooses the best peer according to a block height lag threshold. The maximum block height of all peers is
        #   determined and the peers whose block heights are under the maximum height but above a provided "lag" threshold are load
        #   balanced. The other peers are not considered.
        # 根据块高度延迟阈值选择最佳的对等节点。所有对等节点的最大块高度被确定,并且其块高度低于最大高度但高于提供的“滞后”阈值的对等节点被负载平衡。
        # 其他同行不考虑。
        # Balanced:
        #   Chooses peers using the configured balancer.
        # 使用配置的均衡器选择对等节点。
        resolverStrategy: PreferOrg

        # [Optional] balancer is the balancer to use when choosing a peer to connect to
        # 平衡器是在选择要连接的对等点时使用的平衡器
        # Possible values: [Random (default), RoundRobin]
        balancer: RoundRobin

        # [Optional] blockHeightLagThreshold sets the block height lag threshold. This value is used for choosing a peer
        # to connect to. If a peer is lagging behind the most up-to-date peer by more than the given number of
        # blocks then it will be excluded from selection.
        # Note that this parameter is applicable only when minBlockHeightResolverMode is set to ResolveByThreshold.
        # blockHeightLagThreshold块高度滞后阈值。此值用于选择要连接的对等点。如果一个对等节点落后于最新的对等点超过给定的块数,那么它将被排除在选择之外。
		# 需要注意的是,当minBlockHeightResolverMode设置为ResolveByThreshold时,此参数才会生效
        # Default: 5
        blockHeightLagThreshold: 2

        # [Optional] reconnectBlockHeightLagThreshold - the event client will disconnect from the peer if the peer's
        # block height falls behind the specified number of blocks and will reconnect to a better performing peer.
        # Note that this parameter is only applicable if peerMonitor is set to Enabled (default).
        # reconnectBlockHeightLagThreshold—如果对等节点的块高度低于指定的块数量,事件客户端将断开与对等节点的连接,并重新连接到性能更好的对等节点。
		# 注意:只有当“peerMonitor”设置为“Enabled”(默认)时,此参数才有效。
        # Default: 10
        # NOTES:
        #   - Setting this value too low may cause the event client to disconnect/reconnect too frequently, thereby
        #     affecting performance.
        reconnectBlockHeightLagThreshold: 5

        # [Optional] peerMonitorPeriod is the period in which the connected peer is monitored to see if
        # the event client should disconnect from it and reconnect to another peer.
        # peerMonitorPeriod是监控已连接的对等节点的周期,以查看事件客户端是否应该断开连接并重新连接到另一个对等节点。
        # Default: 0 (disabled) for Balanced resolverStrategy; 5s for PreferOrg and MinBlockHeight strategy
        peerMonitorPeriod: 3s

  # Mychannel overrides initialBackoff for discovery
  # Mychannel覆盖发现的initialBackoff
  mychannel:
    policies:
      discovery:
        retryOpts:
          #[Required for override] number of retry attempts
          attempts: 4
          initialBackoff: 1s

  # multi-org test channel
  orgchannel:

#    orderers:
#      - orderer.example.com

    peers:
      peer0.org1.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

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

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

4. 组织部分

#
# list of participating organizations in this network
# 参与该网络的组织名单
#
organizations:
  org1:
    mspid: Org1MSP

    # This org's MSP store (absolute path or relative to client.cryptoconfig)
    # MSP存储路径
    cryptoPath:  peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp

    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.
    # 在基于Fabric的网络中,证书颁发机构为识别目的颁发证书。通常,证书提供是在运行时网络之外的单独进程中完成的。
    # Fabric-CA是一个特殊的证书颁发机构,它为动态证书管理(注册、撤销、重新注册)提供了一个REST api。以下内容仅适用于Fabric-CA服务器。
    certificateAuthorities:
      - ca.org1.example.com
      - tlsca.org1.example.com

  # 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.
  # 这个概要文件将包含关于它所属组织以外的其他组织的公共信息。
  # 这些是使事务生命周期正常工作所必需的信息,包括MSP id和使用公共URL发送事务建议的对等节点。
  # 该文件将不包含为组织成员保留的私人信息,如管理员密钥和证书、fabric-ca注册商注册ID和秘密等。
  org2:
    mspid: Org2MSP

    # This org's MSP store (absolute path or relative to client.cryptoconfig)
    cryptoPath:  peerOrganizations/org2.example.com/users/{username}@org2.example.com/msp

    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

      # Needed to load users crypto keys and certs for this org (absolute path or relative to global crypto path, DEV mode)
      cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp

5. 排序节点部分

#
# 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.
#
# 要发送事务和通道创建/更新请求的订购者列表。目前只需要一份订单。如果定义了多个,那么SDK将使用哪个是特定于实现的。
# 请查阅每个SDK的文档,了解它对排序的处理。
orderers:
  _default:
    # common orderer config items between all the orderers in  config
    # these are standard properties defined by the gRPC library
    # they will be passed in as-is to gRPC client constructor
    # 这些是gRPC库定义的标准属性,它们将被原样传递给gRPC客户端构造器
    grpcOptions:
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      # 这些参数的设置应该与服务器上的keepalive策略相协调,因为不兼容的设置可能导致连接关闭。
      # 当“keep-alive-time”的持续时间设置为0或更小时,保持活跃的客户端参数将被禁用
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      # 如果address没有定义协议,Allow-insecure将被考虑,如果address为true则GRPC或GRPCS
      allow-insecure: false

  orderer.example.com:
    # [Optional] Default: Infer from hostname
    url: orderer.example.com:7050

    # these are standard properties defined by the gRPC library
    # they will be passed in as-is to gRPC client constructor
    # 这些是gRPC库定义的标准属性,它们将原样传递给gRPC客户端构造函数
    grpcOptions:
      #ssl-target-name-override: orderer.example.com
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      # 这些参数的设置应该与服务器上的keepalive策略相协调,因为不兼容的设置可能导致连接关闭。
      # 当“keep-alive-time”的持续时间设置为0或更小时,保持活跃的客户端参数将被禁用
      #keep-alive-time: 0s
      #keep-alive-timeout: 20s
      #keep-alive-permit: false
      #fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      # 如果address没有定义协议,Allow-insecure将被考虑,如果address为true则GRPC或GRPCS
      #allow-insecure: false

    tlsCACerts:
      # Certificate location absolute path
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem

6. 对等节点部分

#
# List of peers to send various requests to, including endorsement, query
# and event listener registration.
# 发送各种请求的对等点节点列表,包括背书、查询和事件侦听器注册。
#
peers:
  _default:
    #common grpc options between all the peers
    # 所有对等节点的通用GRPC选项
    grpcOptions:
      # These parameters should be set in coordination with the keepalive policy on the server,
      # as incompatible settings can result in closing of connection.
      # When duration of the 'keep-alive-time' is set to 0 or less the keep alive client parameters are disabled
      # 这些参数的设置应该与服务器上的keepalive策略相协调,因为不兼容的设置可能导致连接关闭。
      # 当“keep-alive-time”的持续时间设置为0或更小时,保持活跃的客户端参数将被禁用
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      # allow-insecure will be taken into consideration if address has no protocol defined, if true then grpc or else grpcs
      # 如果address没有定义协议,Allow-insecure将被考虑,如果address为true则GRPC或GRPCS
      allow-insecure: false

  peer0.org1.example.com:
    # this URL is used to send endorsement and query requests
    # 此URL用于发送背书和查询请求
    # [Optional] Default: Infer from hostname
    url: peer0.org1.example.com:7051

    #grpcOptions:
    #  ssl-target-name-override: peer0.org1.example.com

    tlsCACerts:
      # 证书本地绝对路径
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer1.org1.example.com:
    # 此URL用于发送背书和查询请求
    url: peer1.org1.example.com:7151

    #grpcOptions:
    #  ssl-target-name-override: peer1.org1.example.com

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

  peer0.org2.example.com:
    url: peer0.org2.example.com:8051
    #grpcOptions:
    #  ssl-target-name-override: peer0.org2.example.com

    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

  peer1.org2.example.com:
    url: peer1.org2.example.com:9051
    #grpcOptions:
    #  ssl-target-name-override: peer1.org2.example.com

    tlsCACerts:
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

7. CA部分

#
# 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.
# Fabric- ca是Hyperledger Fabric提供的一种特殊的证书颁发机构,它允许通过REST api进行证书管理。
# 应用程序可以选择使用标准的证书颁发机构而不是Fabric-CA,在这种情况下,将不指定此部分。
#
certificateAuthorities:
  ca.org1.example.com:
    # [Optional] Default: Infer from hostname
    url: https://ca.org1.example.com:7054
    # [Optional] The optional server name for target override
    #grpcOptions:
    #  ssl-target-name-override: ca.org1.example.com
    tlsCACerts:
      # Comma-Separated list of paths
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      client:
        key:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
        cert:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/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.
    # Fabric-CA支持通过REST api动态注册用户。注册和调用新用户需要一个“根”用户,即注册商
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org1.example.com
  tlsca.org1.example.com:
    # [Optional] Default: Infer from hostname
    url: https://ca.org1.example.com:7154
    tlsCACerts:
      # Comma-Separated list of paths
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      client:
        key:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
        cert:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/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: admin2
      enrollSecret: adminpw2
    # [Optional] The optional name of the CA.
    caName: tlsca.org1.example.com
  ca.org2.example.com:
    url: https://ca.org2.example.com:8054
    # [Optional] The optional server name for target override
    #grpcOptions:
    #  ssl-target-name-override: ca.org2.example.com
    tlsCACerts:
      # Comma-Separated list of paths
      path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem
      # Client key and cert for SSL handshake with Fabric CA
      client:
        key:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/peerOrganizations/tls.example.com/users/User1@tls.example.com/tls/client.key
        cert:
          path: ${FABRIC_SDK_GO_PROJECT_PATH}/${CRYPTOCONFIG_FIXTURES_PATH}/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.
     # Fabric-CA支持通过REST api动态注册用户。注册和调用新用户需要一个“根”用户,即注册商。
    registrar:
      enrollId: admin
      enrollSecret: adminpw
    # [Optional] The optional name of the CA.
    caName: ca.org2.example.com

8. EntityMatchers部分

# EntityMatchers enable substitution of network hostnames with static configurations
 # so that properties can be mapped. Regex can be used for this purpose
# UrlSubstitutionExp can be empty which means the same network hostname will be used
# UrlSubstitutionExp can be given same as mapped peer url, so that mapped peer url can be used
# UrlSubstitutionExp can have golang regex matchers like ${1}.local.example.${2}:${3} for pattern
 # like peer0.org1.example.com:1234 which converts peer0.org1.example.com to peer0.org1.local.example.com:1234
# sslTargetOverrideUrlSubstitutionExp follow in the same lines as
 # SubstitutionExp for the fields gprcOptions.ssl-target-name-override respectively
# In any case mappedHost's config will be used, so mapped host cannot be empty, if entityMatchers are used
# EntityMatchers支持使用静态配置替换网络主机名,以便可以映射属性。
# 正则表达式可以用于此目的
# UrlSubstitutionExp可以为空,这意味着将使用相同的网络主机名
# UrlSubstitutionExp可以与映射的对等节点url相同,这样就可以使用映射的对等url
#entityMatchers:
entityMatchers:
  peer:
    - pattern: (\w+).org1.example.com:(\d+)
      urlSubstitutionExp: ${1}.org1.example.com:${2}
      sslTargetOverrideUrlSubstitutionExp: ${1}.org1.example.com
      mappedHost: peer0.org1.example.com

    - pattern: (\w+).org2.example.com:(\d+)
      urlSubstitutionExp: ${1}.org2.example.com:${2}
      sslTargetOverrideUrlSubstitutionExp: ${1}.org2.example.com
      mappedHost: peer0.org2.example.com

    - pattern: (\w+).org1.example.(\w+)
      urlSubstitutionExp: peer0.org1.example.com:7051
      sslTargetOverrideUrlSubstitutionExp: peer0.org1.example.com
      mappedHost: peer0.org1.example.com

    - pattern: (\w+).org2.example.(\w+)
      urlSubstitutionExp: peer0.org2.example.com:8051
      sslTargetOverrideUrlSubstitutionExp: peer0.org2.example.com
      mappedHost: peer0.org2.example.com

    - pattern: (\w+).example1.(\w+):(\d+)
      urlSubstitutionExp: ${1}.org1.example.${2}:${3}
      sslTargetOverrideUrlSubstitutionExp: ${1}.org1.example.${2}
      mappedHost: peer0.org1.example.com

    - pattern: (\w+).org1.example.(\w+):(\d+)
      urlSubstitutionExp: peer0.org1.example.com:7051
      sslTargetOverrideUrlSubstitutionExp: peer0.org1.example.com
      mappedHost: peer0.org1.example.com

  orderer:
    - pattern: (\w+).example.(\w+)
      urlSubstitutionExp: orderer.example.com:7050
      sslTargetOverrideUrlSubstitutionExp: orderer.example.com
      mappedHost: orderer.example.com

    - pattern: (\w+).example2.(\w+)
      urlSubstitutionExp: localhost:7050
      sslTargetOverrideUrlSubstitutionExp: localhost
      mappedHost: orderer.example.com

    - pattern: (\w+).example3.(\w+)
      urlSubstitutionExp:
      sslTargetOverrideUrlSubstitutionExp:
      mappedHost: orderer.example.com

    - pattern: (\w+).example4.(\w+):(\d+)
      urlSubstitutionExp: ${1}.example.${2}:${3}
      sslTargetOverrideUrlSubstitutionExp: ${1}.example.${2}
      mappedHost: orderer.example.com

  certificateAuthority:
    - pattern: (\w+).org1.example.(\w+)
      urlSubstitutionExp:
      sslTargetOverrideUrlSubstitutionExp: ca.example.com
      mappedHost: ca.org1.example.com

    - pattern: (\w+).org2.example.(\w+)
      urlSubstitutionExp:
      sslTargetOverrideUrlSubstitutionExp: ca.example.com
      mappedHost: ca.org2.example.com

9. operations部分

#################################################################################    Operations部分#    从Fabric core.yaml复制而来###############################################################################operations:  # host and port for the operations server  listenAddress: 127.0.0.1:8080  # TLS configuration for the operations endpoint  tls:    # TLS enabled    enabled: false    # TODO replace cert.file, key.file and clientRootCAs.files to tlsCACerts as used elsewhere in this config file    # path to PEM encoded server certificate for the operations server    cert:      file:    # path to PEM encoded server key for the operations server    key:      file:    # require client certificate authentication to access all resources    clientAuthRequired: false    # paths to PEM encoded ca certificates to trust for client authentication    clientRootCAs:      files: []

10. Metrics部分

#################################################################################    Metrics 部分#    copied from Fabric's core.yaml###############################################################################metrics:  # metrics provide选项可以是statsd, prometheus,或disabled  provider: prometheus  # statsd configuration  # statsd配置  statsd:    # network类型: tcp或udp    network: udp    # statsd server address    address: 127.0.0.1:8125    # the interval at which locally cached counters and gauges are pushed    # to statsd; timings are pushed immediately    # 本地缓存的计数器和计量表被推入statsd的时间间隔;时间被立即推后    writeInterval: 10s    # prefix is prepended to all emitted statsd metrics    prefix:

11. 改写一个配置文件

现在我们来改写一个适用于我们网络的配置文件。在fabric-sdk-go路径下新建一个名为config.yaml的配置文件并编辑。我们将官方的config_e2e_multiorg_bootstrap.yaml文件进行改写,相关文件可在官方githubhttps://github.com/hyperledger/fabric-sdk-go/blob/main/test/fixtures/config/config_e2e_multiorg_bootstrap.yaml上找到。我们只将所有的证书路径改成我们项目的对应路径,以及将端口号改为在基础网络中对应的端口号。改好的配置文件如下:

version: 1.0.0

client:
  organization: org1
  logging:
    level: info
  cryptoconfig:
    path: /root/go/src/fabric-sdk-go/fixtures/crypto-config
  credentialStore:
    path: "/tmp/state-store"
    cryptoStore:
      path: /tmp/msp
  BCCSP:
    security:
     enabled: true
     default:
      provider: "SW"
     hashAlgorithm: "SHA2"
     softVerify: true
     level: 256
  tlsCerts:
    systemCertPool: true
    client:
      key:
        path: /root/go/src/fabric-sdk-go/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.key
      cert:
        path: /root/go/src/fabric-sdk-go/fixtures/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/tls/client.crt

channels:
  orgchannel:
    peers:
      peer0.org1.example.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

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

    policies:
      queryChannelConfig:
        minResponses: 1
        maxTargets: 1
        retryOpts:
          attempts: 5
          initialBackoff: 500ms
          maxBackoff: 5s
          backoffFactor: 2.0

organizations:
  org1:
    mspid: Org1MSP 
    cryptoPath:  peerOrganizations/org1.example.com/users/{username}@org1.example.com/msp
    peers:
      - peer0.org1.example.com

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

  ordererorg:   
      mspID: OrdererMSP
      cryptoPath: ordererOrganizations/example.com/users/{username}@example.com/msp

orderers:
  orderer.example.com:
    url: orderer.example.com:7050
    grpcOptions:
      ssl-target-name-override: orderer.example.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: /root/go/src/fabric-sdk-go/fixtures/crypto-config/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem

peers:
  peer0.org1.example.com:
    url: peer0.org1.example.com: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
      allow-insecure: false
    tlsCACerts:
      path: /root/go/src/fabric-sdk-go/fixtures/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem

  peer0.org2.example.com:
    url: peer0.org2.example.com: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
      allow-insecure: false
    tlsCACerts:
      path: /root/go/src/fabric-sdk-go/fixtures/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem

entityMatchers:
  peer:
    - pattern: (\w+).org1.example.com:(\d+)
      urlSubstitutionExp: ${1}.org1.example.com:${2}
      sslTargetOverrideUrlSubstitutionExp: ${1}.org1.example.com
      mappedHost: peer0.org1.example.com

    - pattern: (\w+).org2.example.com:(\d+)
      urlSubstitutionExp: ${1}.org2.example.com:${2}
      sslTargetOverrideUrlSubstitutionExp: ${1}.org2.example.com
      mappedHost: peer0.org2.example.com

四、初始化及加入通道

1. 定义所需结构体

在项目路径下新建名为sdkInit的文件夹,此文件夹用于存放实现sdk的代码。在sdkInit路径下新建sdkInfo.go并编辑如下:

导入所需包

package sdkInit
import (
	mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
)
  • mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"

    msp包允许在Fabric网络上创建和更新用户。msp客户端支持以下操作:注册、重注册、登记和获取身份签名。基本工作流为:

    • 准备客户端上下文

    • 创建msp客户端

    • 登记用户

    • 注册用户

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

    resmgmt包用于在Fabric网络中创建和更新资源。它允许管理员创建和/或更新通道,并允许对等节点加入通道。管理员还可以在对等节点上执行链码相关操作,如安装、实例化、升级链码等。基本工作流为:

    • 准备客户端上下文

    • 创建资源管理器客户端

    • 创建新通道

    • 节点加入通道

    • 查询对等节点的通道,安装/实例化的链代码等。

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

    提供所需上下文接口

type OrgInfo struct {
	OrgAdminUser          string //  管理员用户名,如"Admin"
	OrgName               string //  组织名,如"Org1"
	OrgMspId              string //  组织MSPid,如"Org1MSP"
	OrgUser               string //  用户名,如"User1"
	orgMspClient          *mspclient.Client // MSP客户端
	OrgAdminClientContext *contextAPI.ClientProvider // 客户端上下文信息
	OrgResMgmt            *resmgmt.Client // 资源管理客户端
	OrgPeerNum            int // 组织节点个数
	OrgAnchorFile         string //   锚节点配置文件路径 
}

type SdkEnvInfo struct {
	// 通道信息
	ChannelID     string // 通道名称,如"simplecc"
	ChannelConfig string // 通道配置文件路径

	// 组织信息
	Orgs []*OrgInfo
	// 排序服务节点信息
	OrdererAdminUser     string // orederer管理员用户名,如"Admin"
	OrdererOrgName       string // orderer组织名,如"OrdererOrg"
	OrdererEndpoint      string // orderer端点,如"orderer.example.com"
	OrdererClientContext *contextAPI.ClientProvider // orderer客户端上下文
	// 链码信息
	ChaincodeID      string // 链码名称
	ChaincodePath    string // 链码路径
	ChaincodeVersion string // 链码版本
}

2.初始化

sdkInit路径下新建sdkSetting.go并编辑如下:

导入所需包

package sdkInit
import (
	"fmt"
	mspclient "github.com/hyperledger/fabric-sdk-go/pkg/client/msp"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/resmgmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
	"github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"
)
  • "github.com/hyperledger/fabric-sdk-go/pkg/core/config":获取所需配置文件

  • "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk"fabsdk包允许客户端使用Hyperledger Fabric网络。

func Setup(configFile string, info *SdkEnvInfo) (*fabsdk.FabricSDK, error) {
	var err error
    // 使用fabsdk包的new方法根据config.yaml文件提供的网络信息初始化sdk
	sdk, err := fabsdk.New(config.FromFile(configFile)) 
	if err != nil {
		return nil, err
	}
	// 为组织获得Client句柄和Context信息
	for _, org := range info.Orgs {
        // 初始化组织msp客户端
		org.orgMspClient, err = mspclient.New(sdk.Context(), mspclient.WithOrg(org.OrgName))
		if err != nil {
			return nil, err
		}
        // 创建所有所需上下文信息
		orgContext := sdk.Context(fabsdk.WithUser(org.OrgAdminUser), fabsdk.WithOrg(org.OrgName))
		org.OrgAdminClientContext = &orgContext

		// 新建客户端资源管理器实例
		resMgmtClient, err := resmgmt.New(orgContext)
		if err != nil {
			return nil, fmt.Errorf("根据指定的资源管理客户端Context创建通道管理客户端失败: %v", err)
		}
		org.OrgResMgmt = resMgmtClient
	}

	// 为Orderer获得Context信息
	ordererClientContext := sdk.Context(fabsdk.WithUser(info.OrdererAdminUser), fabsdk.WithOrg(info.OrdererOrgName))
	info.OrdererClientContext = &ordererClientContext
	return sdk, nil
}

3. 创建及加入通道

继续在sdkSetting.go文件中编辑如下:

  • 导入所需包

import (
    [......]
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
)
  • 调用创建通道函数及加入通道

func CreateAndJoinChannel(info *SdkEnvInfo) error {
	fmt.Println(">> 开始创建通道......")
	if len(info.Orgs) == 0 {
		return fmt.Errorf("通道组织不能为空,请提供组织信息")
	}

	// 获得所有组织的签名信息
	signIds := []msp.SigningIdentity{}
	for _, org := range info.Orgs {
		// Get signing identity that is used to sign create channel request
		orgSignId, err := org.orgMspClient.GetSigningIdentity(org.OrgAdminUser)
		if err != nil {
			return fmt.Errorf("GetSigningIdentity error: %v", err)
		}
		signIds = append(signIds, orgSignId)
	}

	// 创建通道,createChannel方法在下面定义
	if err := createChannel(signIds, info); err != nil {
		return fmt.Errorf("Create channel error: %v", err)
	}

	fmt.Println(">> 创建通道成功")

	fmt.Println(">> 加入通道......")
	for _, org := range info.Orgs {
		// 加入通道
		if err := org.OrgResMgmt.JoinChannel(info.ChannelID, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
			return fmt.Errorf("%s peers failed to JoinChannel: %v", org.OrgName, err)
		}
	}
	fmt.Println(">> 加入通道成功")
	return nil
}
  • 创建通道

func createChannel(signIDs []msp.SigningIdentity, info *SdkEnvInfo) error {
	// Channel management client 负责管理通道,如创建更新通道
	chMgmtClient, err := resmgmt.New(*info.OrdererClientContext)
	if err != nil {
		return fmt.Errorf("Channel management client create error: %v", err)
	}

	// 根据channel.tx创建通道
	req := resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
		ChannelConfigPath: info.ChannelConfig,
		SigningIdentities: signIDs}

	if _, err := chMgmtClient.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
		return fmt.Errorf("error should be nil for SaveChannel of orgchannel: %v", err)
	}

	fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置...")
	//根据锚节点文件更新锚节点,与上面创建通道流程相同
	for i, org := range info.Orgs {
		req = resmgmt.SaveChannelRequest{ChannelID: info.ChannelID,
			ChannelConfigPath: org.OrgAnchorFile,
			SigningIdentities: []msp.SigningIdentity{signIDs[i]}}

		if _, err = org.OrgResMgmt.SaveChannel(req, resmgmt.WithRetry(retry.DefaultResMgmtOpts), resmgmt.WithOrdererEndpoint("orderer.example.com")); err != nil {
			return fmt.Errorf("SaveChannel for anchor org %s error: %v", org.OrgName, err)
		}
	}
	fmt.Println(">>>> 使用每个org的管理员身份更新锚节点配置完成")
	
	return nil
}

五、链码生命周期

链码运行在一个隔离于背书peer节点进程的安全的Docker容器中。链码通过应用提交的交易来初始化以及管理账本状态。从hyperledger fabric v2.0版本开始启用了新的链码生命周期,Fabric 链码生命周期是一个过程,它允许多个组织在使用一个链码之前就如何操作达成一致。Fabric链码生命周期需要组织同意定义一个链码的参数,比如说名称、版本以及链码背书策略。通道成员通过以下四步达成共识。不是通道上的每一个组织都需要完成每一步。

  • 打包链码:这一步可以被一个或者每一个组织完成。

  • 安装链码在你的 peer 节点上:每一个用链码的组织需要完成这一步。

  • 为你的组织批准链码定义:使用链码的每一个组织需要完成这一步。链码能够在通道上运行之前,链码定义需要被足够多的组织批准来满足通道的生命周期背书(LifecycleEndorsement)策略(默认为大多数组织)。

  • 提交链码定义到链上:一旦通道上所需数量的组织已经同意,提交交易需要被提交。提交者首先从已同意组织中的足够的peer节点中收集背书,然后通过提交交易来提交链码声明。

1. 添加DiscoverLocalPeers方法

DiscoverLocalPeers方法可以自动查找的所有节点

sdkInit路径下新建一个名为intergration.go的文件。编辑如下:

package sdkInit

import (
	"fmt"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
	contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
	fabAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
	contextImpl "github.com/hyperledger/fabric-sdk-go/pkg/context"
)
// 查找本地节点
func DiscoverLocalPeers(ctxProvider contextAPI.ClientProvider, expectedPeers int) ([]fabAPI.Peer, error) {
	ctx, err := contextImpl.NewLocal(ctxProvider)
	if err != nil {
		return nil, fmt.Errorf("error creating local context: %v", err)
	}

	discoveredPeers, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
		func() (interface{}, error) {
			peers, serviceErr := ctx.LocalDiscoveryService().GetPeers()
			if serviceErr != nil {
				return nil, fmt.Errorf("getting peers for MSP [%s] error: %v", ctx.Identifier().MSPID, serviceErr)
			}
			if len(peers) < expectedPeers {
				return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("Expecting %d peers but got %d", expectedPeers, len(peers)), nil)
			}
			return peers, nil
		},
	)
	if err != nil {
		return nil, err
	}

	return discoveredPeers.([]fabAPI.Peer), nil
}

2. 链码自动化生命周期

继续在sdkSetting.go文件中编辑如下:

  • 导入所需包

import (
	[......]
	mb "github.com/hyperledger/fabric-protos-go/msp"
	pb "github.com/hyperledger/fabric-protos-go/peer"
	"github.com/hyperledger/fabric-sdk-go/pkg/client/channel"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
	lcpackager "github.com/hyperledger/fabric-sdk-go/pkg/fab/ccpackager/lifecycle"
	"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/common/policydsl"
	"strings"
)
  • 打包链码

    在被安装到peer节点之前,链码需要被打包进一个tar文件。当你创建一个链码包的时候,你需要提交一个用来创建简明易读的包描述的链码包标签。

    使用fabric-go-sdk将会自动以这个格式来创建文件。

    • 链码需要被打包进一个以 .tar.gz 文件扩展名结尾的tar文件。

    • tar文件需要包含两个文件(没有目录):metadata.json和另一个包含了链码文件的 tar 文件code.tar.gz

    • metadata.json包含了指定链码语言、代码路径、以及包标签的 JSON 文件。

func packageCC(ccName, ccVersion, ccpath string) (string, []byte, error) {
   
	label := ccName + "_" + ccVersion // 链码的标签
	desc := &lcpackager.Descriptor{   // 使用lcpackager包中的Descriptor结构体添加描述信息
		Path:  ccpath, //链码路径
		Type:  pb.ChaincodeSpec_GOLANG, //链码的语言
		Label: label, // 链码的标签
	}
	ccPkg, err := lcpackager.NewCCPackage(desc) // 使用lcpackager包中NewCCPackage方法对链码进行打包
	if err != nil {
		return "", nil, fmt.Errorf("Package chaincode source error: %v", err)
	}
	return desc.Label, ccPkg, nil
}
  • 安装链码

    你需要在每个要执行和背书交易的peer节点上安装链码包。使用SDK时,你需要以 Peer Administrator(peer所在组织的管理员) 的身份来完成这步。链码安装后,你的 peer 节点会构建链码,并且如果你的链码有问题,会返回一个构建错误。建议每个组织只打包链码一次,然后安装相同的包在属于他们组织的每一个peer节点上。如果某个通道希望确保每个组织都运行同样的链码,某一个组织可以打包链码并通过带外数据(不通过链上)把它发送给其他通道成员.

    通过指令成功安装链码后会返回链码包标识符,它是包标签和包哈希值的结合。这个包标识符用来关联安装在你的peer节点上的链码包已被批准的链码。为下一步的操作保存这个标识符。你也可以查询安装在peer节点上的包来查看包标识符。

func installCC(label string, ccPkg []byte, orgs []*OrgInfo) error {
	installCCReq := resmgmt.LifecycleInstallCCRequest{
		Label:   label,
		Package: ccPkg,
	}
	// 使用lcpackager中的ComputePackageID方法查询并返回链码的packageID
	packageID := lcpackager.ComputePackageID(installCCReq.Label, installCCReq.Package)
	for _, org := range orgs {
		orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
		if err != nil {
			fmt.Errorf("DiscoverLocalPeers error: %v", err)
		}
        // 检查是否安装链码,如果未安装则继续执行
		if flag, _ := checkInstalled(packageID, orgPeers[0], org.OrgResMgmt); flag == false {
            // 使用resmgmt中的LifecycleInstallCC方法安装链码,其中WithRetry方法为安装不成功时重试安装,DefaultResMgmtOpts为默认的重试安装规则
			if _, err := org.OrgResMgmt.LifecycleInstallCC(installCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithRetry(retry.DefaultResMgmtOpts)); err != nil {
				return fmt.Errorf("LifecycleInstallCC error: %v", err)
			}
		}
	}
	return nil
}
//检查是否安装过链码
func checkInstalled(packageID string, peer fab.Peer, client *resmgmt.Client) (bool, error) {
	flag := false
	resp1, err := client.LifecycleQueryInstalledCC(resmgmt.WithTargets(peer))
	if err != nil {
		return flag, fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
	}
	for _, t := range resp1 {
		if t.PackageID == packageID { 
			flag = true
		}
	}
	return flag, nil
}
  • 获取已安装链码包

func getInstalledCCPackage(packageID string, org *OrgInfo) error {
	// use org1
	orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
	if err != nil {
		return fmt.Errorf("DiscoverLocalPeers error: %v", err)
	}
	// 使用resmgmt中的LifecycleGetInstalledCCPackage方法,对于给定的packageID检索已安装的链码包
	if _, err := org.OrgResMgmt.LifecycleGetInstalledCCPackage(packageID, resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...)); err != nil {
		return fmt.Errorf("LifecycleGetInstalledCCPackage error: %v", err)
	}
	return nil
}
  • 查询安装

func queryInstalled(packageID string, org *OrgInfo) error {
	orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, 1)
	if err != nil {
		return fmt.Errorf("DiscoverLocalPeers error: %v", err)
	}
    // 使用resmgmt中的LifecycleQueryInstalledCC方法,返回在指定节点上安装的链码packageID
	resp1, err := org.OrgResMgmt.LifecycleQueryInstalledCC(resmgmt.WithTargets([]fab.Peer{orgPeers[0]}...))
	if err != nil {
		return fmt.Errorf("LifecycleQueryInstalledCC error: %v", err)
	}
	packageID1 := ""
	for _, t := range resp1 {
		if t.PackageID == packageID {
			packageID1 = t.PackageID
		}
	}
    // 查询的packageID与给定的packageID不一致则报错
	if !strings.EqualFold(packageID, packageID1) {
		return fmt.Errorf("check package id error")
	}
	return nil
}
  • 各组织批准链码

    通过 链码定义来管理链码。当通道成员批准一个链码定义,这个批准便作为一个组织在接受链码参数方面的投票。这些同意的组织定义允许通道成员在链码可以在通道上使用之前达成一致意见(同意链码运行在此通道上)。链码定义包含了以下需要持续在组织之间保持一致的参数:

    • 名称:应用调用链码时使用的名称。

    • 版本:一个版本号或者和给定链码包关联的值。如果你升级链码二进制文件(译者注:打包后的链码文件),你也需要改变你的链码版本。

    • 序列号:链码被定义的次数。这个值是一个整数,并且被用来追踪链码的更新次数。例如当你第一次安装并且同意一个链码定义,这个序列号会是1。当你下一次更新链码,序列号会是2。

    • 背书策略:哪些组织需要执行并且验证交易输出。背书策略可以表达为传递给 CLI 工具的字符串或者它能参考通道配置中的一个策略。默认情况下,背书策略设置为 Channel/Application/Endorsement,默认通道中大多数组织为一笔交易背书。

    • 集合配置(私有数据集合配置):和你链码相关的私有数据集合定义文件的路径。了解更多关于私有数据集合的信息。

    • ESCC/VSCC插件:这个链码使用的定制的背书或者验证插件名称。

    • 初始化: 如果你使用 Fabric Chaincode Shim API 提供的低级别的 API,你的链码需要包含用来初始化链码的 Init 方法。链码接口需要这个方法,但不必要被你的应用调用。 当你批准一个链码定义时,你可以指定是否 Init 方法必须在调用(调用非 init 方法)之前被执行。如果你指定需要 Init,Fabric 会确保Init 方法在链码中的其他方法之前被调用,并且只会被调用一次。 请求执行 Init 方法允许你实现链码初始化时运行的逻辑,例如设置一些初始状态。每次你的链码版本更新,你都需要调用 Init 来初始化链码,假定链码定义增加了版本号意味着 Init 是需要的。

func approveCC(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error {
	mspIDs := []string{}
    // 获取各个组织的mspID
	for _, org := range orgs {
		mspIDs = append(mspIDs, org.OrgMspId)
	}
    // 签名策略,由所有给出的mspid签名
	ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
    // approve所需参数
	approveCCReq := resmgmt.LifecycleApproveCCRequest{
		Name:              ccName,      // 链码名
		Version:           ccVersion,   // 版本
		PackageID:         packageID,   // 链码包id
		Sequence:          sequence,    // 序列号
		EndorsementPlugin: "escc",      // 系统内置链码escc
		ValidationPlugin:  "vscc",      // 系统内置链码vscc
		SignaturePolicy:   ccPolicy,    // 组织签名策略
		InitRequired:      true,        // 是否初始化
	}

	for _, org := range orgs{
		orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
		fmt.Printf(">>> chaincode approved by %s peers:\n", org.OrgName)
		for _, p := range orgPeers {
			fmt.Printf("	%s\n", p.URL())
		}

		if err!=nil{
			return fmt.Errorf("DiscoverLocalPeers error: %v", err)
		}
        // 使用resmgmt中的LifecycleApproveCC方法为组织批准链码
		if _, err := org.OrgResMgmt.LifecycleApproveCC(channelID, approveCCReq, resmgmt.WithTargets(orgPeers...), resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts));err != nil {
			fmt.Errorf("LifecycleApproveCC error: %v", err)
		}
	}
	return nil
}
  • 查询已批准的链码

func queryApprovedCC(ccName string, sequence int64, channelID string, orgs []*OrgInfo) error {
	// queryApproved所需参数
    queryApprovedCCReq := resmgmt.LifecycleQueryApprovedCCRequest{
		Name:     ccName,  // 链码名称
		Sequence: sequence,// 序列号
	}

	for _, org := range orgs{
		orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
		if err!=nil{
			return fmt.Errorf("DiscoverLocalPeers error: %v", err)
		}
		// Query approve cc
		for _, p := range orgPeers {
			resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
				func() (interface{}, error) {
                    // LifecycleQueryApprovedCC返回有关已批准的链码定义的信息
					resp1, err := org.OrgResMgmt.LifecycleQueryApprovedCC(channelID, queryApprovedCCReq, resmgmt.WithTargets(p))
					if err != nil {
						return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryApprovedCC returned error: %v", err), nil)
					}
					return resp1, err
				},
			)
			if err != nil {
				return fmt.Errorf("Org %s Peer %s NewInvoker error: %v", org.OrgName, p.URL(), err)
			}
			if resp==nil{
				return fmt.Errorf("Org %s Peer %s Got nil invoker", org.OrgName, p.URL())
			}
		}
	}
	return nil
}
  • 检查智能合约是否就绪

func checkCCCommitReadiness(packageID string, ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo) error {
	mspIds := []string{}
	for _, org := range orgs {
		mspIds = append(mspIds, org.OrgMspId)
	}
    // 签名策略,由所有给出的mspid签名
	ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIds)), mb.MSPRole_MEMBER, mspIds)
    // 所需所有参数,同上
	req := resmgmt.LifecycleCheckCCCommitReadinessRequest{
		Name:              ccName,
		Version:           ccVersion,
		//PackageID:         packageID,
		EndorsementPlugin: "escc",
		ValidationPlugin:  "vscc",
		SignaturePolicy:   ccPolicy,
		Sequence:          sequence,
		InitRequired:      true,
	}
	for _, org := range orgs{
		orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
		if err!=nil{
			fmt.Errorf("DiscoverLocalPeers error: %v", err)
		}
		for _, p := range orgPeers {
			resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
				func() (interface{}, error) {
                    // 使用resmgmt中的LifecycleCheckCCCommitReadiness方法检查链代码的“提交准备”,返回组织批准。
					resp1, err := org.OrgResMgmt.LifecycleCheckCCCommitReadiness(channelID, req, resmgmt.WithTargets(p))
					fmt.Printf("LifecycleCheckCCCommitReadiness cc = %v, = %v\n", ccName, resp1)
					if err != nil {
						return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned error: %v", err), nil)
					}
					flag := true
					for _, r := range resp1.Approvals {
						flag = flag && r
					}
					if !flag {
						return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleCheckCCCommitReadiness returned : %v", resp1), nil)
					}
					return resp1, err
				},
			)
			if err != nil {
				return fmt.Errorf("NewInvoker error: %v", err)
			}
			if resp==nil{
				return fmt.Errorf("Got nill invoker response")
			}
		}
	}

	return nil
}
  • 提交智能合约定义

    一旦足够多的通道成员同意一个链码定义,某个组织能够提交定义到通道。你可以用上述 checkcommitreadiness 方法在将链码定义提交到通道之前,基于哪个通道成员已经批准了该定义,来检查提交链码定义是否应该成功。(根据通道成员同意的状况,来判断提交是否可能成功)。提交交易请求首先发送给通道成员的 peer节点,peer节点会查询链码定义被他们组织同意的状况,并且为定义背书如果所在组织已经同意了。交易然后提交给排序服务,排序服务会把链码定义提交给通道。提交定义交易需要以 Organization Administrator 身份来提交。

    链码在被成功提交到通道之前,需要被同意的组织的数量是通过 Channel/Application/LifecycleEndorsement 策略来管理的。默认情况下,这个策略需要通道中大多数的组织来给交易背书。生命周期背书策略不同于链码背书策略。例如,尽管一个链码背书策略只需要一个或两个组织的签名,根据默认策略大多数的通道成员仍然需要批准链码定义。当提交一个通道定义,你需要面向足够多的 peer 组织,以确保你的生命周期背书策略被满足。

    你也可以设置 Channel/Application/LifecycleEndorsement 策略为一个签名策略并且明确指明通道上可以批准链码定义的组织集合。这允许你创建一个其中大多数组织作为链码管理者并且治理通道业务逻辑的通道。如果你的通道有大量的Idemix(身份混合,实现零知识证明)组织,你也可以用一个签名策略(策略只需要一个签名),因为这些组织不能批准链码定义或者为链码背书并且可能阻碍通道达成大多数成员同意的结果。

    一个组织在不安装链码包的条件下能够批准链码定义。如果一个组织不需要使用链码,他们可以在没有包身份的情况下批准一个链码定义来确保生命周期背书策略被满足。

    在链码定义已经提交到通道上后,链码容器会在所有的链码安装到的 peer 节点上启动,来允许通道成员开始使用链码。可能会花费几分钟的时间来启动链码容器。你可以用链码定义来要求调用 Init 方法初始化链码。如果 Init 方法调用是需要的,链码的第一个调用必须是调用 Init 方法。Init 方法的调用服从于链码的背书策略。

func commitCC(ccName, ccVersion string, sequence int64, channelID string, orgs []*OrgInfo, ordererEndpoint string) error{
	mspIDs := []string{}
	for _, org := range orgs {
		mspIDs = append(mspIDs, org.OrgMspId)
	}
	ccPolicy := policydsl.SignedByNOutOfGivenRole(int32(len(mspIDs)), mb.MSPRole_MEMBER, mspIDs)
	// commit所需参数信息,内容同上
	req := resmgmt.LifecycleCommitCCRequest{
		Name:              ccName,
		Version:           ccVersion,
		Sequence:          sequence,
		EndorsementPlugin: "escc",
		ValidationPlugin:  "vscc",
		SignaturePolicy:   ccPolicy,
		InitRequired:      true,
	}
    // LifecycleCommitCC将链代码提交给给定的通道
	_, err := orgs[0].OrgResMgmt.LifecycleCommitCC(channelID, req, resmgmt.WithOrdererEndpoint(ordererEndpoint), resmgmt.WithRetry(retry.DefaultResMgmtOpts))
	if err != nil {
		return fmt.Errorf("LifecycleCommitCC error: %v", err)
	}
	return nil
}
  • 查询已提交的智能合约定义

func queryCommittedCC( ccName string, channelID string, sequence int64, orgs []*OrgInfo) error {
	req := resmgmt.LifecycleQueryCommittedCCRequest{
		Name: ccName,
	}

	for _, org := range orgs {
		orgPeers, err := DiscoverLocalPeers(*org.OrgAdminClientContext, org.OrgPeerNum)
		if err!=nil{
			return fmt.Errorf("DiscoverLocalPeers error: %v", err)
		}
		for _, p := range orgPeers {
			resp, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
				func() (interface{}, error) {
                    // LifecycleQueryCommittedCC查询给定通道上提交的链码
					resp1, err := org.OrgResMgmt.LifecycleQueryCommittedCC(channelID, req, resmgmt.WithTargets(p))
					if err != nil {
						return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned error: %v", err), nil)
					}
					flag := false
					for _, r := range resp1 {
						if r.Name == ccName && r.Sequence == sequence {
							flag = true
							break
						}
					}
					if !flag {
						return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("LifecycleQueryCommittedCC returned : %v", resp1), nil)
					}
					return resp1, err
				},
			)
			if err != nil {
				return  fmt.Errorf("NewInvoker error: %v", err)
			}
			if resp==nil{
				return fmt.Errorf("Got nil invoker response")
			}
		}
	}
	return nil
}
  • 智能合约初始化

func initCC(ccName string, upgrade bool, channelID string, org *OrgInfo, sdk *fabsdk.FabricSDK) error {
	// 准备通道客户端上下文
	clientChannelContext := sdk.ChannelContext(channelID, fabsdk.WithUser(org.OrgUser), fabsdk.WithOrg(org.OrgName))
	// 通道客户端用于查询执行交易
	client, err := channel.New(clientChannelContext)
	if err != nil {
		return fmt.Errorf("Failed to create new channel client: %s", err)
	}

	// 调用链码初始化
	_, err = client.Execute(channel.Request{ChaincodeID: ccName, Fcn: "init", Args: nil, IsInit: true},
		channel.WithRetry(retry.DefaultChannelOpts))
	if err != nil {
		return fmt.Errorf("Failed to init: %s", err)
	}
	return nil
}
  • 智能合约完整生命周期(即整合调用上述方法)

func CreateCCLifecycle(info *SdkEnvInfo, sequence int64, upgrade bool, sdk *fabsdk.FabricSDK) error {
	if len(info.Orgs) == 0 {
		return fmt.Errorf("the number of organization should not be zero.")
	}
	// 打包链码
	fmt.Println(">> 开始打包链码......")
	label, ccPkg, err := packageCC(info.ChaincodeID, info.ChaincodeVersion, info.ChaincodePath)
	if err != nil {
		return fmt.Errorf("pakcagecc error: %v", err)
	}
	packageID := lcpackager.ComputePackageID(label, ccPkg)
	fmt.Println(">> 打包链码成功")

	// 安装链码
	fmt.Println(">> 开始安装链码......")
	if err := installCC(label, ccPkg, info.Orgs); err != nil {
		return fmt.Errorf("installCC error: %v", err)
	}

	// 检索已安装链码包
	if err := getInstalledCCPackage(packageID, info.Orgs[0]); err != nil {
		return fmt.Errorf("getInstalledCCPackage error: %v", err)
	}

	// 查询已安装链码
	if err := queryInstalled(packageID, info.Orgs[0]); err != nil {
		return fmt.Errorf("queryInstalled error: %v", err)
	}
	fmt.Println(">> 安装链码成功")

	// 批准链码
	fmt.Println(">> 组织认可智能合约定义......")
	if err := approveCC(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint); err != nil {
		return fmt.Errorf("approveCC error: %v", err)
	}

	// 查询批准
	if err:=queryApprovedCC(info.ChaincodeID, sequence, info.ChannelID, info.Orgs);err!=nil{
		return fmt.Errorf("queryApprovedCC error: %v", err)
	}
	fmt.Println(">> 组织认可智能合约定义完成")

	// 检查智能合约是否就绪
	fmt.Println(">> 检查智能合约是否就绪......")
	if err:=checkCCCommitReadiness(packageID, info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs); err!=nil{
		return fmt.Errorf("checkCCCommitReadiness error: %v", err)
	}
	fmt.Println(">> 智能合约已经就绪")

	// Commit
	fmt.Println(">> 提交智能合约定义......")
	if err:=commitCC(info.ChaincodeID, info.ChaincodeVersion, sequence, info.ChannelID, info.Orgs, info.OrdererEndpoint);err!=nil{
		return fmt.Errorf("commitCC error: %v", err)
	}
	// 查询Commit结果
	if err:=queryCommittedCC(info.ChaincodeID, info.ChannelID, sequence, info.Orgs); err!=nil{
		return fmt.Errorf("queryCommittedCC error: %v", err)
	}
	fmt.Println(">> 智能合约定义提交完成")

	// 初始化
	fmt.Println(">> 调用智能合约初始化方法......")
	if err:=initCC(info.ChaincodeID, upgrade, info.ChannelID, info.Orgs[0], sdk); err!=nil{
		return fmt.Errorf("initCC error: %v", err)
	}
	fmt.Println(">> 完成智能合约初始化")
	return nil
}

六、项目启动

1. main方法

在项目的根目录下新建一个名为main.go的文件,为项目的主函数。在这里我们实现将组织通道等相关信息实例化,以及调用前面的函数实现创建通道加入通道将链码实例化。

package main

import (

	"fmt"
	"fabric-go-sdk/sdkInit"
	"os"

)
// 定义链码名称与版本
const (
	cc_name = "simplecc"
	cc_version = "1.0.0"
)
func main() {
	// 初始化组织信息
	orgs := []*sdkInit.OrgInfo{
		{
			OrgAdminUser:  "Admin",
			OrgName:       "Org1",
			OrgMspId:      "Org1MSP",
			OrgUser:       "User1",
			OrgPeerNum:    1,
			OrgAnchorFile: os.Getenv("GOPATH") + "/src/fabric-go-sdk/fixtures/channel-artifacts/Org1MSPanchors.tx",
		},
		{
			OrgAdminUser:  "Admin",
			OrgName:       "Org2",
			OrgMspId:      "Org2MSP",
			OrgUser:       "User1",
			OrgPeerNum:    1,
			OrgAnchorFile: os.Getenv("GOPATH") + "/src/fabric-go-sdk/fixtures/channel-artifacts/Org2MSPanchors.tx",
		},
	}

	// 初始化sdk相关信息
	info := sdkInit.SdkEnvInfo{
		ChannelID:        "mychannel",
		ChannelConfig:    os.Getenv("GOPATH") + "/src/fabric-go-sdk/fixtures/channel-artifacts/channel.tx",
		Orgs:             orgs,
		OrdererAdminUser: "Admin",
		OrdererOrgName:   "OrdererOrg",
		OrdererEndpoint:  "orderer.example.com",
		ChaincodeID:      cc_name,
		ChaincodePath:    os.Getenv("GOPATH") + "/src/fabric-go-sdk/chaincode/",
		ChaincodeVersion: cc_version,
	}

	// 调用setup方法将sdk初始化
	sdk, err := sdkInit.Setup("config.yaml", &info)
	if err != nil {
		fmt.Println(">> SDK setup error:", err)
		os.Exit(-1)
	}

	// 调用CreateAndJoinChannel方法,创建并加入通道
	if err := sdkInit.CreateAndJoinChannel(&info); err != nil {
		fmt.Println(">> Create channel and join error:", err)
		os.Exit(-1)
	}

	// 调用CreateCCLifecycle方法实现链码生命周期
	if err := sdkInit.CreateCCLifecycle(&info, 1, false, sdk); err != nil {
		fmt.Println(">> create chaincode lifecycle error: %v", err)
		os.Exit(-1)
	}

	// invoke chaincode set status
	fmt.Println(">> 通过链码外部服务设置链码状态......")

}

2. 添加链码文件

在项目根目录下新建一个名为chaincode的文件夹,在chaincode目录下添加一个名为sacc.go的智能合约(此智能合约为官方测试网络中的示例链码),编辑如下:

package main

import (
	"fmt"

	"github.com/hyperledger/fabric-chaincode-go/shim"
	"github.com/hyperledger/fabric-protos-go/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
	// Get the args from the transaction proposal
	args := stub.GetStringArgs()
	if len(args) != 2 {
		return shim.Error("Incorrect arguments. Expecting a key and a value")
	}

	// Set up any variables or assets here by calling stub.PutState()

	// We store the key and the value on the ledger
	err := stub.PutState(args[0], []byte(args[1]))
	if err != nil {
		return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
	}
	return shim.Success(nil)
}

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
	// Extract the function and args from the transaction proposal
	fn, args := stub.GetFunctionAndParameters()

	var result string
	var err error
	if fn == "set" {
		result, err = set(stub, args)
	} else { // assume 'get' even if fn is nil
		result, err = get(stub, args)
	}
	if err != nil {
		return shim.Error(err.Error())
	}

	// Return the result as success payload
	return shim.Success([]byte(result))
}

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
	if len(args) != 2 {
		return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
	}

	err := stub.PutState(args[0], []byte(args[1]))
	if err != nil {
		return "", fmt.Errorf("Failed to set asset: %s", args[0])
	}
	return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
	if len(args) != 1 {
		return "", fmt.Errorf("Incorrect arguments. Expecting a key")
	}

	value, err := stub.GetState(args[0])
	if err != nil {
		return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
	}
	if value == nil {
		return "", fmt.Errorf("Asset not found: %s", args[0])
	}
	return string(value), nil
}

// main function starts up the chaincode in the container during instantiate
func main() {
	if err := shim.Start(new(SimpleAsset)); err != nil {
		fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
	}
}

3. 启动项目

在命令行中进入chaincode路径,并使用以下命令为链码添加依赖包。

go mod init
go mod vendor

在命令行中进入项目根目录,并使用以下命令为项目添加依赖包。

go mod init
go mod tidy

在命令行中进入fixture路径,并使用以下命令启动网络。

docker-compose up -d

在命令行中进入项目根目录,并使用以下命令build整个项目。

go build

接着使用以下命令运行项目。

./fabric-go-sdk

运行成功后输出:

>> 开始创建通道......
>>>> 使用每个org的管理员身份更新锚节点配置...
>>>> 使用每个org的管理员身份更新锚节点配置完成
>> 创建通道成功
>> 加入通道......
>> 加入通道成功
>> 开始打包链码......
>> 打包链码成功
>> 开始安装链码......
>> 安装链码成功
>> 组织认可智能合约定义......
>>> chaincode approved by Org1 peers:
        peer0.org1.example.com:7051
>>> chaincode approved by Org2 peers:
        peer0.org2.example.com:9051
>> 组织认可智能合约定义完成
>> 检查智能合约是否就绪......
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org2MSP:true]}
>> 智能合约已经就绪
>> 提交智能合约定义......
>> 智能合约定义提交完成
>> 调用智能合约初始化方法......
>> 完成智能合约初始化
>> 通过链码外部服务设置链码状态......

Last updated