前面的文章都是在讲解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版本:

  1. curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
  2. sudo apt-get install -y nodejs
  1. 安装完成后我们可以使用以下两个命令来查看安装的Node版本和npm版本。
  1. node v
  2. npm -v
  1. 关于NPM,这个是一个包管理器,我觉得很像VS里面的NuGet,关于NPM的基础知识,我们可以参考这篇博客:http://www.ruanyifeng.com/blog/2016/01/npm-install.html
  1. 只要安装好nodenpm,接下来我们就可以进行Fabric Node SDK Application的开发了。
  1. 由于我们想基于官方Examplee2e_cli里面的Fabric网络来写程序,关于Fabric网络的搭建我就不多说,大家可以参考我之前的博客。总之结果就是我们现在已经成功运行了e2e_cli这个网络,也就是说Example02这个ChainCode已经安装部署,并且测试通过了,我们接下来只是换用Node SDK的方式进行查询和调用。

2.编写package.json并下载依赖模块

  1. 我们首先在当前用户的根目录建立一个nodeTest的文件夹,用于存放我们关于node的相关项目文件,然后在其中新建一个包配置文件,package.json
  1. mkdir ~/nodeTest
  2. cd ~/nodeTest
  3. vi package.json
  1. 在这个文件中,我们可以定义很多项目相关的属性,这篇博客详细的介绍了每个属性有什么用,大家可以参考:http://www.cnblogs.com/tzyy/p/5193811.html
  1. 总之,最后我们在package.json中放入了以下内容:
  1. {
  2. "name": "nodeTest",
  3. "version": "1.0.0",
  4. "description": "Hyperledger Fabric Node SDK Test Application",
  5. "scripts": {
  6. "test": "echo \"Error: no test specified\" && exit 1"
  7. },
  8. "dependencies": {
  9. "fabric-ca-client": "^1.0.0",
  10. "fabric-client": "^1.0.0"
  11. },
  12. "author": "Devin Zeng",
  13. "license": "Apache-2.0",
  14. "keywords": [
  15. "Hyperledger",
  16. "Fabric",
  17. "Test",
  18. "Application"
  19. ]
  20. }

最主要的就是dependencies,这里我们放了Fabric CA Client和Fabric Node SDK的Client,虽然本示例中没用到CA Client,但是以后会用到,所以先放在这里了。

  1. 编辑保存好该文件后,我们就可以运行npm install命令来下载所有相关的依赖模块,但是由于npm服务器在国外,所以下载可能会很慢,感谢淘宝为我们提供了国内的npm镜像,使得安装npm模块快很多。运行的命令是:
  1. npm install --registry=https://registry.npm.taobao.org
  1. 运行完毕后我们查看一下nodeTest目录,可以看到多了一个node_modules文件夹。这里就是使用刚才的命令下载下来的所有依赖包。

2.编写对Fabric的Query方法

下面我们新建一个query.js文件,开始我们的Fabric Node SDK编码工作。由于代码比较长,所以我就不分步讲了,直接在代码中增加注释,将完整代码贴出来:

  1. 'use strict';
  2.  
  3. var hfc = require('fabric-client');
  4. var path = require('path');
  5. var sdkUtils = require('fabric-client/lib/utils')
  6. var fs = require('fs');
  7. var options = {
  8. user_id: 'Admin@org1.example.com',
  9. msp_id:'Org1MSP',
  10. channel_id: 'mychannel',
  11. chaincode_id: 'mycc',
  12. network_url: 'grpcs://localhost:7051',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
  13. 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',
  14. 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',
  15. 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',
  16. server_hostname: "peer0.org1.example.com"
  17. };
  18.  
  19. var channel = {};
  20. var client = null;
  21. const getKeyFilesInDir = (dir) => {
  22. //该函数用于找到keystore目录下的私钥文件的路径
  23. var files = fs.readdirSync(dir)
  24. var keyFiles = []
  25. files.forEach((file_name) => {
  26. let filePath = path.join(dir, file_name)
  27. if (file_name.endsWith('_sk')) {
  28. keyFiles.push(filePath)
  29. }
  30. })
  31. return keyFiles
  32. }
  33. Promise.resolve().then(() => {
  34. console.log("Load privateKey and signedCert");
  35. client = new hfc();
  36. var createUserOpt = {
  37. username: options.user_id,
  38. mspid: options.msp_id,
  39. cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
  40. signedCert: options.signedCert }
  41. }
  42. //以上代码指定了当前用户的私钥,证书等基本信息
  43. return sdkUtils.newKeyValueStore({
  44. path: "/tmp/fabric-client-stateStore/"
  45. }).then((store) => {
  46. client.setStateStore(store)
  47. return client.createUser(createUserOpt)
  48. })
  49. }).then((user) => {
  50. channel = client.newChannel(options.channel_id);
  51.  
  52. let data = fs.readFileSync(options.tls_cacerts);
  53. let peer = client.newPeer(options.network_url,
  54. {
  55. pem: Buffer.from(data).toString(),
  56. 'ssl-target-name-override': options.server_hostname
  57. }
  58. );
  59. peer.setName("peer0");
  60. //因为启用了TLS,所以上面的代码就是指定TLS的CA证书
  61. channel.addPeer(peer);
  62. return;
  63. }).then(() => {
  64. console.log("Make query");
  65. var transaction_id = client.newTransactionID();
  66. console.log("Assigning transaction_id: ", transaction_id._transaction_id);
  67. //构造查询request参数
  68. const request = {
  69. chaincodeId: options.chaincode_id,
  70. txId: transaction_id,
  71. fcn: 'query',
  72. args: ['a']
  73. };
  74. return channel.queryByChaincode(request);
  75. }).then((query_responses) => {
  76. console.log("returned from query");
  77. if (!query_responses.length) {
  78. console.log("No payloads were returned from query");
  79. } else {
  80. console.log("Query result count = ", query_responses.length)
  81. }
  82. if (query_responses[0] instanceof Error) {
  83. console.error("error from query = ", query_responses[0]);
  84. }
  85. console.log("Response is ", query_responses[0].toString());//打印返回的结果
  86. }).catch((err) => {
  87. console.error("Caught Error", err);
  88. });

编写完代码,我们想要测试一下我们的代码是否靠谱,直接运行

  1. node query.js

即可,我们可以看到,a账户的余额是90元。

  1. studyzy@ubuntu1:~/nodeTest$ node query.js
  2. Load privateKey and signedCert
  3. Make query
  4. Assigning transaction_id: ee3ac35d40d8510813546a2216ad9c0d91213b8e1bba9b7fe19cfeff3014e38a
  5. returned from query
  6. Query result count = 1
  7. Response is 90

为什么a账户是90?因为我们跑e2e_cli的Fabric网络时,系统会自动安装Example02的ChainCode,然后自动跑查询,转账等操作。

3.编写对Fabric的Invoke方法

相比较于Query方法,Invoke方法要复杂的多,主要是因为Invoke需要和Orderer通信,而且发起了Transaction之后,还要设置EventHub来接收消息。下面贴出invoke.js的全部内容,对于比较重要的部分我进行了注释:

  1. 'use strict';
  2.  
  3. var hfc = require('fabric-client');
  4. var path = require('path');
  5. var util = require('util');
  6. var sdkUtils = require('fabric-client/lib/utils')
  7. const fs = require('fs');
  8. var options = {
  9. user_id: 'Admin@org1.example.com',
  10. msp_id:'Org1MSP',
  11. channel_id: 'mychannel',
  12. chaincode_id: 'mycc',
  13. peer_url: 'grpcs://localhost:7051',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
  14. event_url: 'grpcs://localhost:7053',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
  15. orderer_url: 'grpcs://localhost:7050',//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
  16. 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',
  17. 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',
  18. 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',
  19. 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',
  20. server_hostname: "peer0.org1.example.com"
  21. };
  22.  
  23. var channel = {};
  24. var client = null;
  25. var targets = [];
  26. var tx_id = null;
  27. const getKeyFilesInDir = (dir) => {
  28. //该函数用于找到keystore目录下的私钥文件的路径
  29. const files = fs.readdirSync(dir)
  30. const keyFiles = []
  31. files.forEach((file_name) => {
  32. let filePath = path.join(dir, file_name)
  33. if (file_name.endsWith('_sk')) {
  34. keyFiles.push(filePath)
  35. }
  36. })
  37. return keyFiles
  38. }
  39. Promise.resolve().then(() => {
  40. console.log("Load privateKey and signedCert");
  41. client = new hfc();
  42. var createUserOpt = {
  43. username: options.user_id,
  44. mspid: options.msp_id,
  45. cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
  46. signedCert: options.signedCert }
  47. }
  48. //以上代码指定了当前用户的私钥,证书等基本信息
  49. return sdkUtils.newKeyValueStore({
  50. path: "/tmp/fabric-client-stateStore/"
  51. }).then((store) => {
  52. client.setStateStore(store)
  53. return client.createUser(createUserOpt)
  54. })
  55. }).then((user) => {
  56. channel = client.newChannel(options.channel_id);
  57. let data = fs.readFileSync(options.peer_tls_cacerts);
  58. let peer = client.newPeer(options.peer_url,
  59. {
  60. pem: Buffer.from(data).toString(),
  61. 'ssl-target-name-override': options.server_hostname
  62. }
  63. );
  64. //因为启用了TLS,所以上面的代码就是指定Peer的TLS的CA证书
  65. channel.addPeer(peer);
  66. //接下来连接Orderer的时候也启用了TLS,也是同样的处理方法
  67. let odata = fs.readFileSync(options.orderer_tls_cacerts);
  68. let caroots = Buffer.from(odata).toString();
  69. var orderer = client.newOrderer(options.orderer_url, {
  70. 'pem': caroots,
  71. 'ssl-target-name-override': "orderer.example.com"
  72. });
  73.  
  74. channel.addOrderer(orderer);
  75. targets.push(peer);
  76. return;
  77. }).then(() => {
  78. tx_id = client.newTransactionID();
  79. console.log("Assigning transaction_id: ", tx_id._transaction_id);
  80. //发起转账行为,将a->b 10元
  81. var request = {
  82. targets: targets,
  83. chaincodeId: options.chaincode_id,
  84. fcn: 'invoke',
  85. args: ['a', 'b', '10'],
  86. chainId: options.channel_id,
  87. txId: tx_id
  88. };
  89. return channel.sendTransactionProposal(request);
  90. }).then((results) => {
  91. var proposalResponses = results[0];
  92. var proposal = results[1];
  93. var header = results[2];
  94. let isProposalGood = false;
  95. if (proposalResponses && proposalResponses[0].response &&
  96. proposalResponses[0].response.status === 200) {
  97. isProposalGood = true;
  98. console.log('transaction proposal was good');
  99. } else {
  100. console.error('transaction proposal was bad');
  101. }
  102. if (isProposalGood) {
  103. console.log(util.format(
  104. 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
  105. proposalResponses[0].response.status, proposalResponses[0].response.message,
  106. proposalResponses[0].response.payload, proposalResponses[0].endorsement.signature));
  107. var request = {
  108. proposalResponses: proposalResponses,
  109. proposal: proposal,
  110. header: header
  111. };
  112. // set the transaction listener and set a timeout of 30sec
  113. // if the transaction did not get committed within the timeout period,
  114. // fail the test
  115. var transactionID = tx_id.getTransactionID();
  116. var eventPromises = [];
  117. let eh = client.newEventHub();
  118. //接下来设置EventHub,用于监听Transaction是否成功写入,这里也是启用了TLS
  119. let data = fs.readFileSync(options.peer_tls_cacerts);
  120. let grpcOpts = {
  121. pem: Buffer.from(data).toString(),
  122. 'ssl-target-name-override': options.server_hostname
  123. }
  124. eh.setPeerAddr(options.event_url,grpcOpts);
  125. eh.connect();
  126.  
  127. let txPromise = new Promise((resolve, reject) => {
  128. let handle = setTimeout(() => {
  129. eh.disconnect();
  130. reject();
  131. }, 30000);
  132. //向EventHub注册事件的处理办法
  133. eh.registerTxEvent(transactionID, (tx, code) => {
  134. clearTimeout(handle);
  135. eh.unregisterTxEvent(transactionID);
  136. eh.disconnect();
  137.  
  138. if (code !== 'VALID') {
  139. console.error(
  140. 'The transaction was invalid, code = ' + code);
  141. reject();
  142. } else {
  143. console.log(
  144. 'The transaction has been committed on peer ' +
  145. eh._ep._endpoint.addr);
  146. resolve();
  147. }
  148. });
  149. });
  150. eventPromises.push(txPromise);
  151. var sendPromise = channel.sendTransaction(request);
  152. return Promise.all([sendPromise].concat(eventPromises)).then((results) => {
  153. console.log(' event promise all complete and testing complete');
  154. return results[0]; // the first returned value is from the 'sendPromise' which is from the 'sendTransaction()' call
  155. }).catch((err) => {
  156. console.error(
  157. 'Failed to send transaction and get notifications within the timeout period.'
  158. );
  159. return 'Failed to send transaction and get notifications within the timeout period.';
  160. });
  161. } else {
  162. console.error(
  163. 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'
  164. );
  165. return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...';
  166. }
  167. }, (err) => {
  168. console.error('Failed to send proposal due to error: ' + err.stack ? err.stack :
  169. err);
  170. return 'Failed to send proposal due to error: ' + err.stack ? err.stack :
  171. err;
  172. }).then((response) => {
  173. if (response.status === 'SUCCESS') {
  174. console.log('Successfully sent transaction to the orderer.');
  175. return tx_id.getTransactionID();
  176. } else {
  177. console.error('Failed to order the transaction. Error code: ' + response.status);
  178. return 'Failed to order the transaction. Error code: ' + response.status;
  179. }
  180. }, (err) => {
  181. console.error('Failed to send transaction due to error: ' + err.stack ? err
  182. .stack : err);
  183. return 'Failed to send transaction due to error: ' + err.stack ? err.stack :
  184. err;
  185. });

保存文件并退出,接下来测试一下我们的代码,运行:

  1. node invoke.js

我们可以看到系统返回如下结果:

  1. Load privateKey and signedCert
  2. Assigning transaction_id: 1adbf20ace0d1601b00cc2b9dfdd4a431cfff9a13f6a6f5e5e4a80c897e0f7a8
  3. transaction proposal was good
  4. Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK", metadata - "", endorsement signature: 0D x��N��n�#���/�G���QDw�����As \]��FfWҡ�+������=m9I���� 6i
  5. info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"peer0.org1.example.com","grpc.default_authority":"peer0.org1.example.com"}
  6. The transaction has been committed on peer localhost:7053
  7. event promise all complete and testing complete
  8. 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的更多相关文章

  1. Hyperledger Fabric Node SDK和应用开发

    Hyperledger Fabric 提供了多种语言的SDK版本,其中提出比较早.比较稳定而全面的是Node.js版本的SDK. 前面提到的fabric示例(如first-network和e2e-cl ...

  2. Hyperledger Fabric 实战(十): Fabric node SDK 样例 - 投票DAPP

    Fabric node SDK 样例 - 投票DAPP 参考 fabric-samples 下的 fabcar 加以实现 目录结构 . ├── app │ ├── controllers │ │ └─ ...

  3. 搭建RESTful API来使用Fabric Node SDK 开篇

    在Balance-Transfer中,有关于Node SDK比较完备的例子. SDK的官方文档在这里:https://fabric-sdk-node.github.io/ Balance-Transf ...

  4. fabric Node SDK进行连接

    yum install gcc-c++ npm install --unsafe-perm --registry=https://registry.npm.taobao.org chmod -R

  5. Fabric go sdk初始化所需证书解析

    fabric sdk go 提供的官方文档少之又少,要想入门,主要就靠研究官方的e2e系列示例,这真的是一件挺无奈的事情.没法子,只能硬着头皮上了.研究发现,e2e这个例子是通过cryptogen生成 ...

  6. Node.js SDK与fabric链码交互开发

    1.本篇背景 前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互. 本篇内容基本来自官方 H ...

  7. 搭建基于hyperledger fabric的联盟社区(六) --搭建node.js服务器

    接下来我要做的是用fabric sdk来做出应用程序,代替CLI与整个区块链网络交互.并且实现一个http API,向社区提供一个简单的接口,使社区轻松的与区块链交互. 官方虽然提供了Node.JS, ...

  8. Hyperledger Fabric——balance transfer(一)启动示例

    Blacne transfer是Hyperledger fabric Node SDK的一个示例应用,主要使用了SDK中fabric-client 和 fabric-ca-client 模块中的API ...

  9. 搭建基于hyperledger fabric的联盟社区(四) --chaincode开发

    前几章已经分别把三台虚拟机环境和配置文件准备好了,在启动fabric网络之前我们要准备好写好的chaincode.chaincode的开发一般是使用GO或者JAVA,而我选择的是GO语言.先分析一下官 ...

随机推荐

  1. 深入浅出解读 Java 虚拟机的差别测试技术

    本文分享基于字节码种子生成有效.可执行的字节码文件变种,并用于 JVM 实现的差别测试.本文特别提出用于修改字节码语法的classfuzz技术和修改字节码语义的classming技术.上述变种技术系统 ...

  2. 做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比及最佳实现

    背景 在端上为了提升App的灵活性, 快速解决万变的业务需求,开发者们探索了多种解决方案,如PhoneGap ,React Native ,Weex等,但在Flutter生态还没有好的解决方案.未来闲 ...

  3. 聊聊 scala 的模式匹配

    一. scala 模式匹配(pattern matching) pattern matching 可以说是 scala 中十分强大的一个语言特性,当然这不是 scala 独有的,但这不妨碍它成为 sc ...

  4. Real Developer 应该参加的硬核竞赛来了!

    本文由云+社区发表 Labs Contest 竞赛简介 君君这次又来搞大事情啦,在 Function As A Service,Serverless,PaaS 大行其道的今天,你是否已经忘记了自己动手 ...

  5. DriverManager 驱动管理器类简介 JDBC简介(三)

    驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中 后续的处理就可以对这个列表进行操作 简言之,驱动管理器,就是字面含义,主要负责就是管理 驱动 概述 ...

  6. [七]JavaIO之 PipedInputStream 和 PipedInputStream

    管道简介

  7. SpringBoot整合系列-整合SpringMVC

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9984607.html SpringBoot整合Spring MVC 步骤 第一步:添加必 ...

  8. CentOS 7 镜像文件各个版本区别

    CentOS ISO 镜像文件的功能 引导安装 CentOS ISO 镜像文件包含有安装程序,官方称其为 Anaconda,用来引导安装 CentOS 提供 CentOS 的安装文件 镜像文件不一定包 ...

  9. 一统江湖的大前端(4)shell.js——穿上马甲我照样认识你

    <一统江湖的大前端>系列是自己的前端学习笔记,旨在介绍javascript在非网页开发领域的应用案例和发现各类好玩的js库,不定期更新.如果你对前端的理解还是写写页面绑绑事件,那你真的是有 ...

  10. (摘)sql-索引的作用(超详细)

    (一)深入浅出理解索引结构 实际上,您可以把索引理解为一种特殊的目录.微软的SQL SERVER提供了两种索引:聚集索引(clustered index,也称聚类索引.簇集索引)和非聚集索引(nonc ...