使用 Fabric 完成基礎(chǔ)的網(wǎng)絡(luò)配置和鏈碼的編寫和部署后袜茧,還需要我們進(jìn)一步通過上層應(yīng)用服務(wù)吃靠,完成對(duì)底層Fabric搭建的聯(lián)盟鏈的調(diào)用。本文提供使用 nodejs 編寫的上層 sdk 完成對(duì)鏈碼的調(diào)用操作鱼鸠。當(dāng)然墓塌,也可以使用 go 語言,異曲同工香缺。enjoy手销!
1 基礎(chǔ) chaincode 編寫
- 在 chaincode 文件夾中創(chuàng)建nodejs環(huán)境
# 1.進(jìn)入 chaincode 目錄
cd $GOPATH/src/github.com/hyperledger/fabric/scripts/fabric-samples/chaincode
# 2.創(chuàng)建我們自己的chaincode目錄
mkdir helloworldcc
# 3.初始化npm
npm init
# 4.下載fabric 鏈碼的必要依賴
npm install fabric-shim
# 5.編寫鏈碼,并保存至 helloworld.js 文件中
# 6.修改 package.json 文件图张,指定容器啟動(dòng)時(shí)候執(zhí)行的js程序
vim package.json
"scripts": {
"start": "node helloworld.js"
},
- 編寫鏈碼 helloworld.js
//導(dǎo)入環(huán)境依賴
const shim = require('fabric-shim');
const Chaincode = class{
//鏈碼初始化操作
async Init(stub){
//獲取當(dāng)前方法的名字和參數(shù)
var ret = stub.getFunctionAndParameters();
var args = ret.params;
var a = args[0];
var aValue = args[1];
var b = args[2];
var bValue = args[3];
await stub.putState(a,Buffer.from(aValue));
await stub.putState(b,Buffer.from(bValue));
return shim.success(Buffer.from('chaincod init successs'));
}
async Invoke(stub){
let ret = stub.getFunctionAndParameters();
let fcn = this[ret.fcn];
return fcn(stub,ret.params);
}
//查詢操作
async query(stub,args){
let a = args[0];
let balance = await stub.getState(a);
return shim.success(balance);
}
};
shim.start(new Chaincode());
2 基礎(chǔ)網(wǎng)絡(luò)環(huán)境搭建與鏈碼安裝
- 基礎(chǔ)網(wǎng)絡(luò)配置
# 根據(jù)fabric提供的基礎(chǔ)網(wǎng)絡(luò)環(huán)境配置網(wǎng)絡(luò)
cd $GOPATH/src/github.com/hyperledger/fabric/scripts/fabric-samples/
cp -r basic-network helloworld-network
- node sdk環(huán)境創(chuàng)建
# 創(chuàng)建webapp的工作目錄
cd $GOPATH/src/github.com/hyperledger/fabric/scripts/fabric-samples/
mkdir helloworld-webapp
cd helloworld-webapp
# 初始化環(huán)境锋拖,安裝依賴
npm init
npm install fabric-ca-client fabric-client
- 編寫cli安裝鏈碼腳本
startFabric.sh
#!/bin/bash
set -e
export MSYS_NO_PATHCONV=1
starttime=$(date +%s)
# 啟動(dòng) helloworld 的網(wǎng)絡(luò)環(huán)境,創(chuàng)建 channel 并將 peer 節(jié)點(diǎn)加入網(wǎng)絡(luò)
cd ../helloworld-network
./start.sh
# 啟動(dòng) cli 容器祸轮,用于安裝兽埃,實(shí)例化鏈碼
docker-compose -f ./docker-compose.yml up -d cli
# 安裝鏈碼
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 -l node -n helloworld -v v1.0 -p /opt/gopath/src/github.com/helloworldcc/
# 實(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 instantiate -o orderer.example.com:7050 -C mychannel -n helloworld -v v1.0 -c '{"args":["init","yorick","200","tom","100"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"
# 等待
sleep 10
# 測試鏈碼調(diào)用
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 helloworld -c '{"args":["query","yorick"]}'
printf "\nTotal setup execution time : $(($(date +%s) - starttime)) secs ...\n\n\n"
chmod +x startFabric.sh
- 執(zhí)行腳本,完成網(wǎng)絡(luò)部署 and 鏈碼安裝并調(diào)用測試
./startFabric.sh
- result
# 創(chuàng)建 channel 完畢
2022-12-06 11:44:03.909 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2022-12-06 11:44:03.922 UTC [cli.common] readBlock -> INFO 002 Received block: 0
# peer加入 channel 完畢
2022-12-06 11:44:04.014 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2022-12-06 11:44:04.090 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
# cli docker容器創(chuàng)建完畢
Creating cli ... done
# 鏈碼安裝倔撞,實(shí)例化,調(diào)用
2022-12-06 11:44:05.289 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2022-12-06 11:44:05.289 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2022-12-06 11:44:05.319 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
2022-12-06 11:44:05.425 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2022-12-06 11:44:05.425 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
# 成功獲取到 yorick 的 200 數(shù)據(jù)
2022-12-06 11:44:16.778 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 payload:"200"
Total setup execution time : 38 secs ...
3 CA 創(chuàng)建用戶
- 編寫 node 程序
enrollAdmin.js
慕趴,使用 ca 創(chuàng)建admin賬戶
'use strict';
var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');
var path = require('path');
var util = require('util');
var os = require('os');
var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log(' Store path:'+store_path);
// 創(chuàng)建 key-value-store痪蝇,用于保存用戶的身份信息(公私鑰)
// promise 風(fēng)格
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
// 設(shè)置存儲(chǔ)區(qū)塊鏈網(wǎng)絡(luò)狀態(tài)的目錄路徑鄙陡,比如智能合約的狀態(tài)和交易的狀態(tài)
fabric_client.setStateStore(state_store);
var crypto_suite = Fabric_Client.newCryptoSuite();
// 設(shè)置存儲(chǔ)組織、節(jié)點(diǎn)和用戶的密鑰的目錄路徑(兩個(gè)目前使用相同的目錄存儲(chǔ))
var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);
var tlsOptions = {
trustedRoots: [],
verify: false
};
// be sure to change the http to https when the CA is running TLS enabled
// 設(shè)置 ca 客戶端
fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', tlsOptions , 'ca.example.com', crypto_suite);
// 首次獲取admin賬戶躏啰,查看是否已經(jīng)登記
return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
// 已經(jīng)登記admin趁矾,無需ca再創(chuàng)建
if (user_from_store && user_from_store.isEnrolled()) {
console.log('Successfully loaded admin from persistence');
admin_user = user_from_store;
return null;
} else {
// 需要 ca 服務(wù)端完成admin賬戶創(chuàng)建
// 輸入 ca 的id和password,在docker-compose中已經(jīng)指定
return fabric_ca_client.enroll({
enrollmentID: 'admin',
enrollmentSecret: 'adminpw'
}).then((enrollment) => {
console.log('Successfully enrolled admin user "admin"');
// 創(chuàng)建 admin 賬戶
return fabric_client.createUser(
{username: 'admin',
mspid: 'Org1MSP',
cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
});
}).then((user) => {
// 給 fabric 客戶端設(shè)置 admin
admin_user = user;
return fabric_client.setUserContext(admin_user);
}).catch((err) => {
console.error('Failed to enroll and persist admin. Error: ' + err.stack ? err.stack : err);
throw new Error('Failed to enroll admin');
});
}
}).then(() => {
console.log('Assigned the admin user to the fabric client ::' + admin_user.toString());
}).catch((err) => {
console.error('Failed to enroll admin: ' + err);
});
- 執(zhí)行
enrollAdmin.js
程序给僵,得到ca給其辦法的證書毫捣,hfc-key-store中存有admin用戶的公私鑰信息
$ node enrollAdmin.js
Store path:/opt/go-project/src/github.com/hyperledger/fabric/scripts/fabric-samples/helloworld-webapp/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":"bee3c03e36a357f52a80434a542e8d1e574d576c1f7ad93f95cae78b968ce980","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICATCCAaigAwIBAgIUSQqYMOL+H6y2I8vVFhAOfQ1G2wYwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMjIxMjA2MTE0ODAwWhcNMjMxMjA2MTE1\nMzAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAEvkFbiPCqKdeljnWzEuBwVXa87V4l3Dak3mVbyOO6\nQ75OIpdisxulZHxgNmpNYhMyb1pW3sRtqUP/LPXebnq4yKNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFB9QLZbsSi5gX4Ew0ctTPN4g\nSxAqMCsGA1UdIwQkMCKAIEI5qg3NdtruuLoM2nAYUdFFBNMarRst3dusalc2Xkl8\nMAoGCCqGSM49BAMCA0cAMEQCIELJMJjIk2Qz19XZh1VmF0tJPshSiU9+KHjAltYq\nOANdAiABN0jioRUQBHlvUNONZP3tm798/Vj7akqs0kHCz1/dzQ==\n-----END CERTIFICATE-----\n"}}}
- 編寫 node 程序
registerUser.js
,使用剛剛生成的admin用戶帝际,注冊(cè)u(píng)ser1用戶
'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Register and Enroll a user
*/
var Fabric_Client = require('fabric-client');
var Fabric_CA_Client = require('fabric-ca-client');
var path = require('path');
var util = require('util');
var os = require('os');
//
var fabric_client = new Fabric_Client();
var fabric_ca_client = null;
var admin_user = null;
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log(' Store path:'+store_path);
// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
// assign the store to the fabric client
fabric_client.setStateStore(state_store);
var crypto_suite = Fabric_Client.newCryptoSuite();
// use the same location for the state store (where the users' certificate are kept)
// and the crypto store (where the users' keys are kept)
var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);
var tlsOptions = {
trustedRoots: [],
verify: false
};
// be sure to change the http to https when the CA is running TLS enabled
fabric_ca_client = new Fabric_CA_Client('http://localhost:7054', null , '', crypto_suite);
// first check to see if the admin is already enrolled
return fabric_client.getUserContext('admin', true);
}).then((user_from_store) => {
// 創(chuàng)建 user1 普通用戶前蔓同,必須已經(jīng)有 admin 賬戶,否則拋異常
if (user_from_store && user_from_store.isEnrolled()) {
console.log('Successfully loaded admin from persistence');
admin_user = user_from_store;
} else {
throw new Error('Failed to get admin.... run enrollAdmin.js');
}
// at this point we should have the admin user
// first need to register the user with the CA server
return fabric_ca_client.register({enrollmentID: 'user1', affiliation: 'org1.department1'}, admin_user);
}).then((secret) => {
// next we need to enroll the user with CA server
console.log('Successfully registered user1 - secret:'+ secret);
return fabric_ca_client.enroll({enrollmentID: 'user1', enrollmentSecret: secret});
}).then((enrollment) => {
console.log('Successfully enrolled member user "user1" ');
return fabric_client.createUser(
{username: 'user1',
mspid: 'Org1MSP',
cryptoContent: { privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate }
});
}).then((user) => {
member_user = user;
return fabric_client.setUserContext(member_user);
}).then(()=>{
console.log('User1 was successfully registered and enrolled and is ready to intreact with the fabric network');
}).catch((err) => {
console.error('Failed to register: ' + err);
if(err.toString().indexOf('Authorization') > -1) {
console.error('Authorization failures may be caused by having admin credentials from a previous CA instance.\n' +
'Try again after deleting the contents of the store directory '+store_path);
}
});
- 執(zhí)行
registerUser.js
程序蹲诀,hfc-key-store中存有user1用戶的公私鑰信息
$ node registerUser.js
Store path:/opt/go-project/src/github.com/hyperledger/fabric/scripts/fabric-samples/helloworld-webapp/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:PbjicGKZoMwV
Successfully enrolled member user "user1"
User1 was successfully registered and enrolled and is ready to intreact with the fabric network
- 編寫 node 程序
query.js
斑粱,調(diào)用helloworld鏈碼的query函數(shù),查詢“yorick”的值
'use strict';
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Chaincode query
*/
var Fabric_Client = require('fabric-client');
var path = require('path');
var util = require('util');
var os = require('os');
//
var fabric_client = new Fabric_Client();
// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpc://localhost:7051');
channel.addPeer(peer);
//
var member_user = null;
var store_path = path.join(__dirname, 'hfc-key-store');
console.log('Store path:'+store_path);
var tx_id = null;
// 獲取操作鏈碼的用戶身份信息
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
// assign the store to the fabric client
fabric_client.setStateStore(state_store);
var crypto_suite = Fabric_Client.newCryptoSuite();
// use the same location for the state store (where the users' certificate are kept)
// and the crypto store (where the users' keys are kept)
var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
crypto_suite.setCryptoKeyStore(crypto_store);
fabric_client.setCryptoSuite(crypto_suite);
// get the enrolled user from persistence, this user will sign all requests
return fabric_client.getUserContext('user1', true);
}).then((user_from_store) => {
if (user_from_store && user_from_store.isEnrolled()) {
console.log('Successfully loaded user1 from persistence');
member_user = user_from_store;
} else {
throw new Error('Failed to get user1.... run registerUser.js');
}
// 查詢請(qǐng)求構(gòu)建
const request = {
chaincodeId: 'helloworld',
fcn: 'query',
args: ['yorick']
};
// 向peer節(jié)點(diǎn)發(fā)送請(qǐng)求提案
return channel.queryByChaincode(request);
}).then((query_responses) => {
console.log("Query has completed, checking results");
// query_responses could have more than one results if there multiple peers were used as targets
if (query_responses && query_responses.length == 1) {
if (query_responses[0] instanceof Error) {
console.error("error from query = ", query_responses[0]);
} else {
// 打印查詢的結(jié)果
console.log("Response is ", query_responses[0].toString());
}
} else {
console.log("No payloads were returned from query");
}
}).catch((err) => {
console.error('Failed to query successfully :: ' + err);
});
- 執(zhí)行
query.js
程序脯爪,使用hfc-key-store中存有user1用戶作為身份憑證調(diào)用鏈碼则北,獲取yorick的值信息
$ node query.js
Store path:/opt/go-project/src/github.com/hyperledger/fabric/scripts/fabric-samples/helloworld-webapp/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is 200
至此,完成了通過 node sdk 的方式調(diào)用鏈碼痕慢。后續(xù)可以對(duì)sdk進(jìn)一步封裝尚揣,對(duì)外提供相關(guān)訪問請(qǐng)求crud接口,供上層服務(wù)調(diào)用掖举。