【Hyperledger-fabric入门学习记录】Fabcar
- 实验环境
- 实验目标
- 应用工具
- 步骤
- 搭建环境
- 关闭其他网络
- 实验过程
- 遇到的问题
- 参考文献
实验环境
实验目标
- 编写一个应用程序和智能合约来查询和更新一个分类账
- 使用证书颁发机构生成X.509证书,这些证书由与受许可的区块链交互的应用程序使用
应用工具
应用SDK(application SDK)——调用智能合约SDK(smart contract SDK)查询和更新账本
步骤
搭建环境
关闭其他网络
- 实验前需要关闭其他运行的fabric的测试程序中的容器和网络
一定要关闭其他容器,不然会报错无法运行。
docker rm -f $(docker ps -aq)docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
- 检查正在运行的容器
docker ps -a
- 显示已经没有容器
实验过程
- 进入~/go/src/github.com/hyperledger/fabric-samples/fabcar/目录
- 启动网络
./startFabric.sh
- 进入javascript目录
cd javascript
- 在此文件夹内安装npm
只需在首次运行时安装,之后可以跳过
npm使应用程序能够使用身份、钱包和网关连接到通道、提交事务和等待通知。
\\官方指令sudo npm install
我在安装的时候遇到“找不到指令”的错误:
使用这两条指令
sudo apt-get install npmsudo npm install
安装成功,文件夹内出现node_modules
- 创建admin
当我们创建网络时,需要一个admin用户(简称为admin),作为证书颁发机构(CA)的注册者。
第一步是使用enroll.js程序为admin生成私钥、公钥和X.509证书。
这个过程使用证书签名请求(CSR):私钥和公钥首先在本地生成,然后将公钥发送到CA, CA返回一个编码的证书供应用程序使用。
将这三个凭据存储在钱包中,允许我们充当CA的管理员。 - 步骤:
node enrollAdmin.js
- 创建成功
该命令将CA管理员的凭据存储在钱包目录中。
- 创建user
现在在钱包中已经有了管理员的凭证,我们可以注册一个新用户user1,它将用于查询和更新分类帐:
node registerUser.js
与管理员注册类似,该程序使用CSR注册user1,并将其凭据与管理员的凭据一起存储在钱包中。现在,我们有了两个独立用户的身份——admin和user1,可以被应用程序使用。
- 访问账本
区块链网络中的每个peer都掌握分类帐的一个副本,应用程序可以通过调用智能合约查询分类帐的最新值并将其返回给应用程序,从而查询分类帐。
下面是一个查询的工作示意图:
应用程序使用查询从分类帐中读取数据。最常见的查询涉及分类帐中数据的当前值即世界状态(world state)。世界状态表示为一组键-值对,应用程序可以查询单个键或多个键的数据。此外,可以配置分类帐世界状态以便于使用CouchDB这样的数据库,当键值被建模为JSON数据时,CouchDB支持复杂的查询。这在查找与特定值的特定关键字匹配的所有资产时非常有用,例如,所有拥有特定车主的汽车。
首先,运行query.js程序,返回账本上所有汽车的列表。 - 使用身份user1来访问分类帐:
node query.js
此时返回所有车辆的信息。
接下来详细分析这个过程:
打开node.js文件,可以看到应用程序首先从fabric-network模块引入scope两个关键类:FileSystemWallet和gateway(网关)。这些类将用于定位钱包中的user1身份,并使用它连接到网络:
const { FileSystemWallet, Gateway } = require('fabric-network');
应用程序使用网关连接到网络:
const gateway = new Gateway();await gateway.connect(ccp, { wallet, identity: 'user1' });
创建一个新的网关,然后使用它将应用程序连接到网络。ccp描述网关将使用来自钱包的身份user1访问的网络。
…/…/first-network/ connectionorg1记录了如何加载ccp.json并解析为json文件:
const ccpPath = path.resolve(__dirname, '..', '..', 'first-network', 'connection-org1.json');const ccpJSON = fs.readFileSync(ccpPath, 'utf8');const ccp = JSON.parse(ccpJSON);
网络可以划分为多个通道,将应用程序连接到网络中的一个特定通道mychannel。在这个通道中,我们可以访问智能合约fabcar与账本交互:
const contract = network.getContract('fabcar');
fabcar中有许多不同的事务,我们的应用程序先使用queryAllCars事务来访问分类帐世界状态数据:
const result = await contract.evaluateTransaction('queryAllCars');
其中,evaluateTransaction是区块链网络中与智能合约最简单的交互之一。它只是选择一个在连接配置文件中定义的peer,并将请求发送给它,在那里对请求进行评估。智能合约查询同行的账本副本上的所有汽车,并将结果返回给应用程序。这种交互不会导致账簿的更新。
- Fabcar中的智能合约
打开fabric-samples根目录下的chaincode/fabcar/javascript/lib子目录中的fabcar.js。
使用contract类定义智能合约:
class FabCar extends Contract {...
在这个类结构中,可以看到initLedger、queryCar、queryAllCars、createCar和changeCarOwner的结构定义。
async queryCar(ctx, carNumber) {const carAsBytes = await ctx.stub.getState(carNumber); //从chaincode state中获取车辆信息if (!carAsBytes || carAsBytes.length === 0) {throw new Error(`${carNumber} does not exist`);}console.log(carAsBytes.toString());return carAsBytes.toString();}//向区块链添加一个区块async createCar(ctx, carNumber, make, model, color, owner) {console.info('============= START : Create Car ===========');const car = {color,docType: 'car',make,model,owner,};await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car)));console.info('============= END : Create Car ===========');}//更新分类账async queryAllCars(ctx) {const startKey = 'CAR0';//定义车辆编号范围为0-999const endKey = 'CAR999';const iterator = await ctx.stub.getStateByRange(startKey, endKey);const allResults = [];//遍历查询结果并将它们打包为应用程序的JSON。while (true) {const res = await iterator.next();if (res.value && res.value.value.toString()) {console.log(res.value.value.toString('utf8'));const Key = res.value.key;let Record;try {Record = JSON.parse(res.value.value.toString('utf8'));} catch (err) {console.log(err);Record = res.value.value.toString('utf8');}allResults.push({ Key, Record });}if (res.done) {console.log('end of data');await iterator.close();console.info(allResults);return JSON.stringify(allResults);}}}async changeCarOwner(ctx, carNumber, newOwner) {console.info('============= START : changeCarOwner ===========');const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode stateif (!carAsBytes || carAsBytes.length === 0) {throw new Error(`${carNumber} does not exist`);}const car = JSON.parse(carAsBytes.toString());car.owner = newOwner;await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car)));console.info('============= END : changeCarOwner ===========');}}
下面是应用程序如何在智能合约中调用不同事务的示意图示。每个事务使用一组广泛的api(如getStateByRange)与分类帐进行交互。
当我们打开query.js文件修改evaluatorTransaction中的请求,可以查询特定车辆的信息
原始请求:
修改后的请求:
- 修改后只能查询到特定车辆的信息
遇到的问题
第一天运行结束能够查看全部车辆信息后关闭了网络、容器还有虚拟机。
第二天想要修改query.js中的请求实现只查询特定信息。按照步骤,启动网络,创建管理员和用户身份。
创建的过程中,指令的结果出现了 “An identity for the admin user “admin” already exists in the wallet”和”An identity for the user “user1” already exists in the wallet.”,但是没有在意。
query命令执行之后出现错误,无法访问信息:
mxj@mxj-VirtualBox:~/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript$ node query.jsWallet path: /home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/wallet2022-10-28T13:51:52.325Z - error: [Channel.js]: Channel:mychannel received discovery error:access denied2022-10-28T13:51:52.327Z - error: [Channel.js]: Error: Channel:mychannel Discovery error:access denied2022-10-28T13:51:52.356Z - error: [Channel.js]: Channel:mychannel received discovery error:access denied2022-10-28T13:51:52.357Z - error: [Channel.js]: Error: Channel:mychannel Discovery error:access denied2022-10-28T13:51:52.360Z - error: [Network]: _initializeInternalChannel: Unable to initialize channel. Attempted to contact 2 Peers. Last error was Error: Channel:mychannel Discovery error:access deniedat Channel._discover (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:1254:11)at async Channel._initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:282:32)at async Channel.initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:235:14)at async Network._initializeInternalChannel (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/network.js:112:5)at async Network._initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/network.js:137:3)at async Gateway.getNetwork (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/gateway.js:293:3)at async main (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/query.js:33:25)Failed to evaluate transaction: Error: Unable to initialize channel. Attempted to contact 2 Peers. Last error was Error: Channel:mychannel Discovery error:access deniedat Channel._discover (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:1254:11)at async Channel._initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:282:32)at async Channel.initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-client/lib/Channel.js:235:14)at async Network._initializeInternalChannel (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/network.js:112:5)at async Network._initialize (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/network.js:137:3)at async Gateway.getNetwork (/home/mxj/go/src/github.com/hyperledger/fabric-samples/fabcar/javascript/node_modules/fabric-network/lib/gateway.js:293:3)
百度之后并没有查到完全一致的问题和解决方案,但是发现了存在已经创建身份不会因为容器和网络关闭被删除的问题。
于是重启网络和容器,直接执行 node query.js,依旧报错。
最后删除了/javascript/wallet下的admin和user1文件夹,再次创建admin和user身份,请求成功。
收获经验:每次运行网络要把之前创建的身份都删除才能实现本次网络中的请求,不然身份不对应无法实现操作。
- 更新账本:创建一辆新车
应用程序向区块链网络提交事务,当该事务经过验证和提交后,应用程序将收到事务成功的通知。
这涉及到达成共识的过程,区块链网络的不同组成部分一起工作,以确保对分类帐的每一次更新都是有效的,并以商定的和一致的顺序执行。
invoke.js的程序可以更新分类帐。
构建事务的代码块,可以将信息提交给网络:
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
调用应用程序使用submitTransaction API而不是evaluateTransaction与区块链网络交互。
submitTransaction比evaluateTransaction复杂得多。SDK不是与单个对等点交互,而是将submitTransaction提议发送到区块链网络中每个所需组织的peer。每个peer将使用此提议执行所请求的智能合约,以生成事务响应,并签署该事务响应并返回给SDK。SDK将所有签名的事务响应收集到一个事务中,然后将该事务发送给orderer。orderer从每个应用程序收集事务并将其排序到一个事务块中。然后,它将这些块分发给网络中的每个对等体,在那里每个事务都得到验证和提交。最后,通知SDK,允许它将控制返回给应用程序。
node
- 运行invoke.js
node invoke.js
- 再次访问账本,出现一辆新的车
- 修改query.js中的请求,查询只查询新的车:
node.js中修改为:
查询结果:
- 修改账本中的信息
首先修改invoke.js中的函数,将createCar修改为changeCarOwner :
await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
保存后再次执行invoke.js和query.js
node invoke.jsnode query.js
查询到CAR12的拥有者已经从Tom变更为Dave。
至此,Fabcar中的基本实验操作均已完成。
- 关闭所有网络和容器:
docker rm -f $(docker ps -aq)docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
参考文献
1.官方文档 https://hyperledger-fabric.readthedocs.io/en/release-1.4/write_first_app.html
2.https://www.cnblogs.com/HeartKing/p/14852536.html