本文章所有操作基于的操作系统版本是:ubuntu16.04 64位
本文章基于的Fabric网络环境是《Fabric实战(2)运行一个简单的fabric网络(容器外)》
Fabric的Peer节点和Orderer节点都提供了基于Grpc协议的通信接口,通过这些接口可以和Peer以及Orderer节点进行交互。
Grpc是由Google开发的一款语言中立、平台中立、开源的远程过程调用RPC系统。目前Grpc支持JAVA、GO、C、C++、Node.js、Python、Ruby、Objective-C、PHP、C#等语言。Grpc默认使用protocol buffers作为接口定义语言来描述服务接口和消息结构。
Fabric一共有5个模块(peer、orderer、cryptogen、configtxgen、configtxlator),其中Peer模块和Orderer模块提供了Grpc相关的接口,但是Orderer模块是Peer模块等调用的,在项目开发中并不需要直接调用orderer模块的GRpc接口,所以这里主要介绍Peer模块的Grpc接口。另外fabric-ca-server项目也是提供了JsonRPC接口共第三方应用程序调用,由于fabric-ca-server在实际项目开发中是不可或缺的,所以这里也会介绍Fabric-ca-server的grpc接口调用方式。
Fabric的Peer模块提供的Grpc接口按照功能大致可以分为两类:系统管理和chaincode操作。
Peer模块提供的Grpc接口提供了如下与系统管理相关的功能:
- 获取当前Peer加入了哪些些Channel
- 获取当前Peer加入的某个channel的区块数量
- 通过区块号获取当前的Peer加入的某个Channel的某个区块的详细信息
- 通过区块hash值获取当前的Peer加入的某个Channel的某个区块的详细信息
- 通过交易的hash值获取交易的详细信息
- 获取当前peer服务器中状态为install的chaincode信息
- 获取当前peer加入的某个channel状态为Instantiate的chaincode的详细信息
Peer模块提供的Grpc接口提供了如下与chaincode相关的功能:
- 调用chaincode的query方法
- 调用一个已经部署好的chaincode的invoke方法
必须要安装8.9.x或者更高版本
下载二进制包:https://nodejs.org/dist/latest-v10.x/node-v10.13.0-linux-x64.tar.gz
#解压二进制包
tar zvxf node-v10.13.0-linux-x64.tar.gz -C
#将可执行文件目录加入环境变量
echo "export PATH=$PATH:$HOME/node-v10.13.0-linux-x64/bin" >> ~/.bashrc
#验证是否安装成功
node -v
npm -v
#安装co库
npm install co
创建一个工程目录,并切换到目录下:
mkdir -p ~/fabric-ws/fabric-node-sdk/node-test
cd ~/fabric-ws/fabric-node-sdk/node-test
编辑一个npm的配置文件,如下:
{
"name": "node-sdk-study",
"version": "1.0.0",
"description": "Hyperledger Fabric Node SDK Test Application",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"fabric-ca-client": "^1.0.0",
"fabric-client": "^1.0.0"
},
"author": "Your name",
"license": "Apache-2.0",
"keywords": [
"Hyperledger",
"Fabric",
"Test",
"Application"
]
}
下载依赖模块:
编辑保存好该文件后,我们就可以运行npm install命令来下载所有相关的依赖模块,但是由于npm服务器在国外,所以下载可能会很慢,感谢淘宝为我们提供了国内的npm镜像,使得安装npm模块快很多。
运行命令:
npm install --registry=https://registry.npm.taobao.org
运行完毕后我们查看一下nodeTest目录,可以看到多了一个node_modules文件夹。这里就是使用刚才的命令下载下来的所有依赖包。
编写一个通过Nodejs sdk调用fabric的例子。
'use strict';
var co = require('co');
var hfc = require('fabric-client');
var path = require('path');
var sdkUtils = require('fabric-client/lib/utils');
var fs = require('fs');
var FabricCAService = require('fabric-ca-client');
var User = require('fabric-client/lib/User.js');
var options = {
channel_id: 'testchannel',
chaincode_id: 'r_test_cc6',
};
var tempdir = "/home/zym/fabric-ws/simple-demo/client-kvs"
var client = new hfc();
var cryptoSuite = hfc.newCryptoSuite();
cryptoSuite.setCryptoKeyStore(hfc.newCryptoKeyStore({path:tempdir}));
client.setCryptoSuite(cryptoSuite);
//创建CA客户端
var caClient = new FabricCAService('http://0.0.0.0:7054', null, '', cryptoSuite)
//创建通道客户端代理, 通道名称为:testchannel
var channel = client.newChannel('testchannel');
//创建order,并将order加入到channel中
var order = client.newOrderer('grpc://orderer.simple-network.com:7050');
channel.addOrderer(order);
//创建peer节点的客户端代理
var peer = client.newPeer('grpc://peer0.org1.simple-network.com:7051');
channel.addPeer(peer);
var peer2 = client.newPeer('grpc://peer0.org2.simple-network.com:7061');
channel.addPeer(peer2);
function getKeyFilesInDir(dir){
//该函数用于找到keystore目录下的私钥文件的路径
var files = fs.readdirSync(dir)
var keyFiles = []
files.forEach((file_name) => {
let filePath = path.join(dir, file_name)
if (file_name.endsWith('_sk')) {
keyFiles.push(filePath)
}
})
return keyFiles
}
function getOrgUserLocal(){
var user_id = '[email protected]';
var msp_id = 'Org1MSP';
var privateKey = '/home/zym/fabric-ws/simple-demo/crypto-config/peerOrganizations/org1.simple-network.com/users/[email protected]/msp/keystore/882aff48d7e4fabb31b756dcac1aaa48326ddbd3a0d2424918c64ccefbe2351f_sk';
var signedCert = '/home/zym/fabric-ws/simple-demo/crypto-config/peerOrganizations/org1.simple-network.com/users/[email protected]/msp/signcerts/[email protected]';
return hfc.newDefaultKeyValueStore({path:tempdir}).then((store) => {
client.setStateStore(store);
return client.createUser({
username:user_id,
mspid:msp_id,
cryptoContent:{
privateKey: privateKey,
signedCert: signedCert
}
});
});
};
function getUserCa(username, password){
var member
return hfc.newDefaultKeyValueStore({path:tempdir}).then((store)=>{
client.setStateStore(store);
client._userContext = null;
return client.getUserContext(username, true).then((user)=>{
if (user && user.isEnrolled()){
console.info('success enroll admin');
return user;
}else{
return caClient.enroll({enrollmentID:username, enrollmentSecret:password}).then((enrollment)=>{
console.info('successfully enrolled user\''+ username + '\'');
member = new User(username);
member.setCryptoSuite(client.getCryptoSuite());
return member.setEnrollment(enrollment.key, enrollment.certificate, 'Org1MSP');
}).then(()=>{
return client.setUserContext(member)
}).then(()=>{
return member;
}).catch((err)=>{
console.error('enroll admin err' + err.stack);
return null
})
}
})
})
}
co(
(function*(){
let member = yield getOrgUserLocal();
//请先启动fabric ca服务器,并使用admin账号注册userttest账号
let member = yield getUserCa("usertest", "userwd")
//获取当前通道的账本信息
let result = yield channel.queryInfo(peer);
console.info(JSON.stringify(result))
})()
)
node query.js
输出:
{
"height": {
"low": 133,
"high": 0,
"unsigned": true
},
"currentBlockHash": {
"buffer": {
"type": "Buffer",
"data": [8, 133, 1, 18, 32, 93, 222, 144, 190, 178, 147, 223, 86, 197, 50, 152, 157, 114, 12, 23, 83, 28, 78, 96, 232, 45, 2, 24, 130, 120, 246, 84, 192, 47, 15, 211, 128, 26, 32, 126, 82, 175, 9, 215, 58, 226, 121, 163, 12, 109, 243, 35, 247, 254, 237, 252, 207, 7, 89, 101, 207, 60, 182, 46, 174, 141, 70, 121, 176, 32, 214]
},
"offset": 5,
"markedOffset": -1,
"limit": 37,
"littleEndian": true,
"noAssert": false
},
"previousBlockHash": {
"buffer": {
"type": "Buffer",
"data": [8, 133, 1, 18, 32, 93, 222, 144, 190, 178, 147, 223, 86, 197, 50, 152, 157, 114, 12, 23, 83, 28, 78, 96, 232, 45, 2, 24, 130, 120, 246, 84, 192, 47, 15, 211, 128, 26, 32, 126, 82, 175, 9, 215, 58, 226, 121, 163, 12, 109, 243, 35, 247, 254, 237, 252, 207, 7, 89, 101, 207, 60, 182, 46, 174, 141, 70, 121, 176, 32, 214]
},
"offset": 39,
"markedOffset": -1,
"limit": 71,
"littleEndian": true,
"noAssert": false
}
}
以下实例代码只能在co((function(){})())函数中进行。*
let resultChannels = yield client.queryChannels(peer)
console.info(JSON.stringify(resultChannels, null, 4))
输出:
{
"channels": [
{
"channel_id": "testchannel"
}
]
}
let result = yield channel.queryInfo(peer);
console.info(JSON.stringify(result, null, 4))
let resultBlockInfo = yield channel.queryBlock(1, peer, null)
console.info(JSON.stringify(resultBlockInfo, null, 4))
let resultBlockInfo = yield channel.queryBlockByHash("hashcode", pe er)
console.info(JSON.stringify(resultBlockInfo))
let resultTxInfo = yield channel.queryTransaction("a113aa4f1ab582bd3 54212fa70092b255919ccdd60e0e87e477b355ad6b24ec0", peer)
console.info(JSON.stringify(resultTxInfo))
let resultChaincodes = yield client.queryInstalledChaincodes(peer)
console.info(JSON.stringify(resultChaincodes))
let resultChaincodes = yield channel.queryInstantiatedChaincodes(peer)
console.info(JSON.stringify(resultChaincodes))
let tx_id = client.newTransactionID();
var request = {
chaincodeId: "r_test_cc6",
txId: tx_id,
fcn: "query",
args:["a"]
};
let resultQuery = yield channel.queryByChaincode(request, peer);
console.info(JSON.stringify(resultQuery))
// 发送提案,前面channel.addPeer(peer)添加几个节点,就会向几个节点发送,所以应该根据背书策略,只向背书节点发送。
let tx_id = client.newTransactionID();
var request = {
chaincodeId: "r_test_cc9",
txId: tx_id,
fcn: "invoke",
args:["a", "b", "1"]
};
let chaincodeInvokeResult = yield channel.sendTransactionProposal(request);
var proposalResponses = chaincodeInvokeResult[0]
var proposal = chaincodeInvokeResult[1]
var header = chaincodeInvokeResult[2]
var all_good = true
//背书策略判断
for (var i in proposalResponses){
//console.info(JSON.stringify(proposalResponses))
let one_good = false;
if (proposalResponses && proposalResponses[i].response && proposalResponses[i].response.status === 200){
one_good = true;
console.info('transaction proposal was good')
}else{
console.info('transaction proposal was bad')
}
all_good = all_good & one_good;
}
//如果背书成功,则将提案封装成交易发送给order节点
if (all_good){
var request = {
proposalResponses:proposalResponses,
proposal : proposal,
header: header
};
var transactionID = tx_id.getTransactionID();
var sendPromise = yield channel.sendTransaction(request);
}
})()