

作者已经在上一篇Fabric 1.0测试和运行环境构建及弹珠交易市场应用(一)中介绍了测试环境单机构单节点的环境构建,通道建立和相关chaincode的部署,特别还介绍了防止“双花”方面系统的安全控制,相关的机制可以参看超级账本Hyperledger Fabric 1.0架构解读一文。本篇会讲解实际包括多机构多节点参与的运行环境的构建,并且在这个运行环境上部署实际由多个弹珠交易市场构成的联盟区块链环境和应用。 一、运行环境构建说明 实际的区块链网络运行环境应该是由多个机构若干节点参与运行的复杂拓扑结构网络,为了降低样例演示的复杂度,本文运行的示例模拟由两个机构构成的联盟交易市场,每个交易市场就是一个机构,拥有自己的CA,每个机构自己管理注册用户,拥有两个背书节点,如下图所示。

示例构建了一个超级账本Fabric1.0区块链网络,为弹珠联盟市场建立了专门的通道ch1,在ch1上部署了两个 chaincode,一个是网络和通道测试chaincode example02,另一个就是我们的弹珠交易市场chaincode marbles02,区块链网络使用了两种客户端访问方式, 一种是基于fabric-ca-client和fabric-client的node.js方式,另一种是基于peer命令行cli方式。这是一个由弹珠 交易市场构成的联盟链,示例中有两个交易市场 “United Marbles” 和 “eMarbles”,每个弹珠交易市场都可以进行用户注册,弹珠注册(资产登记)和弹珠转移(资产转移),弹珠除了可以在本交易市场 内进行转移,还可以向其他交易市场上的用户进行转移。如果以现实中的例子做类比,每个交易市场相当于一个证券公司,由证券公司共同构成了场内交易所联盟, 或者也可以类比于银行发行的票据,这些票据在票据交易所进行的流通,或者也可以类比于银行的客户在银行内部转账,以及通过央行大小额或银联/网联进行跨行 转账。示例构建了一个由3个组织构成的通道,OrdererOrg的成员orderer0为整个区块链网络提供交易排序服务,PeerOrg1的成员节点 peer0和peer1为本组织 (“United Marbles”) Org1MSP 的背书节点,PeerOrg2的成员节点peer2和peer3为本组织(“eMarbles”)Org2MSP的背书 节点。ca0为组织 (“United Marbles”) Org1MSP的成员提供enroll和transaction证书,ca1为组织(“eMarbles”) Org2MSP的成员提供enroll和transaction证书。弹珠交易市场的业务逻辑部署在Node.js App请求节点hfc0和hfc1上,程序通过fabric-ca-client同ca0和ca1进行http通讯进行enroll并获取transaction 证书,客户端请求节点在通过ca的认证后,将业务逻辑处理封装成交易建议,然后向成员节点发起背书请求,背书节点在docker-vm进行交易的 chaincode的执行验证,构建读写集合,请求节点接收到背书节点的交易建议响应,基于背书策略进行结果分析,确保读写集合一致性,同时满足背书策略 的交易,请求节点将背书结果合并交易建议一并提交给排序共识服务,共识服务将所有通道上的交易进行排序并按时按大小合并成一个批量block广播给这个通 道的所有订阅者节点peer0~4,peer0~4收到时序服务广播的block后,进行block内的交易验证并提交到本地账本上。
本次实践我们部署的docker都是运行在一个Ubuntu Linux上,这个Ubuntu Linux是我通过vmware虚拟出来的一个虚机,虚机网络都采用NAT方式,共享host主机的外部网络。其主要参数如下: Ubuntu 16.04 LTS IP: 192.168.5.171 gateway: 192.168.5.2 hostname: ubu-blockchain1 Docker Network Bridge gateway: 172.17.0.1 上图15个docker containers,以及chaincode各个docker-vm container的IP如下: "Name": "hfc0", "IPv4Address": "172.18.0.8/16", "Name": "hfc1", "IPv4Address": "172.18.0.7/16", "Name": "cli", "IPv4Address": "172.18.0.11/16", "Name": "ca0", "IPv4Address": "172.18.0.2/16", "Name": "ca1", "IPv4Address": "172.18.0.3/16", "Name": "peer0", "IPv4Address": "172.18.0.6/16", "Name": "peer1", "IPv4Address": "172.18.0.10/16", "Name": "peer2", "IPv4Address": "172.18.0.5/16", "Name": "peer3", "IPv4Address": "172.18.0.9/16", "Name": "orderer0", "IPv4Address": "172.18.0.4/16", "Name": "dev-peer0-marbles02-1.0", "IPv4Address": "172.18.0.16/16", "Name": "dev-peer2-marbles02-1.0", "IPv4Address": "172.18.0.15/16", "Name": "dev-peer0-mycc-1.0", "IPv4Address": "172.18.0.13/16", "Name": "dev-peer2-mycc-1.0", "IPv4Address": "172.18.0.12/16", "Name": "dev-peer3-mycc-1.0", "IPv4Address": "172.18.0.14/16" 二、运行环境构建和应用测试 为了便于读者快速进行环境构建和应用测试,本人已经将fabric 1.0的测试和运行环境需要的相关资源上传至github,https://github.com/blockchain101/fabric-docker

所用到的docker images也都上传至docker hub,参见blockchain101/fabric-hfc:1.0, blockchain101/fabric-cli:1.0, blockchain101/fabric-peer:1.0, blockchain101/fabric-ca:1.0, blockchain101/fabric-orderer:1.0。

2.1 首先请安装Docker容器和docker-compose运行环境 请参照 http://bctrustmachine.cn/forum.php?mod=viewthread&tid=22&fromuid=1 安装Docker容器和docker-compose运行环境。 使用docker-compose比较方便的地方是,可以使用别人已经编好的docker-compose.yaml 自动从docker hub pull images并且自动构建应用。 2.2 读者可自行从github下载fabric-docker 资源
$ git clone https://github.com/blockchain101/fabric-docker.git

2.3 通过docker-compose直接启动运行环境各容器
先从docker hub pull下chaincode的执行环境hyperledger/fabric-ccenv
$ docker pull hyperledger/fabric-ccenv:x86_64-1.0.0-alpha

进入dev的docker compose执行目录,我们启动docker容器ca0,ca1,orderer0,peer0,peer2,hfc0,hfc2,peer1,peer3,cli
$ cd fabric-docker/fabric1.0-docker/fabric-testnet-docker/dockercomposefiles/
$ chmod +x scripts/script-ch1.sh
$ CHANNEL_NAME=ch1 docker-compose -f docker-compose-marblesv3-withcli-withhfc.yaml up
系统启动时,如果还没有相关的docker images,会从docker hub下载上述的容器images:

系统启动时,会由cli通过执行script-ch1.sh自动在相关节点上create channel,join channel, install chaincode, instantiate chaincode, invoke/query chaincode。

整个过程是,预先基于机构组织及证书信息configtx.profile, 通过configtx工具生成orderer运行所需的genesis file:orderer-TwoOrgs.block,另外基于configtx工具我们还生成了适用于组织profile TwoOrgs的通道“ch1”对应的配置交易channel-TwoOrgs-ch1.tx,这个配置交易会作为创建“ch1”通道的唯一交易被封装在block0上,任何想加入通道“ch1”的 peer节点,都需要带上这个block join进通道“ch1”,同一个组织profile上我们可以创建多个通道,每个通道上我们可以部署多个应用chaincode,如果一个peer想要 参与到一个chaincode账本记账,首先这个节点需要使用带有通道信息的配置交易block0 join到这个通道上,然后再部署想要加入的应用chaincode。当节点第一次初始化chaincode时,就会产生了block1。后续的 invoke 交易可以一起被封装在同一个block上参与orderer排序共识,只要在一个block上的交易不存在双花,交易都可以通过状态验证器的验证而被提交 到账本上,如果一个block上的某个交易同另一个交易存在双花关系,排在后面的一笔交易会被mark为非法交易。
默认部署在cli上的初始化脚本会部署测试环境的example02 chaincode 到peer0,peer2,peer3上,脚本会在peer2上执行chaincode初始化,在peer0上执行chaincode查询和调用,在 peer3上执行结果查询,确认在peer0上的调用(从a账户转账10个单位给b账户)被成功执行并且由orderer0广播给peer3并提交到账本 上。在确认环境完备没问题后,会部署marble02,默认地会创建Dummy0(属于United Marbles市场,org1)用户和Dummy1(属于eMarbles市场,org2)用户,Dummy0会被分配5个红色弹珠资产,Dummy1会被分配5个蓝色弹珠资产,然后从Dummy0转移一个红色弹珠资产给Dummy1,同时从Dummy1转移一个蓝色弹珠资产给Dummy0。
2.4 弹珠交易市场操作界面 我们在hfc0(连接peer0,属于org1机构--United Marbles)启动弹珠应用,监听端口3001,在hfc1(连接peer2,属于org2机构--eMarbles)也启动弹珠应用,监听端口3002,这两个操作界面代表了两个组织使用的分布式应用,分别基于部署的chaincode进行业务操作。


初始界面,点击Login

如果显示出本客户端Dapp连接到的orderer,peer和chaincode,说明当前marble02 chaincode还没有部署好,可以点击retry

如果出现上述画面,说明chaincode已经能够成功连上,客户端Dapp会触发一个心跳查询selftest已确认所连接peer chaincode层面是否状态良好。

初始界面是我们通过cli初始化的用户Dummy0和Dummy1,Dummy0在向Dummy1转移了一个红色弹珠,并接收到Dummy1转来的1个蓝色弹珠后的资产状态。 相同的过程对于eMarbles市场使用的客户端Dapp,我们使用3002端口看到的初始界面如下所示:

为了更好的演示Fabric1.0共识的过程,可以通过左上角的Settings,打开“Story Mode”。

下面我们在3001端口的United Marbles Dapp,为Dummy0用户创建一个绿色弹珠。

点击Create后,Story Mode会展示整个共识的过程。

客户端HFC先构建交易建议,交易建议被发往相关背书节点,相关背书节点执行chaincode给出读写状态集并背书,客 户端HFC再将交易建议和相关背书响应发往排序共识服务,排序共识服务全序组装block,并广播block给相关节点,节点在收到block后进行状态 验证,验证通过的交易commit到本地有效账本上,验证不通过的交易废弃。 执行共识后,我们看到event端口收到新的block,新创建的绿色弹珠资产以及出现。

同样的我们再做一次跨Org的资产转移,将刚刚创建的绿色弹珠资产转给eMarbles的Dummy1用户,点中绿色弹珠拖动到Dummy1的框中,执行结果如下

通过eMarbles看到的目前弹珠资产分布是这样的

另外,我们可以创建新的用户,如在3001界面操作创建新用户

创建后的用户会出现在主界面内。

最后资产也可以被销毁。我们在3002 eMarbles市场的操作界面,将之前创建的绿色弹珠资产拖到右上角垃圾桶框内,执行资产的销毁。

2.4 抗“双花”演示 为了演示双花效果,我们重置整个系统后,修改scripts/script-ch1.sh 在chaincodeInvoke 2 '{"Args":["init_owner","o1","dummy1","eMarbles"]}' marbles02后增加编号为o2的rodger用户
在chaincodeInvoke 0 '{"Args":["set_owner","m01","o1","United Marbles"]}' marbles02之前增加一个同时把编号为m01弹珠转移给o2 rodger用户的操作,m01同时转给了o2(from eMarbles)和o1(from eMarbles)用户
假设两种可能,一种是这两个交易被封装在一个block中,程序在执行时,会报出下述警告: peer2 | 2017-04-26 06:11:11.117 UTC [statevalidator] ValidateAndPrepareBatch -> WARN 03c Block [7] Transaction index [1] TxId [e25baceb4fbf59ee2041bc1cc956b229a4528a56841687ded94f51b7f1df5de8] marked as invalid by state validator. Reason code [11] peer0 | 2017-04-26 06:11:11.130 UTC [statevalidator] ValidateAndPrepareBatch -> WARN 043 Block [7] Transaction index [1] TxId [e25baceb4fbf59ee2041bc1cc956b229a4528a56841687ded94f51b7f1df5de8] marked as invalid by state validator. Reason code [11] peer3 | 2017-04-26 06:11:11.182 UTC [statevalidator] ValidateAndPrepareBatch -> WARN 03d Block [7] Transaction index [1] TxId [e25baceb4fbf59ee2041bc1cc956b229a4528a56841687ded94f51b7f1df5de8] marked as invalid by state validator. Reason code [11] peer0 | 2017-04-26 06:11:11.215 UTC [kvledger] Commit -> INFO 044 Channel [ch1]: Created block [7] with 3 transaction(s) peer2 | 2017-04-26 06:11:11.220 UTC [kvledger] Commit -> INFO 03d Channel [ch1]: Created block [7] with 3 transaction(s) peer3 | 2017-04-26 06:11:11.238 UTC [kvledger] Commit -> INFO 03e Channel [ch1]: Created block [7] with 3 transaction(s)
最终我们从画面上看,红色弹珠m01还是转移给了o2 rodger用户
我们查看m01的audit交易记录,从审计记录上可以看到m01资产的转移过程。

另外一种可能是这两个交易不封装在一个block中,程序在执行时,我们人为在把m01转移给o2和o1的交易间sleep 10秒
chaincode invoke -o orderer0:7050 -C ch1 -n marbles02 -c {"Args":["set_owner","m01","o1","United Marbles"]} >&log.txt Error endorsing invoke: rpc error: code = 2 desc = The company 'United Marbles' cannot authorize transfers for 'eMarbles'.
原因是m01所有权已经被转移给了eMarbles的o2 rodger用户,所有权归于eMarbles了,无法再通过United Marbles把资产转给o1 Dummy1了。