0 导言
fabric-sdk-node也是分两种写法,一种是底层写法,一种是比较高层的写法。下面先演示比较高层的写法。借助于fabric-network这个模块可以写更加简捷的代码。
本文档演示了一个汽车资产管理的例子,官方提供相应的链码和客户端示例代码。
1 创建项目目录
注:如果已经有该目录则不需 创建了
$ cd $GOPATH/src/github.com
$ mkdir -p fabric-study/sdk-node-study1
注:如果上述mkdir 不能执行,可能是没有权限,加上sudo就可以了
$ sudo mkdir -p fabric-study/sdk-node-study1
2 搭建运行网络
我们不另行去搭建节点网络了,直接拷贝官网提供的basic-network过来用,执行cp命令进行拷贝.
$ cd fabric-study/sdk-node-study1/
$ cp -r ../../hyperledger/fabric-samples/basic-network ./
basic-network会创建如下节点
序号 | 组织 | IP | 域名 | 端口映射 | 节点 |
---|---|---|---|---|---|
docker 容器1 | 无 | 172.18.0.6 | orderer.example.com | 0.0.0.0:9051->7051/tcp, 0.0.0.0:9053->7053/tcp | orderer节点 |
docker 容器2 | 组织1 - org1 | 172.18.0.7 | peer0.org1.example.com | 0.0.0.0:10051->7051/tcp, 0.0.0.0:10053->7053/tcp | peer1节点 |
docker 容器3 | 无 | 172.18.0.6 | 无 | 无 | cli客户端 |
docker 容器4 | 无 | 172.18.0.6 | 无 | 无 | couchDB服务器 |
docker 容器5 | 无 | 172.18.0.6 | 无 | 无 | ca服务器 |
3 创建链码放置目录
在这里我们不打算自己编写链码了,直接使用官方提供的fabcar示例的链码,如若对该链码不了解,可通过阅读该链码的源码来熟悉它
$ mkdir chaincode
$ cp -r ../../hyperledger/fabric-samples/chaincode/fabcar ./chaincode
4 创建app1目录
$ mkdir app2
5 用vscode打开sdk-node-study2
6 在app1下创建package.json
内容如下
{
"name": "fabcar",
"version": "1.0.0",
"description": "FabCar application implemented in JavaScript",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc mocha --recursive"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-ca-client": "~1.4.0",
"fabric-network": "~1.4.0"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^5.9.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"sinon": "^7.1.1",
"sinon-chai": "^3.3.0"
},
"nyc": {
"exclude": [
"coverage/**",
"test/**"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}
7 创建enrollAdmin.js
该js脚本用来向ca-服务器注册管理员
'use strict';
//向ca服务器注册管理员
const FabricCAServices = require('fabric-ca-client');
const { FileSystemWallet, X509WalletMixin } = require('fabric-network');
const fs = require('fs');
const path = require('path');
//connection.json这个文件可以在basic-network文件夹找到,配置了一些连接的信息
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
async function main() {
try {
// 创建一个CA客户端
const caURL = ccp.certificateAuthorities['ca.example.com'].url;
const ca = new FabricCAServices(caURL);
// 创建一个wallet文件夹,用于管理身份.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 检查是否已经注册管理员.
const adminExists = await wallet.exists('admin');
if (adminExists) {
console.log('An identity for the admin user "admin" already exists in the wallet');
return;
}
// 向ca服务器注册管理员,并将从服务器获得的身份证书导入到wallet.
const enrollment = await ca.enroll({ enrollmentID: 'admin', enrollmentSecret: 'adminpw' });
const identity = X509WalletMixin.createIdentity('Org1MSP', enrollment.certificate, enrollment.key.toBytes());
wallet.import('admin', identity);
console.log('Successfully enrolled admin user "admin" and imported it into the wallet');
} catch (error) {
console.error(`Failed to enroll admin user "admin": ${error}`);
process.exit(1);
}
}
main();
8 创建registerUser.js
该js脚本用来向fabric-ca服务器注册普通用户
'use strict';
//注册普通用户
const { FileSystemWallet, Gateway, X509WalletMixin } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
async function main() {
try {
// 创建wallet文件夹用于管理身份信息.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 检查是否已经注册过user1用户
const userExists = await wallet.exists('user1');
if (userExists) {
console.log('An identity for the user "user1" already exists in the wallet');
return;
}
// 检查是否已经注册了admin用户
const adminExists = await wallet.exists('admin');
if (!adminExists) {
console.log('An identity for the admin user "admin" does not exist in the wallet');
console.log('Run the enrollAdmin.js application before retrying');
return;
}
// 创建一个用于连接peer节点的网关
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'admin', discovery: { enabled: false } });
// 创建一个用于与ca 服务器交互的ca 客户端对象
const ca = gateway.getClient().getCertificateAuthority();
const adminIdentity = gateway.getCurrentIdentity();
// 注册用户到ca服务器,并将从ca服务器获得的身份信息导入到wallet文件夹.
const secret = await ca.register({ affiliation: 'org1.department1', enrollmentID: 'user1', role: 'client' }, adminIdentity);
const enrollment = await ca.enroll({ enrollmentID: 'user1', enrollmentSecret: secret });
const userIdentity = X509WalletMixin.createIdentity('Org1MSP', enrollment.certificate, enrollment.key.toBytes());
wallet.import('user1', userIdentity);
console.log('Successfully registered and enrolled admin user "user1" and imported it into the wallet');
} catch (error) {
console.error(`Failed to register user "user1": ${error}`);
process.exit(1);
}
}
main();
9 创建query.js
该js脚本用来查询汽车信息
'use strict';
//通过链码查询
const { FileSystemWallet, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
async function main() {
try {
// 创建一个用于管理身份的文件夹wallet
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 检查是否已经注册好普通用户user1
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// 创建一个用于与peer节点通信的网关对象
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// 获得我们的链码部署到的channel对象
const network = await gateway.getNetwork('mychannel');
// 通过链码名称获得智能合约对象
const contract = network.getContract('fabcar');
// 执行指定的交易.
// queryCar 交易 - 需要1个参数, 例如: ('queryCar', 'CAR4')
// queryAllCars 交易 - 不需要任何参数, 例如: ('queryAllCars')
//注意,如果不修改状态使用evaluateTransaction
const result = await contract.evaluateTransaction('queryAllCars');
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
}
main();
10 创建invoke.js
该js脚本做invoke调用
'use strict';
//调用链码对应的函数
const { FileSystemWallet, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const ccpPath = path.resolve(__dirname, '..', '..', 'basic-network', 'connection.json');
const ccpJSON = fs.readFileSync(ccpPath, 'utf8');
const ccp = JSON.parse(ccpJSON);
async function main() {
try {
// 创建一个用于管理身份的文件夹wallet
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 检查是否已经注册好普通用户user1
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// 创建一个用于与peer节点通信的网关对象
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// 获得我们的链码部署到的channel对象
const network = await gateway.getNetwork('mychannel');
// 创建一个用于与peer节点通信的网关对象
const contract = network.getContract('fabcar');
// 提交特定的交易 .
// createCar 交易 - 需要5个参数, 例如: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner 交易 - 需要2个参数 , 例如: ('changeCarOwner', 'CAR10', 'Dave')
//注意,涉及到修改状态使用submitTransaction
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
console.log('Transaction has been submitted');
// 断开网关连接
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();
11 启动网络
生成证书(注,只需要执行一次就ok了,以后都不用执行这一步了)
$ ./generate.sh
先停止到旧的网络
$ docker stop -f $(docker ps -aq)
$ docker rm -f $(docker ps -aq)
$ docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
然后启动网络,该脚本只启动了ca、orderer、peer、couchDb这几个容器,没有启动cli容器。并且创建名字为mychannel的通道,随后还让peer0.Org1加入了该通道
$ rm -rf ./hfc-key-store
$ cd basic-network
$ ./start.sh
12 启动cli容器
$ docker-compose -f ./docker-compose.yml up -d cli
13 安装和实例化链码
打开一个新的终端,不用进入cli容器,可以在外边执行,用-exec表示在外边执行
安装链码
$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar/go -l golang
实例化链码
$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -l golang -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
调用initLedger函数初始化账本
$ docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[]}'
14 进入app1目录安装依赖
到了这一步,证书已经生成,节点网络已经启动,链码也安装和实例化好了,接下来就来测试一下sdk的调用是否成功了。
进入app1目录,并执行npm install安装依赖模块
$ cd app1
$ npm install
15 注册管理员
$ node enrollAdmin.js
输出如下信息
Store path:/home/bob/go/src/github.com/fabric-study/sdk-node-study1/app1/hfc-key-store
Successfully enrolled admin user "admin"
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"607d571669d90da409ebdaec5855e3a395bbf7af5aab200cd5a902a4054c9cb0","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICAjCCAaigAwIBAgIUGuRlUgmDVeRPKDsf67/6fom2bvMwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwMzIyMDY0NjAwWhcNMjAwMzIxMDY1\nMTAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE4UAK/Z9BFZE7Fji6S4KTTY/2DGK6Vz0FtuhOQStE\n0rQ5PfwcbUEYFP/Z1m3Lhtvkly09eFWM+vibivprOQAbzaNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFja6ma5kc1yk5ax5h0Fc0zl\niI3XMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0gAMEUCIQDr6unOLXftqasK83fkthyfykHNI4Mt7y7DA+lX\ns2PyHQIgS54OqyzJ0kulh/xyHd9IkDRAM2iJPVsxCzMUP+t/C1s=\n-----END CERTIFICATE-----\n"}}}
16 注册普通用户
$ node register.js
输出如下信息
Store path:/home/bob/go/src/github.com/fabric-study/sdk-node-study1/app1/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:OhvdSyIJrqqM
Successfully enrolled member user "user1"
User1 was successfully registered and enrolled and is ready to interact with the fabric network
17 执行查询
$ node query.js
可以看到,所有的汽车被查出来
Query has completed, checking results
Response is [{"Key":"CAR0", "Record":{"colour":"blue","make":"Toyota","model":"Prius","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"colour":"red","make":"Ford","model":"Mustang","owner":"Brad"}},{"Key":"CAR2", "Record":{"colour":"green","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"colour":"yellow","make":"Volkswagen","model":"Passat","owner":"Max"}},{"Key":"CAR4", "Record":{"colour":"black","make":"Tesla","model":"S","owner":"Adriana"}},{"Key":"CAR5", "Record":{"colour":"purple","make":"Peugeot","model":"205","owner":"Michel"}},{"Key":"CAR6", "Record":{"colour":"white","make":"Chery","model":"S22L","owner":"Aarav"}},{"Key":"CAR7", "Record":{"colour":"violet","make":"Fiat","model":"Punto","owner":"Pari"}},{"Key":"CAR8", "Record":{"colour":"indigo","make":"Tata","model":"Nano","owner":"Valeria"}},{"Key":"CAR9", "Record":{"colour":"brown","make":"Holden","model":"Barina","owner":"Shotaro"}}]
18 执行调用
$ node invoke.js