Tip: The previous article talked about using cryptogen to issue certificates to the orderer and peer in the organization, then using docker to start the orderer and peer, and then creating the channel.
Table of contents
1. Create a channel in the main script
2.1 Create channel genesis block
2.2 Use osnadmin CLI to add orderer to the channel
Summary of problems encountered
Preface
Part One: Centos7 Fabric2.4 Network Construction (1)_Big. boss’s blog-CSDN blog
提示:用的是官方的例子学习搭建自己的网络,本次是用cryptogen生成证书,单机多节点部署
1. Create a channel in the main script
To simplify the channel creation process and enhance the privacy and scalability of channels, it is now possible to create application channels (where transactions involving assets occur) without first creating a "system channel" managed by an orderer. This case is to create a new channel without a system channel by using the configtxgen tool to create a genesis block and using the osnadmin CLI (which runs on the REST API exposed by each orderer node) to connect the orderer node to the channel. This process allows orderer nodes to join (or leave) any number of channels as needed, similar to how peers can participate in multiple channels.
# Step4 创建通道
function createChannel() {
# 如果网络尚未启动,请启动网络
bringUpNetwork="false"
if ! $CONTAINER_CLI info > /dev/null 2>&1 ; then
fatalln "$CONTAINER_CLI network is required to be running to create a channel"
fi
# 检查是否所有容器是否准备好
CONTAINERS=($($CONTAINER_CLI ps | grep hyperledger/ | awk '{print $2}'))
len=$(echo ${#CONTAINERS[@]})
if [[ $len -ge 8 ]] && [[ ! -d "organizations/peerOrganizations" ]]; then
echo "Bringing network down to sync certs with containers"
networkDown
fi
[[ $len -lt 8 ]] || [[ ! -d "organizations/peerOrganizations" ]] && bringUpNetwork="true" || echo "Network Running Already"
if [ $bringUpNetwork == "true" ]; then
infoln "Bringing up network"
networkUp
fi
# 现在运行创建频道的脚本。此脚本使用configtxgen一次性创建通道创建事务和锚节点对等更新
scripts/createChannel.sh $CHANNEL_NAME $CLI_DELAY $MAX_RETRY $VERBOSE
}
2. createChannel.sh
2.1 Create channel genesis block
The process of creating a new channel starts by generating a genesis block for the channel , which is later submitted to the orderer in the request. Only one orderer is required to create the genesis block, and it can be shared out-of-band with other members on the channel, who can check it to ensure they agree with the channel configuration, which is then used by every orderer in the ordering service.
FABRIC_CFG_PATH=${PWD}/configtx
## Create channel genesis block
infoln "Generating channel genesis block '${CHANNEL_NAME}.block'"
createChannelGenesisBlock
# 函数 - 创建通道创世区块
createChannelGenesisBlock() {
# 搜索configtxgen命令,判断是否存在此二进制文件,若不存在,打印错误信息,程序终止
which configtxgen
if [ "$?" -ne 0 ]; then
fatalln "configtxgen tool not found."
fi
set -x #脚本调试,会将下面执行的命令输出到屏幕上
# 创建创世区块:生成创世区块mychannel.block文件,根据配置文件../configtx/configtx.yaml来创建Orderer系统通道的创世块
configtxgen -profile TwoOrgsApplicationGenesis -outputBlock ./channel-artifacts/${CHANNEL_NAME}.block -channelID $CHANNEL_NAME
res=$?
{ set +x; } 2>/dev/null
verifyResult $res "Failed to generate channel configuration transaction..."
}
It involves an important file configtx.yaml. For details, please refer to Hyperledger Fabric configuration file analysis - configtx.yaml_Big. boss's blog - CSDN blog
Different from Fabric2.2 process :
1. No more need for system channels: Besides representing an extra step, the establishment of system channels creates an additional layer of management layer that does not provide any tangible benefit for certain use cases.
2. Consortium is no longer needed: It is no longer necessary to define a group of organizations ("consortium") in configtx.yaml to allow the creation of channels on a specific orderer. In the new process, all channels are application channels, and there is no need to create an organizational list of channels. Any group of organizations can come together and create a channel using a defined set of ordering nodes.
3. Simplify the orderer node creation process: The genesis block of the system channel is no longer needed before creating the orderer node. The admin can now focus on the process of basic setup and ensure that its node is working properly before joining a specific application channel.
# Copyright IBM Corp. All Rights Reserved.
#
# SPDX-License-Identifier: Apache-2.0
#
---
################################################################################
#
# Section: Organizations
#
# - This section defines the different organizational identities which will
# be referenced later in the configuration.
#
################################################################################
Organizations:
# SampleOrg defines an MSP using the sampleconfig. It should never be used
# in production but may be used as a template for other definitions
- &OrdererOrg
# DefaultOrg defines the organization which is used in the sampleconfig
# of the fabric.git development environment
# 组织名称
Name: OrdererOrg
# ID to load the MSP definition as
# 组织ID,ID是引用组织的关键
ID: OrdererMSP
# MSPDir is the filesystem path which contains the MSP configuration
# 组织的MSP证书路径
MSPDir: ../organizations/ordererOrganizations/hmw.com/msp
# Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
# 定义本层级的组织策略,其权威路径为 /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
Policies:
Readers:
Type: Signature
Rule: "OR('OrdererMSP.member')"
Writers:
Type: Signature
Rule: "OR('OrdererMSP.member')"
Admins:
Type: Signature
Rule: "OR('OrdererMSP.admin')"
OrdererEndpoints:
- orderer0.hmw.com:7050
- orderer1.hmw.com:7052
- orderer2.hmw.com:7054
- &Org1
# DefaultOrg defines the organization which is used in the sampleconfig
# of the fabric.git development environment
Name: Org1MSP
# ID to load the MSP definition as
ID: Org1MSP
MSPDir: ../organizations/peerOrganizations/org1.hmw.com/msp
# Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
Policies:
Readers:
Type: Signature
Rule: "OR('Org1MSP.admin', 'Org1MSP.peer', 'Org1MSP.client')"
Writers:
Type: Signature
Rule: "OR('Org1MSP.admin', 'Org1MSP.client')"
Admins:
Type: Signature
Rule: "OR('Org1MSP.admin')"
Endorsement:
Type: Signature
Rule: "OR('Org1MSP.peer')"
- &Org2
# DefaultOrg defines the organization which is used in the sampleconfig
# of the fabric.git development environment
Name: Org2MSP
# ID to load the MSP definition as
ID: Org2MSP
MSPDir: ../organizations/peerOrganizations/org2.hmw.com/msp
# Policies defines the set of policies at this level of the config tree
# For organization policies, their canonical path is usually
# /Channel/<Application|Orderer>/<OrgName>/<PolicyName>
Policies:
Readers:
Type: Signature
Rule: "OR('Org2MSP.admin', 'Org2MSP.peer', 'Org2MSP.client')"
Writers:
Type: Signature
Rule: "OR('Org2MSP.admin', 'Org2MSP.client')"
Admins:
Type: Signature
Rule: "OR('Org2MSP.admin')"
Endorsement:
Type: Signature
Rule: "OR('Org2MSP.peer')"
################################################################################
#
# SECTION: Capabilities
#
# - This section defines the capabilities of fabric network. This is a new
# concept as of v1.1.0 and should not be utilized in mixed networks with
# v1.0.x peers and orderers. Capabilities define features which must be
# present in a fabric binary for that binary to safely participate in the
# fabric network. For instance, if a new MSP type is added, newer binaries
# might recognize and validate the signatures from this type, while older
# binaries without this support would be unable to validate those
# transactions. This could lead to different versions of the fabric binaries
# having different world states. Instead, defining a capability for a channel
# informs those binaries without this capability that they must cease
# processing transactions until they have been upgraded. For v1.0.x if any
# capabilities are defined (including a map with all capabilities turned off)
# then the v1.0.x peer will deliberately crash.
#
################################################################################
Capabilities:
# Channel capabilities apply to both the orderers and the peers and must be
# supported by both.
# Set the value of the capability to true to require it.
Channel: &ChannelCapabilities
# V2_0 capability ensures that orderers and peers behave according
# to v2.0 channel capabilities. Orderers and peers from
# prior releases would behave in an incompatible way, and are therefore
# not able to participate in channels at v2.0 capability.
# Prior to enabling V2.0 channel capabilities, ensure that all
# orderers and peers on a channel are at v2.0.0 or later.
V2_0: true
# Orderer capabilities apply only to the orderers, and may be safely
# used with prior release peers.
# Set the value of the capability to true to require it.
Orderer: &OrdererCapabilities
# V2_0 orderer capability ensures that orderers behave according
# to v2.0 orderer capabilities. Orderers from
# prior releases would behave in an incompatible way, and are therefore
# not able to participate in channels at v2.0 orderer capability.
# Prior to enabling V2.0 orderer capabilities, ensure that all
# orderers on channel are at v2.0.0 or later.
V2_0: true
# Application capabilities apply only to the peer network, and may be safely
# used with prior release orderers.
# Set the value of the capability to true to require it.
Application: &ApplicationCapabilities
# V2_0 application capability ensures that peers behave according
# to v2.0 application capabilities. Peers from
# prior releases would behave in an incompatible way, and are therefore
# not able to participate in channels at v2.0 application capability.
# Prior to enabling V2.0 application capabilities, ensure that all
# peers on channel are at v2.0.0 or later.
V2_0: true
################################################################################
#
# SECTION: Application
#
# - This section defines the values to encode into a config transaction or
# genesis block for application related parameters
#
################################################################################
Application: &ApplicationDefaults
# Organizations is the list of orgs which are defined as participants on
# the application side of the network
Organizations:
# Policies defines the set of policies at this level of the config tree
# For Application policies, their canonical path is
# /Channel/Application/<PolicyName>
Policies:
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
LifecycleEndorsement:
Type: ImplicitMeta
Rule: "MAJORITY Endorsement"
Endorsement:
Type: ImplicitMeta
Rule: "MAJORITY Endorsement"
Capabilities:
<<: *ApplicationCapabilities
################################################################################
#
# SECTION: Orderer
#
# - This section defines the values to encode into a config transaction or
# genesis block for orderer related parameters
#
################################################################################
Orderer: &OrdererDefaults
# Orderer Type: The orderer implementation to start
# 定义order共识机制
OrdererType: etcdraft
# Addresses used to be the list of orderer addresses that clients and peers
# could connect to. However, this does not allow clients to associate orderer
# addresses and orderer organizations which can be useful for things such
# as TLS validation. The preferred way to specify orderer addresses is now
# to include the OrdererEndpoints item in your org definition
Addresses:
- orderer0.hmw.com:7050
- orderer1.hmw.com:7052
- orderer2.hmw.com:7054
EtcdRaft:
Consenters:
- Host: orderer0.hmw.com
Port: 7050
ClientTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer0.hmw.com/tls/server.crt
ServerTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer0.hmw.com/tls/server.crt
- Host: orderer1.hmw.com
Port: 7052
ClientTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer1.hmw.com/tls/server.crt
ServerTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer1.hmw.com/tls/server.crt
- Host: orderer2.hmw.com
Port: 7054
ClientTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer2.hmw.com/tls/server.crt
ServerTLSCert: ../organizations/ordererOrganizations/hmw.com/orderers/orderer2.hmw.com/tls/server.crt
# Batch Timeout: The amount of time to wait before creating a batch
# 区块生成超时时间
BatchTimeout: 2s
# Batch Size: Controls the number of messages batched into a block
BatchSize:
# Max Message Count: The maximum number of messages to permit in a batch
# 区块消息数量
MaxMessageCount: 10
# Absolute Max Bytes: The absolute maximum number of bytes allowed for
# the serialized messages in a batch.
# 区块绝对最大字节数
AbsoluteMaxBytes: 99 MB
# Preferred Max Bytes: The preferred maximum number of bytes allowed for
# the serialized messages in a batch. A message larger than the preferred
# max bytes will result in a batch larger than preferred max bytes.
# 建议消息字节数
PreferredMaxBytes: 512 KB
# Organizations is the list of orgs which are defined as participants on
# the orderer side of the network
Organizations:
# Policies defines the set of policies at this level of the config tree
# For Orderer policies, their canonical path is
# /Channel/Orderer/<PolicyName>
Policies:
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
# BlockValidation specifies what signatures must be included in the block
# from the orderer for the peer to validate it.
BlockValidation:
Type: ImplicitMeta
Rule: "ANY Writers"
################################################################################
#
# CHANNEL
#
# This section defines the values to encode into a config transaction or
# genesis block for channel related parameters.
#
################################################################################
Channel: &ChannelDefaults
# Policies defines the set of policies at this level of the config tree
# For Channel policies, their canonical path is
# /Channel/<PolicyName>
Policies:
# Who may invoke the 'Deliver' API
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
# Who may invoke the 'Broadcast' API
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
# By default, who may modify elements at this config level
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
# Capabilities describes the channel level capabilities, see the
# dedicated Capabilities section elsewhere in this file for a full
# description
Capabilities:
<<: *ChannelCapabilities
################################################################################
#
# Profile
#
# - Different configuration profiles may be encoded here to be specified
# as parameters to the configtxgen tool
#
################################################################################
Profiles:
TwoOrgsApplicationGenesis:
<<: *ChannelDefaults
Orderer:
<<: *OrdererDefaults
Organizations:
- *OrdererOrg
Capabilities: *OrdererCapabilities
Application:
<<: *ApplicationDefaults
Organizations:
- *Org1
- *Org2
Capabilities: *ApplicationCapabilities
2.2 Use osnadmin CLI to add orderer to the channel
Now that the Genesis block has been created, the first ordering node that receives the osnadmin channel connect command effectively "activates" the channel, although the channel is not fully operational until a quorum frequency is established (at least if your policy lists three consents) There are two nodes in total, and you must use the osnadmin channel connection command to join)
# 函数 - 激活通道
createChannel() {
# 调用脚本 envVar.sh中的函数setGlobals,为org1或org2的peer节点设置环境变量,参数01对应的是org1,02对应org2
# 设置环境变量为org1
setGlobals 01
# Poll in case the raft leader is not set yet
local rc=1
local COUNTER=1
# 创建通道若未成功,可尝试5次
while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
sleep $DELAY
set -x
# 创建通道,将执行结果信息记录到log.txt文件中
infoln "Orderer0 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7051 --ca-file "$ORDERER_CA" --client-cert "$ORDERER_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER_ADMIN_TLS_PRIVATE_KEY" >&log.txt
infoln "Orderer1 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7053 --ca-file "$ORDERER_CA" --client-cert "$ORDERER1_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER1_ADMIN_TLS_PRIVATE_KEY" >&log.txt
infoln "Orderer2 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o localhost:7055 --ca-file "$ORDERER_CA" --client-cert "$ORDERER2_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER2_ADMIN_TLS_PRIVATE_KEY" >&log.txt
res=$?
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done
# 打印log.txt,即打印创建通道结果信息
cat log.txt
# 调用脚本 envVar.sh 中的函数verifyResult,用来验证上面执行的命令是否报错,如果报错,则打印错误信息,程序终止
verifyResult $res "Channel creation failed"
}
After success, the orderer will join the channel with a leader height of 1. Because this orderer was added from the genesis block, the status is displayed as "active". And orderer is the console of the channel, so consensusRelation is "consenter".
2.3 peer joins channel
## Join all the peers to the channel
infoln "Joining peer0-org1 to the channel..."
joinChannel 01
infoln "Joining peer1-org1 to the channel..."
joinChannel 11
infoln "Joining peer0-org2 to the channel..."
joinChannel 02
infoln "Joining peer1-org2 to the channel..."
joinChannel 12
# joinChannel ORG
joinChannel() {
FABRIC_CFG_PATH=${PWD}/config/
ORG=$1
# 根据传递的参数设置环境变量,切换组织使用
setGlobals $ORG
local rc=1
local COUNTER=1
# peer节点加入通道若未成功,可尝试5次
while [ $rc -ne 0 -a $COUNTER -lt $MAX_RETRY ] ; do
sleep $DELAY
set -x
# 将节点加入通道,将执行结果信息记录到log.txt文件中
peer channel join -b $BLOCKFILE >&log.txt
res=$?
{ set +x; } 2>/dev/null
let rc=$res
COUNTER=$(expr $COUNTER + 1)
done
cat log.txt
verifyResult $res "After $MAX_RETRY attempts, peer0.org${ORG} has failed to join channel '$CHANNEL_NAME' "
}
2.4 Set anchor node
Finally, after an organization joins its peers to a channel, they should select at least one of them to be an anchor. In order to take advantage of features such as private data and service discovery, anchor nodes are required. Every organization should have multiple anchor nodes on a channel for redundancy. Endpoint information for each organization's anchor node is included in the channel configuration. Each channel member can specify their anchor node by updating the channel.
## Set the anchor peers for each org in the channel
infoln "Setting anchor peer for org1..."
setAnchorPeer 01
infoln "Setting anchor peer for org2..."
setAnchorPeer 02
# 函数 - 设置锚节点
setAnchorPeer() {
ORG=$1
${CONTAINER_CLI} exec cli ./scripts/setAnchorPeer.sh $ORG $CHANNEL_NAME
}
Each channel member can specify their anchor node by updating the channel. We will use configtxlator to update the channel configuration for Org1 and Org2.
Org1 sets environment variables
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.hmw.com/tlsca/tlsca.org1.hmw.com-cert.pem
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.hmw.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:8051
The channel configuration can be obtained through the following command
infoln "Fetching the most recent configuration block for the channel"
set -x
# 获取通道配置
peer channel fetch config config_block.pb -o orderer0.hmw.com:7050 --ordererTLSHostnameOverride orderer0.hmw.com -c $CHANNEL --tls --cafile "$ORDERER_CA"
{ set +x; } 2>/dev/null
res=$?
if [ $res -ne 0 ]; then
fatalln "Failed to fetch the most recent configuration block..."
fi
Since the most recent channel configuration block is the channel genesis block, the command returns block 0 from the channel
We can now start using the configtxlator tool to get started with channel configuration. The first step is to decode the chunks from protobuf into a json object that can be read and edited. We also got rid of unnecessary block data, leaving only the channel configuration.
# 将配置块protobuf格式转成json格式
configtxlator proto_decode --input config_block.pb --type common.Block --output config_block.json
jq .data.data[0].payload.data.config config_block.json >"${OUTPUT}"
These commands convert the channel configuration block into a simplified json, config.json, which will serve as the baseline for our updates.
The org1 anchor node can be added to the channel configuration using the jq tool
set -x
# Modify the configuration to append the anchor peer
# 将锚节点添加至配置文件中
jq '.channel_group.groups.Application.groups.'${CORE_PEER_LOCALMSPID}'.values += {"AnchorPeers":{"mod_policy": "Admins","value":{"anchor_peers": [{"host": "'$HOST'","port": '$PORT'}]},"version": "0"}}' ${CORE_PEER_LOCALMSPID}config.json > ${CORE_PEER_LOCALMSPID}modified_config.json
{ set +x; } 2>/dev/null
After this step, we updated the updated version of the channel configuration in json format in the modified_config.json file. We can now convert the original and modified channel configurations back to protobuf format and calculate the difference between them
# 将原始和修改的通道配置都转换回protobuf格式,并计算它们之间的差异
configtxlator proto_encode --input "${ORIGINAL}" --type common.Config --output original_config.pb
configtxlator proto_encode --input "${MODIFIED}" --type common.Config --output modified_config.pb
configtxlator compute_update --channel_id "${CHANNEL}" --original original_config.pb --updated modified_config.pb --output config_update.pb
The new protobuf file is called config_update.pb and contains the anchor node updates that apply to the channel configuration. We can wrap the configuration updates in a transaction envelope to create a channel configuration update transaction.
# 将配置更新包装在交易Envelope中,以创建通道配置更新交易
configtxlator proto_decode --input config_update.pb --type common.ConfigUpdate --output config_update.json
echo '{"payload":{"header":{"channel_header":{"channel_id":"'$CHANNEL'", "type":2}},"data":{"config_update":'$(cat config_update.json)'}}}' | jq . >config_update_in_envelope.json
configtxlator proto_encode --input config_update_in_envelope.json --type common.Envelope --output "${OUTPUT}"
{ set +x; } 2>/dev/null
We can add anchor nodes by providing new channel configurations through the peer channel update command.
Because we are updating a part of the channel configuration that only affects org1, other channel members do not need to approve the channel update
peer channel update -o orderer0.hmw.com:7050 --ordererTLSHostnameOverride orderer0.hmw.com -c $CHANNEL_NAME -f ${CORE_PEER_LOCALMSPID}anchors.tx --tls --cafile "$ORDERER_CA" >&log.txt
In the same way, set the environment variables and add anchor nodes for Org2.
You can confirm that the channel has been updated with the following command:
export FABRIC_CFG_PATH=${PWD}/config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.hmw.com/tlsca/tlsca.org1.hmw.com-cert.pem
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.hmw.com/users/[email protected]/msp
export CORE_PEER_ADDRESS=localhost:8051
peer channel getinfo -c mychannel
By updating the anchor node above, add two channel configuration files to the genesis block to update the channel, so the block height of the channel becomes 3 and the hash value is also updated.
Please refer to the relevant code: https://blog.csdn.net/humingwei11/article/details/124128401
Summary of problems encountered
1.Error: can't read the block: &{SERVICE_UNAVAILABLE}
This problem has troubled me for a long time. It turns out that when joining the channel, not all orderers were added to the channel. 2
set -x
# 创建通道,将执行结果信息记录到log.txt文件中
infoln "Orderer0 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o orderer0.hmw.com:7051 --ca-file "$ORDERER_CA" --client-cert "$ORDERER_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER_ADMIN_TLS_PRIVATE_KEY" >&log.txt
infoln "Orderer1 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o orderer1.hmw.com:7053 --ca-file "$ORDERER_CA" --client-cert "$ORDERER1_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER1_ADMIN_TLS_PRIVATE_KEY" >&log.txt
infoln "Orderer2 join channel"
osnadmin channel join --channelID $CHANNEL_NAME --config-block ./channel-artifacts/${CHANNEL_NAME}.block -o orderer2.hmw.com:7055 --ca-file "$ORDERER_CA" --client-cert "$ORDERER2_ADMIN_TLS_SIGN_CERT" --client-key "$ORDERER2_ADMIN_TLS_PRIVATE_KEY" >&log.txt
res=$?
{ set +x; } 2>/dev/null
2. When the orderer joins the channel, -o I write localhost and the TLS certificate handshake fails.
The reason is that in crypto-config-orderer.yaml, I forgot to add SANS under each Hostname in Specs.