之前使用过脚本的方式启动了fabric的测试网络,但是这个脚本封装的实在太好了,一行命令之后啥都看不出来,对于我这样的初学者来说感觉帮助不是特别大,所以我决定把这个脚本串行化的分析一遍,来真正看出整个网络的启动过程。
本文假设当前所在目录为test-network,使用fabric版本为2.3.0,并且和network.sh脚本默认情况下一样不使用预先定义的CA证书。
创建org1、org2和org3所需的ca证书,这里会用到organizations/cryptogen下的yaml文件,以crypto-config-org1.yml为例:
# 定义组织管理的所有peer节点
PeerOrgs:
# 节点名称
- Name: Org1
# 节点域名
Domain: org1.example.com
# 是否使用NodeOUs(更细粒度的MSP,可以理解为一个公司的不同部门)
EnableNodeOUs: true
# 这里省略了一段Spec配置,这些配置用于明确的指明每个peer节点的主机名等信息,也可以使用Template模板配置去自动给主机命名
# 利用模板方式配置产生多少个peer节点,并按照某种规则进行命名
Template:
# 要产生的peer节点的数量
Count: 1
# 每个peer的地址
SANS:
- localhost
# 起始序号,默认是0,也就是说对于节点的程序化命名中,第一个节点会是peer{start}
# Start: 5
# 主机名的默认规则,前缀加序号,前缀应该是Org1了。
# Hostname: {{.Prefix}}{{.Index}} # default
# 除了管理员之外的用户数量
Users:
Count: 1
然后利用如下的三条命令创建组织所需要的证书等
cryptogen generate --config=./organizations/cryptogen/crypto-config-org1.yaml --output="organizations"
cryptogen generate --config=./organizations/cryptogen/crypto-config-org2.yaml --output="organizations"
cryptogen generate --config=./organizations/cryptogen/crypto-config-orderer.yaml --output="organizations"
执行完成之后在organizations下会生成peerOrganizations和ordererOrganizations两个文件夹,里面分别存储了peer和order生成所需要的材料。
接下来用ccp-template.yaml和ccp-template.json这两个模板文件来创建CCP文件,这两个文件会分别被保存在organizations/peerOrganizations/org1.example.com和organizations/peerOrganizations/org2.example.com下。
从内容上来看这些文件描述的应该是各peer连接时可能会用到的端口、SSL证书等信息。
执行时只需要调用organizations下的ccp-generate.sh即可完成该步骤。
./organizations/ccp-generate.sh
这里使用docker-compose的方式来启动用到的四个容器,容器的描述文件为docker/docker-compose-test-net.yaml
。通过该文件可以看出网络包含四个服务,一个orderer,两个peer和一个cli,并让他们共同连接到名为test的网络。其中cli应该是用于和org1和org2的peer进行通讯的,它依赖于peer0.org1.example.com和peer0.org2.example.com镜像。使用如下命令启动网络:
export DOCKER_SOCK=/var/run/docker.sock # 不加这句可能会报错,我找不到在哪给这个环境变量赋值的,在docker-compose-test-net.yaml里面DOCKER_SOCK是作为一个环境变量使用的
docker-compose -f docker/docker-compose-test-net.yaml up -d
到这里组成网络的容器都会成功启动了。
首先设置FABRIC配置文件路径为configtx,其下有一个configtx.yaml文件,这个文件里定义了组织及其名称以及背书策略,应用调用权限,Order打包参数配置通道背书策略等,configtxgen命令可以通过该文件创建创世块。
export FABRIC_CFG_PATH=${PWD}/configtx # 设置配置文件路径
export CHANNEL_NAME="mychannel" # 设定通道名称
configtxgen \
-profile TwoOrgsApplicationGenesis \
-outputBlock ./channel-artifacts/${CHANNEL_NAME}.block \
-channelID $CHANNEL_NAME
# -profile configtx.yaml中的属性,用于生成创世块
# outputBlock 创世块输出位置
# -channelID 设定通道名称
执行之后,会在channel-artifacts出现一个mychannel.block,其中写入了对管道建立相关的背书、组织等,作为创世块。
这里首先会将FABRIC配置文件路径设置为…/config,这里有三个文件,core.yaml、orderer.yaml和configtx.yaml,看起来应该是和通道内的选举等定义相关的配置文件。不知道为什么之前用的是configtx下的configtx.yaml,现在又要用…/config下的configtx.yaml,这里猜测应该是之后的步骤只用到了core.yaml和orderer.yaml,而把…/config下的configtx.yaml和那两个文件放到一起可能是因为…/config/configtx.yaml比configtx/configtx.yaml更加通用,因为里面存储的profile更多,能适用更多的情况
export FABRIC_CFG_PATH=$PWD/../config/
设置如下变量来指明目前已知的各组织的加密证书和密钥等信息。
export CORE_PEER_TLS_ENABLED=true
export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
export PEER0_ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export PEER0_ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export PEER0_ORG3_CA=${PWD}/organizations/peerOrganizations/org3.example.com/peers/peer0.org3.example.com/tls/ca.crt
export ORDERER_ADMIN_TLS_SIGN_CERT=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.crt
export ORDERER_ADMIN_TLS_PRIVATE_KEY=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/tls/server.key
然后通过设置环境变量指明目前设置的是组织1相关的信息,即说明由组织1来创建这个通道。
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:7051
最后使用osadmin命令来实现对通道的创建。
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7053 --ca-file "$ORDERER_CA" --client-cert "$ORDERER_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER_ADMIN_TLS_PRIVATE_KEY"
执行之后打印出如下内容,说明通道创建成功:
{
"name": "mychannel",
"url": "/participation/v1/channels/mychannel",
"consensusRelation": "consenter",
"status": "active",
"height": 1
}
需要让两个peer组织依次加入通道,具体可以通过设置环境变量来指明现在设置的是谁的配置,首先是组织1。
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:7051
还要设定一下创世块的位置环境变量。
BLOCKFILE="./channel-artifacts/${CHANNEL_NAME}.block"
然后使用peer命令让peer节点加入通道中。
peer channel join -b $BLOCKFILE
然后打印出如下的内容说明成功加入了通道中。
2021-08-28 09:43:35.072 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2021-08-28 09:43:35.150 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
然后让组织2也进行一下如下的动作。
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:9051
peer channel join -b $BLOCKFILE
然后可以看到组织2也成功加入了通道中。
这里类似加入通道的步骤,会让每个组织都执行一遍对应的命令来完成该操作,这部分必须在我们之前启动的CLI容器中去执行,个人猜想是因为容器的网络是和主机网络隔离的,而且我尝试了直接在主机上去运行这个脚本,会报网络解析错误,容器中执行的是script/setAnchorPeer.sh这个脚本,这个脚本在容器启动的时候已经挂载到容器的目录中了,这里我们只需要简单的执行下面两条米完成锚点的设定:
export ORG=1
docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
export ORG=2
docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
然后看到如下的输出说明锚点设置完成:
2021-08-28 10:11:26.402 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2021-08-28 10:11:26.431 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
当然这里也可以大概看一下setAnchorPeer.sh这个脚本,他里面首先会拉取最近的通道配置,然后通过jq命令对配置进行修改,写入锚点相关信息,最后提交新的通道配置,由此完成锚点peer的设置。
这里我们使用的是go语言编写的链码,因此以下的变量设定和执行等都和GO语言相关。
首先设置相关环境变量,包括链码名称,链码所在路径和链码语言以及运行语言等。
export CC_NAME=basic
export CC_SRC_PATH=../asset-transfer-basic/chaincode-go
export CC_SRC_LANGUAGE=go
export CC_RUNTIME_LANGUAGE=golang
export CC_VERSION=1.0
然后进入链码所在路径进行依赖的构建
pushd $CC_SRC_PATH
GO111MODULE=on go mod vendor
popd
使用peer命令来对链码进行打包。
peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION}
命令执行之后会在本地出现一个basic.tar.gz,这里存储的就是链码了。
组织1和组织2分别给各自的peer上安装链码,通过设置环境变量来修改当前操作的组织。
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG1_CA
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:7051
然后执行如下命令给组织1的peer安装链码。
peer lifecycle chaincode install ${CC_NAME}.tar.gz
打印出如下内容,说明链码安装成功:
2021-08-28 11:16:51.166 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:
2021-08-28 11:16:51.167 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: basic_1.0:0788b09dbb0681d835dad50a770a6f51aabdf53f0ad5da4135d776ad585ea48d
其中
basic_1.0:0788b09dbb0681d835dad50a770a6f51aabdf53f0ad5da4135d776ad585ea48d
为package id,之后批准链码时可能会用到。
切换到组织2再执行链码安装命令,这里不再赘述。
查询这步其实可以省略,因为我们安装好链码之后其实已经可以看到package id了,但如果忘了的话还是可以运行如下命令来查看package id的:
peer lifecycle chaincode queryinstalled
可以看到打印出的内容:
Installed chaincodes on peer:
Package ID: basic_1.0:0788b09dbb0681d835dad50a770a6f51aabdf53f0ad5da4135d776ad585ea48d, Label: basic_1.0
然后我们将package id设置为环境变量:
export PACKAGE_ID=basic_1.0:0788b09dbb0681d835dad50a770a6f51aabdf53f0ad5da4135d776ad585ea48d
由此可以看出批准链码是一个手动的过程。。之前一直以为是自动的。
设定身份为组织1(设置组织环境变量的内容之后不再赘述)
批准之前还需要设定一些环境变量用于批准的参数:
export CC_SEQUENCE=1
export INIT_REQUIRED=--init-required
# 链码序列
# 需要初始化账本
调用如下命令对链码进行批准:
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --package-id ${PACKAGE_ID} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG}
出现如下内容可以说明批准有效:
2021-08-28 12:01:45.568 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [5e3c860acd3a28abc6bd84977d19737eccfe5d5b96cb638b83f5e5dc4872b767] committed with status (VALID) at localhost:7051
为了验证批准的效果,我们可以在各组织上查看链码的批准情况,首先设定身份为组织1,然后运行如下命令查看链码的批准情况:
peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name ${CC_NAME} --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} --output json
通过输出可以看出,对于该链码,组织1批准了,组织2没有批准:
{
"approvals": {
"Org1MSP": true,
"Org2MSP": false
}
}
在组织2的身份上去验证也会得到同样的输出。
之后我们切换到组织2来对链码进行批准以完成预先设定的策略,完成之后再查询批准情况,可以看到输出:
{
"approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}
说明现在两个组织都已经批准了链码。
脚本中写了一个拼接函数parsePeerConnectionParameters来在提交前产生提交链码时需要提交的参数,而这个函数执行时需要切换身份以拼接出各自部分的内容,这里我们可以直接跑出一个执行结果来,待会提交时传入即可。
--peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
然后结合这些参数来对链码进行提交:
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} --peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG}
然后根据输出可以看出链码提交成功
2021-08-28 13:52:36.889 UTC [chaincodeCmd] ClientWait -> INFO 001 txid [b3dcab6ce45f5bddb150863655d67d46e234f370a61bb1d7ef20f8a3a7ec3209] committed with status (VALID) at localhost:7051
2021-08-28 13:52:36.916 UTC [chaincodeCmd] ClientWait -> INFO 002 txid [b3dcab6ce45f5bddb150863655d67d46e234f370a61bb1d7ef20f8a3a7ec3209] committed with status (VALID) at localhost:9051
可以通过以下的命令查看链码是否提交成功:
peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME}
如果有下面的输出,说明链码提交成功
Committed chaincode definition for chaincode 'basic' on channel 'mychannel':
Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true, Org2MSP: true]
到这里,链码也已经部署完毕了,接下来就可以正常调用了。
我们以初始化链码为例展示调用,事实上,通过对查询与修改的命令对比可以看出,调用查询命令时只需要指明通道即可,而对于修改命令则需要传入peer和orderer的相关信息,因为修改的操作需要这些节点背书。这里以初始化链码的操作为例,其他的操作命令可以参考我之前的博客Fabric2.0 demo,使用test-network或者fabric官网https://hyperledger-fabric.readthedocs.io/en/latest/test_network.html进行查看。
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} --peerAddresses localhost:7051 --tlsRootCertFiles organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --isInit -c '{"function":"InitLedger","Args":[]}'
通过输出可以看出初始化成功:
2021-08-28 14:10:54.121 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"Default initiator successful."