经过了将近$\frac{3}{4}$年的浑水摸鱼,我在fabric的道路上越走越偏,也该记录点东西误导下后续的人们了。本片文章分析下我们最常使用的e2e_cli吧。

为什么要分析这个例子呢,因为这个比较简单,而且大家也经常使用。最重要的是,网上有好多人都分析过了,我们容易写,不会写的太错。 闲话少叙,我们开始。

前言

既然是分析,那么启动环节我就不多说了,可能会穿插在各个环节里面。如果需要看启动的可以参考这篇文章。当然了,那会刚开始学,文章写的很烂,还请见谅。

启动

我们启动整个环境是执行的

1
./network.sh up

开始的,这里面实现了很多功能,我们来分析一下。

变量分析

在整个脚本文件,开头是这几个变量的定义

1
2
3
4
UP_DOWN="$1"
CH_NAME="$2"
CLI_TIMEOUT="$3"
IF_COUCHDB="$4"

脚本启动抑或关闭命令,然后是要定义的channel名称,接着是超时时长,最后是否使用couchDB。所以我们也可以这么启动整个脚本

1
./network.sh up testchannel  100 couchdb

后面定义了这几个变量

1
2
3
: ${CLI_TIMEOUT:="10000"}
COMPOSE_FILE=docker-compose-cli.yaml
COMPOSE_FILE_COUCH=docker-compose-couch.yaml

分别是超时时长,我们启动整个系统(底层是否使用couchDB)所使用的配置文件。

启动命令

在启动整个环境前,脚本首先执行了validateArgs检查名称是否正确

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function validateArgs () {
	if [ -z "${UP_DOWN}" ]; then
		echo "Option up / down / restart not mentioned"
		printHelp
		exit 1
	fi
	if [ -z "${CH_NAME}" ]; then
		echo "setting to default channel 'mychannel'"
		CH_NAME=mychannel
	fi
}

内容很简单,就是看看我们有没有写up或者down最基本的命令。而且channel名称是创建过程不可缺少的,所以我们没有指定的话系统默认给我们分配一个mychannel名称。 继续往后面看,我们可以看到是从networkUp这个函数启动的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if [ "${UP_DOWN}" == "up" ]; then
	networkUp
elif [ "${UP_DOWN}" == "down" ]; then ## Clear the network
	networkDown
elif [ "${UP_DOWN}" == "restart" ]; then ## Restart the network
	networkDown
	networkUp
else
	printHelp
	exit 1
fi

最让人无语的是重启就是关闭再启动,也是很有想法(手动狗头) 比较复杂的地方来了,我们看下环境启动函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function networkUp () {
    if [ -f "./crypto-config" ]; then
      echo "crypto-config directory already exists."
    else
      #Generate all the artifacts that includes org certs, orderer genesis block,
      # channel configuration transaction
      source generateArtifacts.sh $CH_NAME
    fi

    if [ "${IF_COUCHDB}" == "couchdb" ]; then
      CHANNEL_NAME=$CH_NAME TIMEOUT=$CLI_TIMEOUT docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1
    else
      CHANNEL_NAME=$CH_NAME TIMEOUT=$CLI_TIMEOUT docker-compose -f $COMPOSE_FILE up -d 2>&1
    fi
    if [ $? -ne 0 ]; then
	echo "ERROR !!!! Unable to pull the images "
	exit 1
    fi
    docker logs -f cli
}

函数首先需要检查是否存在crypto-config文件夹,这个文件夹有什么用呢?它是一些证书文件,创世区块所在的文件夹,是必不可少的,通过generateArtifacts.sh脚本文件生成,我们稍后再看。 到了是否使用couchdb环节,如果使用的话就是多加了这一句-f $COMPOSE_FILE_COUCH,加载这个文件。 实际上,这个地方就是很普通的使用docker-compose启动docker而已,并不复杂。 到这个启动完之后,就相当于启动成功了。

证书创世块

这个脚本文件打开一看,很多乱七八糟的东西。不慌不慌,我们一点一点来。

变量分析

1
2
3
4
5
6
7
CHANNEL_NAME=$1
: ${CHANNEL_NAME:="mychannel"}
echo $CHANNEL_NAME

export FABRIC_ROOT=$PWD/../..
export FABRIC_CFG_PATH=$PWD
OS_ARCH=$(echo "$(uname -s|tr '[:upper:]' '[:lower:]'|sed 's/mingw64_nt.*/windows/')-$(uname -m | sed 's/x86_64/amd64/g')" | awk '{print tolower($0)}')

大家还能看到我们刚刚生成证书时候执行的指令吧

1
source generateArtifacts.sh $CH_NAME

就是把channelname传递了过来,然后FABRIC_ROOT是什么位置呢?就是我们fabric的根路径,是用来查找生成证书的可执行文件的。FABRIC_CFG_PATH就是我们当前路径了。而OS_ARCH就是系统版本了。

生成文件

生成所需文件很简单,三个函数

1
2
3
generateCerts
replacePrivateKey
generateChannelArtifacts

第一个生成证书,第二个替换私钥(估计很多人会纠结什么意思),第三个是生成channel所需文件。

证书生成

首先检查文件是否存在,如果不存在,就需要编译生成所需文件

1
2
3
4
5
6
7
8
CRYPTOGEN=$FABRIC_ROOT/release/$OS_ARCH/bin/cryptogen

if [ -f "$CRYPTOGEN" ]; then
        echo "Using cryptogen -> $CRYPTOGEN"
else
    echo "Building cryptogen"
    make -C $FABRIC_ROOT release
fi

得到了生成证书所需的文件CRYPTOGEN就开始生成文件吧。

1
$CRYPTOGEN generate --config=./crypto-config.yaml

通过使用一个特定的文件crypto-config.yaml来生成所需要的证书文件。 我们简单看下这个文件吧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
OrdererOrgs:
  - Name: Orderer
    Domain: example.com
    Specs:
      - Hostname: orderer
PeerOrgs:
  - Name: Org1
    Domain: org1.example.com
    Template:
      Count: 2
    Users:
      Count: 1
  - Name: Org2
    Domain: org2.example.com
    Template:
      Count: 2
    Users:
      Count: 1

为了方便阅读我就把注释都删除了 整体的含义就是我有OrdererOrgsPeerOrgs,根据我指定的要求生成证书文件。 生成的文件在哪?就在启动时候检查的crypto-config文件夹下面的peerOrg...orderOrg...下面。包含msp,tls等等所需要的文件。

替换私钥

我们上一步生成了证书文件之后,需要在启动的时候使用,也就是说我在启动docker容器的时候就要指定,那现在就得把docker-compose-file文件里面替换了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function replacePrivateKey () {
	ARCH=`uname -s | grep Darwin`
	if [ "$ARCH" == "Darwin" ]; then
		OPTS="-it"
	else
		OPTS="-i"
	fi

	cp docker-compose-e2e-template.yaml docker-compose-e2e.yaml

        CURRENT_DIR=$PWD
        cd crypto-config/peerOrganizations/org1.example.com/ca/
        PRIV_KEY=$(ls *_sk)
        cd $CURRENT_DIR
        sed $OPTS "s/CA1_PRIVATE_KEY/${PRIV_KEY}/g" docker-compose-e2e.yaml
        cd crypto-config/peerOrganizations/org2.example.com/ca/
        PRIV_KEY=$(ls *_sk)
        cd $CURRENT_DIR
        sed $OPTS "s/CA2_PRIVATE_KEY/${PRIV_KEY}/g" docker-compose-e2e.yaml
}

就是我导出了docker-compose-e2e.yaml文件后,替换里面的CA_PRIVATE_KEY为我们的私钥名称。 因为e2e_cli是测试的两个组织,所以就有两个private_key了。 然而,很尴尬的是,我们的网络并没有用到这个docker-compose-e2e.yaml文件,所以,这一步并没有什么用处。

生成区块等

本函数没有那么复杂,就是很单纯的生成指定的文件。

1
2
3
4
5
6
7
CONFIGTXGEN=$FABRIC_ROOT/release/$OS_ARCH/bin/configtxgen
if [ -f "$CONFIGTXGEN" ]; then
        echo "Using configtxgen -> $CONFIGTXGEN"
else
    echo "Building configtxgen"
    make -C $FABRIC_ROOT release
fi

cryptogen一样,没有的话就编译出来。 下面就到了关键的步骤

1
2
3
4
$CONFIGTXGEN -profile TwoOrgsOrdererGenesis -outputBlock ./channel-artifacts/genesis.block
$CONFIGTXGEN -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID $CHANNEL_NAME
$CONFIGTXGEN -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org1MSP
$CONFIGTXGEN -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID $CHANNEL_NAME -asOrg Org2MSP

第一句,根据profile生成创世块。等等!profile文件呢? 就是这个configtx.yaml文件了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Profiles:

    TwoOrgsOrdererGenesis:
        Orderer:
            <<: *OrdererDefaults
            Organizations:
                - *OrdererOrg
        Consortiums:
            SampleConsortium:
                Organizations:
                    - *Org1
                    - *Org2
    TwoOrgsChannel:
        Consortium: SampleConsortium
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2
Organizations:
    - &OrdererOrg
        Name: OrdererOrg
....

文件内容有点长,我不全部粘贴了。这个文件就是配置了组织,channel等等内容。根据这个文件进行生成。 第二句,生成channel,因为我们只有两个组织嘛,这个两个组织加入到同一个channel就可以了,因此我们只配置生成了一个channel。 三四句就是组织加入到channel的过程所需要的交易文件。 到此为止,我们启动docker的准备工作都已经完成了,开始进行下一步操作了。

docker启动

为了简化流程,我们暂时先不考虑使用couchdb,也就是我们使用这句指令启动

1
CHANNEL_NAME=$CH_NAME TIMEOUT=$CLI_TIMEOUT docker-compose -f $COMPOSE_FILE up -d 2>&1

好吧,我们需要看下这个$COMPOSE_FILE也就是docker-compose-cli.yaml文件的内容了。

docker-compose-cli

在目前1.1版中,使用了zookeeperkafka架构,但是并没有做到具体的使用,我们就先忽略。

cli

由于我们日常操作都是使用cli服务,所以我们先看这个服务。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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_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 -c './scripts/script.sh ${CHANNEL_NAME}; sleep $TIMEOUT'
    volumes:
        - /var/run/:/host/var/run/
        - ../chaincode/go/:/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go
        - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/
        - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/
        - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts
    depends_on:
      - orderer.example.com
      - peer0.org1.example.com
      - peer1.org1.example.com
      - peer0.org2.example.com
      - peer1.org2.example.com

真长! 我们先分析文件映射。首先,是把../chaincode/go/映射了进去,所以我们的代码要放到上一层也就是examplechaincode/go文件夹里面。 其次,把证书文件夹映射到了peer/crypto/文件,把通道区块文件夹映射到了peer/channel-artifacts文件夹。 最后映射了一个scripts文件夹,很奇怪吧!我们一会就发现了是干什么用的。 再看环境变量,制定了一堆安全相关的变量,包括tls文件和msp文件。这些就是比较固定的内容!但是注意到了吗?这个client客户端是连接的org的peer0哟! 有一个CORE_PEER_MSPCONFIGPATH是干什么用的呢?指定了mspconfigpath,就是代表了哪一个实体,这个会体现到交易头里面。 最后我们再看下启动后的命令 执行了一句话

1
./scripts/script.sh ${CHANNEL_NAME}; sleep $TIMEOUT

核心内容就是这个script.sh文件了!对cli的分析先告一段落!

peer&orderer

我们可以发现peer和orderer都是继承了base/docker-compose-base.yaml文件的服务,这就是直接定义了一个服务,因此我们需要查看这个base文件。

orderer

整个服务和cli相比还是少了不少内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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=/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]
    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

按照和cli一样的分析模式来看。 首先文件映射,映射进去了创世区块,msp主体和tls安全文件。 其次环境变量,指定了创世区块,还有安全相关的证书文件。 然后是执行指令,就是直接启动orderer 最后是一个端口映射,没有对端口进行改变。 (多说一句,为什么要进行端口映射?因为在一台电脑上运行,肯定会有端口占用冲突的)

peer

分析peer服务的话,这四个几乎是一样的,我们举一个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
peer0.org1.example.com:
    container_name: peer0.org1.example.com
    extends:
      file: peer-base.yaml
      service: peer-base
    environment:
      - CORE_PEER_ID=peer0.org1.example.com
      - CORE_PEER_ADDRESS=peer0.org1.example.com:7051
      - CORE_PEER_CHAINCODELISTENADDRESS=peer0.org1.example.com:7052
      - 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
    ports:
      - 7051:7051
      - 7052:7052
      - 7053:7053

看到没有,环境映射和order是相同的套路,peer的msp和tls是在通信时候不可缺少的,代表了具体的实体。 环境变量代表了本实体和监听的文件端口。 等等!怎么还继承了一个文件?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
peer-base:
    image: hyperledger/fabric-peer
    environment:
      - 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=e2ecli_default
      #- CORE_LOGGING_LEVEL=ERROR
      - CORE_LOGGING_LEVEL=DEBUG
      - 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
    working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer
    command: peer node start

还好还好,就是对peer服务的基础配置。 启动的指令是peer node start,启动peer节点的服务。 到此为止整个服务启动完毕

运行服务

还记得在cli服务里面,我们执行的script.sh吗?现在就到了这一步了。 看到这高达241行的文件,不免让人心慌!

基础变量

1
2
3
4
5
6
CHANNEL_NAME="$1"
: ${CHANNEL_NAME:="mychannel"}
: ${TIMEOUT:="60"}
COUNTER=1
MAX_RETRY=5
ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

CHANNEL_NAME是我们老伙计了。ORDERER_CA干什么用的呢?我们一会就用到了。 其他的这几个变量就是在运行时候需要用到的。我们现在开始。

创建通道

进行fabric,必须得有channel啊!要不然组织之间怎么通信?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
createChannel() {
	setGlobals 0

    if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then
		peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx >&log.txt
	else
		peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA >&log.txt
	fi
	res=$?
	cat log.txt
	verifyResult $res "Channel creation failed"
	echo "===================== Channel \"$CHANNEL_NAME\" is created successfully ===================== "
	echo
}

setGlobals很奇怪的一句指令,干什么用的呢?就是指定是在哪个peer上进行操作的 我们先假设不用tls把。这个地方无所谓的! 创建的指令就一句话

1
peer channel create -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/channel.tx

指定通道的交易,通道名,orderer地址!ok!

加入通道

我们还记得,一共是两个org,一个org下面有两个peer,也就是说一共有四个peer。 那么我们就需要把每个peer都加入进去!

1
2
3
4
5
6
7
8
9
joinChannel () {
	for ch in 0 1 2 3; do
		setGlobals $ch
		joinWithRetry $ch
		echo "===================== PEER$ch joined on the channel \"$CHANNEL_NAME\" ===================== "
		sleep 2
		echo
	done
}

加入到对应的channel很简单,就一句话

1
peer channel join -b $CHANNEL_NAME.block

脚本中说比较慢,所以需要重试几次。

更新锚节点

我们这两个组织都得更新上去,所以需要进行以下操作

1
peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx

我们在很久之前使用的configtxlator就是创建的锚节点交易。 当然,更新锚节点没必要在四个节点上都执行一遍,在org上面的一个peer执行下就好啦。

安装chaincode

channel都做好了,就该安装chaincode了

1
peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02

当然,这个也不需要在每个peer上都执行一遍,像更新锚节点那样,在org上面的一个peer执行下就好啦。

初始化chaincode

到了这一步,都加入了通道,并且都安装了chaincode,就可以初始化了! 没错!在一个节点上初始化,就会同步到整个网络了!

1
peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR	('Org1MSP.member','Org2MSP.member')" 

测试

这个更简单了,就是执行

1
peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'

总结

我们这样就全部通读了一遍,有机会的话,我一条一条执行一遍,然后截图,对于整个过程会理解的更充分些。