# Hyperledger Fabric多机搭建

### 多机搭建前准备

本教程使用的是Ubuntu操作系统，所需要的环境包括`docker`的安装`golang`的安装`fabric`的安装等。

#### 1. 网络结构

我们要搭建一个多机多节点的网络，结构如下。网络中有两个组织分别为`org1`、`org2`，每个组织各有一个`peer`节点，同时还有一个`orderer`节点。

| 名称        | IP          | hosts                  | 组织结构    |
| --------- | ----------- | ---------------------- | ------- |
| Orderer   | 172.17.0.10 | orderer.example.com    | orderer |
| Org1peer0 | 172.17.0.11 | peer0.org1.example.com | org1    |
| Org2peer0 | 172.17.0.12 | peer0.org2.example.com | org2    |

#### 2. 设置网络host

首先右键打开终端，然后使用以下命令，我们在三台虚拟机中分别查看当前虚拟机的IP，其中最后一行为本机IP。

```shell
ifconfig
```

其中`ens3`一栏中的inet地址为本机IP地址。

配置所有服务器网络host,在三台虚拟机中都进行以下操作。

```
vi /etc/hosts
```

在最后插入（IP与host任意指定，确定后不能更改）。

```
172.17.0.10 orderer.example.com
172.17.0.11 peer0.org1.example.com
172.17.0.12 peer0.org2.example.com
```

#### 3. ssh配置

在多机搭建的过程中我们会使用到`scp`命令。Linux `scp` 命令用于 Linux 之间复制文件和目录。

`scp` 是 `secure copy` 的缩写, `scp` 是 Linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令。

以下内容需要在三台虚拟机中都操作。

安装好ssh后输入以下命令切换到`root`账户。

```
sudo su
```

输入以下命令设置`root`账户密码。

```
passwd root
```

输入要修改的`root`用户密码，输出信息如下：

```
Changing password for user root.New password: BAD PASSWORD: The password is shorter than 8 charactersRetype new password: passwd: all authentication tokens updated successfully.
```

现在我们就可以使用`scp`命令来跨主机传输文件了。

### 生成Fabric证书

#### 1. 创建项目目录

在三台虚拟机上使用以下命令创建相同的项目目录（三台虚拟机项目路径要相同）。

```
cd ~/hyperledgermkdir multinodes
```

#### 2. 编写证书文件

首先使用以下命令进入项目目录。

```
cd ~/hyperledger/multinodes
```

使用以下命令将模板文件复制到当前目录下。

```
cryptogen showtemplate > 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   
```

#### 3. 生成证书文件

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

```
cryptogen generate --config=crypto-config.yaml
```

使用`ls`命令查看生成的文件，可以看到生成了`crypto-config`文件，这里存放所有的证书文件。

```
ls
```

```
crypto-config  crypto-config.yaml
```

使用`scp`命令将证书文件复制到其他两台虚拟机中（使用`scp`命令时会要求输入主机密码）。

```
scp -r ./crypto-config root@172.17.0.11:~/hyperledger/multinodes/
```

```
scp -r ./crypto-config root@172.17.0.12:~/hyperledger/multinodes/
```

复制后使用以下命令在其他两台虚拟机的`multinodes`目录下查看是否复制成功。

```
ls
```

```
crypto-config
```

### 生成通道文件

#### 1. 创世块文件的编写

首先回到orderer节点的虚拟机。

首先我们可以参考官方示例项目`test-network`中的`configtx.yaml`配置文件。

将`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.admin', 'Org1MSP.peer', 'Org1MSP.client')"
            Writers:
                Type: Signature
                Rule: "OR('Org1MSP.admin', 'Org1MSP.client')"
            Admins:
                Type: Signature
                Rule: "OR('Org1MSP.admin')"
            Endorsement:
                Type: Signature
                Rule: "OR('Org1MSP.peer')"
        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.admin', 'Org2MSP.peer', 'Org2MSP.client')"
            Writers:
                Type: Signature
                Rule: "OR('Org2MSP.admin', 'Org2MSP.client')"
            Admins:
                Type: Signature
                Rule: "OR('Org2MSP.admin')"
            Endorsement:
                Type: Signature
                Rule: "OR('Org2MSP.peer')"

        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:

    Policies:
        Readers:
            Type: ImplicitMeta
            Rule: "ANY Readers"
        Writers:
            Type: ImplicitMeta
            Rule: "ANY Writers"
        Admins:
            Type: ImplicitMeta
            Rule: "MAJORITY Admins"
        LifecycleEndorsement:
            Type: ImplicitMeta
            Rule: "MAJORITY Endorsement"
        Endorsement:
            Type: ImplicitMeta
            Rule: "MAJORITY Endorsement"

    Capabilities:
        <<: *ApplicationCapabilities

Orderer: &OrdererDefaults

    OrdererType: solo 

    Addresses:
        - orderer.example.com:7050

    EtcdRaft:
        Consenters:
        - Host: orderer.example.com
          Port: 7050
          ClientTLSCert: ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
          ServerTLSCert: ../organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt

    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

```

与单节点搭建的区别：

* Organizations部分多了Org2的配置。
* Profiles的部分创世块名称与通道名称不同。单节点搭建部分为soloOrgsOrdererGenesis和soloOrgsChannel，多节点搭建部分为TwoOrgsOrdererGenesis和TwoOrgsChannel。（创世块名称与通道名称自己任意取，但是后面使用命令生成文件时命令要与配置文件所定义的名称一致）
* Profiles部分创世块配置与通道配置中都多加入了Org2。

#### 2. 生成创世块文件和通道文件

使用以下命令生成创世区块。

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

使用以下命令生成通道文件。

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

使用以下命令为 Org1 定义锚节点。

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

使用以下命令为 Org2 定义锚节点。

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

使用以下命令将生成的文件拷贝到另两台主机（过程中会需要输入宿主机的密码）。

```
scp -r ./channel-artifacts root@172.17.0.11:~/hyperledger/multinodes/scp -r ./channel-artifacts root@172.17.0.11:~/hyperledger/multinodes/
```

复制后使用以下命令在其他两台虚拟机的`multinodes`目录下查看是否复制成功。

```
ls
```

```
channel-artifacts
```

### docker-compose文件编写

在单节点中我们编写过一个docker-compose文件，在其中我们配置了orderer节点与peer节点。在多机部署的时候我们需要为每台虚拟机都编写一个docker-compose文件来配置相应的节点。多机部署与单机部署的配置文件内容大致相同，下面会介绍单机与多机的异同点。

#### 1. orderer节点

使用以下命令在orderer节点的虚拟机的项目路径上创建一个`docker-compose.yaml`文件。

```
cd ~/hyperledger/multinodesvim docker-compose.yaml
```

写入以下内容后，保存退出文件。

```
version: '2'

services:
  orderer.example.com:
    container_name: orderer.example.com
    image: hyperledger/fabric-orderer:latest
    environment:
      - FABRIC_LOGGING_SPEC=INFO
      - 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
      - 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
    ports:
      - 7050:7050
    extra_hosts:
      - "orderer.example.com:172.17.0.10"
      - "peer0.org1.example.com:172.17.0.11"
      - "peer0.org2.example.com:172.17.0.12"

```

与单机搭建的不同：

* 没有了卷挂载目录`orderer.example.com:/var/hyperledger/production/orderer`。
* 单机搭建中的网络名`networks: - example`改为`extra_hosts:`，因为我们是多机搭建有真实的IP，所以网络名称都改为真实的IP地址。

#### 2. org1

Fabric中peer节点的世界状态数据库默认是Leveldb，在这个部分我们将使用Couchdb。

Fabric的状态存储支持可插拔的模式，兼容LevelDB、CouchDB等存储。Fabric使用CouchDB作为状态存储与其他数据库相比具有较多优势：

* CouchDB是一种NoSQL解决方案。它是一个面向文档的数据库，其中文档字段存储为键值映射。字段可以是简单的键值对、列表或映射。除了支持类似LevelDB的键控/合成键/键范围查询之外，CouchDB还支持完整的数据富查询功能，比如针对整个区块链数据的非键查询，因为它的数据内容是以JSON格式存储的，并且是完全可查询的。因此，CouchDB可以满足LevelDB不支持的许多用例的链代码、审计和报告需求。
* CouchDB还可以增强区块链中的遵从性和数据保护的安全性。因为它能够通过筛选和屏蔽事务中的各个属性来实现字段级别的安全性，并且只在需要时授权只读权限。
* CouchDB属于CAP定理的ap类型(可用性和分区公差)。它使用具有最终一致性的主-主复制模型。更多信息可以在CouchDB文档的最终一致性页面上找到。然而，在每个fabric对等点下，没有数据库副本，对数据库的写操作保证一致和持久(而不是最终的一致性)。
* CouchDB是Fabric的第一个外部可插入状态数据库，可以而且应该有其他外部数据库选项。例如，IBM为其区块链启用关系数据库。还可能需要cp类型(一致性和分区容忍度)的数据库，以便在不保证应用层的情况下实现数据一致性。

使用以下命令在org1节点的虚拟机的项目路径上创建一个`docker-compose.yaml`文件。

```
cd ~/hyperledger/multinodesvim docker-compose.yaml
vim docker-compose.yaml
```

```
version: '2'

services:
  couchdb0.org1.example.com:
    container_name: couchdb0.org1.example.com
    image: couchdb:3.1
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=adminpw
    ports:
      - 5984:5984

  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer:latest
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - 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
      - FABRIC_LOGGING_SPEC=INFO
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - 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
      - CORE_CHAINCODE_EXECUTETIMEOUT=300s
      - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0.org1.example.com:5984
      - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
      - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
    depends_on:
      - couchdb0.org1.example.com

    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    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
    ports:
      - 7051:7051
      - 7052:7052
      - 7053:7053
    extra_hosts:
      - "orderer.example.com:172.17.0.10"
      - "peer0.org1.example.com:172.17.0.11"
      - "peer0.org2.example.com:172.17.0.12"
  cli:
    container_name: cli
    image: hyperledger/fabric-tools:latest
    tty: true
    stdin_open: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - FABRIC_LOGGING_SPEC=INFO
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash
    volumes:
      - /var/run/:/host/var/run/
      - ./chaincode/go/:/opt/gopath/src/github.com/hyperledger/fabric-cluster/chaincode/go
      - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
      - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    extra_hosts:
      - "orderer.example.com:172.17.0.10"
      - "peer0.org1.example.com:172.17.0.11"
      - "peer0.org2.example.com:172.17.0.12"

```

与单机搭建的不同：

* 多了couchdb的配置。
* peer0节点环境变量多了`CORE_LEDGER_STATE_STATEDATABASE=CouchDB`，表示peer0节点的状态数据库采用了couchdb。
* 多了`depends_on: - couchdb0.org1.example.com`，表示在couchdb启动后再启动peer0节点。
* 单机搭建中的网络名`networks: - example`改为`extra_hosts:`，因为我们是多机搭建有真实的IP，所以网络名称都改为真实的IP地址。

#### 3. org2

组织二的配置文件与组织一基本相同，唯一不同点是把org1改为org2。

使用以下命令在org2节点的虚拟机的项目路径上创建一个`docker-compose.yaml`文件。

```
cd ~/hyperledger/multinodesvim docker-compose.yaml
```

写入以下内容后，保存退出文件。

```
version: '2'

services:
  couchdb0.org2.example.com:
    container_name: couchdb0.org2.example.com
    image: couchdb:3.1
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=adminpw
    ports:
      - 5984:5984

  peer0.org2.example.com:
    container_name: peer0.org2.example.com
    image: hyperledger/fabric-peer:latest
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_PEER_ID=peer0.org2.example.com
      - CORE_PEER_ADDRESS=peer0.org2.example.com:7051
      - CORE_PEER_LISTENADDRESS=0.0.0.0:7051
      - CORE_PEER_CHAINCODEADDRESS=peer0.org2.example.com:7052
      - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052
      - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org2.example.com:7051
      - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:7051
      - CORE_PEER_LOCALMSPID=Org2MSP
      - FABRIC_LOGGING_SPEC=INFO
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_GOSSIP_USELEADERELECTION=true
      - CORE_PEER_GOSSIP_ORGLEADER=false
      - 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
      - CORE_CHAINCODE_EXECUTETIMEOUT=300s
      - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb0.org2.example.com:5984
      - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
      - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
    depends_on:
      - couchdb0.org2.example.com

    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start
    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
    ports:
      - 7051:7051
      - 7052:7052
      - 7053:7053
    extra_hosts:
      - "orderer.example.com:172.17.0.10"
      - "peer0.org1.example.com:172.17.0.11"
      - "peer0.org2.example.com:172.17.0.12"
  cli:
    container_name: cli
    image: hyperledger/fabric-tools:latest
    tty: true
    stdin_open: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - FABRIC_LOGGING_SPEC=INFO
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org2.example.com:7051
      - CORE_PEER_LOCALMSPID=Org2MSP
      - CORE_PEER_TLS_ENABLED=true
      - CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.crt
      - CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/server.key
      - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash
    volumes:
      - /var/run/:/host/var/run/
      - ./chaincode/go/:/opt/gopath/src/github.com/hyperledger/fabric-cluster/chaincode/go
      - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
      - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    extra_hosts:
      - "orderer.example.com:172.17.0.10"
      - "peer0.org1.example.com:172.17.0.11"
      - "peer0.org2.example.com:172.17.0.12"

```

使用`docker-compose`启动服务(三台机器均需要)。

```
docker-compose up -ddocker ps -a
```

### 通道操作

本节主要介绍的`peer channel`命令，`peer channel`命令主要是用于创建通道以及节点加入通道。

#### 1. 创建通道

使用`docker exec`命令进入客户端容器（在Org1主机上操作）。

```
docker exec -it cli bash
```

使用以下命令在客户端容器中创建通道（在Org1容器上操作）。

```
peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pem
```

* `-o`,`--orderer`: orderer节点的地址。
* `-c`, `--channelID`: 要创建的通道的ID, 必须小写, 在250个字符以内。
* `-f`, `-file`: 由`configtxgen`生成的通道文件, 用于提交给orderer。
* `-t`, `--timeout`: 创建通道的超时时长, 默认为5s。
* `--tls`: 通信时是否使用tls加密。
* `--cafile`: 当前orderer节点pem格式的tls证书文件, 要使用绝对路径。
* orderer节点pem格式的tls证书文件路径为：`crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem。`

使用`ls`命令查看生成的文件（在Org1容器上操作）。

```
ls
```

```
channel-artifacts  crypto  mychannel.block
```

使用以下命令将通道文件 mychannel.block 拷贝到宿主机（在Org1主机上操作）。

```
docker cp cli:/opt/gopath/src/github.com/hyperledger/fabric/peer/mychannel.block ./
```

然后使用以下命令拷贝到其他服务器上用于其他节点加入通道（在Org1主机上操作）。

```
scp mychannel.block root@172.17.0.12:~/hyperledger/multinodes/
```

使用以下命令将通道文件拷贝到容器中（在Org2主机上操作）。

```
docker cp mychannel.block cli:/opt/gopath/src/github.com/hyperledger/fabric/peer/
```

使用以下命令进入 `cli`容器（在Org2主机上操作）。

```
docker exec -it cli bash
```

#### 2. 加入通道

将每个组织的每个节点都加入到通道中需要客户端来完成，一个客户端同时只能连接一个peer节点, 如果想要该客户端连接其他节点, 那么就必须修改当前客户端中相关的环境变量。我们当前在`docker-compose.yaml`文件中所配置的`cli`连接的是Go组织的`peer0`节点。

使用以下命令让`peer0`节点加入通道（在Org1和Org2容器上操作）。

```
peer channel join -b mychannel.block
```

`-b`, `--blockpath`: `block`文件路径（通过 peer channel create 命令生成的通道文件）。

输出如下，此时组织的`peer0`已经加入通道。

```
 -> INFO 002 Successfully submitted proposal to join channel
```

#### 3. 更新锚节点

使用以下命令来更新锚节点（在org1容器上操作）。

```
peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
```

使用以下命令来更新锚节点（在org2容器上操作）

```
peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
```

* `-o`,`--orderer`:`orderer`节点的地址。
* `-c`, `--channelID`: 要创建的通道的ID, 必须小写, 在250个字符以内。
* `-f`, `-file`: 由`cryptogen`生成的锚节点文件。

### 安装调用智能合约

进入org1虚拟机。

首先我们使用以下命令在项目路径下创建一个文件夹名为`chaincode`。

```
mkdir chaincode
```

然后使用以下命令将官方示例的智能合约复制到我们刚刚创建的`chaincode`文件夹中。

```
cd ~/hyperledger/fabric-samples/chaincode
cp -r sacc ~/hyperledger/multinodes/chaincode/go/
```

使用以下命令进入容器。

```
docker exec -it cli bash
```

使用以下命令进入链码所在目录。

```
cd /opt/gopath/src/github.com/hyperledger/fabric-cluster/chaincode/go/sacc
```

使用以下命令设置go语言依赖包。

```
go env -w GOPROXY=https://goproxy.cn,direct
go mod vendor
```

使用以下命令回到peer目录下。

```
cd /opt/gopath/src/github.com/hyperledger/fabric/peer
```

Fabric生命周期将链码打包在易于阅读的tar文件中，方便协调跨多个组织的安装，使用以下命令打包链码。

```
peer lifecycle chaincode package sacc.tar.gz \  --path github.com/hyperledger/fabric-cluster/chaincode/go/sacc/ \  --label sacc_1
```

使用以下命令退出容器。

```
exit
```

使用以下命令将打包好的链码复制到Org2虚拟机中。

```
docker cp cli:/opt/gopath/src/github.com/hyperledger/fabric/peer/sacc.tar.gz ./ scp sacc.tar.gz root@172.10.0.12:~/hyperledger/multinodes
```

在Org2的虚拟机中使用以下命令将打包好的链码复制到`cli`客户端中。

```
docker cp ~/hyperledger/multinodes/sacc.tar.gz cli:/opt/gopath/src/github.com/hyperledger/fabric/peer
```

使用以下命令分别在两个组织的虚拟机上安装链码（Org1和Org2的虚拟机中都要进行以下操作）。

```
peer lifecycle chaincode install sacc.tar.gz
```

使用以下命令查询链码（Org1和Org2的虚拟机中都要进行以下操作）。

```
peer lifecycle chaincode queryinstalled
```

使用以下命令批准链码（Org1和Org2的虚拟机中都要进行以下操作，其中链码的ID要根据上面查询的结果替换到下面的命令中）。

```
peer lifecycle chaincode approveformyorg --channelID mychannel --name sacc --version 1.0 --init-required --package-id sacc_1:1d9838e6893e068a94f055e807b18289559af748e5196a79a640b66305a74428 --sequence 1 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
```

使用以下命令查看链码是否就绪（Org1和Org2的虚拟机中都要进行以下操作）。

```
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name sacc --version 1.0 --init-required --sequence 1 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --output json
```

使用以下命令提交链码（在组织一或者组织二上）。

```
peer lifecycle chaincode commit -o orderer.example.com:7050 --channelID mychannel --name sacc --version 1.0 --sequence 1 --init-required --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
```

使用以下命令将链码初始化。

```
peer chaincode invoke -o orderer.example.com:7050 --isInit --ordererTLSHostnameOverride orderer.example.com --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n sacc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["a","bb"]}' 
```

```
INFO 001 Chaincode invoke successful. result: status:200 
```

使用以下命令查询数据。

```
peer chaincode query -C mychannel -n sacc -c '{"Args":["query","a"]}'
```

```
cc
```

使用以下命令调用链码，新增数据。

```
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n sacc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["set","a","cc"]}'
```

```
INFO 001 Chaincode invoke successful. result: status:200 payload:"cc" 
```

使用以下命令查询数据。

```
peer chaincode query -C mychannel -n sacc -c '{"Args":["query","a"]}'
```

```
cc
```

<br>
