使用Fabric Node SDK进行Invoke和Query
前面的文章都是在讲解Fabric网络的搭建和ChainCode的开发,那么在ChainCode开发完毕后,我们就需要使用Fabric SDK做应用程序的开发了。官方虽然提供了Node.JS,Java,Go,Python等多种语言的SDK,但是由于整个Fabric太新了,很多SDK还不成熟和完善,所以我采用Node JS的SDK,毕竟这个是功能毕竟齐全,而且也是官方示例的时候使用的SDK。由于我从来没有接触过Node.JS的开发,对这个语言理解不深,所以讲的比较肤浅,希望大家见谅。
1.环境准备
Node.js是一个跨平台的语言,可以在Linux,Window和Mac上安装,我们在开发的时候可以在Windows下开发,最后生产环境一般都是Linux,所以我们这里就以Ubuntu为例。Fabric Node SDK支持的Node版本是v6,不支持最新的v8版本。NodeJS官方给我们提供了很方便的安装方法,具体文档在:https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
我们只需要执行以下命令即可安装NodeJS的最新v6版本:
- curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
- sudo apt-get install -y nodejs
安装完成后我们可以使用以下两个命令来查看安装的Node版本和npm版本。
- node –v
- npm -v
关于NPM,这个是一个包管理器,我觉得很像VS里面的NuGet,关于NPM的基础知识,我们可以参考这篇博客:http://www.ruanyifeng.com/blog/2016/01/npm-install.html
只要安装好node和npm,接下来我们就可以进行Fabric Node SDK Application的开发了。
由于我们想基于官方Example的e2e_cli里面的Fabric网络来写程序,关于Fabric网络的搭建我就不多说,大家可以参考我之前的博客。总之结果就是我们现在已经成功运行了e2e_cli这个网络,也就是说Example02这个ChainCode已经安装部署,并且测试通过了,我们接下来只是换用Node SDK的方式进行查询和调用。
2.编写package.json并下载依赖模块
- 我们首先在当前用户的根目录建立一个nodeTest的文件夹,用于存放我们关于node的相关项目文件,然后在其中新建一个包配置文件,package.json
- mkdir ~/nodeTest
- cd ~/nodeTest
- vi package.json
- 在这个文件中,我们可以定义很多项目相关的属性,这篇博客详细的介绍了每个属性有什么用,大家可以参考:http://www.cnblogs.com/tzyy/p/5193811.html
- 总之,最后我们在package.json中放入了以下内容:
- {
- "name": "nodeTest",
- "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": "Devin Zeng",
- "license": "Apache-2.0",
- "keywords": [
- "Hyperledger",
- "Fabric",
- "Test",
- "Application"
- ]
- }
最主要的就是dependencies,这里我们放了Fabric CA Client和Fabric Node SDK的Client,虽然本示例中没用到CA Client,但是以后会用到,所以先放在这里了。
编辑保存好该文件后,我们就可以运行npm install命令来下载所有相关的依赖模块,但是由于npm服务器在国外,所以下载可能会很慢,感谢淘宝为我们提供了国内的npm镜像,使得安装npm模块快很多。运行的命令是:
- npm install --registry=https://registry.npm.taobao.org
- 运行完毕后我们查看一下nodeTest目录,可以看到多了一个node_modules文件夹。这里就是使用刚才的命令下载下来的所有依赖包。
2.编写对Fabric的Query方法
下面我们新建一个query.js文件,开始我们的Fabric Node SDK编码工作。由于代码比较长,所以我就不分步讲了,直接在代码中增加注释,将完整代码贴出来:
- 'use strict';
- var hfc = require('fabric-client');
- var path = require('path');
- var sdkUtils = require('fabric-client/lib/utils')
- var fs = require('fs');
- var options = {
- user_id: 'Admin@org1.example.com',
- msp_id:'Org1MSP',
- channel_id: 'mychannel',
- chaincode_id: 'mycc',
- network_url: 'grpcs://localhost:7051',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
- privateKeyFolder:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore',
- signedCert:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem',
- tls_cacerts:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt',
- server_hostname: "peer0.org1.example.com"
- };
- var channel = {};
- var client = null;
- const 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
- }
- Promise.resolve().then(() => {
- console.log("Load privateKey and signedCert");
- client = new hfc();
- var createUserOpt = {
- username: options.user_id,
- mspid: options.msp_id,
- cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
- signedCert: options.signedCert }
- }
- //以上代码指定了当前用户的私钥,证书等基本信息
- return sdkUtils.newKeyValueStore({
- path: "/tmp/fabric-client-stateStore/"
- }).then((store) => {
- client.setStateStore(store)
- return client.createUser(createUserOpt)
- })
- }).then((user) => {
- channel = client.newChannel(options.channel_id);
- let data = fs.readFileSync(options.tls_cacerts);
- let peer = client.newPeer(options.network_url,
- {
- pem: Buffer.from(data).toString(),
- 'ssl-target-name-override': options.server_hostname
- }
- );
- peer.setName("peer0");
- //因为启用了TLS,所以上面的代码就是指定TLS的CA证书
- channel.addPeer(peer);
- return;
- }).then(() => {
- console.log("Make query");
- var transaction_id = client.newTransactionID();
- console.log("Assigning transaction_id: ", transaction_id._transaction_id);
- //构造查询request参数
- const request = {
- chaincodeId: options.chaincode_id,
- txId: transaction_id,
- fcn: 'query',
- args: ['a']
- };
- return channel.queryByChaincode(request);
- }).then((query_responses) => {
- console.log("returned from query");
- if (!query_responses.length) {
- console.log("No payloads were returned from query");
- } else {
- console.log("Query result count = ", query_responses.length)
- }
- if (query_responses[0] instanceof Error) {
- console.error("error from query = ", query_responses[0]);
- }
- console.log("Response is ", query_responses[0].toString());//打印返回的结果
- }).catch((err) => {
- console.error("Caught Error", err);
- });
编写完代码,我们想要测试一下我们的代码是否靠谱,直接运行
- node query.js
即可,我们可以看到,a账户的余额是90元。
- studyzy@ubuntu1:~/nodeTest$ node query.js
- Load privateKey and signedCert
- Make query
- Assigning transaction_id: ee3ac35d40d8510813546a2216ad9c0d91213b8e1bba9b7fe19cfeff3014e38a
- returned from query
- Query result count = 1
- Response is 90
为什么a账户是90?因为我们跑e2e_cli的Fabric网络时,系统会自动安装Example02的ChainCode,然后自动跑查询,转账等操作。
3.编写对Fabric的Invoke方法
相比较于Query方法,Invoke方法要复杂的多,主要是因为Invoke需要和Orderer通信,而且发起了Transaction之后,还要设置EventHub来接收消息。下面贴出invoke.js的全部内容,对于比较重要的部分我进行了注释:
- 'use strict';
- var hfc = require('fabric-client');
- var path = require('path');
- var util = require('util');
- var sdkUtils = require('fabric-client/lib/utils')
- const fs = require('fs');
- var options = {
- user_id: 'Admin@org1.example.com',
- msp_id:'Org1MSP',
- channel_id: 'mychannel',
- chaincode_id: 'mycc',
- peer_url: 'grpcs://localhost:7051',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
- event_url: 'grpcs://localhost:7053',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
- orderer_url: 'grpcs://localhost:7050',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
- privateKeyFolder:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore',
- signedCert:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem',
- peer_tls_cacerts:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt',
- orderer_tls_cacerts:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt',
- server_hostname: "peer0.org1.example.com"
- };
- var channel = {};
- var client = null;
- var targets = [];
- var tx_id = null;
- const getKeyFilesInDir = (dir) => {
- //该函数用于找到keystore目录下的私钥文件的路径
- const files = fs.readdirSync(dir)
- const keyFiles = []
- files.forEach((file_name) => {
- let filePath = path.join(dir, file_name)
- if (file_name.endsWith('_sk')) {
- keyFiles.push(filePath)
- }
- })
- return keyFiles
- }
- Promise.resolve().then(() => {
- console.log("Load privateKey and signedCert");
- client = new hfc();
- var createUserOpt = {
- username: options.user_id,
- mspid: options.msp_id,
- cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
- signedCert: options.signedCert }
- }
- //以上代码指定了当前用户的私钥,证书等基本信息
- return sdkUtils.newKeyValueStore({
- path: "/tmp/fabric-client-stateStore/"
- }).then((store) => {
- client.setStateStore(store)
- return client.createUser(createUserOpt)
- })
- }).then((user) => {
- channel = client.newChannel(options.channel_id);
- let data = fs.readFileSync(options.peer_tls_cacerts);
- let peer = client.newPeer(options.peer_url,
- {
- pem: Buffer.from(data).toString(),
- 'ssl-target-name-override': options.server_hostname
- }
- );
- //因为启用了TLS,所以上面的代码就是指定Peer的TLS的CA证书
- channel.addPeer(peer);
- //接下来连接Orderer的时候也启用了TLS,也是同样的处理方法
- let odata = fs.readFileSync(options.orderer_tls_cacerts);
- let caroots = Buffer.from(odata).toString();
- var orderer = client.newOrderer(options.orderer_url, {
- 'pem': caroots,
- 'ssl-target-name-override': "orderer.example.com"
- });
- channel.addOrderer(orderer);
- targets.push(peer);
- return;
- }).then(() => {
- tx_id = client.newTransactionID();
- console.log("Assigning transaction_id: ", tx_id._transaction_id);
- //发起转账行为,将a->b 10元
- var request = {
- targets: targets,
- chaincodeId: options.chaincode_id,
- fcn: 'invoke',
- args: ['a', 'b', '10'],
- chainId: options.channel_id,
- txId: tx_id
- };
- return channel.sendTransactionProposal(request);
- }).then((results) => {
- var proposalResponses = results[0];
- var proposal = results[1];
- var header = results[2];
- let isProposalGood = false;
- if (proposalResponses && proposalResponses[0].response &&
- proposalResponses[0].response.status === 200) {
- isProposalGood = true;
- console.log('transaction proposal was good');
- } else {
- console.error('transaction proposal was bad');
- }
- if (isProposalGood) {
- console.log(util.format(
- 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
- proposalResponses[0].response.status, proposalResponses[0].response.message,
- proposalResponses[0].response.payload, proposalResponses[0].endorsement.signature));
- var request = {
- proposalResponses: proposalResponses,
- proposal: proposal,
- header: header
- };
- // set the transaction listener and set a timeout of 30sec
- // if the transaction did not get committed within the timeout period,
- // fail the test
- var transactionID = tx_id.getTransactionID();
- var eventPromises = [];
- let eh = client.newEventHub();
- //接下来设置EventHub,用于监听Transaction是否成功写入,这里也是启用了TLS
- let data = fs.readFileSync(options.peer_tls_cacerts);
- let grpcOpts = {
- pem: Buffer.from(data).toString(),
- 'ssl-target-name-override': options.server_hostname
- }
- eh.setPeerAddr(options.event_url,grpcOpts);
- eh.connect();
- let txPromise = new Promise((resolve, reject) => {
- let handle = setTimeout(() => {
- eh.disconnect();
- reject();
- }, 30000);
- //向EventHub注册事件的处理办法
- eh.registerTxEvent(transactionID, (tx, code) => {
- clearTimeout(handle);
- eh.unregisterTxEvent(transactionID);
- eh.disconnect();
- if (code !== 'VALID') {
- console.error(
- 'The transaction was invalid, code = ' + code);
- reject();
- } else {
- console.log(
- 'The transaction has been committed on peer ' +
- eh._ep._endpoint.addr);
- resolve();
- }
- });
- });
- eventPromises.push(txPromise);
- var sendPromise = channel.sendTransaction(request);
- return Promise.all([sendPromise].concat(eventPromises)).then((results) => {
- console.log(' event promise all complete and testing complete');
- return results[0]; // the first returned value is from the 'sendPromise' which is from the 'sendTransaction()' call
- }).catch((err) => {
- console.error(
- 'Failed to send transaction and get notifications within the timeout period.'
- );
- return 'Failed to send transaction and get notifications within the timeout period.';
- });
- } else {
- console.error(
- 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'
- );
- return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...';
- }
- }, (err) => {
- console.error('Failed to send proposal due to error: ' + err.stack ? err.stack :
- err);
- return 'Failed to send proposal due to error: ' + err.stack ? err.stack :
- err;
- }).then((response) => {
- if (response.status === 'SUCCESS') {
- console.log('Successfully sent transaction to the orderer.');
- return tx_id.getTransactionID();
- } else {
- console.error('Failed to order the transaction. Error code: ' + response.status);
- return 'Failed to order the transaction. Error code: ' + response.status;
- }
- }, (err) => {
- console.error('Failed to send transaction due to error: ' + err.stack ? err
- .stack : err);
- return 'Failed to send transaction due to error: ' + err.stack ? err.stack :
- err;
- });
保存文件并退出,接下来测试一下我们的代码,运行:
- node invoke.js
我们可以看到系统返回如下结果:
- Load privateKey and signedCert
- Assigning transaction_id: 1adbf20ace0d1601b00cc2b9dfdd4a431cfff9a13f6a6f5e5e4a80c897e0f7a8
- transaction proposal was good
- Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK", metadata - "", endorsement signature: 0D x��N��n�#���/�G���QD�w�����As� \]��FfWҡ�+������=m9I���� 6�i
- info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"peer0.org1.example.com","grpc.default_authority":"peer0.org1.example.com"}
- The transaction has been committed on peer localhost:7053
- event promise all complete and testing complete
- Successfully sent transaction to the orderer.
从打印出的结果看,我们的转账已经成功了,我们可以重新调用之前写的query.js重新查询,可以看到a账户的余额已经变少了10元。
4.总结
我们以上的query和Invoke都是参照了官方的fabcar示例,该示例在https://github.com/hyperledger/fabric-samples/tree/release/fabcar
这只是简单的测试Node SDK是否可用,如果我们要做项目,那么就会复杂很多,可以参考官方的两个项目:
https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer
https://github.com/IBM-Blockchain/marbles
我之前一直卡在怎么基于某个用户的私钥和证书来设置当前的Context,后来感谢neswater的帮助,终于才解决了这个问题。还有就是TLS的问题,官方给出的fabcar是没有TLS的,我搞了半天才搞定,原来除了制定TLS证书之外,我们访问Peer的URL也是不一样的。
最后,大家如果想进一步探讨Fabric或者使用中遇到什么问题可以加入QQ群【494085548】大家一起讨论。
使用Fabric Node SDK进行Invoke和Query的更多相关文章
- Hyperledger Fabric Node SDK和应用开发
Hyperledger Fabric 提供了多种语言的SDK版本,其中提出比较早.比较稳定而全面的是Node.js版本的SDK. 前面提到的fabric示例(如first-network和e2e-cl ...
- Hyperledger Fabric 实战(十): Fabric node SDK 样例 - 投票DAPP
Fabric node SDK 样例 - 投票DAPP 参考 fabric-samples 下的 fabcar 加以实现 目录结构 . ├── app │ ├── controllers │ │ └─ ...
- 搭建RESTful API来使用Fabric Node SDK 开篇
在Balance-Transfer中,有关于Node SDK比较完备的例子. SDK的官方文档在这里:https://fabric-sdk-node.github.io/ Balance-Transf ...
- fabric Node SDK进行连接
yum install gcc-c++ npm install --unsafe-perm --registry=https://registry.npm.taobao.org chmod -R
- Fabric go sdk初始化所需证书解析
fabric sdk go 提供的官方文档少之又少,要想入门,主要就靠研究官方的e2e系列示例,这真的是一件挺无奈的事情.没法子,只能硬着头皮上了.研究发现,e2e这个例子是通过cryptogen生成 ...
- Node.js SDK与fabric链码交互开发
1.本篇背景 前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互. 本篇内容基本来自官方 H ...
- 搭建基于hyperledger fabric的联盟社区(六) --搭建node.js服务器
接下来我要做的是用fabric sdk来做出应用程序,代替CLI与整个区块链网络交互.并且实现一个http API,向社区提供一个简单的接口,使社区轻松的与区块链交互. 官方虽然提供了Node.JS, ...
- Hyperledger Fabric——balance transfer(一)启动示例
Blacne transfer是Hyperledger fabric Node SDK的一个示例应用,主要使用了SDK中fabric-client 和 fabric-ca-client 模块中的API ...
- 搭建基于hyperledger fabric的联盟社区(四) --chaincode开发
前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动fabric网络之前我们要准备好写好的chaincode.chaincode的开发一般是使用GO或者JAVA,而我选择的是GO语言.先分析一下官 ...
随机推荐
- 深入浅出解读 Java 虚拟机的差别测试技术
本文分享基于字节码种子生成有效.可执行的字节码文件变种,并用于 JVM 实现的差别测试.本文特别提出用于修改字节码语法的classfuzz技术和修改字节码语义的classming技术.上述变种技术系统 ...
- 做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比及最佳实现
背景 在端上为了提升App的灵活性, 快速解决万变的业务需求,开发者们探索了多种解决方案,如PhoneGap ,React Native ,Weex等,但在Flutter生态还没有好的解决方案.未来闲 ...
- 聊聊 scala 的模式匹配
一. scala 模式匹配(pattern matching) pattern matching 可以说是 scala 中十分强大的一个语言特性,当然这不是 scala 独有的,但这不妨碍它成为 sc ...
- Real Developer 应该参加的硬核竞赛来了!
本文由云+社区发表 Labs Contest 竞赛简介 君君这次又来搞大事情啦,在 Function As A Service,Serverless,PaaS 大行其道的今天,你是否已经忘记了自己动手 ...
- DriverManager 驱动管理器类简介 JDBC简介(三)
驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中 后续的处理就可以对这个列表进行操作 简言之,驱动管理器,就是字面含义,主要负责就是管理 驱动 概述 ...
- [七]JavaIO之 PipedInputStream 和 PipedInputStream
管道简介
- SpringBoot整合系列-整合SpringMVC
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9984607.html SpringBoot整合Spring MVC 步骤 第一步:添加必 ...
- CentOS 7 镜像文件各个版本区别
CentOS ISO 镜像文件的功能 引导安装 CentOS ISO 镜像文件包含有安装程序,官方称其为 Anaconda,用来引导安装 CentOS 提供 CentOS 的安装文件 镜像文件不一定包 ...
- 一统江湖的大前端(4)shell.js——穿上马甲我照样认识你
<一统江湖的大前端>系列是自己的前端学习笔记,旨在介绍javascript在非网页开发领域的应用案例和发现各类好玩的js库,不定期更新.如果你对前端的理解还是写写页面绑绑事件,那你真的是有 ...
- (摘)sql-索引的作用(超详细)
(一)深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录.微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引.簇集索引)和非聚集索引(nonc ...