system: [SYSTEM]

user: 4. 方案实现方法 4.1. 区块链集群部署 按照上节给出的系统构架，本文的方案遵循以下步骤部署联盟链网络： 命令1：使用cryptogen工具为每一个Orderer和Peer节点和它们的管理员用户创建必须的证书。 cryptogen generate --config=/path/to/ cryptogen_config --output /path/to/output/dir 这些证书包括各个组织的根证书、节点或管理员的身份证书与私钥等。它们存储在固定结构的文件树里。通过这些证书，系统可以使用tls来加密请求，并对所有的对链操作进行签名。 命令2：使用configtxgen工具为Orderer节点生成初始块文件。 configtxgen -configPath /path/to/config -profile OrdererGenesisProfile -outputBlock /path/to/output/block 初始块是区块链中的一个特殊区块，它作为最开始的区块，没有指向前一个区块的指针。Orderer集群会共同维护相同的区块链，这个区块链中会存储对整个区块链网络的操作记录，包括建立Channel，在Channel上安装与实例化链码等。 命令3：对于每一个Channel，都先使用configtxgen工具，生成用于建立Channel的事务文件。 configtxgen -configPath /path/to/config -profile AllChannelProfile -outputCreateChannelTx /path/to/AllChannel.tx -channelID allchannel 这个文件内结构化地描述了Channel的信息，尤其是包括了Channel所属的区块链网络的成员方的信息。 命令4：对于每一个组织，都使用configtxgen工具生成用于更新Anchor Peer信息的事务文件。 configtxgen -configPath /path/to/config -profile AllChannel -outputAnchorPeersUpdate /path/to/anchors.tx -channelID allchannel -asOrg OrgName 在同一个组织里可能有多个Peer，这些Peer中会有且只有一个Anchor Peer。Anchor Peer接受其他组织的节点发来的网络信息，并广播给自己组织的Peer，以确保不同组织的Peer之间知道互相的位置。 命令5：在部署任何一个Orderer节点之前，为将部署该节点的主机准备Kafka与ZooKeeper环境。 docker-compose -f docker-compose-zk.yml up -d docker-compose -f docker-compose-kafka.yml up -d Kafka是一个高吞吐的分布式的消息队列，它的运行依赖ZooKeeper。ZooKeeper是一个分布式协调服务，可以为分布式系统提供一致性服务。 命令6：完成了上述所有的准备工作后，分别使用如下所示的命令，启动所有的Orderer和Peer节点。 orderer start peer node start Orderer节点在启动时，会读取初始块文件，并以此开始维护该区块链。 命令7：在所有的Orderer节点和Peer节点启动后，Peer节点消费先前生成的事务文件，以建立Channel和更新Anchor Peer信息。 configtxgen -configPath /path/to/config -profile OrdererGenesisProfile -outputBlock /path/to/output/block 命令8：Channel建立完成之后，则在Channel上安装和实例化链码。 configtxgen -configPath /path/to/config -profile OrdererGenesisProfile -outputBlock /path/to/output/block 实例化的链码是一种智能合约，运行在Docker容器中。链码被用于对所属的Channel的账本进行读写操作。 至此，就完成了整个联盟链网络的部署。对于测试和开发环境，使用Docker作为应用程序引擎，打包所有的应用程序，并编写脚本完成上述步骤，实现一键启动开发环境。对于生产环境，每个Peer、Orderer和其他应用服务都直接运行在单独的主机上，根据各自配置文件中指定的地址来尝试连接其他节点网络。 4.2. 中间层egg.js egg.js是一个高可定制的Web应用层框架 [ 16 ]。在本文的样例应用服务中，egg.js被用于设计一个应用层和区块链网络之间的中间层。其向上提供Web应用服务，向下与区块链网络进行通信，以调用智能合约，修改账本。 图3. 中间层与区块链网络的通信 如图3所示，当应用服务需要与账本中的数据进行交互时，该中间层会向区块链网络发送多次gRPC请求进行通信。在第一次通信请求中，请求体的内容包括：待调用的链码所属的Channel、待调用的链码名称及具体的函数名、提供给这次链码调用的参数、临时生成的事务ID。并且，这次请求会使用当前执行操作的用户的私钥进行签名。这次请求仅向区块链网络提交了一个提案。如果这个提案通过，则意味着请求方的身份符合该链码的调用策略，且该链码的模拟调用过程中也没有发生其他错误。 提案通过后，区块链网络会返回一个响应，该响应是相关节点对提案的背书。响应体的内容包括：该提案的模拟运行结果、区块链网络中与该链码相关的节点对该结果的签名。需要注意的是，区块链网络虽然会根据这次提案模拟地调用链码，但并不会因此实际更新账本。 该中间层在拿到这份背书响应后，会以该背书作为请求体，向区块链网络发起第二次gRPC请求。这次请求会发起一次正式的事务。区块链网络的Orderer节点对该事务完成排序后，会返回成功的状态信息。成功的状态信息表明该事务被网络一致排序，即将打入区块并分发给维护该账本的所有节点。从请求回执中拿到状态信息之后，该中间层会认为这次对账本数据的交互已经结束，向上层应用返回响应。上述中间层与区块链网络通信的伪代码如下所示。 代码1：中间层与区块链网络通信 async function invokeChaincode(ca， channelMeta， chaincodeMeta， invokeArgs){ const txId = generateTxId(); const proposalRequest = combineProposalRequest(ca， channelMeta， chaincodeMeta， invokeArgs， txId); const proposalResponse = await sendTransactionProposal(proposalRequest); const transactionRequest =generateTransactionRequest(proposalResponse); const transactionResponse = await sendTransaction(transactionRequest); return transactionResponse; } 每当该中间层试图与账本数据进行交互，都必须进行上述流程。然而，如果一个业务逻辑希望与账本数据进行多次交互，很可能会因为频繁进行上述流程而拖长应用服务的响应时间。在为该中间层设计与编写业务逻辑时，应当针对这一点进行优化。 此外，该中间层还负责解决一些横切关注点。例如，在本文的样例应用服务中，在该中间层编写了一些额外的中间件，用于鉴权和记录系统运行状况。 4.3. 链码设计 基于实践经验，本文的方案建议将业务逻辑从区块链网络层分离，降低开发成本。但如上一节所述，当一个业务逻辑需要中间层与账本数据进行频繁交互时，可能会严重地拖长响应时间。对该问题的一个解决方案是将这种多次交互的逻辑移入链码中。 例如，当业务需要批量查询或添加一批数据时，可以将该逻辑从中间层移入链码。如此一来，中间层只需要进行一次链码调用，仅进行一次上一节所述的通信流程，以避开损耗时间的线性增长。该逻辑的伪代码如下所示。 代码2：合并批量请求 function putBulkKeyValue(asset){ kvlist := unmarshal(asset) for index := 0; index < len(kvlist); index++ { stub.PutState(kvlist[index].key， kvlist[index].value) } } function getBulkValue(asset){ klist := unmarshal(asset) kvlist := new KVList() for index := 0; index < len(kvlist); index++ { value := stub.GetState(klist[index].key) kv := combineKeyValue(klist[index].key， value) kvlist.push(kv) } return kvlist }