├── .gitignore ├── LICENSE ├── README.md └── balance-transfer-app ├── README-original.md ├── README.cn.md ├── README.md ├── app.js ├── app ├── create-channel.js ├── helper.js ├── install-chaincode.js ├── instantiate-chaincode.js ├── invoke-transaction.js ├── join-channel.js └── query.js ├── artifacts ├── org1.yaml ├── org2.yaml └── src │ └── github.com │ └── example_cc │ ├── go │ └── example_cc.go │ └── node │ ├── example_cc.js │ └── package.json ├── config-manual.json ├── config.js ├── download-from-fabric-network.sh ├── package-lock.json ├── package.json ├── runApp.sh ├── testAPIs.sh └── typescript ├── .gitignore ├── README.md ├── api ├── chaincode.ts ├── channel.ts ├── index.ts ├── users.ts └── utils.ts ├── app.ts ├── app_config.json ├── artifacts ├── config.ts ├── interfaces.ts ├── lib ├── chaincode.ts ├── channel.ts ├── helper.ts └── network-config.json ├── package.json ├── runApp.sh ├── testAPIs.sh ├── tsconfig.json ├── tslint.json └── types └── fabric-ca-client └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | target/ 3 | .classpath 4 | .vscode 5 | .project 6 | .settings/ 7 | **/node_modules/ 8 | **/artifacts/crypto-config 9 | **/artifacts/channel/* 10 | **/fabric-client-kv* 11 | balance-transfer-app/app/network-config.* 12 | balance-transfer-app/config.json 13 | blockchain-explorer/app/network-config-tls.json 14 | blockchain-explorer/config.json 15 | balance-transfer-app/testAPIs-part.sh 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solution-blockchain-demo 2 | 3 | This is a project for source codes of the sample blockchain applications which are used for testing and demonstration of Blockchain Solution of Alibaba Cloud Container Service. 4 | 5 | This project contains sub-projects which were forked from below Hyperledger projects: 6 | 7 | * https://github.com/hyperledger/fabric-samples 8 | 9 | Please note that we are using a snapshot (not necessarily the latest) of the above source codes, which is then customized and tested with the Blockchain Solution of Alibaba Cloud Container Service. 10 | 11 | ## Sub-project: balance-transfer-app 12 | 13 | This sub-project contains source codes of a sample balance transfer application, which uses Hyperledger Fabric Node.js SDK to connect to Hyperledger Fabric blockchain network, and performs balance transfer transactions. 14 | 15 | For details, please refer to the README of this sub-project: 16 | 17 | * English version: [README.md](balance-transfer-app/README.md) 18 | * Chinese version: [README.cn.md](balance-transfer-app/README.cn.md) 19 | 20 | ## Notes 21 | 22 | * Since Hyperledger Fabric 1.1, the Blockchain Solution of Alibaba Cloud Container Service has already integrated Blockchain Explorer during deployment by default. So the previous sub-project `blockchain-explorer` is not included now. 23 | 24 | ## License 25 | 26 | Apache 2.0 license 27 | 28 | ## Contributors 29 | 30 | * Shan Yu () 31 | 32 | -------------------------------------------------------------------------------- /balance-transfer-app/README-original.md: -------------------------------------------------------------------------------- 1 | ## Balance transfer 2 | 3 | A sample Node.js app to demonstrate **__fabric-client__** & **__fabric-ca-client__** Node.js SDK APIs 4 | 5 | ### Prerequisites and setup: 6 | 7 | * [Docker](https://www.docker.com/products/overview) - v1.12 or higher 8 | * [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher 9 | * [Git client](https://git-scm.com/downloads) - needed for clone commands 10 | * **Node.js** v8.4.0 or higher 11 | * [Download Docker images](http://hyperledger-fabric.readthedocs.io/en/latest/samples.html#binaries) 12 | 13 | ``` 14 | cd fabric-samples/balance-transfer/ 15 | ``` 16 | 17 | Once you have completed the above setup, you will have provisioned a local network with the following docker container configuration: 18 | 19 | * 2 CAs 20 | * A SOLO orderer 21 | * 4 peers (2 peers per Org) 22 | 23 | #### Artifacts 24 | * Crypto material has been generated using the **cryptogen** tool from Hyperledger Fabric and mounted to all peers, the orderering node and CA containers. More details regarding the cryptogen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#crypto-generator). 25 | * An Orderer genesis block (genesis.block) and channel configuration transaction (mychannel.tx) has been pre generated using the **configtxgen** tool from Hyperledger Fabric and placed within the artifacts folder. More details regarding the configtxgen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#configuration-transaction-generator). 26 | 27 | ## Running the sample program 28 | 29 | There are two options available for running the balance-transfer sample 30 | For each of these options, you may choose to run with chaincode written in golang or in node.js. 31 | 32 | ### Option 1: 33 | 34 | ##### Terminal Window 1 35 | 36 | * Launch the network using docker-compose 37 | 38 | ``` 39 | docker-compose -f artifacts/docker-compose.yaml up 40 | ``` 41 | ##### Terminal Window 2 42 | 43 | * Install the fabric-client and fabric-ca-client node modules 44 | 45 | ``` 46 | npm install 47 | ``` 48 | 49 | * Start the node app on PORT 4000 50 | 51 | ``` 52 | PORT=4000 node app 53 | ``` 54 | 55 | ##### Terminal Window 3 56 | 57 | * Execute the REST APIs from the section [Sample REST APIs Requests](https://github.com/hyperledger/fabric-samples/tree/master/balance-transfer#sample-rest-apis-requests) 58 | 59 | 60 | ### Option 2: 61 | 62 | ##### Terminal Window 1 63 | 64 | ``` 65 | cd fabric-samples/balance-transfer 66 | 67 | ./runApp.sh 68 | 69 | ``` 70 | 71 | * This lauches the required network on your local machine 72 | * Installs the fabric-client and fabric-ca-client node modules 73 | * And, starts the node app on PORT 4000 74 | 75 | ##### Terminal Window 2 76 | 77 | 78 | In order for the following shell script to properly parse the JSON, you must install ``jq``: 79 | 80 | instructions [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/) 81 | 82 | With the application started in terminal 1, next, test the APIs by executing the script - **testAPIs.sh**: 83 | ``` 84 | cd fabric-samples/balance-transfer 85 | 86 | ## To use golang chaincode execute the following command 87 | 88 | ./testAPIs.sh -l golang 89 | 90 | ## OR use node.js chaincode 91 | 92 | ./testAPIs.sh -l node 93 | ``` 94 | 95 | 96 | ## Sample REST APIs Requests 97 | 98 | ### Login Request 99 | 100 | * Register and enroll new users in Organization - **Org1**: 101 | 102 | `curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=Org1'` 103 | 104 | **OUTPUT:** 105 | 106 | ``` 107 | { 108 | "success": true, 109 | "secret": "RaxhMgevgJcm", 110 | "message": "Jim enrolled Successfully", 111 | "token": "" 112 | } 113 | ``` 114 | 115 | The response contains the success/failure status, an **enrollment Secret** and a **JSON Web Token (JWT)** that is a required string in the Request Headers for subsequent requests. 116 | 117 | ### Create Channel request 118 | 119 | ``` 120 | curl -s -X POST \ 121 | http://localhost:4000/channels \ 122 | -H "authorization: Bearer " \ 123 | -H "content-type: application/json" \ 124 | -d '{ 125 | "channelName":"mychannel", 126 | "channelConfigPath":"../artifacts/channel/mychannel.tx" 127 | }' 128 | ``` 129 | 130 | Please note that the Header **authorization** must contain the JWT returned from the `POST /users` call 131 | 132 | ### Join Channel request 133 | 134 | ``` 135 | curl -s -X POST \ 136 | http://localhost:4000/channels/mychannel/peers \ 137 | -H "authorization: Bearer " \ 138 | -H "content-type: application/json" \ 139 | -d '{ 140 | "peers": ["peer0.org1.example.com","peer1.org1.example.com"] 141 | }' 142 | ``` 143 | ### Install chaincode 144 | 145 | ``` 146 | curl -s -X POST \ 147 | http://localhost:4000/chaincodes \ 148 | -H "authorization: Bearer " \ 149 | -H "content-type: application/json" \ 150 | -d '{ 151 | "peers": ["peer0.org1.example.com","peer1.org1.example.com"], 152 | "chaincodeName":"mycc", 153 | "chaincodePath":"github.com/example_cc/go", 154 | "chaincodeType": "golang", 155 | "chaincodeVersion":"v0" 156 | }' 157 | ``` 158 | **NOTE:** *chaincodeType* must be set to **node** when node.js chaincode is used and *chaincodePath* must be set to the location of the node.js chaincode. Also put in the $PWD 159 | ``` 160 | ex: 161 | curl -s -X POST \ 162 | http://localhost:4000/chaincodes \ 163 | -H "authorization: Bearer " \ 164 | -H "content-type: application/json" \ 165 | -d '{ 166 | "peers": ["peer0.org1.example.com","peer1.org1.example.com"], 167 | "chaincodeName":"mycc", 168 | "chaincodePath":"$PWD/artifacts/src/github.com/example_cc/node", 169 | "chaincodeType": "node", 170 | "chaincodeVersion":"v0" 171 | }' 172 | ``` 173 | 174 | ### Instantiate chaincode 175 | 176 | ``` 177 | curl -s -X POST \ 178 | http://localhost:4000/channels/mychannel/chaincodes \ 179 | -H "authorization: Bearer " \ 180 | -H "content-type: application/json" \ 181 | -d '{ 182 | "peers": ["peer0.org1.example.com","peer1.org1.example.com"], 183 | "chaincodeName":"mycc", 184 | "chaincodeVersion":"v0", 185 | "chaincodeType": "golang", 186 | "args":["a","100","b","200"] 187 | }' 188 | ``` 189 | **NOTE:** *chaincodeType* must be set to **node** when node.js chaincode is used 190 | 191 | ### Invoke request 192 | 193 | ``` 194 | curl -s -X POST \ 195 | http://localhost:4000/channels/mychannel/chaincodes/mycc \ 196 | -H "authorization: Bearer " \ 197 | -H "content-type: application/json" \ 198 | -d '{ 199 | "peers": ["peer0.org1.example.com","peer1.org1.example.com"], 200 | "fcn":"move", 201 | "args":["a","b","10"] 202 | }' 203 | ``` 204 | **NOTE:** Ensure that you save the Transaction ID from the response in order to pass this string in the subsequent query transactions. 205 | 206 | ### Chaincode Query 207 | 208 | ``` 209 | curl -s -X GET \ 210 | "http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer0.org1.example.com&fcn=query&args=%5B%22a%22%5D" \ 211 | -H "authorization: Bearer " \ 212 | -H "content-type: application/json" 213 | ``` 214 | 215 | ### Query Block by BlockNumber 216 | 217 | ``` 218 | curl -s -X GET \ 219 | "http://localhost:4000/channels/mychannel/blocks/1?peer=peer0.org1.example.com" \ 220 | -H "authorization: Bearer " \ 221 | -H "content-type: application/json" 222 | ``` 223 | 224 | ### Query Transaction by TransactionID 225 | 226 | ``` 227 | curl -s -X GET http://localhost:4000/channels/mychannel/transactions/?peer=peer0.org1.example.com \ 228 | -H "authorization: Bearer " \ 229 | -H "content-type: application/json" 230 | ``` 231 | **NOTE**: The transaction id can be from any previous invoke transaction, see results of the invoke request, will look something like `8a95b1794cb17e7772164c3f1292f8410fcfdc1943955a35c9764a21fcd1d1b3`. 232 | 233 | 234 | ### Query ChainInfo 235 | 236 | ``` 237 | curl -s -X GET \ 238 | "http://localhost:4000/channels/mychannel?peer=peer0.org1.example.com" \ 239 | -H "authorization: Bearer " \ 240 | -H "content-type: application/json" 241 | ``` 242 | 243 | ### Query Installed chaincodes 244 | 245 | ``` 246 | curl -s -X GET \ 247 | "http://localhost:4000/chaincodes?peer=peer0.org1.example.com&type=installed" \ 248 | -H "authorization: Bearer " \ 249 | -H "content-type: application/json" 250 | ``` 251 | 252 | ### Query Instantiated chaincodes 253 | 254 | ``` 255 | curl -s -X GET \ 256 | "http://localhost:4000/chaincodes?peer=peer0.org1.example.com&type=instantiated" \ 257 | -H "authorization: Bearer " \ 258 | -H "content-type: application/json" 259 | ``` 260 | 261 | ### Query Channels 262 | 263 | ``` 264 | curl -s -X GET \ 265 | "http://localhost:4000/channels?peer=peer0.org1.example.com" \ 266 | -H "authorization: Bearer " \ 267 | -H "content-type: application/json" 268 | ``` 269 | 270 | ### Network configuration considerations 271 | 272 | You have the ability to change configuration parameters by either directly editing the network-config.yaml file or provide an additional file for an alternative target network. The app uses an optional environment variable "TARGET_NETWORK" to control the configuration files to use. For example, if you deployed the target network on Amazon Web Services EC2, you can add a file "network-config-aws.yaml", and set the "TARGET_NETWORK" environment to 'aws'. The app will pick up the settings inside the "network-config-aws.yaml" file. 273 | 274 | #### IP Address** and PORT information 275 | 276 | If you choose to customize your docker-compose yaml file by hardcoding IP Addresses and PORT information for your peers and orderer, then you MUST also add the identical values into the network-config.yaml file. The url and eventUrl settings will need to be adjusted to match your docker-compose yaml file. 277 | 278 | ``` 279 | peer1.org1.example.com: 280 | url: grpcs://x.x.x.x:7056 281 | eventUrl: grpcs://x.x.x.x:7058 282 | 283 | ``` 284 | 285 | #### Discover IP Address 286 | 287 | To retrieve the IP Address for one of your network entities, issue the following command: 288 | 289 | ``` 290 | # this will return the IP Address for peer0 291 | docker inspect peer0 | grep IPAddress 292 | ``` 293 | 294 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. 295 | -------------------------------------------------------------------------------- /balance-transfer-app/README.cn.md: -------------------------------------------------------------------------------- 1 | # Sample Blockchain Application: Balance Transfer 2 | 3 | 本示例程序基于Hyperledger项目下的fabric-samples的部分源代码(balance transfer),如需参考原始代码和官方文档,可访问:https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer 4 | 5 | Balance Transfer是一个基于Hyperledger Fabric SDK for Node.js的、演示简单的转账交易场景的示例程序。 6 | 7 | 本文档介绍了基于阿里云容器服务的Hyperledger Fabric v1.1网络的Balance Transfer示例程序的具体使用方法。 8 | 9 | 10 | ## 环境准备 11 | 12 | 1. 安装Node.js v8.4.0 或更高版本 13 | 2. 安装jq: https://stedolan.github.io/jq/ 14 | 3. (仅适用CentOS或RedHat Linux)安装g++, 命令示例: `sudo yum install -y gcc-c++` 15 | 4. 使用阿里云容器服务区块链解决方案完成创建Hyperledger Fabric区块链网络(无需完成CLI测试)。参考文档:https://help.aliyun.com/document_detail/64311.html 16 | 17 | 18 | 19 | ## 运行示例程序 20 | 21 | ### 1. 从阿里云上的区块链网络下载配置文件(artifacts) 22 | 23 | 在区块链网络的部署过程中,容器服务区块链解决方案已经自动生成了供区块链应用程序使用的配置文件。这些配置文件包括证书、密钥、区块链网络配置等区块链应用程序运行所需的要素。 24 | 25 | 用户需要提供区块链网络的名称、NAS的挂载点地址、可用于SSH连接下载的公网地址,将其作为环境变量,以运行配置文件自动下载的脚本。运行命令示例如下: 26 | 27 | ``` 28 | cd balance-transfer-app 29 | SSH_ADDRESS=1.2.3.4 SHARED_STORAGE=your_nas_mounting_address FABRIC_NETWORK=network01 ./download-from-fabric-network.sh 30 | ``` 31 | 在下载过程中,用户可能会被提示输入对应ECS节点root账户的登录密码以进行文件的远程拷贝。 32 | 33 | (Optional) 检查`artifacts/org1.yaml`和`artifacts/org2.yaml`看是否需要更新organization名称 34 | 35 | ### 2. 启动Node服务器 36 | 37 | 打开命令窗口1,运行以下命令: 38 | 39 | ``` 40 | cd balance-transfer-app 41 | ./runApp.sh 42 | ``` 43 | 44 | 此命令将清理任何已有的docker容器和chaincode镜像,执行“npm install”命令安装node模块,启动node服务器。如需了解进一步细节,可查看runApp.sh文件内容。 45 | 46 | 当server启动成功后可看到类似如下信息: 47 | 48 | ``` 49 | [2017-08-10 14:15:31.036] [INFO] SampleWebApp - ****************** SERVER STARTED ************************ 50 | [2017-08-10 14:15:31.036] [INFO] SampleWebApp - ************** http://localhost:4000 ****************** 51 | ``` 52 | 53 | 54 | 55 | #### 3. 运行所有测试 56 | 57 | 首先,需要用户按如下方式修改balance-transfer-app目录下的testAPIs.sh: 58 | 59 | * 将所有 **network01-peer1** 模式的service名称替换成实际的区块链service名称,以artifacts目录中从区块链网络下载的`network-config.yaml`文件内容为准 60 | * 将通道名称 **bankchannel** 替换成实际的区块链channel名称,以artifacts目录中从区块链网络下载的`network-config.yaml`文件内容为准 61 | 62 | 然后,打开命令窗口2,运行以下命令: 63 | 64 | ``` 65 | cd balance-transfer-app 66 | ./testAPIs.sh 67 | ``` 68 | 69 | 该命令会自动完成以下操作: 70 | 71 | - Enroll users 72 | - Create a new channel 73 | - Join channel for peers (two organizations, four peers) 74 | - Install chaincode on all peers 75 | - Instantiate (实例化) chaincode 76 | - Invoke chaincode (i.e. transfer money from b to a) 77 | - Query chaincode (i.e. check the balance of a) 78 | - Query transaction using a TransactionID 79 | - Query general info, like chain info, installed chaincodes, instantiated chaincodes, channel 80 | 81 | 如需了解进一步细节,可查看```testAPIs.sh```文件内容 82 | 83 | 所有测试完成后,你将看到类似下面的信息:   84 | 85 | ``` 86 | Total execution time : 25 secs ... 87 | ``` 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /balance-transfer-app/README.md: -------------------------------------------------------------------------------- 1 | # Sample Blockchain Application: Balance Transfer 2 | 3 | This project is based on a part (balance transfer) of the source codes of fabric-samples under Hyperledger project. You can access below URL for original source codes and official documentation: 4 | https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer 5 | 6 | Balance Transfer is a sample application based on Hyperledger Fabric SDK for Node.js. It demonstrate a basic scenario of balance transfer on Hyperledger Fabric network. 7 | 8 | This document introduce how to use Balance Transfer sample application to work with Hyperledger Fabric v1.1 network on Alibaba Cloud Container Service. 9 | 10 | 11 | ## Prepare the environment 12 | 13 | 1. Install Node.js v8.4.0 or higher 14 | 2. Install jq: https://stedolan.github.io/jq/ 15 | 3. (For CentOS or RedHat Linux only) Install g++, for example: `sudo yum install -y gcc-c++` 16 | 4. Use Blockchain Solution of Container Service of Alibaba Cloud to create a blockchain network (No need to do CLI test). Reference: https://help.aliyun.com/document_detail/64311.html 17 | 18 | 19 | 20 | ## Run the sample application 21 | 22 | ### 1. Download artifacts from blockchain network on Alibaba Cloud 23 | 24 | The Blockchain Solution of Container Service should have already generated artifacts for blockchain application during the deployment phase of blockchain network. The artifacts include certificates, keys, configurations to be used by blockchain application. 25 | 26 | To download these artifacts, use the name of blockchain network, NAS mounting address, and public address for SSH download as environment variable and execute the shell script to download artifacts. 27 | 28 | For example: 29 | 30 | ``` 31 | cd balance-transfer-app 32 | SSH_ADDRESS=1.2.3.4 SHARED_STORAGE=your_nas_mounting_address FABRIC_NETWORK=network01 ./download-from-fabric-network.sh 33 | ``` 34 | 35 | During downloading process, you may be prompted to input password for root account of your ECS node. 36 | 37 | (Optional) Check `artifacts/org1.yaml` and `artifacts/org2.yaml` to see if we need to update organization name 38 | 39 | ### 2. Run Node server 40 | 41 | Launch terminal 1 and execute the following command: 42 | 43 | ``` 44 | cd balance-transfer-app 45 | ./runApp.sh 46 | ``` 47 | 48 | The runApp.sh will: 49 | 50 | - Clean up all existing docker containers and chaincode images 51 | - Execute `npm install` command to install node modules 52 | - Start Node server 53 | 54 | If you want to learn more details, you can refer to the source codes of runApp.sh. 55 | 56 | After the server is started successfully, you will see output messages similar to the following: 57 | 58 | ``` 59 | [2017-08-10 14:15:31.036] [INFO] SampleWebApp - ****************** SERVER STARTED ************************ 60 | [2017-08-10 14:15:31.036] [INFO] SampleWebApp - ************** http://localhost:4000 ****************** 61 | ``` 62 | 63 | 64 | 65 | #### 3. Run all tests 66 | 67 | First, you need to modify balance-transfer-app/testAPIs.sh as below: 68 | 69 | * Replace names like **network01-peer1** with actual blockchain service names, according to artifacts/network-config.yaml downloaded from blockchain network 70 | * Replace names like **bankchannel** with actual channel name, according to artifacts/network-config.yaml downloaded from blockchain network 71 | 72 | Then launch terminal 2 and execute the following command: 73 | 74 | ``` 75 | cd balance-transfer-app 76 | ./testAPIs.sh 77 | ``` 78 | 79 | The testAPIs.sh will perform the follow operations: 80 | 81 | - Enroll users 82 | - Create a new channel 83 | - Join channel for peers (two organizations, four peers) 84 | - Install chaincode on all peers 85 | - Instantiate chaincode 86 | - Invoke chaincode (i.e. transfer money from b to a) 87 | - Query chaincode (i.e. check the balance of a) 88 | - Query transaction using a TransactionID 89 | - Query general info, like chain info, installed chaincodes, instantiated chaincodes, channel 90 | 91 | If you want to learn more details, please refer to the source codes of `testAPIs.sh` 92 | 93 | After all tests are done, you will see output messages similar to the following: 94 | 95 | ``` 96 | Total execution time : 25 secs ... 97 | ``` 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /balance-transfer-app/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 'use strict'; 17 | var log4js = require('log4js'); 18 | var logger = log4js.getLogger('SampleWebApp'); 19 | var express = require('express'); 20 | var session = require('express-session'); 21 | var cookieParser = require('cookie-parser'); 22 | var bodyParser = require('body-parser'); 23 | var http = require('http'); 24 | var util = require('util'); 25 | var app = express(); 26 | var expressJWT = require('express-jwt'); 27 | var jwt = require('jsonwebtoken'); 28 | var bearerToken = require('express-bearer-token'); 29 | var cors = require('cors'); 30 | 31 | require('./config.js'); 32 | var hfc = require('fabric-client'); 33 | 34 | var helper = require('./app/helper.js'); 35 | var createChannel = require('./app/create-channel.js'); 36 | var join = require('./app/join-channel.js'); 37 | var install = require('./app/install-chaincode.js'); 38 | var instantiate = require('./app/instantiate-chaincode.js'); 39 | var invoke = require('./app/invoke-transaction.js'); 40 | var query = require('./app/query.js'); 41 | var host = process.env.HOST || hfc.getConfigSetting('host'); 42 | var port = process.env.PORT || hfc.getConfigSetting('port'); 43 | /////////////////////////////////////////////////////////////////////////////// 44 | //////////////////////////////// SET CONFIGURATONS //////////////////////////// 45 | /////////////////////////////////////////////////////////////////////////////// 46 | app.options('*', cors()); 47 | app.use(cors()); 48 | //support parsing of application/json type post data 49 | app.use(bodyParser.json()); 50 | //support parsing of application/x-www-form-urlencoded post data 51 | app.use(bodyParser.urlencoded({ 52 | extended: false 53 | })); 54 | // set secret variable 55 | app.set('secret', 'thisismysecret'); 56 | app.use(expressJWT({ 57 | secret: 'thisismysecret' 58 | }).unless({ 59 | path: ['/users'] 60 | })); 61 | app.use(bearerToken()); 62 | app.use(function(req, res, next) { 63 | logger.debug(' ------>>>>>> new request for %s',req.originalUrl); 64 | if (req.originalUrl.indexOf('/users') >= 0) { 65 | return next(); 66 | } 67 | 68 | var token = req.token; 69 | jwt.verify(token, app.get('secret'), function(err, decoded) { 70 | if (err) { 71 | res.send({ 72 | success: false, 73 | message: 'Failed to authenticate token. Make sure to include the ' + 74 | 'token returned from /users call in the authorization header ' + 75 | ' as a Bearer token' 76 | }); 77 | return; 78 | } else { 79 | // add the decoded user name and org name to the request object 80 | // for the downstream code to use 81 | req.username = decoded.username; 82 | req.orgname = decoded.orgName; 83 | logger.debug(util.format('Decoded from JWT token: username - %s, orgname - %s', decoded.username, decoded.orgName)); 84 | return next(); 85 | } 86 | }); 87 | }); 88 | 89 | /////////////////////////////////////////////////////////////////////////////// 90 | //////////////////////////////// START SERVER ///////////////////////////////// 91 | /////////////////////////////////////////////////////////////////////////////// 92 | var server = http.createServer(app).listen(port, function() {}); 93 | logger.info('****************** SERVER STARTED ************************'); 94 | logger.info('*************** http://%s:%s ******************',host,port); 95 | server.timeout = 240000; 96 | 97 | function getErrorMessage(field) { 98 | var response = { 99 | success: false, 100 | message: field + ' field is missing or Invalid in the request' 101 | }; 102 | return response; 103 | } 104 | 105 | /////////////////////////////////////////////////////////////////////////////// 106 | ///////////////////////// REST ENDPOINTS START HERE /////////////////////////// 107 | /////////////////////////////////////////////////////////////////////////////// 108 | // Register and enroll user 109 | app.post('/users', async function(req, res) { 110 | var username = req.body.username; 111 | var orgName = req.body.orgName; 112 | logger.debug('End point : /users'); 113 | logger.debug('User name : ' + username); 114 | logger.debug('Org name : ' + orgName); 115 | if (!username) { 116 | res.json(getErrorMessage('\'username\'')); 117 | return; 118 | } 119 | if (!orgName) { 120 | res.json(getErrorMessage('\'orgName\'')); 121 | return; 122 | } 123 | var token = jwt.sign({ 124 | exp: Math.floor(Date.now() / 1000) + parseInt(hfc.getConfigSetting('jwt_expiretime')), 125 | username: username, 126 | orgName: orgName 127 | }, app.get('secret')); 128 | let response = await helper.getRegisteredUser(username, orgName, true); 129 | logger.debug('-- returned from registering the username %s for organization %s',username,orgName); 130 | if (response && typeof response !== 'string') { 131 | logger.debug('Successfully registered the username %s for organization %s',username,orgName); 132 | response.token = token; 133 | res.json(response); 134 | } else { 135 | logger.debug('Failed to register the username %s for organization %s with::%s',username,orgName,response); 136 | res.json({success: false, message: response}); 137 | } 138 | 139 | }); 140 | // Create Channel 141 | app.post('/channels', async function(req, res) { 142 | logger.info('<<<<<<<<<<<<<<<<< C R E A T E C H A N N E L >>>>>>>>>>>>>>>>>'); 143 | logger.debug('End point : /channels'); 144 | var channelName = req.body.channelName; 145 | var channelConfigPath = req.body.channelConfigPath; 146 | logger.debug('Channel name : ' + channelName); 147 | logger.debug('channelConfigPath : ' + channelConfigPath); //../artifacts/channel/mychannel.tx 148 | if (!channelName) { 149 | res.json(getErrorMessage('\'channelName\'')); 150 | return; 151 | } 152 | if (!channelConfigPath) { 153 | res.json(getErrorMessage('\'channelConfigPath\'')); 154 | return; 155 | } 156 | 157 | let message = await createChannel.createChannel(channelName, channelConfigPath, req.username, req.orgname); 158 | res.send(message); 159 | }); 160 | // Join Channel 161 | app.post('/channels/:channelName/peers', async function(req, res) { 162 | logger.info('<<<<<<<<<<<<<<<<< J O I N C H A N N E L >>>>>>>>>>>>>>>>>'); 163 | var channelName = req.params.channelName; 164 | var peers = req.body.peers; 165 | logger.debug('channelName : ' + channelName); 166 | logger.debug('peers : ' + peers); 167 | logger.debug('username :' + req.username); 168 | logger.debug('orgname:' + req.orgname); 169 | 170 | if (!channelName) { 171 | res.json(getErrorMessage('\'channelName\'')); 172 | return; 173 | } 174 | if (!peers || peers.length == 0) { 175 | res.json(getErrorMessage('\'peers\'')); 176 | return; 177 | } 178 | 179 | let message = await join.joinChannel(channelName, peers, req.username, req.orgname); 180 | res.send(message); 181 | }); 182 | // Install chaincode on target peers 183 | app.post('/chaincodes', async function(req, res) { 184 | logger.debug('==================== INSTALL CHAINCODE =================='); 185 | var peers = req.body.peers; 186 | var chaincodeName = req.body.chaincodeName; 187 | var chaincodePath = req.body.chaincodePath; 188 | var chaincodeVersion = req.body.chaincodeVersion; 189 | var chaincodeType = req.body.chaincodeType; 190 | logger.debug('peers : ' + peers); // target peers list 191 | logger.debug('chaincodeName : ' + chaincodeName); 192 | logger.debug('chaincodePath : ' + chaincodePath); 193 | logger.debug('chaincodeVersion : ' + chaincodeVersion); 194 | logger.debug('chaincodeType : ' + chaincodeType); 195 | if (!peers || peers.length == 0) { 196 | res.json(getErrorMessage('\'peers\'')); 197 | return; 198 | } 199 | if (!chaincodeName) { 200 | res.json(getErrorMessage('\'chaincodeName\'')); 201 | return; 202 | } 203 | if (!chaincodePath) { 204 | res.json(getErrorMessage('\'chaincodePath\'')); 205 | return; 206 | } 207 | if (!chaincodeVersion) { 208 | res.json(getErrorMessage('\'chaincodeVersion\'')); 209 | return; 210 | } 211 | if (!chaincodeType) { 212 | res.json(getErrorMessage('\'chaincodeType\'')); 213 | return; 214 | } 215 | let message = await install.installChaincode(peers, chaincodeName, chaincodePath, chaincodeVersion, chaincodeType, req.username, req.orgname) 216 | res.send(message);}); 217 | // Instantiate chaincode on target peers 218 | app.post('/channels/:channelName/chaincodes', async function(req, res) { 219 | logger.debug('==================== INSTANTIATE CHAINCODE =================='); 220 | var peers = req.body.peers; 221 | var chaincodeName = req.body.chaincodeName; 222 | var chaincodeVersion = req.body.chaincodeVersion; 223 | var channelName = req.params.channelName; 224 | var chaincodeType = req.body.chaincodeType; 225 | var fcn = req.body.fcn; 226 | var args = req.body.args; 227 | logger.debug('peers : ' + peers); 228 | logger.debug('channelName : ' + channelName); 229 | logger.debug('chaincodeName : ' + chaincodeName); 230 | logger.debug('chaincodeVersion : ' + chaincodeVersion); 231 | logger.debug('chaincodeType : ' + chaincodeType); 232 | logger.debug('fcn : ' + fcn); 233 | logger.debug('args : ' + args); 234 | if (!chaincodeName) { 235 | res.json(getErrorMessage('\'chaincodeName\'')); 236 | return; 237 | } 238 | if (!chaincodeVersion) { 239 | res.json(getErrorMessage('\'chaincodeVersion\'')); 240 | return; 241 | } 242 | if (!channelName) { 243 | res.json(getErrorMessage('\'channelName\'')); 244 | return; 245 | } 246 | if (!chaincodeType) { 247 | res.json(getErrorMessage('\'chaincodeType\'')); 248 | return; 249 | } 250 | if (!args) { 251 | res.json(getErrorMessage('\'args\'')); 252 | return; 253 | } 254 | 255 | let message = await instantiate.instantiateChaincode(peers, channelName, chaincodeName, chaincodeVersion, chaincodeType, fcn, args, req.username, req.orgname); 256 | res.send(message); 257 | }); 258 | // Invoke transaction on chaincode on target peers 259 | app.post('/channels/:channelName/chaincodes/:chaincodeName', async function(req, res) { 260 | logger.debug('==================== INVOKE ON CHAINCODE =================='); 261 | var peers = req.body.peers; 262 | var chaincodeName = req.params.chaincodeName; 263 | var channelName = req.params.channelName; 264 | var fcn = req.body.fcn; 265 | var args = req.body.args; 266 | logger.debug('channelName : ' + channelName); 267 | logger.debug('chaincodeName : ' + chaincodeName); 268 | logger.debug('fcn : ' + fcn); 269 | logger.debug('args : ' + args); 270 | if (!chaincodeName) { 271 | res.json(getErrorMessage('\'chaincodeName\'')); 272 | return; 273 | } 274 | if (!channelName) { 275 | res.json(getErrorMessage('\'channelName\'')); 276 | return; 277 | } 278 | if (!fcn) { 279 | res.json(getErrorMessage('\'fcn\'')); 280 | return; 281 | } 282 | if (!args) { 283 | res.json(getErrorMessage('\'args\'')); 284 | return; 285 | } 286 | 287 | let message = await invoke.invokeChaincode(peers, channelName, chaincodeName, fcn, args, req.username, req.orgname); 288 | res.send(message); 289 | }); 290 | // Query on chaincode on target peers 291 | app.get('/channels/:channelName/chaincodes/:chaincodeName', async function(req, res) { 292 | logger.debug('==================== QUERY BY CHAINCODE =================='); 293 | var channelName = req.params.channelName; 294 | var chaincodeName = req.params.chaincodeName; 295 | let args = req.query.args; 296 | let fcn = req.query.fcn; 297 | let peer = req.query.peer; 298 | 299 | logger.debug('channelName : ' + channelName); 300 | logger.debug('chaincodeName : ' + chaincodeName); 301 | logger.debug('fcn : ' + fcn); 302 | logger.debug('args : ' + args); 303 | 304 | if (!chaincodeName) { 305 | res.json(getErrorMessage('\'chaincodeName\'')); 306 | return; 307 | } 308 | if (!channelName) { 309 | res.json(getErrorMessage('\'channelName\'')); 310 | return; 311 | } 312 | if (!fcn) { 313 | res.json(getErrorMessage('\'fcn\'')); 314 | return; 315 | } 316 | if (!args) { 317 | res.json(getErrorMessage('\'args\'')); 318 | return; 319 | } 320 | args = args.replace(/'/g, '"'); 321 | args = JSON.parse(args); 322 | logger.debug(args); 323 | 324 | let message = await query.queryChaincode(peer, channelName, chaincodeName, args, fcn, req.username, req.orgname); 325 | res.send(message); 326 | }); 327 | // Query Get Block by BlockNumber 328 | app.get('/channels/:channelName/blocks/:blockId', async function(req, res) { 329 | logger.debug('==================== GET BLOCK BY NUMBER =================='); 330 | let blockId = req.params.blockId; 331 | let peer = req.query.peer; 332 | logger.debug('channelName : ' + req.params.channelName); 333 | logger.debug('BlockID : ' + blockId); 334 | logger.debug('Peer : ' + peer); 335 | if (!blockId) { 336 | res.json(getErrorMessage('\'blockId\'')); 337 | return; 338 | } 339 | 340 | let message = await query.getBlockByNumber(peer, req.params.channelName, blockId, req.username, req.orgname); 341 | res.send(message); 342 | }); 343 | // Query Get Transaction by Transaction ID 344 | app.get('/channels/:channelName/transactions/:trxnId', async function(req, res) { 345 | logger.debug('================ GET TRANSACTION BY TRANSACTION_ID ======================'); 346 | logger.debug('channelName : ' + req.params.channelName); 347 | let trxnId = req.params.trxnId; 348 | let peer = req.query.peer; 349 | if (!trxnId) { 350 | res.json(getErrorMessage('\'trxnId\'')); 351 | return; 352 | } 353 | 354 | let message = await query.getTransactionByID(peer, req.params.channelName, trxnId, req.username, req.orgname); 355 | res.send(message); 356 | }); 357 | // Query Get Block by Hash 358 | app.get('/channels/:channelName/blocks', async function(req, res) { 359 | logger.debug('================ GET BLOCK BY HASH ======================'); 360 | logger.debug('channelName : ' + req.params.channelName); 361 | let hash = req.query.hash; 362 | let peer = req.query.peer; 363 | if (!hash) { 364 | res.json(getErrorMessage('\'hash\'')); 365 | return; 366 | } 367 | 368 | let message = await query.getBlockByHash(peer, req.params.channelName, hash, req.username, req.orgname); 369 | res.send(message); 370 | }); 371 | //Query for Channel Information 372 | app.get('/channels/:channelName', async function(req, res) { 373 | logger.debug('================ GET CHANNEL INFORMATION ======================'); 374 | logger.debug('channelName : ' + req.params.channelName); 375 | let peer = req.query.peer; 376 | 377 | let message = await query.getChainInfo(peer, req.params.channelName, req.username, req.orgname); 378 | res.send(message); 379 | }); 380 | //Query for Channel instantiated chaincodes 381 | app.get('/channels/:channelName/chaincodes', async function(req, res) { 382 | logger.debug('================ GET INSTANTIATED CHAINCODES ======================'); 383 | logger.debug('channelName : ' + req.params.channelName); 384 | let peer = req.query.peer; 385 | 386 | let message = await query.getInstalledChaincodes(peer, req.params.channelName, 'instantiated', req.username, req.orgname); 387 | res.send(message); 388 | }); 389 | // Query to fetch all Installed/instantiated chaincodes 390 | app.get('/chaincodes', async function(req, res) { 391 | var peer = req.query.peer; 392 | var installType = req.query.type; 393 | logger.debug('================ GET INSTALLED CHAINCODES ======================'); 394 | 395 | let message = await query.getInstalledChaincodes(peer, null, 'installed', req.username, req.orgname) 396 | res.send(message); 397 | }); 398 | // Query to fetch channels 399 | app.get('/channels', async function(req, res) { 400 | logger.debug('================ GET CHANNELS ======================'); 401 | logger.debug('peer: ' + req.query.peer); 402 | var peer = req.query.peer; 403 | if (!peer) { 404 | res.json(getErrorMessage('\'peer\'')); 405 | return; 406 | } 407 | 408 | let message = await query.getChannels(peer, req.username, req.orgname); 409 | res.send(message); 410 | }); 411 | -------------------------------------------------------------------------------- /balance-transfer-app/app/create-channel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var util = require('util'); 17 | var fs = require('fs'); 18 | var path = require('path'); 19 | 20 | var helper = require('./helper.js'); 21 | var logger = helper.getLogger('Create-Channel'); 22 | //Attempt to send a request to the orderer with the sendTransaction method 23 | var createChannel = async function(channelName, channelConfigPath, username, orgName) { 24 | logger.debug('\n====== Creating Channel \'' + channelName + '\' ======\n'); 25 | try { 26 | // first setup the client for this org 27 | var client = await helper.getClientForOrg(orgName); 28 | logger.debug('Successfully got the fabric client for the organization "%s"', orgName); 29 | 30 | // read in the envelope for the channel config raw bytes 31 | var envelope = fs.readFileSync(path.join(__dirname, channelConfigPath)); 32 | // extract the channel config bytes from the envelope to be signed 33 | var channelConfig = client.extractChannelConfig(envelope); 34 | 35 | //Acting as a client in the given organization provided with "orgName" param 36 | // sign the channel config bytes as "endorsement", this is required by 37 | // the orderer's channel creation policy 38 | // this will use the admin identity assigned to the client when the connection profile was loaded 39 | let signature = client.signChannelConfig(channelConfig); 40 | 41 | let request = { 42 | config: channelConfig, 43 | signatures: [signature], 44 | name: channelName, 45 | txId: client.newTransactionID(true) // get an admin based transactionID 46 | }; 47 | 48 | // send to orderer 49 | var response = await client.createChannel(request) 50 | logger.debug(' response ::%j', response); 51 | if (response && response.status === 'SUCCESS') { 52 | logger.debug('Successfully created the channel.'); 53 | let response = { 54 | success: true, 55 | message: 'Channel \'' + channelName + '\' created Successfully' 56 | }; 57 | return response; 58 | } else { 59 | logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName + 60 | '\' !!!!!!!!!\n\n'); 61 | throw new Error('Failed to create the channel \'' + channelName + '\''); 62 | } 63 | } catch (err) { 64 | logger.error('Failed to initialize the channel: ' + err.stack ? err.stack : err); 65 | throw new Error('Failed to initialize the channel: ' + err.toString()); 66 | } 67 | }; 68 | 69 | exports.createChannel = createChannel; 70 | -------------------------------------------------------------------------------- /balance-transfer-app/app/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 'use strict'; 17 | var log4js = require('log4js'); 18 | var logger = log4js.getLogger('Helper'); 19 | logger.setLevel('DEBUG'); 20 | 21 | var path = require('path'); 22 | var util = require('util'); 23 | var copService = require('fabric-ca-client'); 24 | 25 | var hfc = require('fabric-client'); 26 | hfc.setLogger(logger); 27 | var ORGS = hfc.getConfigSetting('network-config'); 28 | 29 | var clients = {}; 30 | var channels = {}; 31 | var caClients = {}; 32 | 33 | var sleep = async function (sleep_time_ms) { 34 | return new Promise(resolve => setTimeout(resolve, sleep_time_ms)); 35 | } 36 | 37 | async function getClientForOrg (userorg, username) { 38 | logger.debug('getClientForOrg - ****** START %s %s', userorg, username) 39 | // get a fabric client loaded with a connection profile for this org 40 | let config = '-connection-profile-path'; 41 | 42 | // build a client context and load it with a connection profile 43 | // lets only load the network settings and save the client for later 44 | let client = hfc.loadFromConfig(hfc.getConfigSetting('network'+config)); 45 | 46 | // This will load a connection profile over the top of the current one one 47 | // since the first one did not have a client section and the following one does 48 | // nothing will actually be replaced. 49 | // This will also set an admin identity because the organization defined in the 50 | // client section has one defined 51 | client.loadFromConfig(hfc.getConfigSetting(userorg+config)); 52 | 53 | // this will create both the state store and the crypto store based 54 | // on the settings in the client section of the connection profile 55 | await client.initCredentialStores(); 56 | 57 | // The getUserContext call tries to get the user from persistence. 58 | // If the user has been saved to persistence then that means the user has 59 | // been registered and enrolled. If the user is found in persistence 60 | // the call will then assign the user to the client object. 61 | if(username) { 62 | let user = await client.getUserContext(username, true); 63 | if(!user) { 64 | throw new Error(util.format('User was not found :', username)); 65 | } else { 66 | logger.debug('User %s was found to be registered and enrolled', username); 67 | } 68 | } 69 | logger.debug('getClientForOrg - ****** END %s %s \n\n', userorg, username) 70 | 71 | return client; 72 | } 73 | 74 | var getRegisteredUser = async function(username, userOrg, isJson) { 75 | try { 76 | var client = await getClientForOrg(userOrg); 77 | logger.debug('Successfully initialized the credential stores'); 78 | // client can now act as an agent for organization Org1 79 | // first check to see if the user is already enrolled 80 | var user = await client.getUserContext(username, true); 81 | if (user && user.isEnrolled()) { 82 | logger.info('Successfully loaded member from persistence'); 83 | } else { 84 | // user was not enrolled, so we will need an admin user object to register 85 | logger.info('User %s was not enrolled, so we will need an admin user object to register',username); 86 | var admins = hfc.getConfigSetting('admins'); 87 | let adminUserObj = await client.setUserContext({username: admins[0].username, password: admins[0].secret}); 88 | let caClient = client.getCertificateAuthority(); 89 | let secret = await caClient.register({ 90 | enrollmentID: username, 91 | affiliation: userOrg.toLowerCase() + '.department1' 92 | }, adminUserObj); 93 | logger.debug('Successfully got the secret for user %s',username); 94 | user = await client.setUserContext({username:username, password:secret}); 95 | logger.debug('Successfully enrolled username %s and setUserContext on the client object', username); 96 | } 97 | if(user && user.isEnrolled) { 98 | if (isJson && isJson === true) { 99 | var response = { 100 | success: true, 101 | secret: user._enrollmentSecret, 102 | message: username + ' enrolled Successfully', 103 | }; 104 | return response; 105 | } 106 | } else { 107 | throw new Error('User was not enrolled '); 108 | } 109 | } catch(error) { 110 | logger.error('Failed to get registered user: %s with error: %s', username, error.toString()); 111 | return 'failed '+error.toString(); 112 | } 113 | 114 | }; 115 | 116 | 117 | var setupChaincodeDeploy = function() { 118 | process.env.GOPATH = path.join(__dirname, hfc.getConfigSetting('CC_SRC_PATH')); 119 | }; 120 | 121 | var getLogger = function(moduleName) { 122 | var logger = log4js.getLogger(moduleName); 123 | logger.setLevel('DEBUG'); 124 | return logger; 125 | }; 126 | 127 | exports.getClientForOrg = getClientForOrg; 128 | exports.getLogger = getLogger; 129 | exports.setupChaincodeDeploy = setupChaincodeDeploy; 130 | exports.getRegisteredUser = getRegisteredUser; 131 | -------------------------------------------------------------------------------- /balance-transfer-app/app/install-chaincode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 'use strict'; 17 | var path = require('path'); 18 | var fs = require('fs'); 19 | var util = require('util'); 20 | var config = require('../config.json'); 21 | var helper = require('./helper.js'); 22 | var logger = helper.getLogger('install-chaincode'); 23 | var tx_id = null; 24 | 25 | var installChaincode = async function(peers, chaincodeName, chaincodePath, 26 | chaincodeVersion, chaincodeType, username, org_name) { 27 | logger.debug('\n\n============ Install chaincode on organizations ============\n'); 28 | helper.setupChaincodeDeploy(); 29 | let error_message = null; 30 | try { 31 | logger.info('Calling peers in organization "%s" to join the channel', org_name); 32 | 33 | // first setup the client for this org 34 | var client = await helper.getClientForOrg(org_name, username); 35 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 36 | 37 | tx_id = client.newTransactionID(true); //get an admin transactionID 38 | var request = { 39 | targets: peers, 40 | chaincodePath: chaincodePath, 41 | chaincodeId: chaincodeName, 42 | chaincodeVersion: chaincodeVersion, 43 | chaincodeType: chaincodeType 44 | }; 45 | let results = await client.installChaincode(request); 46 | // the returned object has both the endorsement results 47 | // and the actual proposal, the proposal will be needed 48 | // later when we send a transaction to the orederer 49 | var proposalResponses = results[0]; 50 | var proposal = results[1]; 51 | 52 | // lets have a look at the responses to see if they are 53 | // all good, if good they will also include signatures 54 | // required to be committed 55 | var all_good = true; 56 | for (var i in proposalResponses) { 57 | let one_good = false; 58 | if (proposalResponses && proposalResponses[i].response && 59 | proposalResponses[i].response.status === 200) { 60 | one_good = true; 61 | logger.info('install proposal was good'); 62 | } else { 63 | logger.error('install proposal was bad %j',proposalResponses.toJSON()); 64 | } 65 | all_good = all_good & one_good; 66 | } 67 | if (all_good) { 68 | logger.info('Successfully sent install Proposal and received ProposalResponse'); 69 | } else { 70 | error_message = 'Failed to send install Proposal or receive valid response. Response null or status is not 200' 71 | logger.error(error_message); 72 | } 73 | } catch(error) { 74 | logger.error('Failed to install due to error: ' + error.stack ? error.stack : error); 75 | error_message = error.toString(); 76 | } 77 | 78 | if (!error_message) { 79 | let message = util.format('Successfully install chaincode'); 80 | logger.info(message); 81 | // build a response to send back to the REST caller 82 | let response = { 83 | success: true, 84 | message: message 85 | }; 86 | return response; 87 | } else { 88 | let message = util.format('Failed to install due to:%s',error_message); 89 | logger.error(message); 90 | throw new Error(message); 91 | } 92 | }; 93 | exports.installChaincode = installChaincode; 94 | -------------------------------------------------------------------------------- /balance-transfer-app/app/instantiate-chaincode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 'use strict'; 17 | var path = require('path'); 18 | var fs = require('fs'); 19 | var util = require('util'); 20 | var hfc = require('fabric-client'); 21 | var helper = require('./helper.js'); 22 | var logger = helper.getLogger('instantiate-chaincode'); 23 | 24 | var instantiateChaincode = async function(peers, channelName, chaincodeName, chaincodeVersion, functionName, chaincodeType, args, username, org_name) { 25 | logger.debug('\n\n============ Instantiate chaincode on channel ' + channelName + 26 | ' ============\n'); 27 | var error_message = null; 28 | var eventhubs_in_use = []; 29 | try { 30 | // first setup the client for this org 31 | var client = await helper.getClientForOrg(org_name, username); 32 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 33 | var channel = client.getChannel(channelName); 34 | if(!channel) { 35 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 36 | logger.error(message); 37 | throw new Error(message); 38 | } 39 | var tx_id = client.newTransactionID(true); // Get an admin based transactionID 40 | // An admin based transactionID will 41 | // indicate that admin identity should 42 | // be used to sign the proposal request. 43 | // will need the transaction ID string for the event registration later 44 | var deployId = tx_id.getTransactionID(); 45 | 46 | // send proposal to endorser 47 | var request = { 48 | targets : peers, 49 | chaincodeId: chaincodeName, 50 | chaincodeType: chaincodeType, 51 | chaincodeVersion: chaincodeVersion, 52 | args: args, 53 | txId: tx_id 54 | }; 55 | 56 | if (functionName) 57 | request.fcn = functionName; 58 | 59 | let results = await channel.sendInstantiateProposal(request, 300000); //instantiate takes much longer 60 | 61 | // the returned object has both the endorsement results 62 | // and the actual proposal, the proposal will be needed 63 | // later when we send a transaction to the orderer 64 | var proposalResponses = results[0]; 65 | var proposal = results[1]; 66 | 67 | // lets have a look at the responses to see if they are 68 | // all good, if good they will also include signatures 69 | // required to be committed 70 | var all_good = true; 71 | for (var i in proposalResponses) { 72 | let one_good = false; 73 | if (proposalResponses && proposalResponses[i].response && 74 | proposalResponses[i].response.status === 200) { 75 | one_good = true; 76 | logger.info('instantiate proposal was good'); 77 | } else { 78 | logger.error('instantiate proposal was bad'); 79 | } 80 | all_good = all_good & one_good; 81 | } 82 | 83 | if (all_good) { 84 | logger.info(util.format( 85 | 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 86 | proposalResponses[0].response.status, proposalResponses[0].response.message, 87 | proposalResponses[0].response.payload, proposalResponses[0].endorsement 88 | .signature)); 89 | 90 | // tell each peer to join and wait for the event hub of each peer to tell us 91 | // that the channel has been created on each peer 92 | var promises = []; 93 | let event_hubs = client.getEventHubsForOrg(org_name); 94 | logger.debug('found %s eventhubs for this organization %s',event_hubs.length, org_name); 95 | event_hubs.forEach((eh) => { 96 | let instantiateEventPromise = new Promise((resolve, reject) => { 97 | logger.debug('instantiateEventPromise - setting up event'); 98 | let event_timeout = setTimeout(() => { 99 | let message = 'REQUEST_TIMEOUT:' + eh._ep._endpoint.addr; 100 | logger.error(message); 101 | eh.disconnect(); 102 | reject(new Error(message)); 103 | }, 60000); 104 | eh.registerTxEvent(deployId, (tx, code) => { 105 | logger.info('The chaincode instantiate transaction has been committed on peer %s',eh._ep._endpoint.addr); 106 | clearTimeout(event_timeout); 107 | eh.unregisterTxEvent(deployId); 108 | 109 | if (code !== 'VALID') { 110 | let message = until.format('The chaincode instantiate transaction was invalid, code:%s',code); 111 | logger.error(message); 112 | reject(new Error(message)); 113 | } else { 114 | let message = 'The chaincode instantiate transaction was valid.'; 115 | logger.info(message); 116 | resolve(message); 117 | } 118 | }, (err) => { 119 | clearTimeout(event_timeout); 120 | eh.unregisterTxEvent(deployId); 121 | let message = 'Problem setting up the event hub :'+ err.toString(); 122 | logger.error(message); 123 | reject(new Error(message)); 124 | }); 125 | }); 126 | promises.push(instantiateEventPromise); 127 | eh.connect(); 128 | eventhubs_in_use.push(eh); 129 | }); 130 | 131 | var orderer_request = { 132 | txId: tx_id, // must include the transaction id so that the outbound 133 | // transaction to the orderer will be signed by the admin 134 | // id as was the proposal above, notice that transactionID 135 | // generated above was based on the admin id not the current 136 | // user assigned to the 'client' instance. 137 | proposalResponses: proposalResponses, 138 | proposal: proposal 139 | }; 140 | var sendPromise = channel.sendTransaction(orderer_request); 141 | // put the send to the orderer last so that the events get registered and 142 | // are ready for the orderering and committing 143 | promises.push(sendPromise); 144 | let results = await Promise.all(promises); 145 | logger.debug(util.format('------->>> R E S P O N S E : %j', results)); 146 | let response = results.pop(); // orderer results are last in the results 147 | if (response.status === 'SUCCESS') { 148 | logger.info('Successfully sent transaction to the orderer.'); 149 | } else { 150 | error_message = util.format('Failed to order the transaction. Error code: %s',response.status); 151 | logger.debug(error_message); 152 | } 153 | 154 | // now see what each of the event hubs reported 155 | for(let i in results) { 156 | let event_hub_result = results[i]; 157 | let event_hub = event_hubs[i]; 158 | logger.debug('Event results for event hub :%s',event_hub._ep._endpoint.addr); 159 | if(typeof event_hub_result === 'string') { 160 | logger.debug(event_hub_result); 161 | } else { 162 | if(!error_message) error_message = event_hub_result.toString(); 163 | logger.debug(event_hub_result.toString()); 164 | } 165 | } 166 | } else { 167 | error_message = util.format('Failed to send Proposal and receive all good ProposalResponse'); 168 | logger.debug(error_message); 169 | } 170 | } catch (error) { 171 | logger.error('Failed to send instantiate due to error: ' + error.stack ? error.stack : error); 172 | error_message = error.toString(); 173 | } 174 | 175 | // need to shutdown open event streams 176 | eventhubs_in_use.forEach((eh) => { 177 | eh.disconnect(); 178 | }); 179 | 180 | if (!error_message) { 181 | let message = util.format( 182 | 'Successfully instantiate chaingcode in organization %s to the channel \'%s\'', 183 | org_name, channelName); 184 | logger.info(message); 185 | // build a response to send back to the REST caller 186 | let response = { 187 | success: true, 188 | message: message 189 | }; 190 | return response; 191 | } else { 192 | let message = util.format('Failed to instantiate. cause:%s',error_message); 193 | logger.error(message); 194 | throw new Error(message); 195 | } 196 | }; 197 | exports.instantiateChaincode = instantiateChaincode; 198 | -------------------------------------------------------------------------------- /balance-transfer-app/app/invoke-transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 'use strict'; 17 | var path = require('path'); 18 | var fs = require('fs'); 19 | var util = require('util'); 20 | var hfc = require('fabric-client'); 21 | var helper = require('./helper.js'); 22 | var logger = helper.getLogger('invoke-chaincode'); 23 | 24 | var invokeChaincode = async function(peerNames, channelName, chaincodeName, fcn, args, username, org_name) { 25 | logger.debug(util.format('\n============ invoke transaction on channel %s ============\n', channelName)); 26 | var error_message = null; 27 | var eventhubs_in_use = []; 28 | var tx_id_string = null; 29 | try { 30 | // first setup the client for this org 31 | var client = await helper.getClientForOrg(org_name, username); 32 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 33 | var channel = client.getChannel(channelName); 34 | if(!channel) { 35 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 36 | logger.error(message); 37 | throw new Error(message); 38 | } 39 | var tx_id = client.newTransactionID(); 40 | // will need the transaction ID string for the event registration later 41 | tx_id_string = tx_id.getTransactionID(); 42 | 43 | // send proposal to endorser 44 | var request = { 45 | targets: peerNames, 46 | chaincodeId: chaincodeName, 47 | fcn: fcn, 48 | args: args, 49 | chainId: channelName, 50 | txId: tx_id 51 | }; 52 | 53 | let results = await channel.sendTransactionProposal(request); 54 | 55 | // the returned object has both the endorsement results 56 | // and the actual proposal, the proposal will be needed 57 | // later when we send a transaction to the orderer 58 | var proposalResponses = results[0]; 59 | var proposal = results[1]; 60 | 61 | // lets have a look at the responses to see if they are 62 | // all good, if good they will also include signatures 63 | // required to be committed 64 | var all_good = true; 65 | for (var i in proposalResponses) { 66 | let one_good = false; 67 | if (proposalResponses && proposalResponses[i].response && 68 | proposalResponses[i].response.status === 200) { 69 | one_good = true; 70 | logger.info('invoke chaincode proposal was good'); 71 | } else { 72 | logger.error('invoke chaincode proposal was bad'); 73 | } 74 | all_good = all_good & one_good; 75 | } 76 | 77 | if (all_good) { 78 | logger.info(util.format( 79 | 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 80 | proposalResponses[0].response.status, proposalResponses[0].response.message, 81 | proposalResponses[0].response.payload, proposalResponses[0].endorsement 82 | .signature)); 83 | 84 | // tell each peer to join and wait for the event hub of each peer to tell us 85 | // that the channel has been created on each peer 86 | var promises = []; 87 | let event_hubs = client.getEventHubsForOrg(org_name); 88 | event_hubs.forEach((eh) => { 89 | logger.debug('invokeEventPromise - setting up event'); 90 | let invokeEventPromise = new Promise((resolve, reject) => { 91 | let event_timeout = setTimeout(() => { 92 | let message = 'REQUEST_TIMEOUT:' + eh._ep._endpoint.addr; 93 | logger.error(message); 94 | eh.disconnect(); 95 | reject(new Error(message)); 96 | }, 10000); //yushan: change timeout from 3 seconds into 10 seconds 97 | eh.registerTxEvent(tx_id_string, (tx, code) => { 98 | logger.info('The chaincode invoke chaincode transaction has been committed on peer %s',eh._ep._endpoint.addr); 99 | clearTimeout(event_timeout); 100 | eh.unregisterTxEvent(tx_id_string); 101 | 102 | if (code !== 'VALID') { 103 | let message = util.format('The invoke chaincode transaction was invalid, code:%s',code); 104 | logger.error(message); 105 | reject(new Error(message)); 106 | } else { 107 | let message = 'The invoke chaincode transaction was valid.'; 108 | logger.info(message); 109 | resolve(message); 110 | } 111 | }, (err) => { 112 | clearTimeout(event_timeout); 113 | eh.unregisterTxEvent(tx_id_string); 114 | let message = 'Problem setting up the event hub :'+ err.toString(); 115 | logger.error(message); 116 | reject(new Error(message)); 117 | }); 118 | }); 119 | promises.push(invokeEventPromise); 120 | eh.connect(); 121 | eventhubs_in_use.push(eh); 122 | }); 123 | 124 | var orderer_request = { 125 | txId: tx_id, 126 | proposalResponses: proposalResponses, 127 | proposal: proposal 128 | }; 129 | var sendPromise = channel.sendTransaction(orderer_request); 130 | // put the send to the orderer last so that the events get registered and 131 | // are ready for the orderering and committing 132 | promises.push(sendPromise); 133 | let results = await Promise.all(promises); 134 | logger.debug(util.format('------->>> R E S P O N S E : %j', results)); 135 | let response = results.pop(); // orderer results are last in the results 136 | if (response.status === 'SUCCESS') { 137 | logger.info('Successfully sent transaction to the orderer.'); 138 | } else { 139 | error_message = util.format('Failed to order the transaction. Error code: %s',response.status); 140 | logger.debug(error_message); 141 | } 142 | 143 | // now see what each of the event hubs reported 144 | for(let i in results) { 145 | let event_hub_result = results[i]; 146 | let event_hub = event_hubs[i]; 147 | logger.debug('Event results for event hub :%s',event_hub._ep._endpoint.addr); 148 | if(typeof event_hub_result === 'string') { 149 | logger.debug(event_hub_result); 150 | } else { 151 | if(!error_message) error_message = event_hub_result.toString(); 152 | logger.debug(event_hub_result.toString()); 153 | } 154 | } 155 | } else { 156 | error_message = util.format('Failed to send Proposal and receive all good ProposalResponse'); 157 | logger.debug(error_message); 158 | } 159 | } catch (error) { 160 | logger.error('Failed to invoke due to error: ' + error.stack ? error.stack : error); 161 | error_message = error.toString(); 162 | } 163 | 164 | // need to shutdown open event streams 165 | eventhubs_in_use.forEach((eh) => { 166 | eh.disconnect(); 167 | }); 168 | 169 | if (!error_message) { 170 | let message = util.format( 171 | 'Successfully invoked the chaincode %s to the channel \'%s\' for transaction ID: %s', 172 | org_name, channelName, tx_id_string); 173 | logger.info(message); 174 | 175 | return tx_id_string; 176 | } else { 177 | let message = util.format('Failed to invoke chaincode. cause:%s',error_message); 178 | logger.error(message); 179 | throw new Error(message); 180 | } 181 | }; 182 | 183 | exports.invokeChaincode = invokeChaincode; 184 | -------------------------------------------------------------------------------- /balance-transfer-app/app/join-channel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the 'License'); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an 'AS IS' BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var util = require('util'); 17 | var path = require('path'); 18 | var fs = require('fs'); 19 | 20 | var helper = require('./helper.js'); 21 | var logger = helper.getLogger('Join-Channel'); 22 | 23 | /* 24 | * Have an organization join a channel 25 | */ 26 | var joinChannel = async function(channel_name, peers, username, org_name) { 27 | logger.debug('\n\n============ Join Channel start ============\n') 28 | var error_message = null; 29 | var all_eventhubs = []; 30 | try { 31 | logger.info('Calling peers in organization "%s" to join the channel', org_name); 32 | 33 | // first setup the client for this org 34 | var client = await helper.getClientForOrg(org_name, username); 35 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 36 | var channel = client.getChannel(channel_name); 37 | if(!channel) { 38 | let message = util.format('Channel %s was not defined in the connection profile', channel_name); 39 | logger.error(message); 40 | throw new Error(message); 41 | } 42 | 43 | // next step is to get the genesis_block from the orderer, 44 | // the starting point for the channel that we want to join 45 | let request = { 46 | txId : client.newTransactionID(true) //get an admin based transactionID 47 | }; 48 | let genesis_block = await channel.getGenesisBlock(request); 49 | 50 | // tell each peer to join and wait for the event hub of each peer to tell us 51 | // that the channel has been created on each peer 52 | var promises = []; 53 | var block_registration_numbers = []; 54 | let event_hubs = client.getEventHubsForOrg(org_name); 55 | event_hubs.forEach((eh) => { 56 | let configBlockPromise = new Promise((resolve, reject) => { 57 | let event_timeout = setTimeout(() => { 58 | let message = 'REQUEST_TIMEOUT:' + eh._ep._endpoint.addr; 59 | logger.error(message); 60 | eh.disconnect(); 61 | reject(new Error(message)); 62 | }, 60000); 63 | let block_registration_number = eh.registerBlockEvent((block) => { 64 | clearTimeout(event_timeout); 65 | // a peer may have more than one channel so 66 | // we must check that this block came from the channel we 67 | // asked the peer to join 68 | if (block.data.data.length === 1) { 69 | // Config block must only contain one transaction 70 | var channel_header = block.data.data[0].payload.header.channel_header; 71 | if (channel_header.channel_id === channel_name) { 72 | let message = util.format('EventHub % has reported a block update for channel %s',eh._ep._endpoint.addr,channel_name); 73 | logger.info(message) 74 | resolve(message); 75 | } else { 76 | let message = util.format('Unknown channel block event received from %s',eh._ep._endpoint.addr); 77 | logger.error(message); 78 | reject(new Error(message)); 79 | } 80 | } 81 | }, (err) => { 82 | clearTimeout(event_timeout); 83 | let message = 'Problem setting up the event hub :'+ err.toString(); 84 | logger.error(message); 85 | reject(new Error(message)); 86 | }); 87 | // save the registration handle so able to deregister 88 | block_registration_numbers.push(block_registration_number); 89 | all_eventhubs.push(eh); //save for later so that we can shut it down 90 | }); 91 | promises.push(configBlockPromise); 92 | eh.connect(); //this opens the event stream that must be shutdown at some point with a disconnect() 93 | }); 94 | 95 | let join_request = { 96 | targets: peers, //using the peer names which only is allowed when a connection profile is loaded 97 | txId: client.newTransactionID(true), //get an admin based transactionID 98 | block: genesis_block 99 | }; 100 | let join_promise = channel.joinChannel(join_request); 101 | promises.push(join_promise); 102 | let results = await Promise.all(promises); 103 | logger.debug(util.format('Join Channel R E S P O N S E : %j', results)); 104 | 105 | // lets check the results of sending to the peers which is 106 | // last in the results array 107 | let peers_results = results.pop(); 108 | // then each peer results 109 | for(let i in peers_results) { 110 | let peer_result = peers_results[i]; 111 | if(peer_result.response && peer_result.response.status == 200) { 112 | logger.info('Successfully joined peer to the channel %s',channel_name); 113 | } else { 114 | let message = util.format('Failed to joined peer to the channel %s',channel_name); 115 | error_message = message; 116 | logger.error(message); 117 | } 118 | } 119 | // now see what each of the event hubs reported 120 | for(let i in results) { 121 | let event_hub_result = results[i]; 122 | let event_hub = event_hubs[i]; 123 | let block_registration_number = block_registration_numbers[i]; 124 | logger.debug('Event results for event hub :%s',event_hub._ep._endpoint.addr); 125 | if(typeof event_hub_result === 'string') { 126 | logger.debug(event_hub_result); 127 | } else { 128 | if(!error_message) error_message = event_hub_result.toString(); 129 | logger.debug(event_hub_result.toString()); 130 | } 131 | event_hub.unregisterBlockEvent(block_registration_number); 132 | } 133 | } catch(error) { 134 | logger.error('Failed to join channel due to error: ' + error.stack ? error.stack : error); 135 | error_message = error.toString(); 136 | } 137 | 138 | // need to shutdown open event streams 139 | all_eventhubs.forEach((eh) => { 140 | eh.disconnect(); 141 | }); 142 | 143 | if (!error_message) { 144 | let message = util.format( 145 | 'Successfully joined peers in organization %s to the channel:%s', 146 | org_name, channel_name); 147 | logger.info(message); 148 | // build a response to send back to the REST caller 149 | let response = { 150 | success: true, 151 | message: message 152 | }; 153 | return response; 154 | } else { 155 | let message = util.format('Failed to join all peers to channel. cause:%s',error_message); 156 | logger.error(message); 157 | throw new Error(message); 158 | } 159 | }; 160 | exports.joinChannel = joinChannel; 161 | -------------------------------------------------------------------------------- /balance-transfer-app/app/query.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 IBM All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var path = require('path'); 17 | var fs = require('fs'); 18 | var util = require('util'); 19 | var hfc = require('fabric-client'); 20 | var helper = require('./helper.js'); 21 | var logger = helper.getLogger('Query'); 22 | 23 | var queryChaincode = async function(peer, channelName, chaincodeName, args, fcn, username, org_name) { 24 | try { 25 | // first setup the client for this org 26 | var client = await helper.getClientForOrg(org_name, username); 27 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 28 | var channel = client.getChannel(channelName); 29 | if(!channel) { 30 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 31 | logger.error(message); 32 | throw new Error(message); 33 | } 34 | 35 | // send query 36 | var request = { 37 | targets : [peer], //queryByChaincode allows for multiple targets 38 | chaincodeId: chaincodeName, 39 | fcn: fcn, 40 | args: args 41 | }; 42 | let response_payloads = await channel.queryByChaincode(request); 43 | if (response_payloads) { 44 | for (let i = 0; i < response_payloads.length; i++) { 45 | logger.info(args[0]+' now has ' + response_payloads[i].toString('utf8') + 46 | ' after the move'); 47 | } 48 | return args[0]+' now has ' + response_payloads[0].toString('utf8') + 49 | ' after the move'; 50 | } else { 51 | logger.error('response_payloads is null'); 52 | return 'response_payloads is null'; 53 | } 54 | } catch(error) { 55 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 56 | return error.toString(); 57 | } 58 | }; 59 | var getBlockByNumber = async function(peer, channelName, blockNumber, username, org_name) { 60 | try { 61 | // first setup the client for this org 62 | var client = await helper.getClientForOrg(org_name, username); 63 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 64 | var channel = client.getChannel(channelName); 65 | if(!channel) { 66 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 67 | logger.error(message); 68 | throw new Error(message); 69 | } 70 | 71 | let response_payload = await channel.queryBlock(parseInt(blockNumber, peer)); 72 | if (response_payload) { 73 | logger.debug(response_payload); 74 | return response_payload; 75 | } else { 76 | logger.error('response_payload is null'); 77 | return 'response_payload is null'; 78 | } 79 | } catch(error) { 80 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 81 | return error.toString(); 82 | } 83 | }; 84 | var getTransactionByID = async function(peer, channelName, trxnID, username, org_name) { 85 | try { 86 | // first setup the client for this org 87 | var client = await helper.getClientForOrg(org_name, username); 88 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 89 | var channel = client.getChannel(channelName); 90 | if(!channel) { 91 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 92 | logger.error(message); 93 | throw new Error(message); 94 | } 95 | 96 | let response_payload = await channel.queryTransaction(trxnID, peer); 97 | if (response_payload) { 98 | logger.debug(response_payload); 99 | return response_payload; 100 | } else { 101 | logger.error('response_payload is null'); 102 | return 'response_payload is null'; 103 | } 104 | } catch(error) { 105 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 106 | return error.toString(); 107 | } 108 | }; 109 | var getBlockByHash = async function(peer, channelName, hash, username, org_name) { 110 | try { 111 | // first setup the client for this org 112 | var client = await helper.getClientForOrg(org_name, username); 113 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 114 | var channel = client.getChannel(channelName); 115 | if(!channel) { 116 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 117 | logger.error(message); 118 | throw new Error(message); 119 | } 120 | 121 | let response_payload = await channel.queryBlockByHash(Buffer.from(hash), peer); 122 | if (response_payload) { 123 | logger.debug(response_payload); 124 | return response_payload; 125 | } else { 126 | logger.error('response_payload is null'); 127 | return 'response_payload is null'; 128 | } 129 | } catch(error) { 130 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 131 | return error.toString(); 132 | } 133 | }; 134 | var getChainInfo = async function(peer, channelName, username, org_name) { 135 | try { 136 | // first setup the client for this org 137 | var client = await helper.getClientForOrg(org_name, username); 138 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 139 | var channel = client.getChannel(channelName); 140 | if(!channel) { 141 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 142 | logger.error(message); 143 | throw new Error(message); 144 | } 145 | 146 | let response_payload = await channel.queryInfo(peer); 147 | if (response_payload) { 148 | logger.debug(response_payload); 149 | return response_payload; 150 | } else { 151 | logger.error('response_payload is null'); 152 | return 'response_payload is null'; 153 | } 154 | } catch(error) { 155 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 156 | return error.toString(); 157 | } 158 | }; 159 | //getInstalledChaincodes 160 | var getInstalledChaincodes = async function(peer, channelName, type, username, org_name) { 161 | try { 162 | // first setup the client for this org 163 | var client = await helper.getClientForOrg(org_name, username); 164 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 165 | 166 | let response = null 167 | if (type === 'installed') { 168 | response = await client.queryInstalledChaincodes(peer, true); //use the admin identity 169 | } else { 170 | var channel = client.getChannel(channelName); 171 | if(!channel) { 172 | let message = util.format('Channel %s was not defined in the connection profile', channelName); 173 | logger.error(message); 174 | throw new Error(message); 175 | } 176 | response = await channel.queryInstantiatedChaincodes(peer, true); //use the admin identity 177 | } 178 | if (response) { 179 | if (type === 'installed') { 180 | logger.debug('<<< Installed Chaincodes >>>'); 181 | } else { 182 | logger.debug('<<< Instantiated Chaincodes >>>'); 183 | } 184 | var details = []; 185 | for (let i = 0; i < response.chaincodes.length; i++) { 186 | logger.debug('name: ' + response.chaincodes[i].name + ', version: ' + 187 | response.chaincodes[i].version + ', path: ' + response.chaincodes[i].path 188 | ); 189 | details.push('name: ' + response.chaincodes[i].name + ', version: ' + 190 | response.chaincodes[i].version + ', path: ' + response.chaincodes[i].path 191 | ); 192 | } 193 | return details; 194 | } else { 195 | logger.error('response is null'); 196 | return 'response is null'; 197 | } 198 | } catch(error) { 199 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 200 | return error.toString(); 201 | } 202 | }; 203 | var getChannels = async function(peer, username, org_name) { 204 | try { 205 | // first setup the client for this org 206 | var client = await helper.getClientForOrg(org_name, username); 207 | logger.debug('Successfully got the fabric client for the organization "%s"', org_name); 208 | 209 | let response = await client.queryChannels(peer); 210 | if (response) { 211 | logger.debug('<<< channels >>>'); 212 | var channelNames = []; 213 | for (let i = 0; i < response.channels.length; i++) { 214 | channelNames.push('channel id: ' + response.channels[i].channel_id); 215 | } 216 | logger.debug(channelNames); 217 | return response; 218 | } else { 219 | logger.error('response_payloads is null'); 220 | return 'response_payloads is null'; 221 | } 222 | } catch(error) { 223 | logger.error('Failed to query due to error: ' + error.stack ? error.stack : error); 224 | return error.toString(); 225 | } 226 | }; 227 | 228 | exports.queryChaincode = queryChaincode; 229 | exports.getBlockByNumber = getBlockByNumber; 230 | exports.getTransactionByID = getTransactionByID; 231 | exports.getBlockByHash = getBlockByHash; 232 | exports.getChainInfo = getChainInfo; 233 | exports.getInstalledChaincodes = getInstalledChaincodes; 234 | exports.getChannels = getChannels; 235 | -------------------------------------------------------------------------------- /balance-transfer-app/artifacts/org1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # The network connection profile provides client applications the information about the target 4 | # blockchain network that are necessary for the applications to interact with it. These are all 5 | # knowledge that must be acquired from out-of-band sources. This file provides such a source. 6 | # 7 | name: "balance-transfer-org1" 8 | 9 | # 10 | # Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming 11 | # in HTTP headers or swagger properties work. The SDK will simply ignore these fields and leave 12 | # them for the applications to process. This is a mechanism for different components of an application 13 | # to exchange information that are not part of the standard schema described below. In particular, 14 | # the "x-type" property with the "hlfv1" value example below is used by Hyperledger Composer to 15 | # determine the type of Fabric networks (v0.6 vs. v1.0) it needs to work with. 16 | # 17 | x-type: "hlfv1" 18 | 19 | # 20 | # Describe what the target network is/does. 21 | # 22 | description: "Balance Transfer Network - client definition for Org1" 23 | 24 | # 25 | # Schema version of the content. Used by the SDK to apply the corresponding parsing rules. 26 | # 27 | version: "1.0" 28 | 29 | # 30 | # The client section is SDK-specific. The sample below is for the node.js SDK 31 | # 32 | client: 33 | # Which organization does this application instance belong to? The value must be the name of an org 34 | # defined under "organizations" 35 | organization: Org1 36 | 37 | # Some SDKs support pluggable KV stores, the properties under "credentialStore" 38 | # are implementation specific 39 | credentialStore: 40 | # [Optional]. Specific to FileKeyValueStore.js or similar implementations in other SDKs. Can be others 41 | # if using an alternative impl. For instance, CouchDBKeyValueStore.js would require an object 42 | # here for properties like url, db name, etc. 43 | path: "./fabric-client-kv-org1" 44 | 45 | # [Optional]. Specific to the CryptoSuite implementation. Software-based implementations like 46 | # CryptoSuite_ECDSA_AES.js in node SDK requires a key store. PKCS#11 based implementations does 47 | # not. 48 | cryptoStore: 49 | # Specific to the underlying KeyValueStore that backs the crypto key store. 50 | path: "/tmp/fabric-client-kv-org1" 51 | 52 | # [Optional]. Specific to Composer environment 53 | wallet: wallet-name 54 | -------------------------------------------------------------------------------- /balance-transfer-app/artifacts/org2.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # 3 | # The network connection profile provides client applications the information about the target 4 | # blockchain network that are necessary for the applications to interact with it. These are all 5 | # knowledge that must be acquired from out-of-band sources. This file provides such a source. 6 | # 7 | name: "balance-transfer-org2" 8 | 9 | # 10 | # Any properties with an "x-" prefix will be treated as application-specific, exactly like how naming 11 | # in HTTP headers or swagger properties work. The SDK will simply ignore these fields and leave 12 | # them for the applications to process. This is a mechanism for different components of an application 13 | # to exchange information that are not part of the standard schema described below. In particular, 14 | # the "x-type" property with the "hlfv1" value example below is used by Hyperledger Composer to 15 | # determine the type of Fabric networks (v0.6 vs. v1.0) it needs to work with. 16 | # 17 | x-type: "hlfv1" 18 | 19 | # 20 | # Describe what the target network is/does. 21 | # 22 | description: "Balance Transfer Network - client definition for Org2" 23 | 24 | # 25 | # Schema version of the content. Used by the SDK to apply the corresponding parsing rules. 26 | # 27 | version: "1.0" 28 | 29 | # 30 | # The client section is SDK-specific. The sample below is for the node.js SDK 31 | # 32 | client: 33 | # Which organization does this application instance belong to? The value must be the name of an org 34 | # defined under "organizations" 35 | organization: Org2 36 | 37 | # Some SDKs support pluggable KV stores, the properties under "credentialStore" 38 | # are implementation specific 39 | credentialStore: 40 | # [Optional]. Specific to FileKeyValueStore.js or similar implementations in other SDKs. Can be others 41 | # if using an alternative impl. For instance, CouchDBKeyValueStore.js would require an object 42 | # here for properties like url, db name, etc. 43 | path: "./fabric-client-kv-org2" 44 | 45 | # [Optional]. Specific to the CryptoSuite implementation. Software-based implementations like 46 | # CryptoSuite_ECDSA_AES.js in node SDK requires a key store. PKCS#11 based implementations does 47 | # not. 48 | cryptoStore: 49 | # Specific to the underlying KeyValueStore that backs the crypto key store. 50 | path: "/tmp/fabric-client-kv-org2" 51 | 52 | # [Optional]. Specific to Composer environment 53 | wallet: wallet-name 54 | -------------------------------------------------------------------------------- /balance-transfer-app/artifacts/src/github.com/example_cc/go/example_cc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright IBM Corp. 2016 All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | 20 | import ( 21 | "fmt" 22 | "strconv" 23 | 24 | "github.com/hyperledger/fabric/core/chaincode/shim" 25 | pb "github.com/hyperledger/fabric/protos/peer" 26 | ) 27 | 28 | var logger = shim.NewLogger("example_cc0") 29 | 30 | // SimpleChaincode example simple Chaincode implementation 31 | type SimpleChaincode struct { 32 | } 33 | 34 | func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 35 | logger.Info("########### example_cc0 Init ###########") 36 | 37 | _, args := stub.GetFunctionAndParameters() 38 | var A, B string // Entities 39 | var Aval, Bval int // Asset holdings 40 | var err error 41 | 42 | // Initialize the chaincode 43 | A = args[0] 44 | Aval, err = strconv.Atoi(args[1]) 45 | if err != nil { 46 | return shim.Error("Expecting integer value for asset holding") 47 | } 48 | B = args[2] 49 | Bval, err = strconv.Atoi(args[3]) 50 | if err != nil { 51 | return shim.Error("Expecting integer value for asset holding") 52 | } 53 | logger.Info("Aval = %d, Bval = %d\n", Aval, Bval) 54 | 55 | // Write the state to the ledger 56 | err = stub.PutState(A, []byte(strconv.Itoa(Aval))) 57 | if err != nil { 58 | return shim.Error(err.Error()) 59 | } 60 | 61 | err = stub.PutState(B, []byte(strconv.Itoa(Bval))) 62 | if err != nil { 63 | return shim.Error(err.Error()) 64 | } 65 | 66 | return shim.Success(nil) 67 | 68 | 69 | } 70 | 71 | // Transaction makes payment of X units from A to B 72 | func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 73 | logger.Info("########### example_cc0 Invoke ###########") 74 | 75 | function, args := stub.GetFunctionAndParameters() 76 | 77 | if function == "delete" { 78 | // Deletes an entity from its state 79 | return t.delete(stub, args) 80 | } 81 | 82 | if function == "query" { 83 | // queries an entity state 84 | return t.query(stub, args) 85 | } 86 | if function == "move" { 87 | // Deletes an entity from its state 88 | return t.move(stub, args) 89 | } 90 | 91 | logger.Errorf("Unknown action, check the first argument, must be one of 'delete', 'query', or 'move'. But got: %v", args[0]) 92 | return shim.Error(fmt.Sprintf("Unknown action, check the first argument, must be one of 'delete', 'query', or 'move'. But got: %v", args[0])) 93 | } 94 | 95 | func (t *SimpleChaincode) move(stub shim.ChaincodeStubInterface, args []string) pb.Response { 96 | // must be an invoke 97 | var A, B string // Entities 98 | var Aval, Bval int // Asset holdings 99 | var X int // Transaction value 100 | var err error 101 | 102 | if len(args) != 3 { 103 | return shim.Error("Incorrect number of arguments. Expecting 4, function followed by 2 names and 1 value") 104 | } 105 | 106 | A = args[0] 107 | B = args[1] 108 | 109 | // Get the state from the ledger 110 | // TODO: will be nice to have a GetAllState call to ledger 111 | Avalbytes, err := stub.GetState(A) 112 | if err != nil { 113 | return shim.Error("Failed to get state") 114 | } 115 | if Avalbytes == nil { 116 | return shim.Error("Entity not found") 117 | } 118 | Aval, _ = strconv.Atoi(string(Avalbytes)) 119 | 120 | Bvalbytes, err := stub.GetState(B) 121 | if err != nil { 122 | return shim.Error("Failed to get state") 123 | } 124 | if Bvalbytes == nil { 125 | return shim.Error("Entity not found") 126 | } 127 | Bval, _ = strconv.Atoi(string(Bvalbytes)) 128 | 129 | // Perform the execution 130 | X, err = strconv.Atoi(args[2]) 131 | if err != nil { 132 | return shim.Error("Invalid transaction amount, expecting a integer value") 133 | } 134 | Aval = Aval - X 135 | Bval = Bval + X 136 | logger.Infof("Aval = %d, Bval = %d\n", Aval, Bval) 137 | 138 | // Write the state back to the ledger 139 | err = stub.PutState(A, []byte(strconv.Itoa(Aval))) 140 | if err != nil { 141 | return shim.Error(err.Error()) 142 | } 143 | 144 | err = stub.PutState(B, []byte(strconv.Itoa(Bval))) 145 | if err != nil { 146 | return shim.Error(err.Error()) 147 | } 148 | 149 | return shim.Success(nil); 150 | } 151 | 152 | // Deletes an entity from state 153 | func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { 154 | if len(args) != 1 { 155 | return shim.Error("Incorrect number of arguments. Expecting 1") 156 | } 157 | 158 | A := args[0] 159 | 160 | // Delete the key from the state in ledger 161 | err := stub.DelState(A) 162 | if err != nil { 163 | return shim.Error("Failed to delete state") 164 | } 165 | 166 | return shim.Success(nil) 167 | } 168 | 169 | // Query callback representing the query of a chaincode 170 | func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response { 171 | 172 | var A string // Entities 173 | var err error 174 | 175 | if len(args) != 1 { 176 | return shim.Error("Incorrect number of arguments. Expecting name of the person to query") 177 | } 178 | 179 | A = args[0] 180 | 181 | // Get the state from the ledger 182 | Avalbytes, err := stub.GetState(A) 183 | if err != nil { 184 | jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}" 185 | return shim.Error(jsonResp) 186 | } 187 | 188 | if Avalbytes == nil { 189 | jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}" 190 | return shim.Error(jsonResp) 191 | } 192 | 193 | jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}" 194 | logger.Infof("Query Response:%s\n", jsonResp) 195 | return shim.Success(Avalbytes) 196 | } 197 | 198 | func main() { 199 | err := shim.Start(new(SimpleChaincode)) 200 | if err != nil { 201 | logger.Errorf("Error starting Simple chaincode: %s", err) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /balance-transfer-app/artifacts/src/github.com/example_cc/node/example_cc.js: -------------------------------------------------------------------------------- 1 | /* 2 | # Copyright IBM Corp. All Rights Reserved. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | const shim = require('fabric-shim'); 8 | const util = require('util'); 9 | 10 | var Chaincode = class { 11 | 12 | // Initialize the chaincode 13 | async Init(stub) { 14 | console.info('========= example_cc Init ========='); 15 | let ret = stub.getFunctionAndParameters(); 16 | console.info(ret); 17 | let args = ret.params; 18 | // initialise only if 4 parameters passed. 19 | if (args.length != 4) { 20 | return shim.error('Incorrect number of arguments. Expecting 4'); 21 | } 22 | 23 | let A = args[0]; 24 | let B = args[2]; 25 | let Aval = args[1]; 26 | let Bval = args[3]; 27 | 28 | if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') { 29 | return shim.error('Expecting integer value for asset holding'); 30 | } 31 | 32 | try { 33 | await stub.putState(A, Buffer.from(Aval)); 34 | try { 35 | await stub.putState(B, Buffer.from(Bval)); 36 | return shim.success(); 37 | } catch (err) { 38 | return shim.error(err); 39 | } 40 | } catch (err) { 41 | return shim.error(err); 42 | } 43 | } 44 | 45 | async Invoke(stub) { 46 | let ret = stub.getFunctionAndParameters(); 47 | console.info(ret); 48 | let method = this[ret.fcn]; 49 | if (!method) { 50 | console.error('no method of name:' + ret.fcn + ' found'); 51 | return shim.error('no method of name:' + ret.fcn + ' found'); 52 | } 53 | 54 | console.info('\nCalling method : ' + ret.fcn); 55 | try { 56 | let payload = await method(stub, ret.params); 57 | return shim.success(payload); 58 | } catch (err) { 59 | console.log(err); 60 | return shim.error(err); 61 | } 62 | } 63 | 64 | async move(stub, args) { 65 | if (args.length != 3) { 66 | throw new Error('Incorrect number of arguments. Expecting 3'); 67 | } 68 | 69 | let A = args[0]; 70 | let B = args[1]; 71 | if (!A || !B) { 72 | throw new Error('asset holding must not be empty'); 73 | } 74 | 75 | // Get the state from the ledger 76 | let Avalbytes = await stub.getState(A); 77 | if (!Avalbytes) { 78 | throw new Error('Failed to get state of asset holder A'); 79 | } 80 | let Aval = parseInt(Avalbytes.toString()); 81 | 82 | let Bvalbytes = await stub.getState(B); 83 | if (!Bvalbytes) { 84 | throw new Error('Failed to get state of asset holder B'); 85 | } 86 | 87 | let Bval = parseInt(Bvalbytes.toString()); 88 | // Perform the execution 89 | let amount = parseInt(args[2]); 90 | if (typeof amount !== 'number') { 91 | throw new Error('Expecting integer value for amount to be transaferred'); 92 | } 93 | 94 | Aval = Aval - amount; 95 | Bval = Bval + amount; 96 | console.info(util.format('Aval = %d, Bval = %d\n', Aval, Bval)); 97 | 98 | // Write the states back to the ledger 99 | await stub.putState(A, Buffer.from(Aval.toString())); 100 | await stub.putState(B, Buffer.from(Bval.toString())); 101 | 102 | } 103 | 104 | // Deletes an entity from state 105 | async delete(stub, args) { 106 | if (args.length != 1) { 107 | throw new Error('Incorrect number of arguments. Expecting 1'); 108 | } 109 | 110 | let A = args[0]; 111 | 112 | // Delete the key from the state in ledger 113 | await stub.deleteState(A); 114 | } 115 | 116 | // query callback representing the query of a chaincode 117 | async query(stub, args) { 118 | if (args.length != 1) { 119 | throw new Error('Incorrect number of arguments. Expecting name of the person to query') 120 | } 121 | 122 | let jsonResp = {}; 123 | let A = args[0]; 124 | 125 | // Get the state from the ledger 126 | let Avalbytes = await stub.getState(A); 127 | if (!Avalbytes) { 128 | jsonResp.error = 'Failed to get state for ' + A; 129 | throw new Error(JSON.stringify(jsonResp)); 130 | } 131 | 132 | jsonResp.name = A; 133 | jsonResp.amount = Avalbytes.toString(); 134 | console.info('Query Response:'); 135 | console.info(jsonResp); 136 | return Avalbytes; 137 | } 138 | }; 139 | 140 | shim.start(new Chaincode()); 141 | -------------------------------------------------------------------------------- /balance-transfer-app/artifacts/src/github.com/example_cc/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example_cc", 3 | "version": "1.0.0", 4 | "description": "node-js version of example_02.go chaincode", 5 | "engines": { 6 | "node": ">=8.4.0", 7 | "npm": ">=5.3.0" 8 | }, 9 | "scripts": { "start" : "node example_cc.js" }, 10 | "engine-strict": true, 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "fabric-shim": "unstable" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /balance-transfer-app/config-manual.json: -------------------------------------------------------------------------------- 1 | { 2 | "host":"localhost", 3 | "port":"4000", 4 | "jwt_expiretime": "36000", 5 | "channelName":"bankchannel", 6 | "CC_SRC_PATH":"../artifacts", 7 | "eventWaitTime":"300000", 8 | "admins":[ 9 | { 10 | "username":"admin", 11 | "secret":"adminpw" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /balance-transfer-app/config.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var path = require('path'); 3 | var hfc = require('fabric-client'); 4 | 5 | var file = 'network-config%s.yaml'; 6 | 7 | var env = process.env.TARGET_NETWORK; 8 | if (env) 9 | file = util.format(file, '-' + env); 10 | else 11 | file = util.format(file, ''); 12 | // indicate to the application where the setup file is located so it able 13 | // to have the hfc load it to initalize the fabric client instance 14 | hfc.setConfigSetting('network-connection-profile-path',path.join(__dirname, 'artifacts' ,file)); 15 | hfc.setConfigSetting('Org1-connection-profile-path',path.join(__dirname, 'artifacts', 'org1.yaml')); 16 | hfc.setConfigSetting('Org2-connection-profile-path',path.join(__dirname, 'artifacts', 'org2.yaml')); 17 | // some other settings the application might need to know 18 | hfc.addConfigFile(path.join(__dirname, 'config.json')); 19 | -------------------------------------------------------------------------------- /balance-transfer-app/download-from-fabric-network.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ##################################################################################### 3 | # How to use: 4 | # For example: 5 | # SSH_ADDRESS=k8s_master_ssh_address FABRIC_NETWORK=your_fabric_network_name SHARED_STORAGE=your_nas_mounting_address ./download-from-fabric-network.sh" 6 | ##################################################################################### 7 | 8 | # Debug mode 9 | set -x 10 | 11 | if [ -z $SHARED_STORAGE ] || [ -z $SSH_ADDRESS ] || [ -z $FABRIC_NETWORK ]; then 12 | echo "Usage: SSH_ADDRESS=k8s_master_ssh_address FABRIC_NETWORK=your_fabric_network_name SHARED_STORAGE=your_nas_mounting_address ./download-from-fabric-network.sh" 13 | exit 1 14 | fi 15 | 16 | # Automatically generate a dynamic mounting dir for NAS 17 | DIR_NAME=`date "+%Y-%m-%d-%H-%M-%S"` 18 | ssh -l root ${SSH_ADDRESS} "mkdir /data-${DIR_NAME}" 19 | ssh -l root ${SSH_ADDRESS} "mount ${SHARED_STORAGE}:/ /data-${DIR_NAME}" 20 | 21 | rm -rf ./artifacts/channel/* 22 | 23 | scp -r root@${SSH_ADDRESS}:/data-${DIR_NAME}/fabric/${FABRIC_NETWORK}/sdk/* ./artifacts/ 24 | scp -r root@${SSH_ADDRESS}:/data-${DIR_NAME}/fabric/${FABRIC_NETWORK}/config/app/network-config.yaml ./artifacts/network-config.yaml 25 | scp -r root@${SSH_ADDRESS}:/data-${DIR_NAME}/fabric/${FABRIC_NETWORK}/config/app/config.json ./config.json 26 | 27 | # For data safety, we are just unmounting the data dir, but not deleting the dir yet. 28 | # You can consider manual cleanup of these unused data dirs 29 | ssh -l root ${SSH_ADDRESS} "umount /data-${DIR_NAME}" 30 | 31 | -------------------------------------------------------------------------------- /balance-transfer-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "balance-transfer", 3 | "version": "1.0.0", 4 | "description": "A balance-transfer example node program to demonstrate using node.js SDK APIs", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "keywords": [ 10 | "fabric-client sample app", 11 | "balance-transfer node sample", 12 | "v1.0 fabric nodesdk sample" 13 | ], 14 | "engines": { 15 | "node": ">=6.9.5 <7.0", 16 | "npm": ">=3.10.10 <4.0" 17 | }, 18 | "license": "Apache-2.0", 19 | "dependencies": { 20 | "body-parser": "^1.17.1", 21 | "cookie-parser": "^1.4.3", 22 | "cors": "^2.8.3", 23 | "express": "^4.15.2", 24 | "express-bearer-token": "^2.1.0", 25 | "express-jwt": "^5.1.0", 26 | "express-session": "^1.15.2", 27 | "fabric-ca-client": "1.1.0", 28 | "fabric-client": "1.1.0", 29 | "fs-extra": "^2.0.0", 30 | "jsonwebtoken": "^7.3.0", 31 | "log4js": "^0.6.38" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /balance-transfer-app/runApp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | 9 | function cleanEnv() { 10 | echo 11 | 12 | #Cleanup the stores 13 | rm -rf ./fabric-client-kv-org* 14 | } 15 | 16 | function installNodeModules() { 17 | echo 18 | if [ -d node_modules ]; then 19 | echo "============== node modules installed already =============" 20 | else 21 | echo "============== Installing node modules =============" 22 | if [ -f /etc/centos-release ] || [ -f /etc/redhat-release ]; then 23 | npm install --registry=https://registry.npm.taobao.org --unsafe-perm 24 | else 25 | npm install --registry=https://registry.npm.taobao.org 26 | fi 27 | fi 28 | echo 29 | } 30 | 31 | 32 | cleanEnv 33 | 34 | installNodeModules 35 | 36 | PORT=4000 node app 37 | -------------------------------------------------------------------------------- /balance-transfer-app/testAPIs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | jq --version > /dev/null 2>&1 9 | if [ $? -ne 0 ]; then 10 | echo "Please Install 'jq' https://stedolan.github.io/jq/ to execute this script" 11 | echo 12 | exit 1 13 | fi 14 | 15 | starttime=$(date +%s) 16 | 17 | # Print the usage message 18 | function printHelp () { 19 | echo "Usage: " 20 | echo " ./testAPIs.sh -l golang|node" 21 | echo " -l - chaincode language (defaults to \"golang\")" 22 | } 23 | # Language defaults to "golang" 24 | LANGUAGE="golang" 25 | 26 | # Parse commandline args 27 | while getopts "h?l:" opt; do 28 | case "$opt" in 29 | h|\?) 30 | printHelp 31 | exit 0 32 | ;; 33 | l) LANGUAGE=$OPTARG 34 | ;; 35 | esac 36 | done 37 | 38 | ##set chaincode path 39 | function setChaincodePath(){ 40 | LANGUAGE=`echo "$LANGUAGE" | tr '[:upper:]' '[:lower:]'` 41 | case "$LANGUAGE" in 42 | "golang") 43 | CC_SRC_PATH="github.com/example_cc/go" 44 | ;; 45 | "node") 46 | CC_SRC_PATH="$PWD/artifacts/src/github.com/example_cc/node" 47 | ;; 48 | *) printf "\n ------ Language $LANGUAGE is not supported yet ------\n"$ 49 | exit 1 50 | esac 51 | } 52 | 53 | setChaincodePath 54 | 55 | echo "POST request Enroll on Org1 ..." 56 | echo 57 | ORG1_TOKEN=$(curl -s -X POST \ 58 | http://localhost:4000/users \ 59 | -H "content-type: application/x-www-form-urlencoded" \ 60 | -d 'username=Jim&orgName=Org1') 61 | echo $ORG1_TOKEN 62 | ORG1_TOKEN=$(echo $ORG1_TOKEN | jq ".token" | sed "s/\"//g") 63 | echo 64 | echo "ORG1 token is $ORG1_TOKEN" 65 | echo 66 | echo "POST request Enroll on Org2 ..." 67 | echo 68 | ORG2_TOKEN=$(curl -s -X POST \ 69 | http://localhost:4000/users \ 70 | -H "content-type: application/x-www-form-urlencoded" \ 71 | -d 'username=Barry&orgName=Org2') 72 | echo $ORG2_TOKEN 73 | ORG2_TOKEN=$(echo $ORG2_TOKEN | jq ".token" | sed "s/\"//g") 74 | echo 75 | echo "ORG2 token is $ORG2_TOKEN" 76 | echo 77 | echo 78 | echo "POST request Create channel ..." 79 | echo 80 | curl -s -X POST \ 81 | http://localhost:4000/channels \ 82 | -H "authorization: Bearer $ORG1_TOKEN" \ 83 | -H "content-type: application/json" \ 84 | -d '{ 85 | "channelName":"bankchannel", 86 | "channelConfigPath":"../artifacts/channel/bankchannel.tx" 87 | }' 88 | echo 89 | echo 90 | sleep 5 91 | echo "POST request Join channel on Org1" 92 | echo 93 | curl -s -X POST \ 94 | http://localhost:4000/channels/bankchannel/peers \ 95 | -H "authorization: Bearer $ORG1_TOKEN" \ 96 | -H "content-type: application/json" \ 97 | -d '{ 98 | "peers": ["network01-peer1","network01-peer2"] 99 | }' 100 | echo 101 | echo 102 | 103 | echo "POST request Join channel on Org2" 104 | echo 105 | curl -s -X POST \ 106 | http://localhost:4000/channels/bankchannel/peers \ 107 | -H "authorization: Bearer $ORG2_TOKEN" \ 108 | -H "content-type: application/json" \ 109 | -d '{ 110 | "peers": ["network01-peer3","network01-peer4"] 111 | }' 112 | echo 113 | echo 114 | 115 | echo "POST Install chaincode on Org1" 116 | echo 117 | curl -s -X POST \ 118 | http://localhost:4000/chaincodes \ 119 | -H "authorization: Bearer $ORG1_TOKEN" \ 120 | -H "content-type: application/json" \ 121 | -d "{ 122 | \"peers\": [\"network01-peer1\",\"network01-peer2\"], 123 | \"chaincodeName\":\"mycc\", 124 | \"chaincodePath\":\"$CC_SRC_PATH\", 125 | \"chaincodeType\": \"$LANGUAGE\", 126 | \"chaincodeVersion\":\"v0\" 127 | }" 128 | echo 129 | echo 130 | 131 | echo "POST Install chaincode on Org2" 132 | echo 133 | curl -s -X POST \ 134 | http://localhost:4000/chaincodes \ 135 | -H "authorization: Bearer $ORG2_TOKEN" \ 136 | -H "content-type: application/json" \ 137 | -d "{ 138 | \"peers\": [\"network01-peer3\",\"network01-peer4\"], 139 | \"chaincodeName\":\"mycc\", 140 | \"chaincodePath\":\"$CC_SRC_PATH\", 141 | \"chaincodeType\": \"$LANGUAGE\", 142 | \"chaincodeVersion\":\"v0\" 143 | }" 144 | echo 145 | echo 146 | 147 | echo "POST instantiate chaincode on peer1 of Org1" 148 | echo 149 | curl -s -X POST \ 150 | http://localhost:4000/channels/bankchannel/chaincodes \ 151 | -H "authorization: Bearer $ORG1_TOKEN" \ 152 | -H "content-type: application/json" \ 153 | -d "{ 154 | \"chaincodeName\":\"mycc\", 155 | \"chaincodeVersion\":\"v0\", 156 | \"chaincodeType\": \"$LANGUAGE\", 157 | \"args\":[\"a\",\"100\",\"b\",\"200\"] 158 | }" 159 | echo 160 | echo 161 | 162 | echo "POST invoke chaincode on peers of Org1" 163 | echo 164 | TRX_ID=$(curl -s -X POST \ 165 | http://localhost:4000/channels/bankchannel/chaincodes/mycc \ 166 | -H "authorization: Bearer $ORG1_TOKEN" \ 167 | -H "content-type: application/json" \ 168 | -d '{ 169 | "peers": ["network01-peer1","network01-peer2"], 170 | "fcn":"move", 171 | "args":["a","b","10"] 172 | }') 173 | echo "Transacton ID is $TRX_ID" 174 | echo 175 | echo 176 | 177 | echo "GET query chaincode on peer1 of Org1" 178 | echo 179 | curl -s -X GET \ 180 | "http://localhost:4000/channels/bankchannel/chaincodes/mycc?peer=network01-peer1&fcn=query&args=%5B%22a%22%5D" \ 181 | -H "authorization: Bearer $ORG1_TOKEN" \ 182 | -H "content-type: application/json" 183 | echo 184 | echo 185 | 186 | echo "GET query Block by blockNumber" 187 | echo 188 | curl -s -X GET \ 189 | "http://localhost:4000/channels/bankchannel/blocks/1?peer=network01-peer1" \ 190 | -H "authorization: Bearer $ORG1_TOKEN" \ 191 | -H "content-type: application/json" 192 | echo 193 | echo 194 | 195 | echo "GET query Transaction by TransactionID" 196 | echo 197 | curl -s -X GET http://localhost:4000/channels/bankchannel/transactions/$TRX_ID?peer=network01-peer1 \ 198 | -H "authorization: Bearer $ORG1_TOKEN" \ 199 | -H "content-type: application/json" 200 | echo 201 | echo 202 | 203 | ############################################################################ 204 | ### TODO: What to pass to fetch the Block information 205 | ############################################################################ 206 | #echo "GET query Block by Hash" 207 | #echo 208 | #hash=???? 209 | #curl -s -X GET \ 210 | # "http://localhost:4000/channels/bankchannel/blocks?hash=$hash&peer=peer1" \ 211 | # -H "authorization: Bearer $ORG1_TOKEN" \ 212 | # -H "cache-control: no-cache" \ 213 | # -H "content-type: application/json" \ 214 | # -H "x-access-token: $ORG1_TOKEN" 215 | #echo 216 | #echo 217 | 218 | echo "GET query ChainInfo" 219 | echo 220 | curl -s -X GET \ 221 | "http://localhost:4000/channels/bankchannel?peer=network01-peer1" \ 222 | -H "authorization: Bearer $ORG1_TOKEN" \ 223 | -H "content-type: application/json" 224 | echo 225 | echo 226 | 227 | echo "GET query Installed chaincodes" 228 | echo 229 | curl -s -X GET \ 230 | "http://localhost:4000/chaincodes?peer=network01-peer1" \ 231 | -H "authorization: Bearer $ORG1_TOKEN" \ 232 | -H "content-type: application/json" 233 | echo 234 | echo 235 | 236 | echo "GET query Instantiated chaincodes" 237 | echo 238 | curl -s -X GET \ 239 | "http://localhost:4000/channels/bankchannel/chaincodes?peer=network01-peer1" \ 240 | -H "authorization: Bearer $ORG1_TOKEN" \ 241 | -H "content-type: application/json" 242 | echo 243 | echo 244 | 245 | echo "GET query Channels" 246 | echo 247 | curl -s -X GET \ 248 | "http://localhost:4000/channels?peer=network01-peer1" \ 249 | -H "authorization: Bearer $ORG1_TOKEN" \ 250 | -H "content-type: application/json" 251 | echo 252 | echo 253 | 254 | 255 | echo "Total execution time : $(($(date +%s)-starttime)) secs ..." 256 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | dist 4 | types/fabric-client -------------------------------------------------------------------------------- /balance-transfer-app/typescript/README.md: -------------------------------------------------------------------------------- 1 | ## Balance transfer 2 | 3 | This is a sample Node.js application written using typescript which demonstrates 4 | the **__fabric-client__** and **__fabric-ca-client__** Node.js SDK APIs for typescript. 5 | 6 | ### Prerequisites and setup: 7 | 8 | * [Docker](https://www.docker.com/products/overview) - v1.12 or higher 9 | * [Docker Compose](https://docs.docker.com/compose/overview/) - v1.8 or higher 10 | * [Git client](https://git-scm.com/downloads) - needed for clone commands 11 | * **Node.js** v6.9.0 - 6.10.0 ( __Node v7+ is not supported__ ) 12 | * [Download Docker images](http://hyperledger-fabric.readthedocs.io/en/latest/samples.html#binaries) 13 | 14 | ``` 15 | cd fabric-samples/balance-transfer/ 16 | ``` 17 | 18 | Once you have completed the above setup, you will have provisioned a local network with the following docker container configuration: 19 | 20 | * 2 CAs 21 | * A SOLO orderer 22 | * 4 peers (2 peers per Org) 23 | 24 | #### Artifacts 25 | 26 | * Crypto material has been generated using the **cryptogen** tool from Hyperledger Fabric and mounted to all peers, the orderering node and CA containers. More details regarding the cryptogen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#crypto-generator). 27 | 28 | * An Orderer genesis block (genesis.block) and channel configuration transaction (mychannel.tx) has been pre generated using the **configtxgen** tool from Hyperledger Fabric and placed within the artifacts folder. More details regarding the configtxgen tool are available [here](http://hyperledger-fabric.readthedocs.io/en/latest/build_network.html#configuration-transaction-generator). 29 | 30 | ## Running the sample program 31 | 32 | There are two options available for running the balance-transfer sample as shown below. 33 | 34 | ### Option 1 35 | 36 | ##### Terminal Window 1 37 | 38 | ``` 39 | cd fabric-samples/balance-transfer/typescript 40 | 41 | ./runApp.sh 42 | 43 | ``` 44 | 45 | This performs the following steps: 46 | * lauches the required network on your local machine 47 | * installs the fabric-client and fabric-ca-client node modules 48 | * starts the node app on PORT 4000 49 | 50 | ##### Terminal Window 2 51 | 52 | NOTE: In order for the following shell script to properly parse the JSON, you must install ``jq``. 53 | 54 | See instructions at [https://stedolan.github.io/jq/](https://stedolan.github.io/jq/). 55 | 56 | Test the APIs as follows: 57 | ``` 58 | cd fabric-samples/balance-transfer/typescript 59 | 60 | ./testAPIs.sh 61 | 62 | ``` 63 | 64 | ### Option 2 is a more manual approach 65 | 66 | ##### Terminal Window 1 67 | 68 | * Launch the network using docker-compose 69 | 70 | ``` 71 | docker-compose -f artifacts/docker-compose.yaml up 72 | ``` 73 | ##### Terminal Window 2 74 | 75 | * Install the fabric-client and fabric-ca-client node modules 76 | 77 | ``` 78 | npm install 79 | ``` 80 | 81 | *** NOTE - If running this before the new version of the node SDK is published which includes the typescript definition files, you will need to do the following: 82 | 83 | ``` 84 | cp types/fabric-client/index.d.tx node_modules/fabric-client/index.d.ts 85 | cp types/fabric-ca-client/index.d.tx node_modules/fabric-ca-client/index.d.ts 86 | ``` 87 | 88 | * Start the node app on PORT 4000 89 | 90 | ``` 91 | PORT=4000 ts-node app.ts 92 | ``` 93 | 94 | ##### Terminal Window 3 95 | 96 | * Execute the REST APIs from the section [Sample REST APIs Requests](https://github.com/hyperledger/fabric-samples/tree/master/balance-transfer#sample-rest-apis-requests) 97 | 98 | ## Sample REST APIs Requests 99 | 100 | ### Login Request 101 | 102 | * Register and enroll new users in Organization - **Org1**: 103 | 104 | `curl -s -X POST http://localhost:4000/users -H "content-type: application/x-www-form-urlencoded" -d 'username=Jim&orgName=org1'` 105 | 106 | **OUTPUT:** 107 | 108 | ``` 109 | { 110 | "success": true, 111 | "secret": "RaxhMgevgJcm", 112 | "message": "Jim enrolled Successfully", 113 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" 114 | } 115 | ``` 116 | 117 | The response contains the success/failure status, an **enrollment Secret** and a **JSON Web Token (JWT)** that is a required string in the Request Headers for subsequent requests. 118 | 119 | ### Create Channel request 120 | 121 | ``` 122 | curl -s -X POST \ 123 | http://localhost:4000/channels \ 124 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 125 | -H "content-type: application/json" \ 126 | -d '{ 127 | "channelName":"mychannel", 128 | "channelConfigPath":"../artifacts/channel/mychannel.tx" 129 | }' 130 | ``` 131 | 132 | Please note that the Header **authorization** must contain the JWT returned from the `POST /users` call 133 | 134 | ### Join Channel request 135 | 136 | ``` 137 | curl -s -X POST \ 138 | http://localhost:4000/channels/mychannel/peers \ 139 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 140 | -H "content-type: application/json" \ 141 | -d '{ 142 | "peers": ["peer1","peer2"] 143 | }' 144 | ``` 145 | ### Install chaincode 146 | 147 | ``` 148 | curl -s -X POST \ 149 | http://localhost:4000/chaincodes \ 150 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 151 | -H "content-type: application/json" \ 152 | -d '{ 153 | "peers": ["peer1","peer2"], 154 | "chaincodeName":"mycc", 155 | "chaincodePath":"github.com/example_cc/go", 156 | "chaincodeVersion":"v0" 157 | }' 158 | ``` 159 | 160 | ### Instantiate chaincode 161 | 162 | ``` 163 | curl -s -X POST \ 164 | http://localhost:4000/channels/mychannel/chaincodes \ 165 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 166 | -H "content-type: application/json" \ 167 | -d '{ 168 | "chaincodeName":"mycc", 169 | "chaincodeVersion":"v0", 170 | "args":["a","100","b","200"] 171 | }' 172 | ``` 173 | 174 | ### Invoke request 175 | 176 | ``` 177 | curl -s -X POST \ 178 | http://localhost:4000/channels/mychannel/chaincodes/mycc \ 179 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 180 | -H "content-type: application/json" \ 181 | -d '{ 182 | "fcn":"move", 183 | "args":["a","b","10"] 184 | }' 185 | ``` 186 | **NOTE:** Ensure that you save the Transaction ID from the response in order to pass this string in the subsequent query transactions. 187 | 188 | ### Chaincode Query 189 | 190 | ``` 191 | curl -s -X GET \ 192 | "http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \ 193 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 194 | -H "content-type: application/json" 195 | ``` 196 | 197 | ### Query Block by BlockNumber 198 | 199 | ``` 200 | curl -s -X GET \ 201 | "http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \ 202 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 203 | -H "content-type: application/json" 204 | ``` 205 | 206 | ### Query Transaction by TransactionID 207 | 208 | ``` 209 | curl -s -X GET http://localhost:4000/channels/mychannel/transactions/TRX_ID?peer=peer1 \ 210 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 211 | -H "content-type: application/json" 212 | ``` 213 | **NOTE**: Here the TRX_ID can be from any previous invoke transaction 214 | 215 | 216 | ### Query ChainInfo 217 | 218 | ``` 219 | curl -s -X GET \ 220 | "http://localhost:4000/channels/mychannel?peer=peer1" \ 221 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 222 | -H "content-type: application/json" 223 | ``` 224 | 225 | ### Query Installed chaincodes 226 | 227 | ``` 228 | curl -s -X GET \ 229 | "http://localhost:4000/chaincodes?peer=peer1&type=installed" \ 230 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 231 | -H "content-type: application/json" 232 | ``` 233 | 234 | ### Query Instantiated chaincodes 235 | 236 | ``` 237 | curl -s -X GET \ 238 | "http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \ 239 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 240 | -H "content-type: application/json" 241 | ``` 242 | 243 | ### Query Channels 244 | 245 | ``` 246 | curl -s -X GET \ 247 | "http://localhost:4000/channels?peer=peer1" \ 248 | -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ 249 | -H "content-type: application/json" 250 | ``` 251 | 252 | ### Network configuration considerations 253 | 254 | You have the ability to change configuration parameters by either directly editing the network-config.json file or provide an additional file for an alternative target network. The app uses an optional environment variable "TARGET_NETWORK" to control the configuration files to use. For example, if you deployed the target network on Amazon Web Services EC2, you can add a file "network-config-aws.json", and set the "TARGET_NETWORK" environment to 'aws'. The app will pick up the settings inside the "network-config-aws.json" file. 255 | 256 | #### IP Address** and PORT information 257 | 258 | If you choose to customize your docker-compose yaml file by hardcoding IP Addresses and PORT information for your peers and orderer, then you MUST also add the identical values into the network-config.json file. The paths shown below will need to be adjusted to match your docker-compose yaml file. 259 | 260 | ``` 261 | "orderer": { 262 | "url": "grpcs://x.x.x.x:7050", 263 | "server-hostname": "orderer0", 264 | "tls_cacerts": "../artifacts/tls/orderer/ca-cert.pem" 265 | }, 266 | "org1": { 267 | "ca": "http://x.x.x.x:7054", 268 | "peer1": { 269 | "requests": "grpcs://x.x.x.x:7051", 270 | "events": "grpcs://x.x.x.x:7053", 271 | ... 272 | }, 273 | "peer2": { 274 | "requests": "grpcs://x.x.x.x:7056", 275 | "events": "grpcs://x.x.x.x:7058", 276 | ... 277 | } 278 | }, 279 | "org2": { 280 | "ca": "http://x.x.x.x:8054", 281 | "peer1": { 282 | "requests": "grpcs://x.x.x.x:8051", 283 | "events": "grpcs://x.x.x.x:8053", 284 | ... }, 285 | "peer2": { 286 | "requests": "grpcs://x.x.x.x:8056", 287 | "events": "grpcs://x.x.x.x:8058", 288 | ... 289 | } 290 | } 291 | 292 | ``` 293 | 294 | #### Discover IP Address 295 | 296 | To retrieve the IP Address for one of your network entities, issue the following command: 297 | 298 | ``` 299 | # The following will return the IP Address for peer0 300 | docker inspect peer0 | grep IPAddress 301 | ``` 302 | 303 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. 304 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/api/chaincode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as express from 'express'; 18 | import log4js = require('log4js'); 19 | const logger = log4js.getLogger('SampleWebApp'); 20 | import hfc = require('fabric-client'); 21 | import * as jwt from 'jsonwebtoken'; 22 | import * as helper from '../lib/helper'; 23 | import * as channelApi from '../lib/channel'; 24 | import * as chainCodeApi from '../lib/chaincode'; 25 | import { RequestEx } from '../interfaces'; 26 | import { getErrorMessage } from './utils'; 27 | 28 | export default function chainCodeHandlers(app: express.Application) { 29 | 30 | async function installChainCode(req: RequestEx, res: express.Response) { 31 | logger.debug('==================== INSTALL CHAINCODE =================='); 32 | 33 | const peers = req.body.peers; 34 | const chaincodeName = req.body.chaincodeName; 35 | const chaincodePath = req.body.chaincodePath; 36 | const chaincodeVersion = req.body.chaincodeVersion; 37 | 38 | logger.debug('peers : ' + peers); // target peers list 39 | logger.debug('chaincodeName : ' + chaincodeName); 40 | logger.debug('chaincodePath : ' + chaincodePath); 41 | logger.debug('chaincodeVersion : ' + chaincodeVersion); 42 | 43 | if (!peers || peers.length === 0) { 44 | res.json(getErrorMessage('\'peers\'')); 45 | return; 46 | } 47 | if (!chaincodeName) { 48 | res.json(getErrorMessage('\'chaincodeName\'')); 49 | return; 50 | } 51 | if (!chaincodePath) { 52 | res.json(getErrorMessage('\'chaincodePath\'')); 53 | return; 54 | } 55 | if (!chaincodeVersion) { 56 | res.json(getErrorMessage('\'chaincodeVersion\'')); 57 | return; 58 | } 59 | 60 | const message = await chainCodeApi.installChaincode( 61 | peers, chaincodeName, chaincodePath, chaincodeVersion, req.username, req.orgname); 62 | 63 | res.send(message); 64 | } 65 | 66 | async function queryChainCode(req: RequestEx, res: express.Response) { 67 | const peer = req.query.peer; 68 | const installType = req.query.type; 69 | // TODO: add Constnats 70 | if (installType === 'installed') { 71 | logger.debug( 72 | '================ GET INSTALLED CHAINCODES ======================'); 73 | } else { 74 | logger.debug( 75 | '================ GET INSTANTIATED CHAINCODES ======================'); 76 | } 77 | 78 | const message = await chainCodeApi.getInstalledChaincodes( 79 | peer, installType, req.username, req.orgname); 80 | 81 | res.send(message); 82 | } 83 | 84 | const API_ENDPOINT_CHAINCODE_INSTALL = '/chaincodes'; 85 | const API_ENDPOINT_CHAINCODE_QUERY = '/chaincodes'; 86 | 87 | app.post(API_ENDPOINT_CHAINCODE_INSTALL, installChainCode); 88 | app.get(API_ENDPOINT_CHAINCODE_QUERY, queryChainCode); 89 | } 90 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/api/channel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as express from 'express'; 18 | import log4js = require('log4js'); 19 | const logger = log4js.getLogger('SampleWebApp'); 20 | import hfc = require('fabric-client'); 21 | import * as jwt from 'jsonwebtoken'; 22 | import * as helper from '../lib/helper'; 23 | import * as channelApi from '../lib/channel'; 24 | import { RequestEx } from '../interfaces'; 25 | import { getErrorMessage } from './utils'; 26 | 27 | export default function channelHandlers(app: express.Application) { 28 | 29 | async function createNewChannel(req: RequestEx, res: express.Response) { 30 | logger.info('<<<<<<<<<<<<<<<<< C R E A T E C H A N N E L >>>>>>>>>>>>>>>>>'); 31 | logger.debug('End point : /channels'); 32 | 33 | const channelName = req.body.channelName; 34 | const channelConfigPath = req.body.channelConfigPath; 35 | 36 | logger.debug('Channel name : ' + channelName); 37 | // ../artifacts/channel/mychannel.tx 38 | logger.debug('channelConfigPath : ' + channelConfigPath); 39 | 40 | if (!channelName) { 41 | res.json(getErrorMessage('\'channelName\'')); 42 | return; 43 | } 44 | if (!channelConfigPath) { 45 | res.json(getErrorMessage('\'channelConfigPath\'')); 46 | return; 47 | } 48 | 49 | const response = await channelApi.createChannel( 50 | channelName, channelConfigPath, req.username, req.orgname); 51 | 52 | res.send(response); 53 | } 54 | 55 | async function joinChannel(req: RequestEx, res: express.Response) { 56 | logger.info('<<<<<<<<<<<<<<<<< J O I N C H A N N E L >>>>>>>>>>>>>>>>>'); 57 | 58 | const channelName = req.params.channelName; 59 | const peers = req.body.peers; 60 | logger.debug('channelName : ' + channelName); 61 | logger.debug('peers : ' + peers); 62 | if (!channelName) { 63 | res.json(getErrorMessage('\'channelName\'')); 64 | return; 65 | } 66 | if (!peers || peers.length === 0) { 67 | res.json(getErrorMessage('\'peers\'')); 68 | return; 69 | } 70 | 71 | const message = await channelApi.joinChannel(channelName, peers, req.username, req.orgname); 72 | res.send(message); 73 | } 74 | 75 | async function instantiateChainCode(req: RequestEx, res: express.Response) { 76 | logger.debug('==================== INSTANTIATE CHAINCODE =================='); 77 | const chaincodeName = req.body.chaincodeName; 78 | const chaincodeVersion = req.body.chaincodeVersion; 79 | const channelName = req.params.channelName; 80 | const fcn = req.body.fcn; 81 | const args = req.body.args; 82 | logger.debug('channelName : ' + channelName); 83 | logger.debug('chaincodeName : ' + chaincodeName); 84 | logger.debug('chaincodeVersion : ' + chaincodeVersion); 85 | logger.debug('fcn : ' + fcn); 86 | logger.debug('args : ' + args); 87 | if (!chaincodeName) { 88 | res.json(getErrorMessage('\'chaincodeName\'')); 89 | return; 90 | } 91 | if (!chaincodeVersion) { 92 | res.json(getErrorMessage('\'chaincodeVersion\'')); 93 | return; 94 | } 95 | if (!channelName) { 96 | res.json(getErrorMessage('\'channelName\'')); 97 | return; 98 | } 99 | if (!args) { 100 | res.json(getErrorMessage('\'args\'')); 101 | return; 102 | } 103 | 104 | const message = await channelApi.instantiateChainCode( 105 | channelName, chaincodeName, chaincodeVersion, fcn, args, req.username, req.orgname); 106 | res.send(message); 107 | } 108 | 109 | async function invokeChainCode(req: RequestEx, res: express.Response) { 110 | logger.debug('==================== INVOKE ON CHAINCODE =================='); 111 | const peers = req.body.peers; 112 | const chaincodeName = req.params.chaincodeName; 113 | const channelName = req.params.channelName; 114 | const fcn = req.body.fcn; 115 | const args = req.body.args; 116 | logger.debug('channelName : ' + channelName); 117 | logger.debug('chaincodeName : ' + chaincodeName); 118 | logger.debug('fcn : ' + fcn); 119 | logger.debug('args : ' + args); 120 | if (!chaincodeName) { 121 | res.json(getErrorMessage('\'chaincodeName\'')); 122 | return; 123 | } 124 | if (!channelName) { 125 | res.json(getErrorMessage('\'channelName\'')); 126 | return; 127 | } 128 | if (!fcn) { 129 | res.json(getErrorMessage('\'fcn\'')); 130 | return; 131 | } 132 | if (!args) { 133 | res.json(getErrorMessage('\'args\'')); 134 | return; 135 | } 136 | 137 | const message = await channelApi.invokeChaincode( 138 | peers, channelName, chaincodeName, fcn, args, req.username, req.orgname); 139 | 140 | res.send(message); 141 | } 142 | 143 | async function queryChainCode(req: RequestEx, res: express.Response) { 144 | const channelName = req.params.channelName; 145 | const chaincodeName = req.params.chaincodeName; 146 | let args = req.query.args; 147 | const fcn = req.query.fcn; 148 | const peer = req.query.peer; 149 | 150 | logger.debug('channelName : ' + channelName); 151 | logger.debug('chaincodeName : ' + chaincodeName); 152 | logger.debug('fcn : ' + fcn); 153 | logger.debug('args : ' + args); 154 | 155 | if (!chaincodeName) { 156 | res.json(getErrorMessage('\'chaincodeName\'')); 157 | return; 158 | } 159 | if (!channelName) { 160 | res.json(getErrorMessage('\'channelName\'')); 161 | return; 162 | } 163 | if (!fcn) { 164 | res.json(getErrorMessage('\'fcn\'')); 165 | return; 166 | } 167 | if (!args) { 168 | res.json(getErrorMessage('\'args\'')); 169 | return; 170 | } 171 | 172 | args = args.replace(/'/g, '"'); 173 | args = JSON.parse(args); 174 | logger.debug(args); 175 | 176 | const message = await channelApi.queryChaincode( 177 | peer, channelName, chaincodeName, args, fcn, req.username, req.orgname); 178 | 179 | res.send(message); 180 | } 181 | 182 | async function queryByBlockNumber(req: RequestEx, res: express.Response) { 183 | logger.debug('==================== GET BLOCK BY NUMBER =================='); 184 | const blockId = req.params.blockId; 185 | const peer = req.query.peer; 186 | logger.debug('channelName : ' + req.params.channelName); 187 | logger.debug('BlockID : ' + blockId); 188 | logger.debug('Peer : ' + peer); 189 | if (!blockId) { 190 | res.json(getErrorMessage('\'blockId\'')); 191 | return; 192 | } 193 | 194 | const message = await channelApi.getBlockByNumber(peer, blockId, req.username, req.orgname); 195 | res.send(message); 196 | } 197 | 198 | async function queryByTransactionId(req: RequestEx, res: express.Response) { 199 | logger.debug( 200 | '================ GET TRANSACTION BY TRANSACTION_ID ======================' 201 | ); 202 | logger.debug('channelName : ' + req.params.channelName); 203 | const trxnId = req.params.trxnId; 204 | const peer = req.query.peer; 205 | if (!trxnId) { 206 | res.json(getErrorMessage('\'trxnId\'')); 207 | return; 208 | } 209 | 210 | const message = await channelApi.getTransactionByID( 211 | peer, trxnId, req.username, req.orgname); 212 | 213 | res.send(message); 214 | } 215 | 216 | async function queryChannelInfo(req: RequestEx, res: express.Response) { 217 | logger.debug( 218 | '================ GET CHANNEL INFORMATION ======================'); 219 | logger.debug('channelName : ' + req.params.channelName); 220 | const peer = req.query.peer; 221 | 222 | const message = await channelApi.getChainInfo(peer, req.username, req.orgname); 223 | 224 | res.send(message); 225 | } 226 | 227 | async function queryChannels(req: RequestEx, res: express.Response) { 228 | logger.debug('================ GET CHANNELS ======================'); 229 | logger.debug('peer: ' + req.query.peer); 230 | const peer = req.query.peer; 231 | if (!peer) { 232 | res.json(getErrorMessage('\'peer\'')); 233 | return; 234 | } 235 | 236 | const message = await channelApi.getChannels(peer, req.username, req.orgname); 237 | res.send(message); 238 | } 239 | 240 | const API_ENDPOINT_CHANNEL_CREATE = '/channels'; 241 | const API_ENDPOINT_CHANNEL_JOIN = '/channels/:channelName/peers'; 242 | const API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE = '/channels/:channelName/chaincodes'; 243 | const API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE = 244 | '/channels/:channelName/chaincodes/:chaincodeName'; 245 | const API_ENDPOINT_CHANNEL_QUERY_CHAINCODE = '/channels/:channelName/chaincodes/:chaincodeName'; 246 | const API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER = '/channels/:channelName/blocks/:blockId'; 247 | const API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID 248 | = '/channels/:channelName/transactions/:trxnId'; 249 | const API_ENDPOINT_CHANNEL_INFO = '/channels/:channelName'; 250 | const API_ENDPOINT_CHANNEL_QUERY = '/channels'; 251 | 252 | app.post(API_ENDPOINT_CHANNEL_CREATE, createNewChannel); 253 | app.post(API_ENDPOINT_CHANNEL_JOIN, joinChannel); 254 | app.post(API_ENDPOINT_CHANNEL_INSTANTIATE_CHAINCODE, instantiateChainCode); 255 | app.post(API_ENDPOINT_CHANNEL_INVOKE_CHAINCODE, invokeChainCode); 256 | app.get(API_ENDPOINT_CHANNEL_QUERY_CHAINCODE, queryChainCode); 257 | app.get(API_ENDPOINT_CHANNEL_QUERY_BY_BLOCKNUMBER, queryByBlockNumber); 258 | app.get(API_ENDPOINT_CHANNEL_QUERY_BY_TRANSACTIONID, queryByTransactionId); 259 | app.get(API_ENDPOINT_CHANNEL_INFO, queryChannelInfo); 260 | app.get(API_ENDPOINT_CHANNEL_QUERY, queryChannels); 261 | } 262 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/api/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as express from 'express'; 18 | import userHandlers from './users'; 19 | import channelHandlers from './channel'; 20 | import chainCodeHandlers from './chaincode'; 21 | 22 | export default function entryPoint(app: express.Application) { 23 | // various handlers 24 | userHandlers(app); 25 | channelHandlers(app); 26 | chainCodeHandlers(app); 27 | } 28 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/api/users.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { RequestEx } from '../interfaces'; 18 | import * as express from 'express'; 19 | import log4js = require('log4js'); 20 | const logger = log4js.getLogger('SampleWebApp'); 21 | import hfc = require('fabric-client'); 22 | import * as jwt from 'jsonwebtoken'; 23 | import * as helper from '../lib/helper'; 24 | import { getErrorMessage } from './utils'; 25 | 26 | export default function userHandlers(app: express.Application) { 27 | 28 | async function registerUser(req: RequestEx, res: express.Response) { 29 | const username = req.body.username; 30 | const orgName = req.body.orgName; 31 | 32 | logger.debug('End point : /users'); 33 | logger.debug('User name : ' + username); 34 | logger.debug('Org name : ' + orgName); 35 | 36 | if (!username) { 37 | res.json(getErrorMessage('\'username\'')); 38 | return; 39 | } 40 | if (!orgName) { 41 | res.json(getErrorMessage('\'orgName\'')); 42 | return; 43 | } 44 | const token = jwt.sign({ 45 | exp: Math.floor(Date.now() / 1000) + parseInt( 46 | hfc.getConfigSetting('jwt_expiretime'), 10), 47 | username, 48 | orgName 49 | }, app.get('secret')); 50 | 51 | const response = await helper.getRegisteredUsers(username, orgName); 52 | 53 | if (response && typeof response !== 'string') { 54 | res.json({ 55 | success: true, 56 | token 57 | }); 58 | } else { 59 | res.json({ 60 | success: false, 61 | message: response 62 | }); 63 | } 64 | } 65 | 66 | const API_ENDPOINT_REGISTER_USER = '/users'; 67 | 68 | app.post(API_ENDPOINT_REGISTER_USER, registerUser); 69 | } 70 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/api/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | export function getErrorMessage(field: string) { 18 | const response = { 19 | success: false, 20 | message: field + ' field is missing or Invalid in the request' 21 | }; 22 | return response; 23 | } 24 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/app.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import log4js = require('log4js'); 18 | import * as util from 'util'; 19 | import * as http from 'http'; 20 | import * as express from 'express'; 21 | import * as jwt from 'jsonwebtoken'; 22 | import * as bodyParser from 'body-parser'; 23 | import expressJWT = require('express-jwt'); 24 | // tslint:disable-next-line:no-var-requires 25 | const bearerToken = require('express-bearer-token'); 26 | import cors = require('cors'); 27 | import hfc = require('fabric-client'); 28 | import * as helper from './lib/helper'; 29 | import { RequestEx } from './interfaces'; 30 | import api from './api'; 31 | 32 | helper.init(); 33 | 34 | const SERVER_HOST = process.env.HOST || hfc.getConfigSetting('host'); 35 | const SERVER_PORT = process.env.PORT || hfc.getConfigSetting('port'); 36 | 37 | const logger = log4js.getLogger('SampleWebApp'); 38 | 39 | // create express App 40 | const app = express(); 41 | 42 | app.options('*', cors()); 43 | app.use(cors()); 44 | app.use(bodyParser.json()); 45 | app.use(bodyParser.urlencoded({ 46 | extended: false 47 | })); 48 | app.set('secret', 'thisismysecret'); 49 | app.use(expressJWT({ 50 | secret: 'thisismysecret' 51 | }).unless({ 52 | path: ['/users'] 53 | })); 54 | app.use(bearerToken()); 55 | 56 | app.use((req: RequestEx, res, next) => { 57 | if (req.originalUrl.indexOf('/users') >= 0) { 58 | return next(); 59 | } 60 | 61 | const token = req.token; 62 | jwt.verify(token, app.get('secret'), (err: Error, decoded: any) => { 63 | if (err) { 64 | res.send({ 65 | success: false, 66 | message: 'Failed to authenticate token. Make sure to include the ' + 67 | 'token returned from /users call in the authorization header ' + 68 | ' as a Bearer token' 69 | }); 70 | return; 71 | } else { 72 | // add the decoded user name and org name to the request object 73 | // for the downstream code to use 74 | req.username = decoded.username; 75 | req.orgname = decoded.orgName; 76 | logger.debug( 77 | util.format('Decoded from JWT token: username - %s, orgname - %s', 78 | decoded.username, decoded.orgName)); 79 | return next(); 80 | } 81 | }); 82 | }); 83 | 84 | // configure various routes 85 | api(app); 86 | 87 | const server = http.createServer(app); 88 | server.listen(SERVER_PORT); 89 | 90 | logger.info('****************** SERVER STARTED ************************'); 91 | logger.info('************** http://' + SERVER_HOST + ':' + SERVER_PORT + ' ******************'); 92 | server.timeout = 240000; 93 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/app_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "localhost", 3 | "port": "4000", 4 | "jwt_expiretime": "36000", 5 | "channelName": "mychannel", 6 | "CC_SRC_PATH": "../artifacts", 7 | "keyValueStore": "/tmp/fabric-client-kvs", 8 | "eventWaitTime": "30000", 9 | "admins": [{ 10 | "username": "admin", 11 | "secret": "adminpw" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/artifacts: -------------------------------------------------------------------------------- 1 | ../artifacts -------------------------------------------------------------------------------- /balance-transfer-app/typescript/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as util from 'util'; 18 | 19 | let file = 'network-config%s.json'; 20 | 21 | const env = process.env.TARGET_NETWORK; 22 | if (env) { 23 | file = util.format(file, '-' + env); 24 | } else { 25 | file = util.format(file, ''); 26 | } 27 | 28 | export default { 29 | networkConfigFile: file 30 | }; 31 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/interfaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as express from 'express'; 18 | 19 | export interface RequestEx extends express.Request { 20 | username?: any; 21 | orgname?: any; 22 | token?: any; 23 | } 24 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/lib/chaincode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as util from 'util'; 18 | import * as fs from 'fs'; 19 | import * as path from 'path'; 20 | import * as helper from './helper'; 21 | 22 | // tslint:disable-next-line:no-var-requires 23 | const config = require('../app_config.json'); 24 | const logger = helper.getLogger('ChaincodeApi'); 25 | 26 | function buildTarget(peer: string, org: string): Peer { 27 | let target: Peer = null; 28 | if (typeof peer !== 'undefined') { 29 | const targets: Peer[] = helper.newPeers([peer], org); 30 | if (targets && targets.length > 0) { 31 | target = targets[0]; 32 | } 33 | } 34 | 35 | return target; 36 | } 37 | 38 | export async function installChaincode( 39 | peers: string[], chaincodeName: string, chaincodePath: string, 40 | chaincodeVersion: string, username: string, org: string) { 41 | 42 | logger.debug( 43 | '\n============ Install chaincode on organizations ============\n'); 44 | 45 | helper.setupChaincodeDeploy(); 46 | 47 | const channel = helper.getChannelForOrg(org); 48 | const client = helper.getClientForOrg(org); 49 | 50 | const admin = await helper.getOrgAdmin(org); 51 | 52 | const request = { 53 | targets: helper.newPeers(peers, org), 54 | chaincodePath, 55 | chaincodeId: chaincodeName, 56 | chaincodeVersion 57 | }; 58 | 59 | try { 60 | 61 | const results = await client.installChaincode(request); 62 | 63 | const proposalResponses = results[0]; 64 | const proposal = results[1]; 65 | let allGood = true; 66 | 67 | proposalResponses.forEach((pr) => { 68 | let oneGood = false; 69 | if (pr.response && pr.response.status === 200) { 70 | oneGood = true; 71 | logger.info('install proposal was good'); 72 | } else { 73 | logger.error('install proposal was bad'); 74 | } 75 | allGood = allGood && oneGood; 76 | }); 77 | 78 | if (allGood) { 79 | logger.info(util.format( 80 | 'Successfully sent install Proposal and received ProposalResponse: Status - %s', 81 | proposalResponses[0].response.status)); 82 | logger.debug('\nSuccessfully Installed chaincode on organization ' + org + 83 | '\n'); 84 | return 'Successfully Installed chaincode on organization ' + org; 85 | } else { 86 | logger.error( 87 | // tslint:disable-next-line:max-line-length 88 | 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...' 89 | ); 90 | // tslint:disable-next-line:max-line-length 91 | return 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...'; 92 | } 93 | 94 | } catch (err) { 95 | logger.error('Failed to send install proposal due to error: ' + err.stack ? 96 | err.stack : err); 97 | throw new Error('Failed to send install proposal due to error: ' + err.stack ? 98 | err.stack : err); 99 | } 100 | } 101 | 102 | export async function getInstalledChaincodes( 103 | peer: string, type: string, username: string, org: string) { 104 | 105 | const target = buildTarget(peer, org); 106 | const channel = helper.getChannelForOrg(org); 107 | const client = helper.getClientForOrg(org); 108 | 109 | const user = await helper.getOrgAdmin(org); 110 | 111 | try { 112 | 113 | let response: ChaincodeQueryResponse = null; 114 | 115 | if (type === 'installed') { 116 | response = await client.queryInstalledChaincodes(target); 117 | } else { 118 | response = await channel.queryInstantiatedChaincodes(target); 119 | } 120 | 121 | if (response) { 122 | if (type === 'installed') { 123 | logger.debug('<<< Installed Chaincodes >>>'); 124 | } else { 125 | logger.debug('<<< Instantiated Chaincodes >>>'); 126 | } 127 | 128 | const details: string[] = []; 129 | response.chaincodes.forEach((c) => { 130 | logger.debug('name: ' + c.name + ', version: ' + 131 | c.version + ', path: ' + c.path 132 | ); 133 | details.push('name: ' + c.name + ', version: ' + 134 | c.version + ', path: ' + c.path 135 | ); 136 | }); 137 | 138 | return details; 139 | } else { 140 | logger.error('response is null'); 141 | return 'response is null'; 142 | } 143 | 144 | } catch (err) { 145 | logger.error('Failed to query with error:' + err.stack ? err.stack : err); 146 | return 'Failed to query with error:' + err.stack ? err.stack : err; 147 | } 148 | } -------------------------------------------------------------------------------- /balance-transfer-app/typescript/lib/channel.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as util from 'util'; 18 | import * as fs from 'fs'; 19 | import * as path from 'path'; 20 | import * as helper from './helper'; 21 | const logger = helper.getLogger('ChannelApi'); 22 | // tslint:disable-next-line:no-var-requires 23 | const config = require('../app_config.json'); 24 | 25 | const allEventhubs: EventHub[] = []; 26 | 27 | function buildTarget(peer: string, org: string): Peer { 28 | let target: Peer = null; 29 | if (typeof peer !== 'undefined') { 30 | const targets: Peer[] = helper.newPeers([peer], org); 31 | if (targets && targets.length > 0) { 32 | target = targets[0]; 33 | } 34 | } 35 | 36 | return target; 37 | } 38 | 39 | // Attempt to send a request to the orderer with the sendCreateChain method 40 | export async function createChannel( 41 | channelName: string, channelConfigPath: string, username: string, orgName: string) { 42 | 43 | logger.debug('\n====== Creating Channel \'' + channelName + '\' ======\n'); 44 | 45 | const client = helper.getClientForOrg(orgName); 46 | const channel = helper.getChannelForOrg(orgName); 47 | 48 | // read in the envelope for the channel config raw bytes 49 | const envelope = fs.readFileSync(path.join(__dirname, channelConfigPath)); 50 | // extract the channel config bytes from the envelope to be signed 51 | const channelConfig = client.extractChannelConfig(envelope); 52 | 53 | // Acting as a client in the given organization provided with "orgName" param 54 | const admin = await helper.getOrgAdmin(orgName); 55 | 56 | logger.debug(util.format('Successfully acquired admin user for the organization "%s"', 57 | orgName)); 58 | 59 | // sign the channel config bytes as "endorsement", this is required by 60 | // the orderer's channel creation policy 61 | const signature = client.signChannelConfig(channelConfig); 62 | 63 | const request = { 64 | config: channelConfig, 65 | signatures: [signature], 66 | name: channelName, 67 | orderer: channel.getOrderers()[0], 68 | txId: client.newTransactionID() 69 | }; 70 | 71 | try { 72 | const response = await client.createChannel(request); 73 | 74 | if (response && response.status === 'SUCCESS') { 75 | logger.debug('Successfully created the channel.'); 76 | return { 77 | success: true, 78 | message: 'Channel \'' + channelName + '\' created Successfully' 79 | }; 80 | } else { 81 | logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName + 82 | '\' !!!!!!!!!\n\n'); 83 | throw new Error('Failed to create the channel \'' + channelName + '\''); 84 | } 85 | 86 | } catch (err) { 87 | logger.error('\n!!!!!!!!! Failed to create the channel \'' + channelName + 88 | '\' !!!!!!!!!\n\n'); 89 | throw new Error('Failed to create the channel \'' + channelName + '\''); 90 | } 91 | } 92 | 93 | export async function joinChannel( 94 | channelName: string, peers: string[], username: string, org: string) { 95 | 96 | // on process exit, always disconnect the event hub 97 | const closeConnections = (isSuccess: boolean) => { 98 | if (isSuccess) { 99 | logger.debug('\n============ Join Channel is SUCCESS ============\n'); 100 | } else { 101 | logger.debug('\n!!!!!!!! ERROR: Join Channel FAILED !!!!!!!!\n'); 102 | } 103 | logger.debug(''); 104 | 105 | allEventhubs.forEach((hub) => { 106 | console.log(hub); 107 | if (hub && hub.isconnected()) { 108 | hub.disconnect(); 109 | } 110 | }); 111 | }; 112 | 113 | // logger.debug('\n============ Join Channel ============\n') 114 | logger.info(util.format( 115 | 'Calling peers in organization "%s" to join the channel', org)); 116 | 117 | const client = helper.getClientForOrg(org); 118 | const channel = helper.getChannelForOrg(org); 119 | 120 | const admin = await helper.getOrgAdmin(org); 121 | 122 | logger.info(util.format('received member object for admin of the organization "%s": ', org)); 123 | const request = { 124 | txId: client.newTransactionID() 125 | }; 126 | 127 | const genesisBlock = await channel.getGenesisBlock(request); 128 | 129 | const request2 = { 130 | targets: helper.newPeers(peers, org), 131 | txId: client.newTransactionID(), 132 | block: genesisBlock 133 | }; 134 | 135 | const eventhubs = helper.newEventHubs(peers, org); 136 | eventhubs.forEach((eh) => { 137 | eh.connect(); 138 | allEventhubs.push(eh); 139 | }); 140 | 141 | const eventPromises: Array> = []; 142 | eventhubs.forEach((eh) => { 143 | const txPromise = new Promise((resolve, reject) => { 144 | const handle = setTimeout(reject, parseInt(config.eventWaitTime, 10)); 145 | eh.registerBlockEvent((block: any) => { 146 | clearTimeout(handle); 147 | // in real-world situations, a peer may have more than one channels so 148 | // we must check that this block came from the channel we asked the peer to join 149 | if (block.data.data.length === 1) { 150 | // Config block must only contain one transaction 151 | const channel_header = block.data.data[0].payload.header.channel_header; 152 | if (channel_header.channel_id === channelName) { 153 | resolve(); 154 | } else { 155 | reject(); 156 | } 157 | } 158 | }); 159 | }); 160 | eventPromises.push(txPromise); 161 | }); 162 | 163 | const sendPromise = channel.joinChannel(request2); 164 | const results = await Promise.all([sendPromise].concat(eventPromises)); 165 | 166 | logger.debug(util.format('Join Channel R E S P O N S E : %j', results)); 167 | if (results[0] && results[0][0] && results[0][0].response && results[0][0] 168 | .response.status === 200) { 169 | logger.info(util.format( 170 | 'Successfully joined peers in organization %s to the channel \'%s\'', 171 | org, channelName)); 172 | closeConnections(true); 173 | const response = { 174 | success: true, 175 | message: util.format( 176 | 'Successfully joined peers in organization %s to the channel \'%s\'', 177 | org, channelName) 178 | }; 179 | return response; 180 | } else { 181 | logger.error(' Failed to join channel'); 182 | closeConnections(false); 183 | throw new Error('Failed to join channel'); 184 | } 185 | } 186 | 187 | export async function instantiateChainCode( 188 | channelName: string, chaincodeName: string, chaincodeVersion: string, 189 | functionName: string, args: string[], username: string, org: string) { 190 | 191 | logger.debug('\n============ Instantiate chaincode on organization ' + org + 192 | ' ============\n'); 193 | 194 | const channel = helper.getChannelForOrg(org); 195 | const client = helper.getClientForOrg(org); 196 | 197 | const admin = await helper.getOrgAdmin(org); 198 | await channel.initialize(); 199 | 200 | const txId = client.newTransactionID(); 201 | // send proposal to endorser 202 | const request = { 203 | chaincodeId: chaincodeName, 204 | chaincodeVersion, 205 | args, 206 | txId, 207 | fcn: functionName 208 | }; 209 | 210 | try { 211 | 212 | const results = await channel.sendInstantiateProposal(request); 213 | 214 | const proposalResponses = results[0]; 215 | const proposal = results[1]; 216 | 217 | let allGood = true; 218 | 219 | proposalResponses.forEach((pr) => { 220 | let oneGood = false; 221 | if (pr.response && pr.response.status === 200) { 222 | oneGood = true; 223 | logger.info('install proposal was good'); 224 | } else { 225 | logger.error('install proposal was bad'); 226 | } 227 | allGood = allGood && oneGood; 228 | }); 229 | 230 | if (allGood) { 231 | logger.info(util.format( 232 | // tslint:disable-next-line:max-line-length 233 | 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 234 | proposalResponses[0].response.status, proposalResponses[0].response.message, 235 | proposalResponses[0].response.payload, proposalResponses[0].endorsement 236 | .signature)); 237 | 238 | const request2 = { 239 | proposalResponses, 240 | proposal 241 | }; 242 | // set the transaction listener and set a timeout of 30sec 243 | // if the transaction did not get committed within the timeout period, 244 | // fail the test 245 | const deployId = txId.getTransactionID(); 246 | const ORGS = helper.getOrgs(); 247 | 248 | const eh = client.newEventHub(); 249 | const data = fs.readFileSync(path.join(__dirname, ORGS[org].peers['peer1'][ 250 | 'tls_cacerts' 251 | ])); 252 | 253 | eh.setPeerAddr(ORGS[org].peers['peer1']['events'], { 254 | 'pem': Buffer.from(data).toString(), 255 | 'ssl-target-name-override': ORGS[org].peers['peer1']['server-hostname'] 256 | }); 257 | eh.connect(); 258 | 259 | const txPromise: Promise = new Promise((resolve, reject) => { 260 | const handle = setTimeout(() => { 261 | eh.disconnect(); 262 | reject(); 263 | }, 30000); 264 | 265 | eh.registerTxEvent(deployId, (tx, code) => { 266 | // logger.info( 267 | // 'The chaincode instantiate transaction has been committed on peer ' + 268 | // eh._ep._endpoint.addr); 269 | 270 | clearTimeout(handle); 271 | eh.unregisterTxEvent(deployId); 272 | eh.disconnect(); 273 | 274 | if (code !== 'VALID') { 275 | logger.error( 276 | 'The chaincode instantiate transaction was invalid, code = ' + code); 277 | reject(); 278 | } else { 279 | logger.info('The chaincode instantiate transaction was valid.'); 280 | resolve(); 281 | } 282 | }); 283 | }); 284 | 285 | const sendPromise = channel.sendTransaction(request2); 286 | const transactionResults = await Promise.all([sendPromise].concat([txPromise])); 287 | 288 | const response = transactionResults[0]; 289 | if (response.status === 'SUCCESS') { 290 | logger.info('Successfully sent transaction to the orderer.'); 291 | return 'Chaincode Instantiation is SUCCESS'; 292 | } else { 293 | logger.error('Failed to order the transaction. Error code: ' + response.status); 294 | return 'Failed to order the transaction. Error code: ' + response.status; 295 | } 296 | 297 | } else { 298 | logger.error( 299 | // tslint:disable-next-line:max-line-length 300 | 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...' 301 | ); 302 | // tslint:disable-next-line:max-line-length 303 | return 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...'; 304 | } 305 | 306 | } catch (err) { 307 | logger.error('Failed to send instantiate due to error: ' + err.stack ? err 308 | .stack : err); 309 | return 'Failed to send instantiate due to error: ' + err.stack ? err.stack : 310 | err; 311 | } 312 | } 313 | 314 | export async function invokeChaincode( 315 | peerNames: string[], channelName: string, 316 | chaincodeName: string, fcn: string, args: string[], username: string, org: string) { 317 | 318 | logger.debug( 319 | util.format('\n============ invoke transaction on organization %s ============\n', org)); 320 | 321 | const client = helper.getClientForOrg(org); 322 | const channel = helper.getChannelForOrg(org); 323 | const targets = (peerNames) ? helper.newPeers(peerNames, org) : undefined; 324 | 325 | const user = await helper.getRegisteredUsers(username, org); 326 | 327 | const txId = client.newTransactionID(); 328 | logger.debug(util.format('Sending transaction "%j"', txId)); 329 | // send proposal to endorser 330 | const request: ChaincodeInvokeRequest = { 331 | chaincodeId: chaincodeName, 332 | fcn, 333 | args, 334 | txId 335 | }; 336 | 337 | if (targets) { 338 | request.targets = targets; 339 | } 340 | 341 | try { 342 | 343 | const results = await channel.sendTransactionProposal(request); 344 | 345 | const proposalResponses = results[0]; 346 | const proposal = results[1]; 347 | let allGood = true; 348 | 349 | proposalResponses.forEach((pr) => { 350 | let oneGood = false; 351 | if (pr.response && pr.response.status === 200) { 352 | oneGood = true; 353 | logger.info('transaction proposal was good'); 354 | } else { 355 | logger.error('transaction proposal was bad'); 356 | } 357 | allGood = allGood && oneGood; 358 | }); 359 | 360 | if (allGood) { 361 | logger.debug(util.format( 362 | // tslint:disable-next-line:max-line-length 363 | 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', 364 | proposalResponses[0].response.status, proposalResponses[0].response.message, 365 | proposalResponses[0].response.payload, proposalResponses[0].endorsement 366 | .signature)); 367 | 368 | const request2 = { 369 | proposalResponses, 370 | proposal 371 | }; 372 | 373 | // set the transaction listener and set a timeout of 30sec 374 | // if the transaction did not get committed within the timeout period, 375 | // fail the test 376 | const transactionID = txId.getTransactionID(); 377 | const eventPromises: Array> = []; 378 | 379 | if (!peerNames) { 380 | peerNames = channel.getPeers().map((peer) => { 381 | return peer.getName(); 382 | }); 383 | } 384 | 385 | const eventhubs = helper.newEventHubs(peerNames, org); 386 | 387 | eventhubs.forEach((eh: EventHub) => { 388 | eh.connect(); 389 | 390 | const txPromise = new Promise((resolve, reject) => { 391 | const handle = setTimeout(() => { 392 | eh.disconnect(); 393 | reject(); 394 | }, 30000); 395 | 396 | eh.registerTxEvent(transactionID, (tx: string, code: string) => { 397 | clearTimeout(handle); 398 | eh.unregisterTxEvent(transactionID); 399 | eh.disconnect(); 400 | 401 | if (code !== 'VALID') { 402 | logger.error( 403 | 'The balance transfer transaction was invalid, code = ' + code); 404 | reject(); 405 | } else { 406 | // logger.info( 407 | // 'The balance transfer transaction has been committed on peer ' + 408 | // eh._ep._endpoint.addr); 409 | resolve(); 410 | } 411 | }); 412 | }); 413 | eventPromises.push(txPromise); 414 | }); 415 | 416 | const sendPromise = channel.sendTransaction(request2); 417 | const results2 = await Promise.all([sendPromise].concat(eventPromises)); 418 | 419 | logger.debug(' event promise all complete and testing complete'); 420 | 421 | if (results2[0].status === 'SUCCESS') { 422 | logger.info('Successfully sent transaction to the orderer.'); 423 | return txId.getTransactionID(); 424 | } else { 425 | logger.error('Failed to order the transaction. Error code: ' + results2[0].status); 426 | return 'Failed to order the transaction. Error code: ' + results2[0].status; 427 | } 428 | } else { 429 | logger.error( 430 | // tslint:disable-next-line:max-line-length 431 | 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...' 432 | ); 433 | // tslint:disable-next-line:max-line-length 434 | return 'Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...'; 435 | } 436 | 437 | } catch (err) { 438 | logger.error('Failed to send transaction due to error: ' + err.stack ? err 439 | .stack : err); 440 | return 'Failed to send transaction due to error: ' + err.stack ? err.stack : 441 | err; 442 | } 443 | } 444 | 445 | export async function queryChaincode( 446 | peer: string, channelName: string, chaincodeName: string, 447 | args: string[], fcn: string, username: string, org: string) { 448 | 449 | const channel = helper.getChannelForOrg(org); 450 | const client = helper.getClientForOrg(org); 451 | const target = buildTarget(peer, org); 452 | 453 | const user = await helper.getRegisteredUsers(username, org); 454 | 455 | const txId = client.newTransactionID(); 456 | // send query 457 | const request: ChaincodeQueryRequest = { 458 | chaincodeId: chaincodeName, 459 | txId, 460 | fcn, 461 | args 462 | }; 463 | 464 | if (target) { 465 | request.targets = [target]; 466 | } 467 | 468 | try { 469 | const responsePayloads = await channel.queryByChaincode(request); 470 | 471 | if (responsePayloads) { 472 | 473 | responsePayloads.forEach((rp) => { 474 | logger.info(args[0] + ' now has ' + rp.toString('utf8') + 475 | ' after the move'); 476 | return args[0] + ' now has ' + rp.toString('utf8') + 477 | ' after the move'; 478 | }); 479 | 480 | } else { 481 | logger.error('response_payloads is null'); 482 | return 'response_payloads is null'; 483 | } 484 | } catch (err) { 485 | logger.error('Failed to send query due to error: ' + err.stack ? err.stack : 486 | err); 487 | return 'Failed to send query due to error: ' + err.stack ? err.stack : err; 488 | } 489 | } 490 | 491 | export async function getBlockByNumber( 492 | peer: string, blockNumber: string, username: string, org: string) { 493 | 494 | const target = buildTarget(peer, org); 495 | const channel = helper.getChannelForOrg(org); 496 | 497 | const user = await helper.getRegisteredUsers(username, org); 498 | 499 | try { 500 | 501 | const responsePayloads = await channel.queryBlock(parseInt(blockNumber, 10), target); 502 | 503 | if (responsePayloads) { 504 | logger.debug(responsePayloads); 505 | return responsePayloads; // response_payloads.data.data[0].buffer; 506 | } else { 507 | logger.error('response_payloads is null'); 508 | return 'response_payloads is null'; 509 | } 510 | 511 | } catch (err) { 512 | logger.error('Failed to query with error:' + err.stack ? err.stack : err); 513 | return 'Failed to query with error:' + err.stack ? err.stack : err; 514 | } 515 | } 516 | 517 | export async function getTransactionByID( 518 | peer: string, trxnID: string, username: string, org: string) { 519 | 520 | const target = buildTarget(peer, org); 521 | const channel = helper.getChannelForOrg(org); 522 | 523 | const user = await helper.getRegisteredUsers(username, org); 524 | 525 | try { 526 | 527 | const responsePayloads = await channel.queryTransaction(trxnID, target); 528 | 529 | if (responsePayloads) { 530 | logger.debug(responsePayloads); 531 | return responsePayloads; 532 | } else { 533 | logger.error('response_payloads is null'); 534 | return 'response_payloads is null'; 535 | } 536 | 537 | } catch (err) { 538 | logger.error('Failed to query with error:' + err.stack ? err.stack : err); 539 | return 'Failed to query with error:' + err.stack ? err.stack : err; 540 | } 541 | } 542 | 543 | export async function getChainInfo(peer: string, username: string, org: string) { 544 | 545 | const target = buildTarget(peer, org); 546 | const channel = helper.getChannelForOrg(org); 547 | 548 | const user = await helper.getRegisteredUsers(username, org); 549 | 550 | try { 551 | 552 | const blockChainInfo = await channel.queryInfo(target); 553 | 554 | if (blockChainInfo) { 555 | // FIXME: Save this for testing 'getBlockByHash' ? 556 | logger.debug('==========================================='); 557 | logger.debug(blockChainInfo.currentBlockHash); 558 | logger.debug('==========================================='); 559 | // logger.debug(blockchainInfo); 560 | return blockChainInfo; 561 | } else { 562 | logger.error('blockChainInfo is null'); 563 | return 'blockChainInfo is null'; 564 | } 565 | 566 | } catch (err) { 567 | logger.error('Failed to query with error:' + err.stack ? err.stack : err); 568 | return 'Failed to query with error:' + err.stack ? err.stack : err; 569 | } 570 | } 571 | 572 | export async function getChannels(peer: string, username: string, org: string) { 573 | const target = buildTarget(peer, org); 574 | const channel = helper.getChannelForOrg(org); 575 | const client = helper.getClientForOrg(org); 576 | 577 | const user = await helper.getRegisteredUsers(username, org); 578 | 579 | try { 580 | 581 | const response = await client.queryChannels(target); 582 | 583 | if (response) { 584 | logger.debug('<<< channels >>>'); 585 | const channelNames: string[] = []; 586 | response.channels.forEach((ci) => { 587 | channelNames.push('channel id: ' + ci.channel_id); 588 | }); 589 | return response; 590 | } else { 591 | logger.error('response_payloads is null'); 592 | return 'response_payloads is null'; 593 | } 594 | 595 | } catch (err) { 596 | logger.error('Failed to query with error:' + err.stack ? err.stack : err); 597 | return 'Failed to query with error:' + err.stack ? err.stack : err; 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/lib/helper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import log4js = require('log4js'); 18 | import * as path from 'path'; 19 | import * as fs from 'fs'; 20 | import * as util from 'util'; 21 | import config from '../config'; 22 | import hfc = require('fabric-client'); 23 | // tslint:disable-next-line:no-var-requires 24 | const copService = require('fabric-ca-client'); 25 | 26 | const logger = log4js.getLogger('Helper'); 27 | logger.setLevel('DEBUG'); 28 | hfc.setLogger(logger); 29 | 30 | let ORGS: any; 31 | const clients = {}; 32 | const channels = {}; 33 | const caClients = {}; 34 | 35 | function readAllFiles(dir: string) { 36 | const files = fs.readdirSync(dir); 37 | const certs: any = []; 38 | files.forEach((fileName) => { 39 | const filePath = path.join(dir, fileName); 40 | const data = fs.readFileSync(filePath); 41 | certs.push(data); 42 | }); 43 | return certs; 44 | } 45 | 46 | function getKeyStoreForOrg(org: string) { 47 | return hfc.getConfigSetting('keyValueStore') + '_' + org; 48 | } 49 | 50 | function setupPeers(channel: any, org: string, client: Client) { 51 | for (const key in ORGS[org].peers) { 52 | if (key) { 53 | const data = fs.readFileSync( 54 | path.join(__dirname, ORGS[org].peers[key]['tls_cacerts'])); 55 | const peer = client.newPeer( 56 | ORGS[org].peers[key].requests, 57 | { 58 | 'pem': Buffer.from(data).toString(), 59 | 'ssl-target-name-override': ORGS[org].peers[key]['server-hostname'] 60 | } 61 | ); 62 | peer.setName(key); 63 | 64 | channel.addPeer(peer); 65 | } 66 | } 67 | } 68 | 69 | function newOrderer(client: Client) { 70 | const caRootsPath = ORGS.orderer.tls_cacerts; 71 | const data = fs.readFileSync(path.join(__dirname, caRootsPath)); 72 | const caroots = Buffer.from(data).toString(); 73 | return client.newOrderer(ORGS.orderer.url, { 74 | 'pem': caroots, 75 | 'ssl-target-name-override': ORGS.orderer['server-hostname'] 76 | }); 77 | } 78 | 79 | function getOrgName(org: string) { 80 | return ORGS[org].name; 81 | } 82 | 83 | function getMspID(org: string) { 84 | logger.debug('Msp ID : ' + ORGS[org].mspid); 85 | return ORGS[org].mspid; 86 | } 87 | 88 | function newRemotes(names: string[], forPeers: boolean, userOrg: string) { 89 | const client = getClientForOrg(userOrg); 90 | 91 | const targets: any[] = []; 92 | // find the peer that match the names 93 | names.forEach((n) => { 94 | if (ORGS[userOrg].peers[n]) { 95 | // found a peer matching the name 96 | const data = fs.readFileSync( 97 | path.join(__dirname, ORGS[userOrg].peers[n]['tls_cacerts'])); 98 | const grpcOpts = { 99 | 'pem': Buffer.from(data).toString(), 100 | 'ssl-target-name-override': ORGS[userOrg].peers[n]['server-hostname'] 101 | }; 102 | 103 | if (forPeers) { 104 | targets.push(client.newPeer(ORGS[userOrg].peers[n].requests, grpcOpts)); 105 | } else { 106 | const eh = client.newEventHub(); 107 | eh.setPeerAddr(ORGS[userOrg].peers[n].events, grpcOpts); 108 | targets.push(eh); 109 | } 110 | } 111 | }); 112 | 113 | if (targets.length === 0) { 114 | logger.error(util.format('Failed to find peers matching the names %s', names)); 115 | } 116 | 117 | return targets; 118 | } 119 | 120 | async function getAdminUser(userOrg: string): Promise { 121 | const users = hfc.getConfigSetting('admins'); 122 | const username = users[0].username; 123 | const password = users[0].secret; 124 | 125 | const client = getClientForOrg(userOrg); 126 | 127 | const store = await hfc.newDefaultKeyValueStore({ 128 | path: getKeyStoreForOrg(getOrgName(userOrg)) 129 | }); 130 | 131 | client.setStateStore(store); 132 | 133 | const user = await client.getUserContext(username, true); 134 | 135 | if (user && user.isEnrolled()) { 136 | logger.info('Successfully loaded member from persistence'); 137 | return user; 138 | } 139 | 140 | const caClient = caClients[userOrg]; 141 | 142 | const enrollment = await caClient.enroll({ 143 | enrollmentID: username, 144 | enrollmentSecret: password 145 | }); 146 | 147 | logger.info('Successfully enrolled user \'' + username + '\''); 148 | const userOptions: UserOptions = { 149 | username, 150 | mspid: getMspID(userOrg), 151 | cryptoContent: { 152 | privateKeyPEM: enrollment.key.toBytes(), 153 | signedCertPEM: enrollment.certificate 154 | } 155 | }; 156 | 157 | const member = await client.createUser(userOptions); 158 | return member; 159 | } 160 | 161 | export function newPeers(names: string[], org: string) { 162 | return newRemotes(names, true, org); 163 | } 164 | 165 | export function newEventHubs(names: string[], org: string) { 166 | return newRemotes(names, false, org); 167 | } 168 | 169 | export function setupChaincodeDeploy() { 170 | process.env.GOPATH = path.join(__dirname, hfc.getConfigSetting('CC_SRC_PATH')); 171 | } 172 | 173 | export function getOrgs() { 174 | return ORGS; 175 | } 176 | 177 | export function getClientForOrg(org: string): Client { 178 | return clients[org]; 179 | } 180 | 181 | export function getChannelForOrg(org: string): Channel { 182 | return channels[org]; 183 | } 184 | 185 | export function init() { 186 | 187 | hfc.addConfigFile(path.join(__dirname, config.networkConfigFile)); 188 | hfc.addConfigFile(path.join(__dirname, '../app_config.json')); 189 | 190 | ORGS = hfc.getConfigSetting('network-config'); 191 | 192 | // set up the client and channel objects for each org 193 | for (const key in ORGS) { 194 | if (key.indexOf('org') === 0) { 195 | const client = new hfc(); 196 | 197 | const cryptoSuite = hfc.newCryptoSuite(); 198 | // TODO: Fix it up as setCryptoKeyStore is only available for s/w impl 199 | (cryptoSuite as any).setCryptoKeyStore( 200 | hfc.newCryptoKeyStore({ 201 | path: getKeyStoreForOrg(ORGS[key].name) 202 | })); 203 | 204 | client.setCryptoSuite(cryptoSuite); 205 | 206 | const channel = client.newChannel(hfc.getConfigSetting('channelName')); 207 | channel.addOrderer(newOrderer(client)); 208 | 209 | clients[key] = client; 210 | channels[key] = channel; 211 | 212 | setupPeers(channel, key, client); 213 | 214 | const caUrl = ORGS[key].ca; 215 | caClients[key] = new copService( 216 | caUrl, null /*defautl TLS opts*/, '' /* default CA */, cryptoSuite); 217 | } 218 | } 219 | } 220 | 221 | export async function getRegisteredUsers( 222 | username: string, userOrg: string): Promise { 223 | 224 | const client = getClientForOrg(userOrg); 225 | 226 | const store = await hfc.newDefaultKeyValueStore({ 227 | path: getKeyStoreForOrg(getOrgName(userOrg)) 228 | }); 229 | 230 | client.setStateStore(store); 231 | const user = await client.getUserContext(username, true); 232 | 233 | if (user && user.isEnrolled()) { 234 | logger.info('Successfully loaded member from persistence'); 235 | return user; 236 | } 237 | 238 | logger.info('Using admin to enroll this user ..'); 239 | 240 | // get the Admin and use it to enroll the user 241 | const adminUser = await getAdminUser(userOrg); 242 | 243 | const caClient = caClients[userOrg]; 244 | const secret = await caClient.register({ 245 | enrollmentID: username, 246 | affiliation: userOrg + '.department1' 247 | }, adminUser); 248 | 249 | logger.debug(username + ' registered successfully'); 250 | 251 | const message = await caClient.enroll({ 252 | enrollmentID: username, 253 | enrollmentSecret: secret 254 | }); 255 | 256 | if (message && typeof message === 'string' && message.includes( 257 | 'Error:')) { 258 | logger.error(username + ' enrollment failed'); 259 | } 260 | logger.debug(username + ' enrolled successfully'); 261 | 262 | const userOptions: UserOptions = { 263 | username, 264 | mspid: getMspID(userOrg), 265 | cryptoContent: { 266 | privateKeyPEM: message.key.toBytes(), 267 | signedCertPEM: message.certificate 268 | } 269 | }; 270 | 271 | const member = await client.createUser(userOptions); 272 | return member; 273 | } 274 | 275 | export function getLogger(moduleName: string) { 276 | const moduleLogger = log4js.getLogger(moduleName); 277 | moduleLogger.setLevel('DEBUG'); 278 | return moduleLogger; 279 | } 280 | 281 | export async function getOrgAdmin(userOrg: string): Promise { 282 | const admin = ORGS[userOrg].admin; 283 | const keyPath = path.join(__dirname, admin.key); 284 | const keyPEM = Buffer.from(readAllFiles(keyPath)[0]).toString(); 285 | const certPath = path.join(__dirname, admin.cert); 286 | const certPEM = readAllFiles(certPath)[0].toString(); 287 | 288 | const client = getClientForOrg(userOrg); 289 | const cryptoSuite = hfc.newCryptoSuite(); 290 | 291 | if (userOrg) { 292 | (cryptoSuite as any).setCryptoKeyStore( 293 | hfc.newCryptoKeyStore({ path: getKeyStoreForOrg(getOrgName(userOrg)) })); 294 | client.setCryptoSuite(cryptoSuite); 295 | } 296 | 297 | const store = await hfc.newDefaultKeyValueStore({ 298 | path: getKeyStoreForOrg(getOrgName(userOrg)) 299 | }); 300 | 301 | client.setStateStore(store); 302 | 303 | return client.createUser({ 304 | username: 'peer' + userOrg + 'Admin', 305 | mspid: getMspID(userOrg), 306 | cryptoContent: { 307 | privateKeyPEM: keyPEM, 308 | signedCertPEM: certPEM 309 | } 310 | }); 311 | } 312 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/lib/network-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "network-config": { 3 | "orderer": { 4 | "url": "grpcs://localhost:7050", 5 | "server-hostname": "orderer.example.com", 6 | "tls_cacerts": "../artifacts/channel/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt" 7 | }, 8 | "org1": { 9 | "name": "peerOrg1", 10 | "mspid": "Org1MSP", 11 | "ca": "https://localhost:7054", 12 | "peers": { 13 | "peer1": { 14 | "requests": "grpcs://localhost:7051", 15 | "events": "grpcs://localhost:7053", 16 | "server-hostname": "peer0.org1.example.com", 17 | "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" 18 | }, 19 | "peer2": { 20 | "requests": "grpcs://localhost:7056", 21 | "events": "grpcs://localhost:7058", 22 | "server-hostname": "peer1.org1.example.com", 23 | "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt" 24 | } 25 | }, 26 | "admin": { 27 | "key": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore", 28 | "cert": "../artifacts/channel/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts" 29 | } 30 | }, 31 | "org2": { 32 | "name": "peerOrg2", 33 | "mspid": "Org2MSP", 34 | "ca": "https://localhost:8054", 35 | "peers": { 36 | "peer1": { 37 | "requests": "grpcs://localhost:8051", 38 | "events": "grpcs://localhost:8053", 39 | "server-hostname": "peer0.org2.example.com", 40 | "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" 41 | }, 42 | "peer2": { 43 | "requests": "grpcs://localhost:8056", 44 | "events": "grpcs://localhost:8058", 45 | "server-hostname": "peer1.org2.example.com", 46 | "tls_cacerts": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt" 47 | } 48 | }, 49 | "admin": { 50 | "key": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore", 51 | "cert": "../artifacts/channel/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts" 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "balance-transfer-typescript", 3 | "version": "0.1.0", 4 | "description": "The balance transfer sample written using typescript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Kapil Sachdeva", 10 | "license": "Apache-2.0", 11 | "devDependencies": { 12 | "@types/body-parser": "^1.16.5", 13 | "@types/cors": "^2.8.1", 14 | "@types/express-jwt": "0.0.37", 15 | "@types/express-session": "^1.15.3", 16 | "@types/jsonwebtoken": "^7.2.3", 17 | "@types/log4js": "0.0.33", 18 | "@types/node": "^8.0.33", 19 | "express-bearer-token": "^2.1.0", 20 | "jsonwebtoken": "^8.1.0", 21 | "ts-node": "^3.3.0", 22 | "tslint": "^5.6.0", 23 | "tslint-microsoft-contrib": "^5.0.1", 24 | "typescript": "^2.5.3" 25 | }, 26 | "dependencies": { 27 | "body-parser": "^1.18.2", 28 | "cookie-parser": "^1.4.3", 29 | "cors": "^2.8.4", 30 | "express": "^4.16.1", 31 | "express-jwt": "^5.3.0", 32 | "express-session": "^1.15.6", 33 | "fabric-ca-client": "^1.0.2", 34 | "fabric-client": "^1.0.2", 35 | "log4js": "^0.6.38" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/runApp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | function dkcl(){ 9 | CONTAINER_IDS=$(docker ps -aq) 10 | echo 11 | if [ -z "$CONTAINER_IDS" -o "$CONTAINER_IDS" = " " ]; then 12 | echo "========== No containers available for deletion ==========" 13 | else 14 | docker rm -f $CONTAINER_IDS 15 | fi 16 | echo 17 | } 18 | 19 | function dkrm(){ 20 | DOCKER_IMAGE_IDS=$(docker images | grep "dev\|none\|test-vp\|peer[0-9]-" | awk '{print $3}') 21 | echo 22 | if [ -z "$DOCKER_IMAGE_IDS" -o "$DOCKER_IMAGE_IDS" = " " ]; then 23 | echo "========== No images available for deletion ===========" 24 | else 25 | docker rmi -f $DOCKER_IMAGE_IDS 26 | fi 27 | echo 28 | } 29 | 30 | function restartNetwork() { 31 | echo 32 | 33 | #teardown the network and clean the containers and intermediate images 34 | docker-compose -f ../artifacts/docker-compose.yaml down 35 | dkcl 36 | dkrm 37 | 38 | #Cleanup the material 39 | rm -rf /tmp/hfc-test-kvs_peerOrg* $HOME/.hfc-key-store/ /tmp/fabric-client-kvs_peerOrg* 40 | 41 | #Start the network 42 | docker-compose -f ../artifacts/docker-compose.yaml up -d 43 | echo 44 | } 45 | 46 | function installNodeModules() { 47 | echo 48 | if [ -d node_modules ]; then 49 | echo "============== node modules installed already =============" 50 | else 51 | echo "============== Installing node modules =============" 52 | npm install 53 | fi 54 | copyIndex fabric-client/index.d.ts 55 | copyIndex fabric-ca-client/index.d.ts 56 | echo 57 | } 58 | 59 | function copyIndex() { 60 | if [ ! -f node_modules/$1 ]; then 61 | cp types/$1 node_modules/$1 62 | fi 63 | } 64 | 65 | restartNetwork 66 | 67 | installNodeModules 68 | 69 | 70 | 71 | PORT=4000 ts-node app.ts 72 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/testAPIs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright IBM Corp. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | jq --version > /dev/null 2>&1 9 | if [ $? -ne 0 ]; then 10 | echo "Please Install 'jq' https://stedolan.github.io/jq/ to execute this script" 11 | echo 12 | exit 1 13 | fi 14 | starttime=$(date +%s) 15 | 16 | echo "POST request Enroll on Org1 ..." 17 | echo 18 | ORG1_TOKEN=$(curl -s -X POST \ 19 | http://localhost:4000/users \ 20 | -H "content-type: application/x-www-form-urlencoded" \ 21 | -d 'username=Jim&orgName=org1') 22 | echo $ORG1_TOKEN 23 | ORG1_TOKEN=$(echo $ORG1_TOKEN | jq ".token" | sed "s/\"//g") 24 | echo 25 | echo "ORG1 token is $ORG1_TOKEN" 26 | echo 27 | echo "POST request Enroll on Org2 ..." 28 | echo 29 | ORG2_TOKEN=$(curl -s -X POST \ 30 | http://localhost:4000/users \ 31 | -H "content-type: application/x-www-form-urlencoded" \ 32 | -d 'username=Barry&orgName=org2') 33 | echo $ORG2_TOKEN 34 | ORG2_TOKEN=$(echo $ORG2_TOKEN | jq ".token" | sed "s/\"//g") 35 | echo 36 | echo "ORG2 token is $ORG2_TOKEN" 37 | echo 38 | echo 39 | echo "POST request Create channel ..." 40 | echo 41 | curl -s -X POST \ 42 | http://localhost:4000/channels \ 43 | -H "authorization: Bearer $ORG1_TOKEN" \ 44 | -H "content-type: application/json" \ 45 | -d '{ 46 | "channelName":"mychannel", 47 | "channelConfigPath":"../artifacts/channel/mychannel.tx" 48 | }' 49 | echo 50 | echo 51 | sleep 5 52 | echo "POST request Join channel on Org1" 53 | echo 54 | curl -s -X POST \ 55 | http://localhost:4000/channels/mychannel/peers \ 56 | -H "authorization: Bearer $ORG1_TOKEN" \ 57 | -H "content-type: application/json" \ 58 | -d '{ 59 | "peers": ["peer1","peer2"] 60 | }' 61 | echo 62 | echo 63 | 64 | echo "POST request Join channel on Org2" 65 | echo 66 | curl -s -X POST \ 67 | http://localhost:4000/channels/mychannel/peers \ 68 | -H "authorization: Bearer $ORG2_TOKEN" \ 69 | -H "content-type: application/json" \ 70 | -d '{ 71 | "peers": ["peer1","peer2"] 72 | }' 73 | echo 74 | echo 75 | 76 | echo "POST Install chaincode on Org1" 77 | echo 78 | curl -s -X POST \ 79 | http://localhost:4000/chaincodes \ 80 | -H "authorization: Bearer $ORG1_TOKEN" \ 81 | -H "content-type: application/json" \ 82 | -d '{ 83 | "peers": ["peer1", "peer2"], 84 | "chaincodeName":"mycc", 85 | "chaincodePath":"github.com/example_cc/go", 86 | "chaincodeVersion":"v0" 87 | }' 88 | echo 89 | echo 90 | 91 | 92 | echo "POST Install chaincode on Org2" 93 | echo 94 | curl -s -X POST \ 95 | http://localhost:4000/chaincodes \ 96 | -H "authorization: Bearer $ORG2_TOKEN" \ 97 | -H "content-type: application/json" \ 98 | -d '{ 99 | "peers": ["peer1","peer2"], 100 | "chaincodeName":"mycc", 101 | "chaincodePath":"github.com/example_cc/go", 102 | "chaincodeVersion":"v0" 103 | }' 104 | echo 105 | echo 106 | 107 | echo "POST instantiate chaincode on peer1 of Org1" 108 | echo 109 | curl -s -X POST \ 110 | http://localhost:4000/channels/mychannel/chaincodes \ 111 | -H "authorization: Bearer $ORG1_TOKEN" \ 112 | -H "content-type: application/json" \ 113 | -d '{ 114 | "chaincodeName":"mycc", 115 | "chaincodeVersion":"v0", 116 | "args":["a","100","b","200"] 117 | }' 118 | echo 119 | echo 120 | 121 | echo "POST invoke chaincode on peers of Org1 and Org2" 122 | echo 123 | TRX_ID=$(curl -s -X POST \ 124 | http://localhost:4000/channels/mychannel/chaincodes/mycc \ 125 | -H "authorization: Bearer $ORG1_TOKEN" \ 126 | -H "content-type: application/json" \ 127 | -d '{ 128 | "fcn":"move", 129 | "args":["a","b","10"] 130 | }') 131 | echo "Transacton ID is $TRX_ID" 132 | echo 133 | echo 134 | 135 | echo "GET query chaincode on peer1 of Org1" 136 | echo 137 | curl -s -X GET \ 138 | "http://localhost:4000/channels/mychannel/chaincodes/mycc?peer=peer1&fcn=query&args=%5B%22a%22%5D" \ 139 | -H "authorization: Bearer $ORG1_TOKEN" \ 140 | -H "content-type: application/json" 141 | echo 142 | echo 143 | 144 | echo "GET query Block by blockNumber" 145 | echo 146 | curl -s -X GET \ 147 | "http://localhost:4000/channels/mychannel/blocks/1?peer=peer1" \ 148 | -H "authorization: Bearer $ORG1_TOKEN" \ 149 | -H "content-type: application/json" 150 | echo 151 | echo 152 | 153 | echo "GET query Transaction by TransactionID" 154 | echo 155 | curl -s -X GET http://localhost:4000/channels/mychannel/transactions/$TRX_ID?peer=peer1 \ 156 | -H "authorization: Bearer $ORG1_TOKEN" \ 157 | -H "content-type: application/json" 158 | echo 159 | echo 160 | 161 | echo "GET query ChainInfo" 162 | echo 163 | curl -s -X GET \ 164 | "http://localhost:4000/channels/mychannel?peer=peer1" \ 165 | -H "authorization: Bearer $ORG1_TOKEN" \ 166 | -H "content-type: application/json" 167 | echo 168 | echo 169 | 170 | echo "GET query Installed chaincodes" 171 | echo 172 | curl -s -X GET \ 173 | "http://localhost:4000/chaincodes?peer=peer1&type=installed" \ 174 | -H "authorization: Bearer $ORG1_TOKEN" \ 175 | -H "content-type: application/json" 176 | echo 177 | echo 178 | 179 | echo "GET query Instantiated chaincodes" 180 | echo 181 | curl -s -X GET \ 182 | "http://localhost:4000/chaincodes?peer=peer1&type=instantiated" \ 183 | -H "authorization: Bearer $ORG1_TOKEN" \ 184 | -H "content-type: application/json" 185 | echo 186 | echo 187 | 188 | echo "GET query Channels" 189 | echo 190 | curl -s -X GET \ 191 | "http://localhost:4000/channels?peer=peer1" \ 192 | -H "authorization: Bearer $ORG1_TOKEN" \ 193 | -H "content-type: application/json" 194 | echo 195 | echo 196 | 197 | echo "Total execution time : $(($(date +%s)-starttime)) secs ..." 198 | -------------------------------------------------------------------------------- /balance-transfer-app/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "removeComments": false, 4 | "preserveConstEnums": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "noImplicitAny": true, 10 | "noImplicitReturns": true, 11 | "noImplicitThis": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "moduleResolution": "node", 14 | "module": "commonjs", 15 | "target": "es6", 16 | "outDir": "dist", 17 | "baseUrl": ".", 18 | "typeRoots": [ 19 | "types", 20 | "node_modules/@types" 21 | ] 22 | }, 23 | "formatCodeOptions": { 24 | "indentSize": 2, 25 | "tabSize": 2 26 | } 27 | } -------------------------------------------------------------------------------- /balance-transfer-app/typescript/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "tslint-microsoft-contrib" 5 | ], 6 | "rules": { 7 | "trailing-comma": [false, { 8 | "multiline": "always", 9 | "singleline": "never" 10 | }], 11 | "interface-name": [false, "always-prefix"], 12 | "no-console": [true, 13 | "time", 14 | "timeEnd", 15 | "trace" 16 | ], 17 | "max-line-length": [ 18 | true, 19 | 100 20 | ], 21 | "no-string-literal": false, 22 | "no-use-before-declare": true, 23 | "object-literal-sort-keys": false, 24 | "ordered-imports": [false], 25 | "quotemark": [ 26 | true, 27 | "single", 28 | "avoid-escape" 29 | ], 30 | "variable-name": [ 31 | true, 32 | "allow-leading-underscore", 33 | "allow-pascal-case", 34 | "ban-keywords", 35 | "check-format" 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /balance-transfer-app/typescript/types/fabric-ca-client/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Kapil Sachdeva All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | declare module 'fabric-ca-client' { 18 | } --------------------------------------------------------------------------------