Hyperledger Fabric 1.0 实战开发系列 第四课 搭建node.js服务器
接下來我要做的是用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 ~/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; });
三.測試 推薦使用postman進行測試,可以按照https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer這個例子中演示的步驟進行
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
- 下一篇: Node.js中的常用工具类util