fabric-sdk-go的简单使用

使用fabric-sdk-go之前,需要安装好go环境,docker以及docker compose环境,还有 hyperledger fabric 环境.

一. 创建crypto-config.yaml

使用fabric提供的cryptogen工具生成文件模板
$ cryptogen showtemplate > crypto-config.yaml
进行修改,添加一个组织,一个orderer节点.

OrdererOrgs:

  - Name: Orderer
    Domain: xq.com
    Specs:
      - Hostname: orderer

PeerOrgs:

  - Name: Travle
    Domain: travle.xq.com
    EnableNodeOUs: false

    Template:
      Count: 2

    Users:
      Count: 2

根据crypto-config.yaml文件生成证书文件:
$ cryptogen generate --config=crypto-config.yaml

查看生成的证书文件夹结构:

.
├── ordererOrganizations
│   └── xq.com
│       ├── ca
│       │   ├── 1d8deec1977f7abf81692e72c06861e811c00b34278b48c9fe44dc51238d8621_sk
│       │   └── ca.xq.com-cert.pem
│       ├── msp
│       │   ├── admincerts
│       │   ├── cacerts
│       │   └── tlscacerts
│       ├── orderers
│       │   └── orderer.xq.com
│       ├── tlsca
│       │   ├── 5b189397d9390ae07146a411f0e7b6c5bf1fdb809d4b4d51b6f25b739a7cc127_sk
│       │   └── tlsca.xq.com-cert.pem
│       └── users
│           └── [email protected]
└── peerOrganizations
    └── travle.xq.com
        ├── ca
        │   ├── 8ae1b694ae88d19d07fd698b0990b8d4f3bc46782fbfb3620e3e6e3fb0263e1c_sk
        │   └── ca.travle.xq.com-cert.pem
        ├── msp
        │   ├── admincerts
        │   ├── cacerts
        │   └── tlscacerts
        ├── peers
        │   ├── peer0.travle.xq.com
        │   └── peer1.travle.xq.com
        ├── tlsca
        │   ├── d6e2633fe6f35345eca10bc0231a2b34f127c2b9ebc8a86b582b412e875e513c_sk
        │   └── tlsca.travle.xq.com-cert.pem
        └── users
            ├── [email protected]
            ├── [email protected]
            └── [email protected]

二. 生成创世区块文件和 通道

需要从fabric的源码案例中拷贝configtx.yaml文件
$ cp $GOPATH/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml ./
对configtx.yaml文件进行修改.
修改之前,创建一个文件夹,来保存即将创建的创世区块文件

$ mkdir channel-artifacts

将创建区块文件和通道的命令写到一个脚本中! generate.sh

rm -rf ./channel-artifacts/*
rm -rf ./crypto-config/*

#根据crypto-config.yaml文件生成证书
cryptogen generate --config=./crypto-config.yaml

# 生成创始块文件
echo "---------------- Create genesis.block file BEGIN --------------------"
configtxgen -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block
echo "---------------- Create genesis.block file END --------------------"

# 生成 travlechannel 文件
echo "---------------- Create travlechannel.tx file BEGIN -------------------"
configtxgen -profile TravleOrgsChannel -outputCreateChannelTx ./channel-artifacts/travlechannel.tx -channelID travlechannel
echo "---------------- Create travlechannel.tx file END -------------------"

# 生成更新锚节点文件
echo "---------------- Create TravleMSPanchors.tx file BEGIN -------------------"
configtxgen -profile TravleOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/TravleMSPanchors.tx -channelID travlechannel -asOrg TravleMSP
echo "---------------- Create TravleMSPanchors.tx file END -------------------"

脚本文件和配置文件的目录结构:

├── channel-artifacts
├── configtx.yaml
├── crypto-config
│   ├── ordererOrganizations
│   └── peerOrganizations
├── crypto-config.yaml
└── generate.sh

执行generate.sh文件生成创世区块文件和通道,其实只有一个组织,也没必要生成锚节点更新文件..
$ ./generate.sh

三. 通过 docker-compose 启动容器

配置docker-compose文件:

version: '2'

networks:
  xq_travle:

services:
  orderer.xq.com:
    image: hyperledger/fabric-orderer:latest
    container_name: orderer.xq.com
    environment:
      - ORDERER_GENERAL_LOGLEVEL=debug
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_LISTENPORT=7050
      - ORDERER_GENERAL_GENESISPROFILE=Orderer
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/var/hyperledger/orderer/orderer.genesis.block
      - ORDERER_GENERAL_LOCALMSPID=xq.com
      - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp
      - 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]

    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/xq.com/orderers/orderer.xq.com/msp:/var/hyperledger/orderer/msp
      - ./crypto-config/ordererOrganizations/xq.com/orderers/orderer.xq.com/tls:/var/hyperledger/orderer/tls
    ports:
      - 7050:7050
    networks:
      - xq_travle

  peer0.travle.xq.com:
    image: hyperledger/fabric-peer:latest
    container_name: peer0.travle.xq.com
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_VM_DOCKER_ATTACHSTDOUT=true
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/var/hyperledger/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/var/hyperledger/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/var/hyperledger/tls/ca.crt
      - CORE_PEER_ID=peer0.travle.xq.com
      - CORE_PEER_ADDRESSAUTODETECT=true
      - CORE_PEER_ADDRESS=peer0.travle.xq.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.travle.xq.com:7051
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
      - CORE_PEER_LOCALMSPID=travle.xq.com
      - CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp
      - CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer0.travle.xq.com
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    volumes:
      - /var/run/:/host/var/run/
      - ./crypto-config/peerOrganizations/travle.xq.com/peers/peer0.travle.xq.com/msp:/var/hyperledger/msp
      - ./crypto-config/peerOrganizations/travle.xq.com/peers/peer0.travle.xq.com/tls:/var/hyperledger/tls
    ports:
      - 7051:7051
      - 7053:7053
    depends_on:
      - orderer.xq.com
    links:
      - orderer.xq.com
    networks:
      - xq_travle

  peer1.travle.xq.com:
    image: hyperledger/fabric-peer:latest
    container_name: peer1.travle.xq.com
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_VM_DOCKER_ATTACHSTDOUT=true
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_PROFILE_ENABLED=true
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/var/hyperledger/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/var/hyperledger/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/var/hyperledger/tls/ca.crt
      - CORE_PEER_ID=peer1.travle.xq.com
      - CORE_PEER_ADDRESSAUTODETECT=true
      - CORE_PEER_ADDRESS=peer1.travle.xq.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.travle.xq.com:7051
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - CORE_PEER_GOSSIP_SKIPHANDSHAKE=true
      - CORE_PEER_LOCALMSPID=travle.xq.com
      - CORE_PEER_MSPCONFIGPATH=/var/hyperledger/msp
      - CORE_PEER_TLS_SERVERHOSTOVERRIDE=peer1.travle.xq.com
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    volumes:
      - /var/run/:/host/var/run/
      - ./crypto-config/peerOrganizations/travle.xq.com/peers/peer1.travle.xq.com/msp:/var/hyperledger/msp
      - ./crypto-config/peerOrganizations/travle.xq.com/peers/peer1.travle.xq.com/tls:/var/hyperledger/tls
    ports:
      - 8051:7051
      - 8053:7053
    depends_on:
      - orderer.xq.com
    links:
      - orderer.xq.com
    networks:
      - xq_travle

启动容器, 启动后查看容器运行情况
$ docker-compose up -d
$ docker-compose ps

       Name               Command       State                       Ports                     
----------------------------------------------------------------------------------------------
orderer.xq.com        orderer           Up      0.0.0.0:7050->7050/tcp                        
peer0.travle.xq.com   peer node start   Up      0.0.0.0:7051->7051/tcp, 0.0.0.0:7053->7053/tcp
peer1.travle.xq.com   peer node start   Up      0.0.0.0:8051->7051/tcp, 0.0.0.0:8053->7053/tcp

在这里,创建两个脚本文件,用于docker容器的管理
clear_docker.sh 文件:

sudo docker rm -f $(sudo docker ps -aq) # 清除容器们
sudo docker network prune # 来清理没有再被任何容器引用的networks
sudo docker volume prune  # 清理挂载卷

restart.sh 文件:

./clear_docker.sh    # 执行clear_docker.sh脚本文件
docker-compose up -d

四. 创建sdk配置文件

创建配置文件的时候,有两个文件可以进行参考...

$GOPATH/src/github.com/hyperledger/fabric-sdk-go/test/fixtures/config/config_test.yaml
$GOPATH/src/github.com/hyperledger/fabric-sdk-go/pkg/core/config/testdata/template/config.yaml

修改后的sdk配置文件:

name: "driver-service-network"

version: 1.1.0

client:
  organization: Travle
  logging:
    level: info
  cryptoconfig:
    path: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config
  credentialStore:
    path: /tmp/driverStore

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

  tlsCerts:
    systemCertPool: true
    client:
      keyfile: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/peerOrganizations/travle.xq.com/users/[email protected]/tls/client.key
      certfile: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/peerOrganizations/travle.xq.com/users/[email protected]/tls/client.crt

channels:
  travlechannel:
    orderers:
      - orderer.xq.com

    peers:
      peer0.travle.xq.com:
        endorsingPeer: true
        chaincodeQuery: true
        ledgerQuery: true
        eventSource: true

      peer1.travle.xq.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:
  travle:
    # configtx.yaml organizations -> ID
    mspid: travle.xq.com

    cryptoPath: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/peerOrganizations/travle.xq.com/users/{username}@travle.xq.com/msp
    peers:
    - peer0.travle.xq.com
    - peer1.travle.xq.com

  ordererorg:
    mspID: xq.com
    cryptoPath: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/ordererOrganizations/xq.com/users/[email protected]/msp

orderers:
  orderer.xq.com:
    url: localhost:7050
    grpcOptions:
      ssl-target-name-override: orderer.xq.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false

    tlsCACerts:
      path: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/ordererOrganizations/xq.com/tlsca/tlsca.xq.com-cert.pem
peers:
  peer0.travle.xq.com:
    url: grpcs://localhost:7051
    eventUrl: localhost:7053
    grpcOptions:
      ssl-target-name-override: peer0.travle.xq.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/peerOrganizations/travle.xq.com/tlsca/tlsca.travle.xq.com-cert.pem

  peer1.travle.xq.com:
    url: grpcs://localhost:8051
    eventUrl: localhost:8053
    grpcOptions:
      ssl-target-name-override: peer1.travle.xq.com
      keep-alive-time: 0s
      keep-alive-timeout: 20s
      keep-alive-permit: false
      fail-fast: false
      allow-insecure: false
    tlsCACerts:
      path: /Users/xq_mac/Go/src/driverFabricDemo/conf/crypto-config/peerOrganizations/travle.xq.com/tlsca/tlsca.travle.xq.com-cert.pem

五. 实例化sdk

  • 5.1 在工程中定义一个模型,来维护实例化sdk所需要的变量
type FabricModel struct {
    ConfigFile  string //sdk的配置文件路径
    ChainCodeID string // 链码名称
    ChaincodePath string  // 链码在工程中的存放目录
    ChaincodeGoPath string  // GOPATH
    OrgAdmin string // 组织的管理员用户
    OrgName  string // config.yaml ---> organizations ---> travle
    OrgID    string // 组织id
    UserName string // 组织的普通用户
    ChannelID string // 通道id
    ChannelConfigPath string //组织的通道文件路径
    OrdererName string  // config.yaml ---> orderers ---> orderer.xq.com // 将组织添加到通道时候使用!
    Sdk        *fabsdk.FabricSDK // 保存实例化后的sdk
    ResMgmtCli *resmgmt.Client // 资源管理客户端,也需要在安装链码时候的使用
    Channelclient     *channel.Client // 通道客户端
    HasInit  bool // 是否已经初始化了sdk
}

创建出一个模型对象,给其赋值,并开始初始化sdk

func init() {
    fs := FabricModel{
        OrdererName: "orderer.xq.com",
        ChannelID:     "travlechannel",
        ChannelConfigPath: os.Getenv("GOPATH") + "/src/driverFabricDemo/conf/channel-artifacts/travlechannel.tx",
        ChainCodeID:     "mycc",
        ChaincodeGoPath: os.Getenv("GOPATH"),
        ChaincodePath:   "driverFabricDemo/chaincode",
        OrgAdmin:        "Admin",
        OrgName:         "travle",
        ConfigFile:      "conf/config.yaml",
        UserName: "User1",
    }
    // 实例化SDK  创建通道, 将组织节点加入到通道
    fs.Initialization()
}

使用 pkg/fabsdk/fabsdk.go中的New()方法进行实例化

sdk, err := fabsdk.New(config.FromFile(this.ConfigFile))

  • 5.2 根据实例创建资源管理客户端
resCliProvider := sdk.Context(fabsdk.WithUser(this.OrgAdmin),fabsdk.WithOrg(this.OrgName))
resClient, err := resmgmt.New(resCliProvider)
  • 5.3 创建channel. 得到一个操作链代码的客户端
    使用pkg/client/resmgmt/resmgmt.go文件中的方法
// 先创建一个 创建channel的请求
chanReq := resmgmt.SaveChannelRequest{
        ChannelID: this.ChannelID,
        ChannelConfigPath: this.ChannelConfigPath,
}
// 利用 resClient 创建 channel
chanRsp, err := resClient.SaveChannel(chanReq)
  • 5.4 把组织添加到 channel 中, 一般制定一些重试的策略,和指定 orderer节点的网络位置
err = resClient.JoinChannel(
        this.ChannelID,
        resmgmt.WithRetry(retry.DefaultResMgmtOpts),
        resmgmt.WithOrdererEndpoint(this.OrdererName),
)

六. 安装链码,实例化链码

  • 6.1 在工程中创建一个chaincode文件夹,用来存在链码,写一个最简单的链码,实现写入数据和读取数据的功能
package main

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

type ChaincodeXQ struct {

}

func (this * ChaincodeXQ)Init(stub shim.ChaincodeStubInterface) pb.Response  {

    return shim.Success([]byte("初始化成功"))
}

func (this * ChaincodeXQ)Invoke(stub shim.ChaincodeStubInterface) pb.Response  {

    // 获取方法和参数
    fname, params := stub.GetFunctionAndParameters()
    if fname == "set" { // 插入操作
        return set(stub,params[0],params[1])
    }else if fname == "get" { //获取操作

        return get(stub,params[0])
    }
    return shim.Error("Invoke 操作失败!!")
}

func set(stub shim.ChaincodeStubInterface, key string, value string) pb.Response {

    err := stub.PutState(key,[]byte(value))
    if err != nil {
        return shim.Error("PutState 操作失败!!")
    }
    return shim.Success([]byte("PutState 操作成功!!"))
}

func get(stub shim.ChaincodeStubInterface, key string) pb.Response {

    data, err := stub.GetState(key)
    if err != nil {
        return shim.Error("GetState 操作失败!!")
    }
    // If the key does not exist in the state database, (nil, nil) is returned.
    if data == nil {
        return shim.Error("GetState 操作失败!!, data == nil")
    }
    return shim.Success(data)
}

func main()  {

    err := shim.Start(new(ChaincodeXQ))
    if err != nil {
        fmt.Println("开启链码失败,err:",err)
        return
    }
    fmt.Println("开启链码成功")
}
  • 6.2 安装链码
    安装链码之前需要创建请求
InstallCCRequest := resmgmt.InstallCCRequest{
        Name:this.ChainCodeID, // 链码名称
        Path:this.ChaincodePath, //链码在工程中的路径
        Version:"0",
        Package:ccp,
}

创建请求之前,需要使用 gopackager.NewCCPackage 方法生成一个resource.CCPackage 对象,传递两个参数,一个是链码的路径(相对于工程的路径), 一个是GOPATH的路径.

ccp, err := gopackager.NewCCPackage(this.ChaincodePath,this.ChaincodeGoPath)

安装链码,使用pkg/client/resmgmt/resmgmt.go文件中的方法

req2Arr, err := this.ResMgmtCli.InstallCC(InstallCCRequest)
  • 6.3 实例化链码
    实例化链码之前需要创建请求
rsp1 := resmgmt.InstantiateCCRequest{
        Name:this.ChainCodeID,// 链码名称
        Path:this.ChaincodeGoPath,//链码在工程中的路径
        Version:"0",
        Args:nil,
        Policy:ccpolity,
}

创建请求之前,需要生成一个*cb.SignaturePolicyEnvelope类型的对象,使用 third_party/github.com/hyperledger/fabric/common/cauthdsl/cauthdsl_builder.go 文件中的方法即可,提供了好几个方法, 使用任意一个即可.这里使用 SignedByAnyMember 方法: 需要传入所属组织ID

ccpolity := cauthdsl.SignedByAnyMember([]string{this.OrgID})

实例化链码

txID,err := this.ResMgmtCli.InstantiateCC(this.ChannelID,rsp1)
  • 6.4 再创建一个通道的客户端,后面的调用链码需要使用这个客户端
    使用的是 pkg/client/channel/chclient.go 中的方法
// 创建上下文
clientContext := this.Sdk.ChannelContext(this.ChannelID, fabsdk.WithUser(this.UserName))
// 创建channel客户端
channelclient, err := channel.New(clientContext)

七. 调用链码

使用 pkg/client/channel/chclient.go 中的 Execute()方法,来进行数据写入的操作:
rsp, err := model.Channelclient.Execute(req)
写入之前,要创建请求:

req := channel.Request{
        ChaincodeID:model.ChainCodeID, // 链码名称
        Fcn:args[0], // 方法名:  get
        Args:tempArgs, // 传递的参数,是一个二维字符切片  [[49] [49 49 49 49]]  
}

tempArgs是要传给链码的参数,可以做下封装,就不受参数个数的限制了

var tempArgs [][]byte
    for i := 1; i < len(args); i++ {
        tempArgs = append(tempArgs, []byte(args[i]))
}

使用 pkg/client/channel/chclient.go 中的 Query()方法,来进行数据查询的操作: 查询之前,同样需要创建请求.

req := channel.Request{
        ChaincodeID: model.ChainCodeID, 
        Fcn: args[0], 
        Args: tempArgs,
    }
rsp, err := model.Channelclient.Query(req)

遇到的问题

1 链码路径错误

Chaincode status Code: (500) UNKNOWN. Description: error starting container: error starting container: Failed to generate platform-specific docker build: Error returned from build: 1 "can't load package: package chaincode: cannot find package "chaincode" in any of:
        /opt/go/src/chaincode (from $GOROOT)
        /chaincode/input/src/chaincode (from $GOPATH)
        /opt/gopath/src/chaincode

链码在工程中的路径应该是 工程名/chaincode文件夹
比如:
driverFabricDemo/chaincode
而不应该省略掉工程名这样写:chaincode

2 go版本的问题

Failed to build the application: # github.com/cloudflare/cfssl/csr
../github.com/cloudflare/cfssl/csr/csr.go:272:26: cert.URIs undefined (type *x509.Certificate has no field or method URIs)
../github.com/cloudflare/cfssl/csr/csr.go:387:7: tpl.URIs undefined (type x509.CertificateRequest has no field or method URIs)

错误原因:cert.URIs 和 tpl.URIs 这两个字段没有被定义.
进入tpl对象中,/usr/local/go/src/crypto/x509/x509.go 是个结构体,并没有发现URIs字段

对go版本进行升级,从1.9.3升级到1.11.3, 再次进入 /usr/local/go/src/crypto/x509/x509.go 文件中,查看结构体内容:

 // Subject Alternate Name values.
   DNSNames       []string
   EmailAddresses []string
   IPAddresses    []net.IP
   URIs           []*url.URL

3 写入数据到区块链时错误

Transaction processing for endorser [localhost:7051]: Chaincode status Code: (500) UNKNOWN. 
Description: Received unknow function invocatio

在执行sdk的Excute()方法时报错.
方法不存在,一般是由于链码的Invoke方法中的方法名和Excute()方法传入的方法名不一样.
但是可以肯定的是,链码的Invoke方法中的方法名和,项目中执行Excute()方法时传入的方法名是完全一样的! 但是很奇怪了,为什么会出现这个错误呢? 使用docker rmi 删除掉dev-peerx.travle.xq.com 的镜像,再重新运行即可.

4 Cannot use str (type *cb.SignaturePolicyEnvelope) as type *common.SignaturePolicyEnvelope

在创建实例化链码请求的时候

ccpolity := cauthdsl.SignedByAnyMember([]string{this.OrgID})
rsp1 := resmgmt.InstantiateCCRequest{
   Name:this.ChainCodeID,
   Path:this.ChaincodeGoPath,
   Version:"1.0",
   Args:nil,
   Policy:ccpolity,
}

总是提示
Cannot use str (type *cb.SignaturePolicyEnvelope) as type *common.SignaturePolicyEnvelope less... (⌘F1) Inspection info: Reports composite literals with incompatible types and values
明明是相同的类型,却总是报错,应该是IDE的问题.把vendor文件夹删除后,就不会有提示了. 再使用vendor对工程进行init 和 add +external 就好了!!

5 CONNECTION_ Description: dialing connection timed out

出现这个错误,一般都是配置文件哪个地方写错了,需要细心检查

你可能感兴趣的:(fabric-sdk-go的简单使用)