区块链应用开发?来看Fabric测试网络启动流程!

881 阅读6分钟

在阅读这篇文章之前,最好先掌握如何使用fabric测试网络和链码部署 如果你还不熟悉如何使用Hyperledger Fabric, 可以移步juejin.cn/post/713800…

如果本文对你有用,麻烦请给我点个赞👍,这是对我很大的鼓励!

下面我们开始解析network.sh的脚本内容

先理清单机的网络步骤:

生成组织

生成组织的证书

生成创世纪块

生成通道

生成结点加入通道

设定锚结点

然后搞清楚脚本中通过那些指令怎样的步骤启动的:

先来看一个网络的启动参数把

cd test-network/
./network.sh up createChannel -c mychannel -ca && ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-java -ccl java

上面 && 的前后实际上是两行不同的指令

  • 前一个指令运行脚本,带有参数up createChannel ca (带ca和不带ca使用的证书生成方式会不同,目前的工作中我们使用带有ca标签启动) 作用为 启动网络 创建通道 使用ca来生成加密材料

  • 后一个指令运行脚本,带有参数deployCC 作用为在通道上安装链码(智能合约)

运行脚本的目录一定得是在test-network/下

1.networkup( )

  • 先执行network up()函数,该函数中调用了

    • checkPrep() -- 主要检查docker的版本

    • createOrgs() -- createOrgs分为以下步骤:(这里直接以-ca的启动模式为例)

      createOrgs( )

      • 通过docker-compose启动ca结点

      • 载入 registerEnroll.sh --那么这个脚本有什么内容呢?

        • createOrg1()
        • createOrg2()
        • createOrderer()
        • 上面三个函数是这个shell里的定义
      • 然后执行shell里定义的函数

      • createOrg1

      • createOrg2

      • createOrderer

      • 接下来执行ccp-generate.sh脚本

        • 脚本功能是创建证书文件,然后放置在了organizations/ 下

    • 接下来会检查是否使用数据库,如果有使用couchdb,那用docker启动一个相关容器

2.createChannel( )

    那么组织文件证书弄完了,要使用的话,接下来需要创建创世纪块
    创世纪块使用了工具 configtxgen, 创世纪块的配置为TwoOrgsApplicationGenesis
    The channel MSP allows the nodes and users to be recognized as members of the network
    有了组织的加密文件和创世纪块,peers 和 orderer 的服务就能启动了
    
    这一步会在createChannel() 时进行
  • up( ) 的内容结束,网络启动后会创建通道 也就是createChannel( )这个函数

    • 函数第一步是检查网络是否在启动,没有的话就去执行上面的up( )了

    • 如果网络没问题,就去跑scripts/ 下的createChannel.sh -- 那么我们去看看里面有什么

      createChannel.sh的内容

      创建文件夹

      • 首先会创建一个文件夹 channel-artifacts/ 来存放通道的文件

      四个函数的定义

      ​ 1.接下来是一个函数的定义 createChannelGenesisBlock( )

      • 这个函数中的一条重要指令为

        configtxgen -profile TwoOrgsApplicationGenesis -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME
        #通过这条指令,使用之前说到的configtxgen工具创建创世纪块,存放在刚刚创建的 channel-artifacts/ 下
        

        2.定义了一个createChannel( ) -- 内容如下

        #脚本中写了个延时的指令,给raft共识留时间,这里就省略了
        setGlobals 1
        #后面是一条很长指令,看的出来,是根据我们在channel-artifacts/ 中创出来的创世纪块文件来创建通道
        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" >&log.txt
        

        3.这之外还定义了一项函数 joinChannel( ) -- 内容摘要我也写在代码块里

        #先是这么几行指令 $1就是在函数运行时加的第一个参数 
        #Ep:joinChannel 1 
        #那么就是在org1创建peer结点并加入通道
        ORG=$1
        setGlobals $ORG
        #之后脚本中也是一段延时的指令,这里省略
        #接下来就是加入peer结点辣
        peer channel join -b $BLOCKFILE >&log.txt
        

        4.还有一个设定锚节点的函数定义setAnchorpeer( )

        #这个函数也有处理参数
        ORG=$1
        #接下来进入结点的docker容器内部,这里不是用bash而是直接运行了脚本setAnchorPeer.sh
        docker exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
        #那么上面的那个setAnchorPeer.sh里面根据传入参数不同,设定了不同的host名和端口,即确定了锚节点信息
        
        • 那么上面是脚本里的四个函数定义 -- creataChannelGenesisBlock --createChannel --joinChannel --setAnchorpeer()

    接下来就调用这些函数,来完成通道的创建.结点的创建加入和锚节点的设定

        #创建创世纪块
        createChannelGenesisBlock  
        #接下来设定了后面函数里的一些变量位置,比如创世纪块数据的位置BLOCKFILE
        FABRIC_CFG_PATH=$PWD/../config/
        BLOCKFILE="./channel-artifacts/${CHANNEL_NAME}.block"
        #创建通道
        createChannel
        #为组织org1,org2创建peer结点,加入通道
        joinChannel 1
        joinChannel 2
        #为org1,org2设定锚节点
        setAnchorPeer 1
        setAnchorPeer 2
    

3.deployCC( )

  • 网络和通道都已经启动完毕!那么后面的步骤就是在通道上安装链码并编译app了,那么这就用到后面一步函数 deployCC( )

    • 可恶的是,这个函数里面的内容就是运行scripts/ 下的deployCC.sh脚本,当然在运行时会传入很多链码的信息以及通道的信息,这样才能在通道上部署链码,

    • 那还有什么好说的,冲这个脚本!让我们vim进去康康

      deployCC.sh

      因为上面也说了,我们传入了很多参数进去,脚本的开始很长一段就是print 这些变量来检查,并且确认有没有缺少关键信息

      根据传入的链码语言信息不同,编译的工具也不同 Ep: java--gradle javascript/typescript---npm

      #如果是go
      if [ "$CC_SRC_LANGUAGE" = "go" ]; then
        CC_RUNTIME_LANGUAGE=golang
      
        infoln "Vendoring Go dependencies at $CC_SRC_PATH"
        pushd $CC_SRC_PATH
        GO111MODULE=on go mod vendor
        popd
        successln "Finished vendoring Go dependencies"
      
      #java
      elif [ "$CC_SRC_LANGUAGE" = "java" ]; then
        CC_RUNTIME_LANGUAGE=java
      
        infoln "Compiling Java code..."
        pushd $CC_SRC_PATH
        ./gradlew installDist
        popd
        successln "Finished compiling Java code"
        CC_SRC_PATH=$CC_SRC_PATH/build/install/$CC_NAME
      
      #js
      elif [ "$CC_SRC_LANGUAGE" = "javascript" ]; then
        CC_RUNTIME_LANGUAGE=node
      
      elif [ "$CC_SRC_LANGUAGE" = "typescript" ]; then
        CC_RUNTIME_LANGUAGE=node
      
        infoln "Compiling TypeScript code into JavaScript..."
        pushd $CC_SRC_PATH
        npm install
        npm run build
        popd
        successln "Finished compiling TypeScript code into JavaScript"
      

      显而易见,上面的if-elif代码结构并不会全部运行,只会根据你的链码语言参数进行相应的编译,那么脚本中除了上面的编译指令外,还有相当多函数的定义:

      packageChaincode() installChaincode() queryInstalled() approveForMyOrg() checkCommitReadiness()

      commitChaincodeDefinition() queryCommitted() chaincodeInvokeInit() chaincodeQuery()

      整整九个函数!!!

      • packageChaincode()
       #主要是一条打包链码的指令
       peer lifecycle chaincode package ${CC_NAME}.tar.gz --path ${CC_SRC_PATH} --lang ${CC_RUNTIME_LANGUAGE} --label ${CC_NAME}_${CC_VERSION} >&log.txt 
      
      • installChaincode()

        #主要是一条安装链码的指令,带参
        peer lifecycle chaincode install ${CC_NAME}.tar.gz >&log.txt
        
      • queryInstalled()

        #带参
        peer lifecycle chaincode queryinstalled >&log.txt
        
      • approveForMyOrg()

        #带参
        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} >&log.txt
        
      • checkCommitReadiness()

        #带参
        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 >&log.txt
        
      • commitChaincodeDefinition()

         peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" --channelID $CHANNEL_NAME --name ${CC_NAME} "${PEER_CONN_PARMS[@]}" --version ${CC_VERSION} --sequence ${CC_SEQUENCE} ${INIT_REQUIRED} ${CC_END_POLICY} ${CC_COLL_CONFIG} >&log.txt
        
      • queryCommitted()

        #带参
         peer lifecycle chaincode queryinstalled >&log.txt
        
      • chaincodeInvokeInit()

        peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "$ORDERER_CA" -C $CHANNEL_NAME -n ${CC_NAME} "${PEER_CONN_PARMS[@]}" --isInit -c 
        ${fcn_call} >&log.txt
        
      • chaincodeQuery()

        #带参
        peer chaincode query -C $CHANNEL_NAME -n ${CC_NAME} -c '{"Args":["queryAllCars"]}' >&log.txt
        

      上面是九个函数的定义,不难看出,每个函数都是调用了一条指令(有判断是否超时的部分被我省去了)

      函数定义完了,接下来是执行这些函数的指令:

      #首先打包链码
      packageChaincode
      #在组织1的peer安装
      installChaincode 1
      #在组织2的peer安装
      installChaincode 2
      #检测是否安装成功
      queryInstalled 1
      #组织1 批准通过
      approveForMyOrg 1
      #检测链码定义是否通过,这里Org1已经通过了而Org2还没有
      checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": false"
      checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": false"
      #接下来是组织2的相同步骤
      approveForMyOrg 2
      #这时两个组织都已经通过,检查一下
      checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true"
      checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true"
      #两个组织都已经同意,提交链码
      commitChaincodeDefinition 1 2
      #检查org1的提交状态
      queryCommitted 1
      #检查org2的提交状态
      queryCommitted 2
      #如果有初始化参数,就执行链码
      chaincodeInvokeInit 1 2
      

4.networkDown( )

  • 上面的就是使用fabric网络的过程了,那如果关闭网络,有函数network

  • Down( )

  • 通过docker-compose删除容器

  • 通过docker-compose删除网络产生的证书等文件

    关闭网络时直接用脚本down即可

弄明白了网络的启动底层,那么现在来看看如果多机配置需要修改哪些内容呢

docker-compose.yaml

我们通过docker-compose创建3个组织的结点, 那么我们要对这些结点的启动配置文件修改, 使他们连成一个网络

我们创建完orgs的证书文件后,需要拷贝到每台服务器上,保证不同机器上相同组织加密文件仍是一致的

通道文件Channel-artifacts/也同样需要这样拷贝