【注意】最后更新于 March 27, 2018,文中内容可能已过时,请谨慎使用。
经过了将近$\frac{3}{4}$年的浑水摸鱼,我在fabric的道路上越走越偏,也该记录点东西误导下后续的人们了。本片文章分析下我们最常使用的e2e_cli吧。
为什么要分析这个例子呢,因为这个比较简单,而且大家也经常使用。最重要的是,网上有好多人都分析过了,我们容易写,不会写的太错。
闲话少叙,我们开始。
前言
既然是分析,那么启动环节我就不多说了,可能会穿插在各个环节里面。如果需要看启动的可以参考这篇文章。当然了,那会刚开始学,文章写的很烂,还请见谅。
启动
我们启动整个环境是执行的
开始的,这里面实现了很多功能,我们来分析一下。
变量分析
在整个脚本文件,开头是这几个变量的定义
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
|
为了方便阅读我就把注释都删除了
整体的含义就是我有OrdererOrgs
和PeerOrgs
,根据我指定的要求生成证书文件。
生成的文件在哪?就在启动时候检查的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版中,使用了zookeeper
和kafka
架构,但是并没有做到具体的使用,我们就先忽略。
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/
映射了进去,所以我们的代码要放到上一层也就是example
的chaincode/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"]}'
|
总结
我们这样就全部通读了一遍,有机会的话,我一条一条执行一遍,然后截图,对于整个过程会理解的更充分些。