区块链本质上是满足最终一致性的去中心化、点对点,同时可以自建信任的分布式网络。在中心化系统中,经过了多年的理论探索和实践发展,已经比较成熟。而在去中心化的系统中,各个节点因为网络拥堵、性能限制、甚至网络***、作恶节点等原因,带来各自状态的不确定性,要解决一致性问题绝非易事。为此,区块链通过共识机制来解决一致性问题。所谓共识机制就是一种多方协作的运行机制,协调网络中的多参与方达成可被接受的唯一结果,并保证此过程正确有效且能持续运行。常见的共识机制有PoW、PoS、PBFT、Raft等,后续文中的共识以fabric联盟链最常见的Raft展开。
在fabric联盟链的网络中,网络拓扑关系及各项配置以区块的形式保存在创世区块中。任何操作都是一笔交易,无论是调用智能合约执行业务操作或是对配置的更改,都需要形成共识以后出块。如果在已经运行起来的联盟链网络中添加新的业务主体,需要修改现有配置,形成新的配置并通过共识上链,这个过程叫做组织的动态管理,组织动态管理分为动态添加和动态删除。
01
组织动态添加
组织动态添加的时序图
准备好一个已经在运行的fabric联盟链网络,由以下容器构成。
接下来准备需要动态添加的组织org3,使用命令行工具cryptogen generate --config=./org3-crypto.yaml生成组织org3的证书;
使用命令行工具configtxgen -printOrg Org3MSP > ../channel-artifacts/org3.json生成组织org3的配置。
获取最新的配置区块
首先,进入到CLI容器:
docker exec -it cli bash
设置ORDERER_CA和CHANNEL_NAME变量:
export 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 && export CHANNEL_NAME=mychannel
拉取最新版本的配置区块,以二进制protobuf的形式保存在config_block.pb中:
peer channel fetch config config_block.pb -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
新组织配置添加到通道配置上
借助configtxlator二进制工具解析并裁剪PB格式的配置,并输出为config.json:
configtxlator proto_decode –input config_block.pb –type common.Block | jq .data.data[0].payload.data.config > config.json
使用jq工具把准备工作生成的org3组织配置添加到通道的应用组字段,输出文件:modified_config.json:
jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}}' config.json ./channel-artifacts/org3.json > modified_config.json
现在我们有两个非常重要的json配置文件,config.json和modified_config.json,前者是包含org1和org2的通道配置,后者共包含三个组织org1、org2和org3的配置。
重新编码两个json文件并计算差异
首先,将config.json文件倒回到protobuf格式,命名为config.pb:
configtxlator proto_encode --input config.json --type common.Config --output config.pb
下一步,将modified_config.json编码成modified_config.pb:
configtxlator proto_encode --input modified_config.json --type common.Config --output modified_config.pb
现在使用configtxlator去计算两个protobuf配置的差异,并输出一个新的PB二进制文件org3_update.pb:
configtxlator compute_update --channel_id $CHANNEL_NAME --original config.pb --updated modified_config.pb --output org3_update.pb
包装差异封皮
首先,我们将这个差异PB文件解码成可编辑的json格式,并命名为org3_update.json:
configtxlator proto_decode --input org3_update.pb --type common.ConfigUpdate | jq . > org3_update.json
得到解码后的更新文件org3_update.json,接下来我们需要把第二步裁剪掉的头部信息还原回来,并命名为org3_update_in_envelope.json:
echo ‘{“payload”:{“header”:{“channel_header”:{“channel_id”:”mychannel”, “type”:2}},”data”:{“config_update”:’$(cat org3_update.json)’}}}’ | jq . > org3_update_in_envelope.json
最后一次使用configtxlator工具将json转换为Fabric需要的完整独立的protobuf:
configtxlator proto_encode --input org3_update_in_envelope.json --type common.Envelope --output org3_update_in_envelope.pb
用通道现有组织的身份签名
现在我们有一个可以用来更新通道配置的org3_update_in_envelope.pb文件,在把配置文件提交到Orderer节点之前,我们需要通道内现有的两个组织Org1、Org2签名,以满足共识机制,否则Orderer节点会因为不满足策略而拒绝这个交易。
使用Org1管理员的身份签名:
peer channel signconfigtx -f org3_update_in_envelope.pb
接下来我们将MSP身份切换为Org2的管理员,并使用Org2管理员的身份签名。
导出Org2的环境变量:
export CORE_PEER_LOCALMSPID="Org2MSP"export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crtexport CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/mspexport CORE_PEER_ADDRESS=peer0.org2.example.com:9051
使用Org2管理员的身份签名:
peer channel signconfigtx -f org3_update_in_envelope.pb
发起通道配置更新调用
peer channel update -f org3_update_in_envelope.pb -c $CHANNEL_NAME -o orderer.example.com:7050 --tls --cafile $ORDERER_CA
将新组织Org3加入通道
首先启动Org3的节点容器和cli容器:
docker-compose -f docker-compose-org3.yaml up -d
docker-compose文件中已经桥接到了初始网络,cli容器和Org3的两个peer节点容器都可以连接到已经存在的节点和排序节点。
进入Org3的cli容器中:
docker exec -it Org3cli bash
和之前一样,设置好环境变量:
export 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 && export CHANNEL_NAME=mychannel
这时,我们去获取通道的创世区块。因为区块链中每一个账本都要保持一致性,所以必须要获取索引为0的区块:
peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
在之前的步骤里,我们已经将Org3添加到通道配置中,所以Order节点可以成功校验这个请求中的Org3的签名,否则,Order节点会拒绝这个请求。
把Org3的第一个节点peer0加入到通道中(cli容器中默认env为peer0):
peer channel join -b mychannel.block
切换环境变量,把第二个节点peer1加入通道:
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/peers/peer1.org3.example.com/tls/ca.crt && export CORE_PEER_ADDRESS=peer1.org3.example.com:12051peer channel join -b mychannel.block
至此,我们成功的将新组织Org3动态加入到了已经运行的fabric联盟链网络。在裁剪新组织配置时进行了多次protobuf和json格式的转换,究其原因在于:fabric网络只接受完整的二进制protobuf格式文件,而profobuf仅能通过json的转换才可以做到可读的裁剪。同时,也由于fabric对于区块的定义复杂且庞大,UML类图如下:
(图片来源于网络)
02
组织动态退出
动态退出时序图
动态退出就是在已经运行中的联盟链网络中删除一个业务主体组织,实现步骤与动态添加正好相反,准备好的网络环境如下:
获取最新通道配置块
进入cli容器:
docker exec -it cli bash
设置$ORDERER_CA和$CHANNEL_NAME环境变量:
export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/msp/tlscacerts/tlsca.example.com-cert.pemexport CHANNEL_NAME=mychannel
获取通道配置:
peer channel fetch config config_block.pb -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA
删除通道上的现有组织Org3并生成新配置
将上一步获取到的配置config_block.pb转换为json:
configtxlator proto_decode --input config_block.pb --type common.Block | jq .data.data[0].payload.data.config > config.json
删除json文件中的组织配置并生成新的json文件modify_config.json:
jq 'del(.channel_group.groups.Application.groups.Org3MSP)' config.json > modify_config.json
把两个重要的json文件转换为PB:
configtxlator proto_encode --input config.json --type common.Config --output config.pbconfigtxlator proto_encode --input modify_config.json --type common.Config --output modified_config.pb
计算两个配置的差异
使用二进制工具configtxlator计算差异并生成文件org3_delete.pb:
configtxlator compute_update --channel_id $CHANNEL_NAME --original config.pb --updated modified_config.pb --output org3_delete.pb
还原裁剪掉的头信息并包装成信封
把PB文件转换为json:
configtxlator proto_decode --input org3_delete.pb --type common.ConfigUpdate | jq . > org3_delete.json
还原头信息并生成json org3_delete_in_envelope.json:
echo '{"payload":{"header":{"channel_header":{"channel_id":"'$CHANNEL_NAME'", "type":2}},"data":{"config_update":'$(cat org3_delete.json)'}}}' | jq . > org3_delete_in_envelope.json
把json信封转成PB格式:
configtxlator proto_encode --input org3_delete_in_envelope.json --type common.Envelope --output org3_delete_in_envelope.pb
用通道组织身份签名
org1组织签名:
peer channel signconfigtx -f org3_delete_in_envelope.pb
导出org2组织身份的环境变量:
export CORE_PEER_LOCALMSPID="Org2MSP"export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crtexport CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/mspexport CORE_PEER_ADDRESS=peer0.org2.example.com:9051
org2组织签名:
peer channel signconfigtx -f org3_delete_in_envelope.pb
发起通道配置更新调用
由组织org2的身份发起更新调用:
peer channel update -f org3_delete_in_envelope.pb -c $CHANNEL_NAME -o orderer.example.com:7050 --tls --cafile $ORDERER_CA
至此,组织org3就从通道mychannel中删除了。如果org3作为通道中某个链码的背书组织,参与了链码调用过程的背书。则原链码将无法执行任何invoke操作,此时可以通过upgrade链码,更新链码的背书策略,重新指定背书组织来实现链码正常运行。
通过对fabric联盟链的动态操作,可以灵活增减业务参与主体,细粒度的控制联盟内的成员。更好的支撑业务流转,满足不同业务模型,完成区块链网络的高效可信运转。