Fabric最近更新到了2.x版本的第一个LTS版本,官方文档也进行了相应的更新,如当时刚进入2.0beta版本介绍所说,将采用test-network作为学习的基础,因此我也升级到了2.1版本,后续都是在2.1版本上进行。
test network由两个对等节点组织和一个排序组织组成,每个节点组织操作一个对等节点,排序组织操作一个节点raft排序服务。两个对等节点,节点中的账本,orderer和CA分别运行在各自的Docker容器中,在生产环境中,组织一般使用和其他系统共享的存在的CAs,并不是专用于fabric网络。
通过提供的脚本运行网络:
cd fabric-samples/commercial-paper
./network-starter.sh
打开一个新的终端窗口,进入MagnetoCorp目录,首先要做的是监视PaperNet的组件,管理员通过logsout工具可以看到一系列docker容器的所有输出。这个工具收集不同的输出流到同一个地方,使我们能够从一个窗口中看到发生了什么。
cd /commercial-paper/organization/magnetocorp
./configuration/cli/monitordocker.sh net_test
用VScode打开magnetocorp下的contract目录,其中lib目录下的papercontract.js包含了商业票据的智能合约。下面简单介绍一下papercontract.js中的一些关键代码:
const { Contract, Context } = require( 'fabric-contract-api' );
上面声明将两个将会在智能合约中广泛用到的关键Hyperledger Fabric类加入域中——Contract和Context。
class CommmercialPaperContract extends Contract {
基于内置的Fabric Contract类定义了智能合约类CommercialPaperContract,实施issue,buy和redeem的事务的方法将会定义在这个类中。
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime...){
issue方法,传递的参数将用于发行一个新的商业票券。
let paper = CommercialPaper.createInstance(issuer, paperNumber, issueDateTime...);
通过CommercialPaper类在内存中创建了一个新的商业票券。
await ctx.paperList.addPaper(paper);
使用ctx.paperList将新的商业票券加入到账本中,ctx.paperList是PaperList类的一个实例,在智能合约context CommercialPaperContext被初始化的时候被创建。
return paper;
上面的声明返回一个二进制缓存作为调用智能合约issue transaction的响应。
MagnetoCorp管理员可以使用peer CLI和PaperNet交互,但是需要在其命令窗口设置特定的环境变量来使用正确的peer二进制集合,发送命令到MagnetoCorp地址以及用正确的加密材料签署请求。这里可以使用提供的脚本设置环境变量:
source magnetocorp.sh
由于我将macos系统更新到了Catalina,所以默认的是zsh而不是bash,方便起见这里将默认shell切换为bash。
cash -s /bin/bash
脚本执行成功后,所有的环境变量都会列在窗口中,接下来就可以使用这个命令窗口作为MagnetoCorp管理员和PaperNet进行交互了。
首先安装papercontract智能合约,先用peer lifecycle chaincode package命令将智能合约打包在一个链码中:
peer lifecycle chaincode package cp.tar.gz --lang node --path ./contract --label cp_0
然后管理员用peer lifecycle chaincode install命令将链码安装到MagnetoCorp节点上:
peer lifecycle chaincode install cp.tar.gz
安装好智能合约后,需要作为MagnetoCorp批准链码定义。首先使用peer lifecycle chaincode queryinstalled命令找出安装在peer上的链码的packageID;
peer lifecycle chaincode queryinstalled
将packageID保存为环境变量:
export PACKAGE_ID=cp_0:355ef57f6144995eec41cfead08556396e64052ff4a4f9b95a8cb576aa612d98
现在管理员可以代表MagnetoCorp用peer lifecycle chaincode approveformyorg命令批准链码定义:
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name papercontract -v 0 --package-id $PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
通道成员需要就链码定义达成一致最重要的参数之一是认可策略(endorsement policy),认可策略描述了在一个transaction将要生效前需要执行和签名的组织的集合。在上面批准papercontract链码的时候并没有使用--policy标志,这说明MagnetoCorp的管理员使用默认的认可策略,即需要通道中的大部分组织来认可一个transaction。所有的transactions,无论生效还是不生效,都会被记录在区块链上,但是只有生效的transactions会更新world state。
默认的,Fabric Chaincode lifecycle需要通道上大多数组织成功提交链码定义到通道中,因此我们需要作为DigiBank将papernet 链码提交到通道并批准。打开一个新的终端窗口并导航到digibank目录,之后的步骤和第4步中完全一样。
cd commercial-paper/organization/digibank
source digibank.sh
peer lifecycle chaincode package cp.tar.gz --lang node --path ./contract --label cp_0
peer lifecycle chaincode install cp.tar.gz
export PACKAGE_ID=cp_0:126f0d300535d3469008a02a7c9555cb09f9de0c53c02814aa2b3c0772bb7b77
peer lifecycle chaincode approveformyorg --orderer localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name papercontract -v 0 --package-id $PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
两个组织都已经在组织层面批准了链码,接下来只需要一个组织将链码定义提交到通道即可。DigiBank管理员使用peer lifecycle chaincode commit命令将链码定义提交到通道:
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --peerAddresses localhost:7051 --tlsRootCertFiles ${PEER0_ORG1_CA} --peerAddresses localhost:9051 --tlsRootCertFiles ${PEER0_ORG2_CA} --channelID mychannel --name papercontract -v 0 --sequence 1 --tls --cafile $ORDERER_CA --waitForEvent
当链码定义被提交到通道后,新的docker容器将被创建——papercontract容器运行在两个节点上。
上图展示了issue应用程序提交transaction的整个流程,之前都有介绍过,之前有所忽视的一点是,Hyperledger Fabric SDK提供了网关的抽象。
打开一个新的控制台窗口,导航到magnetocorp/application目录下,用VScode打开,下面是issue.js的一些关键语句:
const { Wallets, Gateway } = require('fabric-network');
将两个关键Hyperledger Fabric SDK类加入域中。
const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
标明该应用程序使用isabella的钱包连接到区块链网络通道。
await gateway.connect(connectionProfile, connectionOptions);
这行代码使用connectionProfile标识的网关和ConnectionOptions中提及的身份连接到网络,../gateway/networkConnection.yaml标识了网关,[email protected]是ConnectionOptions中提及的身份。
const network = await gateway.getNetwork('mychannel');
将应用程序连接到网络通道mychannel,即papercontract已经实例化的通道。
const contract = await network.getContract('papercontract');
声明给了应用程序连接到papercontract链码的方式。
const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', ...);
提交一个transaction到网络,使用定义在智能合约中的issue transaction。
let paper = CommercialPaper.fromBuffer(issueResponse);
处理issue transaction的响应,响应需要从缓冲区反序列化到paper,一个可以被应用程序正确解释的CommercialPaper对象。
issue.js需要饱含js-yaml包来处理YAML网关连接文件以及fabric-network包来加入Gateway和Wallet类:
const yaml = require('js-yaml');
const { Wallets, Gateway} = require('fabric-network');
这些包通过npm install命令从npm下载到本地文件系统,通常这些包必须安装在应用程序相应的/node_modules目录以在运行时使用。
"dependencies":{
"fabric-network": "~1.4.0",
"fabric-client" : "~1.4.0",
"js-yaml" : "^3.12.0"
}
package.json文件中标识了需要包的名称和具体版本。
使用npm install安装应用程序依赖,如果出现问题用nvm切换一下node.js和npm的版本,也可能此处出现网络问题,多尝试几次即可。安装完成后,node_modules目录下会多很多的包,因为js-yaml和fabric-network本身建立在其他npm包的基础上。
因为issue.js是以Isabella身份执行的,因此需要通过运行addToWallet.js将Isabella的身份信息加入到她的钱包中:
node addToWallet.js
addToWallet.js是一个简单的文件拷贝程序,它将一个test network中的身份拷贝到Isabella的钱包中,Isabella可以存储多个身份在她的钱包中,查看isabella.id文件可以看到如下重要身份信息:
privateKey:用于签名和Isabella相关的transactions,本人所有,不传播到网络中。
certificate:包含Isabella的公钥和其他在CA建立身份时添加的X.509属性,这个证书会传播到网络中,这样其他角色在不同的时间都可以加密地验证Isabella私钥创立的信息。
在实践中,证书文件还包含一些Fabric特殊的元信息,比如Isabella的组织以及角色等。
现在Isabella可以使用issue.js来提交一个transaction发行MagnetoCorp商业票券00001:
node issue.js
发行新票券的整个流程大概如下:
应用程序调用在papercontract.js中定义的CommercialPaper智能合约中的issue transaction。这个链码被MagnetoCorp管理员安装并实例化在网络mychannel通道中。智能合约通过Fabric APIs和账本交互,最主要通过putState()和getState(),新的商业票券以向量状态的形式保存在world state中,后续将会被同样定义在智能合约中的buy和redeem控制。
底层的Fabric SDK一直处理着transaction的认可,排序和通知过程,使得应用程序逻辑更加简单。SDK使用网关来抽象出区块链网络的细节并用connectionOptions来声明更高级的处理策略例如transaction retry。
Digibank的buy.js应用程序和MagnetoCorp的issue.js非常相似,主要有两个重要不同:
const wallet = await Wallets.newFileSystemWallet('../identity/user/balaji/wallet');
使用的身份不同。
const buyResponse = await contract.submitTransaction('buy',...);
调用的transaction不同。
接下来和上面相似,安装应用程序依赖,并将balaji的身份加入到其钱包中:
cd digibank/application
npm install
node addToWallet.js
node buy.js
node redeem.js
cd fabric-samples/commercial-paper
./network-clean.sh