Hyperledger Fabric v1.0.5 区块链运维入门

本文作者最近在找工作,有意向致电 13113668890

Mr. Neo Chen (陈景峯), netkiller, BG7NYT


中国广东省深圳市龙华新区民治街道溪山美地
518131
+86 13113668890


版权声明

转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。

http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信订阅号 netkiller-ebook (微信扫描二维码)
QQ:13721218 请注明“读者”
QQ群:128659835 请注明“读者”

2018-02-10

摘要

你网上搜索hyperledger大部分文章是讲解开发环境的安装与配置,没有一篇关于怎样运维区块链的文章。当你配置好开发环境,写好合约,怎样落地呢?却很少文章提及。

要将区块链落地,我们必须依赖运维技术,这是IT基础设施,区块链应用将建立在这个基础设施之上,否则区块链就是浮云,悬在空中无法落地。

本文采用碎片化写作,原文会不定期更新,请尽量阅读原文。

http://www.netkiller.cn/journal/hyperledger.cluster.html


目录

1. 背景

由于区块链是区中心化,与传统运维不同,所以之前你积累的经验,不一定适用于区块链。要想运维好区块链项目,就必须理解去中心化这个概念。

首先谈谈传统运维,总结为三个字“中心化”,当然有人反对并抛出“分布式”感念,传统运维的分布式仍然建立在中心化的基础之上。

我们来看看传统应用模式,决多数应用都可以概括为:



用户 -> WEB -> Application -> Cache -> Database 

可以在这个体系下面做灵活变化,例如加入所有引擎、分布式文件系统,大数据等等应用,但都离不开这个模式。

区块链完全不同,如果举一个最接近的例子,我想可能与多数据中心远程异地灾备比较接近。

2. 部署拓扑

什么是区块链呢? 区块链实际上就是数据库,一个只能插入和查询的数据库,数据不能被修改和删除,并且这个数据库没有DBA管理员角色。这么一说你应该明白了把,实际上运维区块链就是在维护一个分布式数据库。

网上的绝大多数安装例子中,均采用 docker 部署方案,但无一例外的是,全部安装在一个物理机上。如果是生产环境,我们必须分开不是,首先要做的工作是化整为零,拆解应用,搞明白每个容器的功能和作用。然后我们将应用拆分,独立部署到物理节点上去。



     +---------------------------------+
     |                SDK               |
     +---------------------------------+
     | golang | nodejs | python | java |
     +---------------------------------+
       |                     |
       |                     |
           |   +--------------+ |
           |   |  fabric-ca   |  |
           |   +--------------+  |
           |                     |
           V                     V
+-------------------+   +-------------------+
| Peer            |   |  Peer             |         
+-------------------+   +-------------------+
     |          |           |        |
     V          |           |        V
+-----------+   |           |   +------------+
| Orderer   |   |           |   | Orderer    |
+-----------+   |           |   +------------+
                V           V
            +-------------------+
            |     Couchdb       |
            +-------------------+

接下来我们要做的工作是将上面拓扑图种的技术点分分击破。

由于 Hyperledger Fabric 是建立在 Docker 基础之上的。所以不建议你去除 Docker 转而使用传统的本地编译安装方式。我们仍然保持使用 Docker 在每个物理节点上,省去软件的编译和安装环节。

2.1. 依赖关系

需要注意的是于其他传统系统一样,Hyperledger Fabric 的启动也是有顺序的,这是因为他们之间存在着依赖关系。

2.2. 准备物理机

CentOS (Minimal ISO)

物理机
  • ca 节点,域名:ca.example.com,IP地址:172.16.0.20,端口:7054

  • orderer 节点,域名 orderer.example.com,IP地址:172.16.0.21,端口:7050

  • peer 节点,域名:peer.example.com,IP地址:172.16.0.22,端口:7051、7053

  • couchdb 节点,域名 couchdb.example.com,IP地址:172.16.0.25,端口:5984

  • tools 节点,域名:tools.example.com,IP地址:172.16.0.20 与 CA 共用一台机器(这里为了节省资源)

在所有节点上运行下面脚本

			
curl -s https://raw.githubusercontent.com/oscm/shell/master/virtualization/docker/docker.centos7.ce.sh	 | bash	
curl -s https://raw.githubusercontent.com/oscm/shell/master/virtualization/docker/docker-compose/docker-compose-1.19.0.sh | bash		
			
			

3. cli 管理节点安装

Tools 在生成创世区块的时候我们就曾经使用,你可以沿用之前的 tools 节点,或者创建一个 cli 节点,这个节点主要是用于管理区块链集群,例如合约部署,调试等等。

3.1. 安装 Docker 镜像

			
docker pull hyperledger/fabric-tools:x86_64-1.0.5
docker tag hyperledger/fabric-tools:x86_64-1.0.5 hyperledger/fabric-tools			
			
			

3.2. docker-compose-cli.yaml

			
version: '3'

networks:
  basic:

services:
			
  cli:
    container_name: cli
    image: hyperledger/fabric-tools
    tty: true
    environment:
      - GOPATH=/opt/gopath
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_ID=cli
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
      - CORE_CHAINCODE_KEEPALIVE=10
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: /bin/bash
    volumes:
        - /var/run/:/host/var/run/
        - ./chaincode/:/opt/gopath/src/github.com/
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ~/netkiller:/root/netkiller
    networks:
        - basic
    #depends_on:
    #  - orderer.example.com
    #  - peer0.org1.example.com
    #  - couchdb	
    extra_hosts:
    		- "ca.example.com:172.16.0.20"
		- "orderer.example.com:172.16.0.21"
		- "peer0.org1.example.com:172.16.0.22"
		- "couchdb.example.com:172.16.0.25"
			
			

3.3. 启动 cli 节点

			
[root@localhost netkiller]# docker-compose -f docker-compose-cli.yaml up -d
			
			

后面合约的部署将在 cli 节点上进行

3.4. 生成证书和创世区块

这里我们需要几个命令(configtxgen configtxlator cryptogen),官方的安装方式:

		
curl -sSL https://goo.gl/byy2Qj | bash -s 1.0.5
		
			

无论如何我都安装不成功,可能是(https://goo.gl/byy2Qj)被天朝给墙了。不过我发现 fabric-tools 里面有这个工具。

提示

经过翻墙发现 https://goo.gl/byy2Qj 地址是 301 到下面地址:

https://raw.githubusercontent.com/hyperledger/fabric/v1.0.5/scripts/bootstrap.sh

		
[root@localhost ~]# mkdir netkiller
[root@localhost ~]# cd netkiller/
[root@localhost netkiller]# mkdir -p {chaincode,crypto-config,config,artifacts}	
		
			

3.4.1. 创建配置文件

3.4.1.1. crypto-config.yaml

创建证书

				
OrdererOrgs:
  - Name: Orderer
    Domain: example.com
    Specs:
      - Hostname: orderer
PeerOrgs:
  - Name: Org1
    Domain: org1.example.com
    Template:
      Count: 1
    Users:
      Count: 1			
				
					
3.4.1.2. configtx.yaml
				
---
Profiles:

    OneOrgOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            SampleConsortium:
                Organizations:
                    - *Org1
    OneOrgChannel:
        Consortium: SampleConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1

Organizations:

    - &OrdererOrg
        Name: OrdererOrg

        ID: OrdererMSP

        MSPDir: crypto-config/ordererOrganizations/example.com/msp

    - &Org1
        Name: Org1MSP

        ID: Org1MSP

        MSPDir: crypto-config/peerOrganizations/org1.example.com/msp

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

Orderer: &OrdererDefaults

    OrdererType: solo

    Addresses:
        - orderer.example.com:7050

    BatchTimeout: 2s

    BatchSize:

        MaxMessageCount: 10

        AbsoluteMaxBytes: 99 MB

        PreferredMaxBytes: 512 KB

    Kafka:
        Brokers:
            - 127.0.0.1:9092

    Organizations:

Application: &ApplicationDefaults

    Organizations:
				
					

3.4.2. 生成证书

命令

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

演示

				
root@8f467a88de99:~/netkiller# cryptogen generate --config=./crypto-config.yaml
org1.example.com

root@8f467a88de99:~/netkiller# ls -1 crypto-config
ordererOrganizations
peerOrganizations
				
				

3.4.3. 生成创世区块

				
root@8f467a88de99:~/netkiller# export FABRIC_CFG_PATH=$PWD				
root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgOrdererGenesis -outputBlock ./config/genesis.block
2018-02-08 08:35:30.121 UTC [common/configtx/tool] main -> INFO 001 Loading configuration
2018-02-08 08:35:30.236 UTC [common/configtx/tool] doOutputBlock -> INFO 002 Generating genesis block
2018-02-08 08:35:30.238 UTC [common/configtx/tool] doOutputBlock -> INFO 003 Writing genesis block			
				
				

3.4.4. 生成通道配置文件

命令

				
CHANNEL_NAME=mychannel
configtxgen -profile OneOrgChannel -outputCreateChannelTx ./config/channel.tx -channelID $CHANNEL_NAME				
				
				

操作演示

				
root@8f467a88de99:~/netkiller# CHANNEL_NAME=mychannel
root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgChannel -outputCreateChannelTx ./config/channel.tx -channelID $CHANNEL_NAME 
2018-02-08 08:41:08.010 UTC [common/configtx/tool] main -> INFO 001 Loading configuration
2018-02-08 08:41:08.020 UTC [common/configtx/tool] doOutputChannelCreateTx -> INFO 002 Generating new channel configtx
2018-02-08 08:41:08.020 UTC [common/configtx/tool] doOutputChannelCreateTx -> INFO 003 Writing new channel tx				
				
				

3.4.5.  generate anchor peer transaction

命令

				
CHANNEL_NAME=mychannel
configtxgen -profile OneOrgChannel -outputAnchorPeersUpdate ./config/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP				
				
				

操作演示

				
root@8f467a88de99:~/netkiller# CHANNEL_NAME=mychannel
root@8f467a88de99:~/netkiller# configtxgen -profile OneOrgChannel -outputAnchorPeersUpdate ./config/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP
2018-02-08 08:46:19.162 UTC [common/configtx/tool] main -> INFO 001 Loading configuration
2018-02-08 08:46:19.176 UTC [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 002 Generating anchor peer update
2018-02-08 08:46:19.177 UTC [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 003 Writing anchor peer update
				
				

至此所有需要生成的配置文件全部生成完毕。

				
[root@localhost netkiller]# tree -L 4 crypto-config
crypto-config
|-- ordererOrganizations
|   `-- example.com
|       |-- ca
|       |   |-- ca.example.com-cert.pem
|       |   `-- de9204448c9c8e2a72d092f53e8ff069e12dea62001b7b8b9a83ae240d80ed57_sk
|       |-- msp
|       |   |-- admincerts
|       |   |-- cacerts
|       |   `-- tlscacerts
|       |-- orderers
|       |   `-- orderer.example.com
|       |-- tlsca
|       |   |-- c0b4dd42bd396d68f468aa07dae8ce944ab2d9832b2593cfafb27e53c69ec5e2_sk
|       |   `-- tlsca.example.com-cert.pem
|       `-- users
|           `-- Admin@example.com
`-- peerOrganizations
    `-- org1.example.com
        |-- ca
        |   |-- 74023bd84cc5e6957f9bc30b3ebcd6c5b7507016721702a014dd640df265b61a_sk
        |   `-- ca.org1.example.com-cert.pem
        |-- msp
        |   |-- admincerts
        |   |-- cacerts
        |   `-- tlscacerts
        |-- peers
        |   `-- peer0.org1.example.com
        |-- tlsca
        |   |-- 71bb82530580707aa20fa5955beab202f266aa4da4b435bef20741ce5e64abb9_sk
        |   `-- tlsca.org1.example.com-cert.pem
        `-- users
            |-- Admin@org1.example.com
            `-- User1@org1.example.com

25 directories, 8 files
				
				
				

将config和crypto-config文件加复制到ca,orderer,peer,cli等节点上去。

3.5. 清理 Docker 容器

至此所需的证书与创世区块都已生产完毕,fabric-tools 容易完成了它的使命,你可以继续保留或者清理干净。

				
[root@localhost netkiller]# docker-compose -f docker-compose-tools.yml down
Stopping tools ... done
Removing tools ... done
Removing network netkiller_basic
				
			

清理 tools 容器

				
docker rm -f $(docker ps -qa)
				
			

4. CA 节点安装

CA 节点需要我们之前生成 crypto-config

4.1. 安装 Docker 镜像

			
docker pull hyperledger/fabric-ca:x86_64-1.0.5
docker tag hyperledger/fabric-ca:x86_64-1.0.5 hyperledger/fabric-ca			
			
			

4.2. docker-compose-ca.yml

			
version: '3'

networks:
  basic:

services:
  ca.example.com:
    image: hyperledger/fabric-ca
    environment:
      - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
      - FABRIC_CA_SERVER_CA_NAME=ca.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/4239aa0dcd76daeeb8ba0cda701851d14504d31aad1b2ddddbac6a57365e497c_sk
    ports:
      - "XXX.XXX.XXX.XXX: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
    container_name: ca.example.com
    networks:
      - basic			
			
			

4.3. 启动 CA 节点

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

5. CouchDB 节点

整个 Hyperledger Fabric 技术栈中只有这个 CouchDB 是个外来户,看到 CouchDB 我就非常兴奋,这是一个NoSQL数据库(它与MongoDB十分类似),所以CouchDB 100%可以独立运行,且最容易分离。

CouchDB 在这里有两个方案可以选择。

  • 采用 Docker 运行 CouchDB的方案。

  • 采用传统方式物理机上本地安装 CouchDB

理论两种方案对实际结果没有什么区别,只需提供IP地址,用户名与密码供其他节点访问即可。但实际我们看到 Hyperledger Fabric 使用的镜像是 hyperledger/fabric-couchdb 不清楚是否有修改过 CouchDB 数据库。

如果你对 Docker 比较熟悉就采用 Docker 方案。如果不熟悉就采用本地安装方式。总之选择一种你能Hold住(掌控)的方案,一旦出现故障,你能第一时间排查并处理。

5.1. 安装 Docker 镜像

			
docker pull hyperledger/fabric-couchdb:x86_64-1.0.5
docker tag hyperledger/fabric-couchdb:x86_64-1.0.5 hyperledger/fabric-couchdb
			
			

5.2. 安装 CouchDB

下面是 Docker 方案

			
[root@localhost netkiller]# vim docker-compose-couchdb.yml
		
version: '3'

networks:
  basic:

services:
  couchdb:
    container_name: couchdb
    image: hyperledger/fabric-couchdb
    # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
    # for CouchDB.  This will prevent CouchDB from operating in an "Admin Party" mode.
    environment:
      - COUCHDB_USER=admin
      - COUCHDB_PASSWORD=passw0rd
    ports:
      - 172.16.0.17:5984:5984
    networks:
      - basic
			
			

5.3. 启动 CouchDB

启动 Docker 容器

			
docker-compose -f docker-compose-couchdb.yml up -d		
			
			

访问CouchDB管理界面,http://172.16.0.17:5984/_utils/ 请使用上面设置的密码进入。若想进入到容器内部可以使用下面命令:

			
docker-compose -f docker-compose-couchdb.yml exec couchdb bash	
			
			

至此 CouchDB 节点部署完毕。

5.4. 备份与恢复 CouchDB

既然是运维区块链,对于运维工作我们最关心的就是如何备份数据,在出现故障的时候恢复数据。

			
npm install --save couchdb-backup-restore			
			
			
			
var cbr = require('couchdb-backup-restore');
 
var config = {credentials: 'http://localhost:5984'};
 
function done(err) {
  if (err) {
    return console.error(err);
  }
  console.log('all done!');
}
 
// backup 
cbr.backup(config, done).pipe(fs.createWriteStream('./db-backup.tar.gz'))

// restore 
fs.createReadStream('./db-backup.tar.gz').pipe(cbr.restore(config, done));
			
			

6. Orderer 节点安装

6.1. 安装 Docker 镜像

			
docker pull hyperledger/fabric-orderer:x86_64-1.0.5
docker tag hyperledger/fabric-orderer:x86_64-1.0.5 hyperledger/fabric-orderer			
			
			

6.2. docker-compose-orderer.yml

			
version: '3'

networks:
  basic:

services:
  orderer.example.com:
    container_name: orderer.example.com
    image: hyperledger/fabric-orderer
    environment:
      - ORDERER_GENERAL_LOGLEVEL=debug
      - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
      - ORDERER_GENERAL_GENESISMETHOD=file
      - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block
      - ORDERER_GENERAL_LOCALMSPID=OrdererMSP
      - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/orderer
    command: orderer
    ports:
      - 7050:7050
    volumes:
        - ./config/:/etc/hyperledger/configtx
        - ./crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/:/etc/hyperledger/msp/orderer
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/:/etc/hyperledger/msp/peerOrg1
    networks:
      - basic			
			
			

6.3. 启动 Orderer 节点

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

7. Peer 节点安装

7.1. 安装 Docker 镜像

			
docker pull hyperledger/fabric-peer:x86_64-1.0.5
docker tag hyperledger/fabric-peer:x86_64-1.0.5 hyperledger/fabric-peer
			
			

7.2. docker-compose-peer.yml

			
version: '3'

networks:
  basic:

services:

  peer0.org1.example.com:
    container_name: peer0.org1.example.com
    image: hyperledger/fabric-peer
    environment:
      - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_LOGGING_PEER=debug
      - CORE_CHAINCODE_LOGGING_LEVEL=DEBUG
      - CORE_PEER_LOCALMSPID=Org1MSP
      - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      # # 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=${COMPOSE_PROJECT_NAME}_basic
      - CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=172.16.0.17:5984
      # The CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME and CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD
      # provide the credentials for ledger to connect to CouchDB.  The username and password must
      # match the username and password set for the associated CouchDB.
      - CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
      - CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=passw0rd
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric
    command: peer node start
    # command: peer node start --peer-chaincodedev=true
    ports:
      - 7051:7051
      - 7053:7053
    volumes:
        - /var/run/:/host/var/run/
        - ./crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp:/etc/hyperledger/msp/peer
        - ./crypto-config/peerOrganizations/org1.example.com/users:/etc/hyperledger/msp/users
        - ./config:/etc/hyperledger/configtx
    #depends_on:
    #  - orderer.example.com
    #  - couchdb
    networks:
      - basic
			
			

Peer 需要连接到 CouchDB 注意配置项 CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=172.16.0.17:5984

同时连接CouchDB的用户与密码要正确

7.3. 启动 Peer 节点

			
[root@localhost netkiller]# docker-compose -f docker-compose-peer.yaml up -d
			
			

7.4. 创建 Channel

进入 Peer 容器

			
docker-compose -f docker-compose-peer.yaml exec peer0.org1.example.com bash
			
			

添加 Orderer 节点并创建 Channel

			
CORE_PEER_LOCALMSPID=Org1MSP
CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp
peer channel create -o orderer.example.com:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx

peer channel create -o 172.16.0.17:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx
			
			

加入到 mychannel

			
CORE_PEER_LOCALMSPID=Org1MSP
CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp
peer channel join -b mychannel.block
			
			

查看通道

			
st t@f39764f58ff7:/opt/gopath/src/github.com/hyperledger/fabric# peer channel list
2018-02-09 08:12:46.454 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-02-09 08:12:46.454 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-02-09 08:12:46.456 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized
2018-02-09 08:12:46.457 UTC [msp/identity] Sign -> DEBU 004 Sign: plaintext: 0A8A070A5C08031A0C08FEAFF5D30510...631A0D0A0B4765744368616E6E656C73 
2018-02-09 08:12:46.458 UTC [msp/identity] Sign -> DEBU 005 Sign: digest: E27446498819AA4FE8EE835ADEF16195489975377A3C18D89C36D37AA24E5CA2 
2018-02-09 08:12:46.469 UTC [channelCmd] list -> INFO 006 Channels peers has joined to: 
2018-02-09 08:12:46.469 UTC [channelCmd] list -> INFO 007 mychannel 
2018-02-09 08:12:46.469 UTC [main] main -> INFO 008 Exiting.....			
			
			

8. 验收与测试

8.1. 准备合约文件

			
[root@localhost netkiller]# cat chaincode/fabcar/fabcar.go 
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * The sample smart contract for documentation topic:
 * Writing Your First Blockchain Application
 */

package main

/* Imports
 * 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
 * 2 specific Hyperledger Fabric specific libraries for Smart Contracts
 */
import (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	sc "github.com/hyperledger/fabric/protos/peer"
)

// Define the Smart Contract structure
type SmartContract struct {
}

// Define the car structure, with 4 properties.  Structure tags are used by encoding/json library
type Car struct {
	Make   string `json:"make"`
	Model  string `json:"model"`
	Colour string `json:"colour"`
	Owner  string `json:"owner"`
}

/*
 * The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
 * Best practice is to have any Ledger initialization in separate function -- see initLedger()
 */
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
	return shim.Success(nil)
}

/*
 * The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
 * The calling application program has also specified the particular smart contract function to be called, with arguments
 */
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

	// Retrieve the requested Smart Contract function and arguments
	function, args := APIstub.GetFunctionAndParameters()
	// Route to the appropriate handler function to interact with the ledger appropriately
	if function == "queryCar" {
		return s.queryCar(APIstub, args)
	} else if function == "initLedger" {
		return s.initLedger(APIstub)
	} else if function == "createCar" {
		return s.createCar(APIstub, args)
	} else if function == "queryAllCars" {
		return s.queryAllCars(APIstub)
	} else if function == "changeCarOwner" {
		return s.changeCarOwner(APIstub, args)
	}

	return shim.Error("Invalid Smart Contract function name.")
}

func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 1 {
		return shim.Error("Incorrect number of arguments. Expecting 1")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	return shim.Success(carAsBytes)
}

func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response {
	cars := []Car{
		Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"},
		Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"},
		Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"},
		Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"},
		Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"},
		Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"},
		Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"},
		Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"},
		Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"},
		Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"},
	}

	i := 0
	for i < len(cars) {
		fmt.Println("i is ", i)
		carAsBytes, _ := json.Marshal(cars[i])
		APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
		fmt.Println("Added", cars[i])
		i = i + 1
	}

	return shim.Success(nil)
}

func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 5 {
		return shim.Error("Incorrect number of arguments. Expecting 5")
	}

	var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]}

	carAsBytes, _ := json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

	startKey := "CAR0"
	endKey := "CAR999"

	resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- queryAllCars:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	if len(args) != 2 {
		return shim.Error("Incorrect number of arguments. Expecting 2")
	}

	carAsBytes, _ := APIstub.GetState(args[0])
	car := Car{}

	json.Unmarshal(carAsBytes, &car)
	car.Owner = args[1]

	carAsBytes, _ = json.Marshal(car)
	APIstub.PutState(args[0], carAsBytes)

	return shim.Success(nil)
}

// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {

	// Create a new Smart Contract
	err := shim.Start(new(SmartContract))
	if err != nil {
		fmt.Printf("Error creating new Smart Contract: %s", err)
	}
}
			
			

8.2. 安装 chaincode

安装合约在 tools 节点上进行。

			
docker-compose -f docker-compose-cli.yaml exec cli bash

CORE_PEER_ADDRESS=172.16.0.17:7051
CORE_PEER_LOCALMSPID=Org1MSP
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp

peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar	
peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}'
			
			
			
root@a90d0d869dd3:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar	
2018-02-09 11:26:28.025 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP
2018-02-09 11:26:28.025 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity
2018-02-09 11:26:28.025 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc
2018-02-09 11:26:28.025 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc
2018-02-09 11:26:28.139 UTC [golang-platform] getCodeFromFS -> DEBU 005 getCodeFromFS github.com/fabcar
2018-02-09 11:26:29.394 UTC [golang-platform] func1 -> DEBU 006 Discarding GOROOT package bytes
2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 007 Discarding GOROOT package encoding/json
2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 008 Discarding GOROOT package fmt
2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 009 Discarding provided package github.com/hyperledger/fabric/core/chaincode/shim
2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 00a Discarding provided package github.com/hyperledger/fabric/protos/peer
2018-02-09 11:26:29.395 UTC [golang-platform] func1 -> DEBU 00b Discarding GOROOT package strconv
2018-02-09 11:26:29.396 UTC [golang-platform] GetDeploymentPayload -> DEBU 00c done
2018-02-09 11:26:29.406 UTC [msp/identity] Sign -> DEBU 00d Sign: plaintext: 0A8A070A5C08031A0C08E58AF6D30510...939FFF060000FFFF9C08DC0700200000 
2018-02-09 11:26:29.406 UTC [msp/identity] Sign -> DEBU 00e Sign: digest: A504EE8048EEE8C77F9E1C39827124474638110FD3980017BCA6D644E3E7EC98 
2018-02-09 11:26:29.426 UTC [chaincodeCmd] install -> DEBU 00f Installed remotely response:<status:200 payload:"OK" > 
2018-02-09 11:26:29.427 UTC [main] main -> INFO 010 Exiting.....			
			
			
			
peer chaincode instantiate -o 172.16.0.17:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')"			
			
			

9. 总结