0 導(dǎo)言
fabric-sdk-node也是分兩種寫法峡蟋,一種是底層寫法坟桅,一種是比較高層的寫法。下面先演示比較高層的寫法蕊蝗。借助于fabric-network這個模塊可以寫更加簡捷的代碼仅乓。
本文檔演示了一個汽車資產(chǎn)管理的例子,官方提供相應(yīng)的鏈碼和客戶端示例代碼蓬戚。
1 創(chuàng)建項目目錄
注:如果已經(jīng)有該目錄則不需 創(chuàng)建了
$ cd $GOPATH/src/github.com
$ mkdir -p fabric-study/sdk-node-study1
注:如果上述mkdir 不能執(zhí)行夸楣,可能是沒有權(quán)限,加上sudo就可以了
$ sudo mkdir -p fabric-study/sdk-node-study1
2 搭建運行網(wǎng)絡(luò)
我們不另行去搭建節(jié)點網(wǎng)絡(luò)了,直接拷貝官網(wǎng)提供的basic-network過來用豫喧,執(zhí)行cp命令進行拷貝.
$ cd fabric-study/sdk-node-study1/
$ cp -r ../../hyperledger/fabric-samples/basic-network ./
basic-network會創(chuàng)建如下節(jié)點
序號 | 組織 | IP | 域名 | 端口映射 | 節(jié)點 |
---|---|---|---|---|---|
docker 容器1 | 無 | 172.18.0.6 | orderer.example.com | 0.0.0.0:9051->7051/tcp, 0.0.0.0:9053->7053/tcp | orderer節(jié)點 |
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節(jié)點 |
docker 容器3 | 無 | 172.18.0.6 | 無 | 無 | cli客戶端 |
docker 容器4 | 無 | 172.18.0.6 | 無 | 無 | couchDB服務(wù)器 |
docker 容器5 | 無 | 172.18.0.6 | 無 | 無 | ca服務(wù)器 |
3 創(chuàng)建鏈碼放置目錄
在這里我們不打算自己編寫鏈碼了石洗,直接使用官方提供的fabcar示例的鏈碼,如若對該鏈碼不了解紧显,可通過閱讀該鏈碼的源碼來熟悉它
$ mkdir chaincode
$ cp -r ../../hyperledger/fabric-samples/chaincode/fabcar ./chaincode
4 創(chuàng)建app1目錄
$ mkdir app2
5 用vscode打開sdk-node-study2
6 在app1下創(chuàng)建package.json
內(nèi)容如下
{
"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 創(chuàng)建enrollAdmin.js
該js腳本用來向ca-服務(wù)器注冊管理員
'use strict';
//向ca服務(wù)器注冊管理員
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 {
// 創(chuàng)建一個CA客戶端
const caURL = ccp.certificateAuthorities['ca.example.com'].url;
const ca = new FabricCAServices(caURL);
// 創(chuàng)建一個wallet文件夾,用于管理身份.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 檢查是否已經(jīng)注冊管理員.
const adminExists = await wallet.exists('admin');
if (adminExists) {
console.log('An identity for the admin user "admin" already exists in the wallet');
return;
}
// 向ca服務(wù)器注冊管理員,并將從服務(wù)器獲得的身份證書導(dǎo)入到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 創(chuàng)建registerUser.js
該js腳本用來向fabric-ca服務(wù)器注冊普通用戶
'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 {
// 創(chuàng)建wallet文件夾用于管理身份信息.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 檢查是否已經(jīng)注冊過user1用戶
const userExists = await wallet.exists('user1');
if (userExists) {
console.log('An identity for the user "user1" already exists in the wallet');
return;
}
// 檢查是否已經(jīng)注冊了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;
}
// 創(chuàng)建一個用于連接peer節(jié)點的網(wǎng)關(guān)
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'admin', discovery: { enabled: false } });
// 創(chuàng)建一個用于與ca 服務(wù)器交互的ca 客戶端對象
const ca = gateway.getClient().getCertificateAuthority();
const adminIdentity = gateway.getCurrentIdentity();
// 注冊用戶到ca服務(wù)器鸟妙,并將從ca服務(wù)器獲得的身份信息導(dǎo)入到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 創(chuàng)建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 {
// 創(chuàng)建一個用于管理身份的文件夾wallet
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 檢查是否已經(jīng)注冊好普通用戶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;
}
// 創(chuàng)建一個用于與peer節(jié)點通信的網(wǎng)關(guān)對象
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');
// 執(zhí)行指定的交易.
// queryCar 交易 - 需要1個參數(shù), 例如: ('queryCar', 'CAR4')
// queryAllCars 交易 - 不需要任何參數(shù), 例如: ('queryAllCars')
//注意焦人,如果不修改狀態(tài)使用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 創(chuàng)建invoke.js
該js腳本做invoke調(diào)用
'use strict';
//調(diào)用鏈碼對應(yīng)的函數(shù)
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 {
// 創(chuàng)建一個用于管理身份的文件夾wallet
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// 檢查是否已經(jīng)注冊好普通用戶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;
}
// 創(chuàng)建一個用于與peer節(jié)點通信的網(wǎng)關(guān)對象
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
// 獲得我們的鏈碼部署到的channel對象
const network = await gateway.getNetwork('mychannel');
// 創(chuàng)建一個用于與peer節(jié)點通信的網(wǎng)關(guān)對象
const contract = network.getContract('fabcar');
// 提交特定的交易 .
// createCar 交易 - 需要5個參數(shù), 例如: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom')
// changeCarOwner 交易 - 需要2個參數(shù) , 例如: ('changeCarOwner', 'CAR10', 'Dave')
//注意挥吵,涉及到修改狀態(tài)使用submitTransaction
await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
console.log('Transaction has been submitted');
// 斷開網(wǎng)關(guān)連接
await gateway.disconnect();
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();
11 啟動網(wǎng)絡(luò)
生成證書(注重父,只需要執(zhí)行一次就ok了,以后都不用執(zhí)行這一步了)
$ ./generate.sh
先停止到舊的網(wǎng)絡(luò)
$ docker stop -f $(docker ps -aq)
$ docker rm -f $(docker ps -aq)
$ docker rmi -f $(docker images | grep fabcar | awk '{print $3}')
然后啟動網(wǎng)絡(luò),該腳本只啟動了ca、orderer忽匈、peer房午、couchDb這幾個容器,沒有啟動cli容器丹允。并且創(chuàng)建名字為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容器雕蔽,可以在外邊執(zhí)行折柠,用-exec表示在外邊執(zhí)行
安裝鏈碼
$ 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')"
調(diào)用initLedger函數(shù)初始化賬本
$ 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目錄安裝依賴
到了這一步,證書已經(jīng)生成批狐,節(jié)點網(wǎng)絡(luò)已經(jīng)啟動扇售,鏈碼也安裝和實例化好了,接下來就來測試一下sdk的調(diào)用是否成功了嚣艇。
進入app1目錄,并執(zhí)行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 執(zhí)行查詢
$ 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 執(zhí)行調(diào)用
$ node invoke.js