Hyperledger Fabric 1.0 实战开发系列 第四课 搭建node.js服务器
来源:互联网 发布:windows 10sfc修复系统 编辑:程序博客网 时间:2024/06/10 02:13
接下来我要做的是用fabric sdk来做出应用程序,代替CLI与整个区块链网络交互。并且实现一个http API,向社区提供一个简单的接口,使社区轻松的与区块链交互。
官方虽然提供了Node.JS,Java,Go(最近刚出了python)等多种语言的SDK,但是很多SDK还不成熟和完善,有的甚至文档都没有。我使用Node.js的原因有二。1.官方例子使用的是Node.js SDK 2.因为Node.js实现一个http服务器是非常简单的。
我们可以选择将node.js安装在本地,或者将node应用部署在docker。需要注意的是fabric目前不支持node7.x版本,需要6.9.x或更高版本和NPM。
一、下载nodejs
1.下载node.js,由于node服务器在国外 所以需要翻墙下载,在Terminal中输入以下命令:
wget https://nodejs.org/download/release/v6.10.0/node-v6.10.0-linux-x64.tar.gz
2、下载下来的tar文件上传到服务器并且解压,然后通过建立软连接变为全局;
1)上传服务器可以是自己任意路径,目前我的放置路径为 /usr/local
2)解压上传(解压后的文件我这边将名字改为了nodejs,这个地方自己随意,只要在建立软连接的时候写正确就可以)
① tar -xvf node-v6.10.0-linux-x64.tar.xz
② mv node-v6.10.0-linux-x64 nodejs
③确认一下nodejs下bin目录是否有node 和npm文件,如果有执行软连接,如果没有重新下载执行上边步骤;
3)建立软连接,变为全局
①ln -s /usr/local/nodejs/bin/npm /usr/local/bin/
②ln -s /usr/local/nodejs/bin/node /usr/local/bin/
4)最后一步检验nodejs是否已变为全局
在Linux命令行node -v 命令会显示nodejs版本,如图所示为大功告成
二.创建node.js程序
1.编写package.json并下载依赖模块
我们首先在当前用户的根目录建立一个nodeTest的文件夹,用于存放我们关于node的相关项目文件,然后在其中新建一个包配置文件,package.json
mkdir ~/nodeTestcd ~/nodeTestvi 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; });
cd fabric-samples/balance-transfer/
docker-compose -f artifacts/docker-compose.yaml up
Login Request
- Register and enroll new users in Organization - Org1:
- 在Termial中输入以下命令
curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=org1'
OUTPUT:
{ "success": true, "secret": "RaxhMgevgJcm", "message": "Jim enrolled Successfully", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI"}
或者在postman中四.总结
这只是简单的测试Node SDK是否可用,如果我们要做项目,那么就会复杂很多,可以参考官方的两个项目:
https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer
https://github.com/IBM-Blockchain/marbles- Hyperledger Fabric 1.0 实战开发系列 第四课 搭建node.js服务器
- Hyperledger Fabric 1.0 实战开发系列 第二课 Fabric环境搭建
- Hyperledger Fabric 1.0 实战开发系列 第三课 chaincode开发
- Hyperledger Fabric 1.0 实战开发系列 第一课 系统环境搭建
- Hyperledger fabric配置node.js
- Hyperledger Fabric 开发环境搭建
- Hyperledger Fabric 开发环境搭建
- ubuntu下搭建HyperLedger fabric开发环境
- Ubuntu下HyperLedger fabric开发环境搭建
- ubuntu16.10 搭建hyperledger-fabric开发环境
- 使用HFC SDK for Node.js 开发 Hyperledger Fabric 客户端应用系统
- hyperledger fabric 1.0环境搭建出错记录
- Hyperledger Fabric 环境搭建
- Hyperledger Fabric Chaincode 开发
- IBM Hyperledger Fabric 开发环境搭建(windows环境)
- Build Hyperledger Fabric 1.0
- Hyperledger Fabric 之 环境搭建
- 在Ubuntu 16.04 上搭建Hyperledger Fabric 1.0
- 十大有关云迁移的提示
- 八种排序方法(四)——快速排序
- 《Learning Redis》学习笔记二:服务器功能
- android的ArrayMap类
- 20171215-学习日记
- Hyperledger Fabric 1.0 实战开发系列 第四课 搭建node.js服务器
- B/S开发之路,如何学习Css
- 解决MVC中使用BundleConfig.RegisterBundles引用Css及js文件发布后丢失的问题
- 域名与网址
- 从Rxjava入门简书学习Java封装,以及设计模式
- 数据结构_动态顺序表
- Kotlin学习-前言
- Mybatis_ 返回List<Map<K,V>> 结构类型数据
- 二分查找及递归