├── .gitignore
├── .jshintignore
├── .jshintrc
├── LICENSE
├── README.md
├── index.js
├── lib
├── helper.js
└── rest.js
├── package.json
└── test
├── ValidLoadTest.js
├── cb_readyTest.js
└── sdkfunctiontest.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .project
3 | node_modules
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | /client/
2 | /node_modules/
3 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": false,
6 | "eqeqeq": false,
7 | "eqnull": true,
8 | "immed": true,
9 | "latedef": "nofunc",
10 | "newcap": true,
11 | "noarg": true,
12 | "quotmark": "single",
13 | "regexp": true,
14 | "undef": true,
15 | "unused": "vars",
16 | "trailing": true,
17 | "sub": true,
18 | "shadow": "inner",
19 | "funcscope": false,
20 | "maxlen": 160,
21 | "maxdepth": 7,
22 | "maxerr": 100,
23 | "maxparams": 6,
24 | "nonew": false
25 | }
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ibm-blockchain-js - [Deprecated]
2 | This is a Node.js library for REST based interaction with [Hyperledger](https://github.com/hyperledger/fabric) chaincode.
3 | All `ibm-blockchain-js` documentation is on this page.
4 |
5 | *7/22/2016 Update! there is a new gRPC based SDK called [HFC](https://www.npmjs.com/package/hfc). I will continue to maintain this SDK for as long as REST exists.*
6 |
7 | *12/15/2016 Update! Hyperledger Fabric v0.7+ no longer has REST apis. Therefore you should use the HFC module instead of this one.*
8 |
9 | **4/28/2017 Update! There will only be bare minimum support for this SDK.** *For Hyperledger Fabric v1.0 it has been replaced by [Fabric Client](https://www.npmjs.com/package/fabric-client). For Hyperledger Fabric v0.6 it has been replaced by [HFC](https://www.npmjs.com/package/hfc)*
10 |
11 | Table Of Contents:
12 |
13 | 1. [v1.0.0 Migration!](#migrate)
14 | 1. [IBC-js Function Documentation](#ibcjs)
15 | 1. [Chaincode Functions](#ccfunc)
16 | 1. [Object Formats](#formats)
17 | 1. [Chaincode Summary File](#ccsf)
18 | 1. [FAQ](#faq)
19 |
20 | ***
21 |
22 | ## Installation
23 |
24 | ```
25 | npm install ibm-blockchain-js
26 | ```
27 |
28 | ***
29 |
30 | ## Usage Steps!
31 | (example code also provided below)
32 |
33 | 1. Require this module
34 | 1. Pass network + chaincode parameters to ibc.load(options, my_cb):
35 | 1. Receive chaincode object from callback to ibc.load(). ie: my_cb(e, chaincode)
36 | 1. You can now deploy your chaincode (if needed) with chaincode.deploy(func, args, null, cb)
37 | 1. Use dot notation on chaincode to call any of your chaincode functions ie:
38 |
39 | ```js
40 | // The functions below need to exist in your actual chaincode GoLang file(s)
41 | chaincode.query.read(['a'], cb); //will read variable "a" from current chaincode state
42 | chaincode.invoke.write(['a', 'test'], cb); //will write to variable "a"
43 | chaincode.invoke.remove(['a'], cb); //will delete variable "a"
44 | chaincode.invoke.init_marbles([ARGS], cb); //calls my custom chaincode function init_marbles() and passes it ARGS
45 | ```
46 |
47 | ## Example
48 |
49 | ```js
50 | // Step 1 ==================================
51 | var Ibc1 = require('ibm-blockchain-js');
52 | var ibc = new Ibc1(/*logger*/); //you can pass a logger such as winston here - optional
53 | var chaincode = {};
54 |
55 | // ==================================
56 | // configure ibc-js sdk
57 | // ==================================
58 | var options = {
59 | network:{
60 | peers: [{
61 | "api_host": "xxx.xxx.xxx.xxx",
62 | "api_port": xxx,
63 | "api_port_tls": xxx,
64 | "id": "xxxxxx-xxxx-xxx-xxx-xxxxxxxxxxxx_vpx"
65 | }],
66 | users: [{
67 | "enrollId": "user1",
68 | "enrollSecret": "xxxxxxxx"
69 | }],
70 | options: { //this is optional
71 | quiet: true,
72 | timeout: 60000
73 | }
74 | },
75 | chaincode:{
76 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip',
77 | unzip_dir: 'marbles-chaincode-master/part2_v1.0.0',
78 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2_v1.0.0'
79 | }
80 | };
81 |
82 | // Step 2 ==================================
83 | ibc.load(options, cb_ready);
84 |
85 | // Step 3 ==================================
86 | function cb_ready(err, cc){ //response has chaincode functions
87 | app1.setup(ibc, cc);
88 | app2.setup(ibc, cc);
89 |
90 | // Step 4 ==================================
91 | if(cc.details.deployed_name === ""){ //decide if I need to deploy or not
92 | cc.deploy('init', ['99'], null, cb_deployed);
93 | }
94 | else{
95 | console.log('chaincode summary file indicates chaincode has been previously deployed');
96 | cb_deployed();
97 | }
98 | }
99 |
100 | // Step 5 ==================================
101 | function cb_deployed(err){
102 | console.log('sdk has deployed code and waited');
103 | chaincode.query.read(['a']);
104 | }
105 | ```
106 |
107 |
108 | ***
109 | ## Migrating from v0.0.x to v1.x.x
110 | The interface to your chaincode functions has changed in v1.0.0 from v0.0.13!
111 | It is only a minor syntax change that should make it more clear to newcomers.
112 | All invocation functions can now be found under `chaincode.invoke` and all query functions can be found under `chaincode.query`.
113 |
114 | Examples:
115 |
116 | **query changes** - name change
117 | ```js
118 | //old code
119 | chaincode.read('a');
120 |
121 | //new code
122 | chaincode.query.read(['a']);
123 | ```
124 |
125 | **invoke changes** - name change
126 | ```js
127 | //old code
128 | chaincode.init_marble(args);
129 | chaincode.remove(args);
130 | chaincode.write(name, value);
131 |
132 | //new code
133 | chaincode.invoke.init_marble(args);
134 | chaincode.invoke.remove(args);
135 | chaincode.invoke.write(args);
136 | ```
137 |
138 | **deploy changes** - added options parameter
139 | ```js
140 | //old code
141 | chaincode.deploy('init', ['99'], './cc_summaries', cb_deployed);
142 |
143 | //new code
144 | chaincode.deploy('init', ['99'], {save_path: './cc_summaries', delay_ms: 60000}, cb_deployed);
145 | ```
146 |
147 | **register changes** - added new parameter
148 | ```js
149 | //old code
150 | ibc.register(i, enrollId, enrollSecret, [callback]);
151 |
152 | //new code
153 | ibc.register(i, enrollId, enrollSecret, maxRetry, [callback]);
154 |
155 | ```
156 |
157 | ***
158 |
159 | ## IBM-Blockchain-JS Documentation
160 | ### Usage
161 |
162 | Example with standard console logging:
163 | ```js
164 | var Ibc1 = require('ibm-blockchain-js');
165 | var ibc = new Ibc1();
166 | ```
167 |
168 | Example with [Winston](https://www.npmjs.com/package/winston) logging:
169 | ```js
170 | var winston = require('winston');
171 | var logger = new (winston.Logger)({
172 | transports: [
173 | new (winston.transports.Console)(),
174 | new (winston.transports.File)({ filename: 'somefile.log' })
175 | ]
176 | });
177 | var Ibc1 = require('ibm-blockchain-js');
178 | var ibc = new Ibc1(logger); //you can pass a logger such as winston here - optional
179 | ```
180 |
181 | ### ibc.load(options, [callback])
182 | This is a function that wraps a typical startup using a standard Bluemix IBM Blockchain network.
183 | Take a look at how this function works, especially how it uses the register() function.
184 | If this is not applicable for your network (ie you have a custom IBM Blockchain network) you can easily create your own version of `ibc.load()` for your needs.
185 | It will run in order:
186 |
187 | 1. ibc.network(options.network.peers, options.network.options) *check out other options in [ibc.network()](#ibcnetwork)*
188 | 1. ibc.register(...)
189 | - It will register the first peer with the first enrollId, the 2nd peer against the 2nd enrollId and so on.
190 | - This function only runs if users are found in options.network.users.
191 | - Any errors in register will stop execution and run callback(err).
192 | 1. ibc.load_chaincode(options.chaincode, [callback])
193 | 1. callback(err, cc)
194 |
195 | Options:
196 | - **maxRetry** = integer - number of times to retry `ibc.register()` before giving up
197 | - [more] - same options as the function [ibc.network()](#ibcnetwork), click for details
198 |
199 | Ex:
200 |
201 | ```js
202 | var options = {
203 | network:{
204 | peers: [{
205 | "api_host": "xxx.xxx.xxx.xxx",
206 | "api_port": xxx,
207 | "api_port_tls": xxx,
208 | "id": "xxxxxx-xxxx-xxx-xxx-xxxxxxxxxxxx_vpx"
209 | }],
210 | users: [{
211 | "enrollId": "user1",
212 | "enrollSecret": "xxxxxxxx"
213 | }],
214 | options: { //this is optional, gets passed to ibc.network(peers, options);
215 | quiet: true,
216 | timeout: 60000,
217 | tls: true,
218 | maxRetry: 3
219 | }
220 | },
221 | chaincode:{
222 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip', //http/https of a link to download zip
223 | unzip_dir: 'marbles-chaincode-master/part2_v1.0.0', //name/path to folder that contains the chaincode you want to deploy (path relative to unzipped root)
224 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2_v1.0.0', //git https URL. should point to the desired chaincode repo AND directory
225 |
226 | deployed_name: null //[optional] this is the hashed name of a deployed chaincode. if you want to run with chaincode that is already deployed set it now, else it will be set when you deploy with the sdk
227 | }
228 | };
229 |
230 | ibc.load(options, function(err, data){
231 | //callback here
232 | });
233 | ```
234 |
235 | ### ibc.load_chaincode(options, [callback])
236 | Load the chaincode you want to use.
237 | It will be downloaded and parsed.
238 | The callback will receive (e, obj) where `e` is the error format and `obj` is the chaincode object.
239 | "e" is null when there are no errors.
240 | The chaincode object will have dot notation to the functions in the your chaincode.
241 |
242 | Ex:
243 |
244 | ```js
245 | var options = {
246 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip', //http/https of a link to download zip
247 | unzip_dir: 'marbles-chaincode-master/part2_v1.0.0', //name/path to folder that contains the chaincode you want to deploy (path relative to unzipped root)
248 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2_v1.0.0', //git https URL. should point to the desired chaincode repo AND directory
249 |
250 | deployed_name: null //[optional] this is the hashed name of a deployed chaincode. if you want to run with chaincode that is already deployed set it now, else it will be set when you deploy with the sdk
251 | };
252 | ibc.load_chaincode(options, cb_ready);
253 | ```
254 |
255 | ### ibc.network(arrayPeers, [options])
256 | Set the information about the peers in the network.
257 | This should be an array of peer objects.
258 | The options parameter is optional.
259 | Each field in `options` is also optional.
260 |
261 | Options:
262 | - **quiet** = boolean - when true will print out only minimal HTTP debug information. Defaults `true`.
263 | - **timeout** = integer - time in ms to wait for a http response. Defaults `60000`.
264 | - **tls** = boolean - when `false` will use HTTP instead of HTTPS. Defaults `true`.
265 |
266 | Ex:
267 |
268 | ```js
269 | var peers = [
270 | {
271 | "api_host": "xxx.xxx.xxx.xxx", //ip or hostname of api for this peer
272 | "api_port": xxx, //port for api, non tls (integer)
273 | "api_port_tls": xxx, //port for api with tls. (integer)
274 | "id": "xxxxxx-xxxx-xxx-xxx-xxxxxxxxxxxx_vpx" //unique id of peer (string)
275 | }
276 | ]
277 | ibc.network(peers, {quiet: false, timeout: 120000}); //can pass config options
278 | ```
279 |
280 | Note **only** the field names you see above (`api_host`, `api_port`, `api_port_tls`, `id`) are required.
281 | If you are using a Bluemix network you will see lots of other fields in the credentials JSON blob, but they are not needed.
282 | Its also fine to include the extra fields.
283 | You can ommit the field `api_port_tls` if your network does not support tls.
284 | Make sure the `options.tls` is `false`.
285 |
286 | ### ibc.save(path [callback])
287 | Save the [Chaincode Summary File](#ccsf) to a path.
288 |
289 | Ex:
290 |
291 | ```js
292 | ibc.save('./');
293 | ```
294 |
295 | ### ibc.clear([callback])
296 | Clear any loaded chaincode files including the downloaded chaincode repo, and [Chaincode Summary File](#ccsf).
297 |
298 | Ex:
299 |
300 | ```js
301 | ibc.clear();
302 | ```
303 |
304 | ### ibc.chain_stats([callback])
305 | Get statistics on the network's chain.
306 |
307 | Ex:
308 |
309 | ```js
310 | ibc.chain_stats(function(e, stats){
311 | console.log('got some stats', stats);
312 | });
313 | ```
314 |
315 | Example Chain Stats:
316 |
317 | ```js
318 | {
319 | "height": 10,
320 | "currentBlockHash": "n7uMlNMiOSUM8s02cslTRzZQQlVfm8wKT9FtL54o0ywy6BkvPMwSzN5R1tpquvqOwFFHyLSoW44n6rkFyvAsBw==",
321 | "previousBlockHash": "OESGPzacJO2Xc+5PB2zpmYVM8XlrwnEky0L2Ghok9oK1Lr/DWoxuBo2WwBca5zzJGq0fOeRQ7aOHgCjMupfL+Q=="
322 | }
323 | ```
324 |
325 | ### ibc.block_stats(id, [callback])
326 | Get statistics on a particular block in the chain.
327 |
328 | Ex:
329 |
330 | ```js
331 | ibc.block_stats(function(e, stats){
332 | console.log('got some stats', stats);
333 | });
334 | ```
335 |
336 | Example Response:
337 |
338 | ```js
339 | {
340 | "transactions": [
341 | {
342 | "type": 3,
343 | "chaincodeID": "EoABNWUzNGJmNWI1MWM1MWZiYzhlMWFmOThkYThhZDg0MGM2OWFjOWM5YTg4ODVlM2U0ZDBlNjNiM2I4MDc0ZWU2NjY2OWFjOTAzNTg4MzE1YTZjOGQ4ODY4M2Y1NjM0MThlMzMwNzQ3ZmVhZmU3ZWYyMGExY2Q1NGZmNzY4NWRhMTk=",
344 | "payload": "CrABCAESgwESgAE1ZTM0YmY1YjUxYzUxZmJjOGUxYWY5OGRhOGFkODQwYzY5YWM5YzlhODg4NWUzZTRkMGU2M2IzYjgwNzRlZTY2NjY5YWM5MDM1ODgzMTVhNmM4ZDg4NjgzZjU2MzQxOGUzMzA3NDdmZWFmZTdlZjIwYTFjZDU0ZmY3Njg1ZGExORomCgtpbml0X21hcmJsZRIHcng2YXRzcBIFZ3JlZW4SAjM1EgNCb2I=",
345 | "uuid": "b3da1d08-19b8-4d8c-a116-b46defb07a7c",
346 | "timestamp": {
347 | "seconds": 1453997627,
348 | "nanos": 856894462
349 | }
350 | }
351 | ],
352 | "stateHash": "81ci8IAOeDh0ZwFM6hE/b3SfXt4tnZFemib7sI95cOsNcYMmtRxBWRBA7qnjPOCGU6snBRsFVnAliZXUigQ03w==",
353 | "previousBlockHash": "tpjUh4sgbaUQFO8wm8S8nrm7yCrBa4rphIiujfaYAlEVfzI8IZ0mjYMf+GiOZ6CZRNWPmf+5bekmGIfr0H6zdw==",
354 | "nonHashData": {
355 | "localLedgerCommitTimestamp": {
356 | "seconds": 1453997627,
357 | "nanos": 868868790
358 | }
359 | }
360 | }
361 | ```
362 |
363 | ### ibc.switchPeer(peerIndex)
364 | The SDK will default to use peer[0]. This function will switch the default peer to another index.
365 |
366 | Ex:
367 |
368 | ```js
369 | ibc.switchPeer(2);
370 | ```
371 |
372 | ### ibc.register(peerIndex, enrollId, enrollsecret, maxRetry, [callback])
373 | Only applicable on a network with security enabled.
374 | `register()` will register against peer[peerIndex] with the provided credentials.
375 | If successful, the peer will now use this `enrollId` to perform any http requests.
376 | - **peerIndex** = integer - position of peer in peers array (the one you fed ibc.networks()) you want to register against.
377 | - **enrollId** = string - name of secure context user.
378 | - **enrollSecret** = string - password/secret/api key of secure context user.
379 | - **maxRetry** = integer - number of times to retry this call before giving up.
380 |
381 | Ex:
382 |
383 | ```js
384 | ibc.register(3, 'user1', 'xxxxxx', 3, my_cb);
385 | ```
386 |
387 | ### ibc.monitor_blockheight(callback)
388 | This will call your callback function whenever the block height has changed.
389 | ie. whenever a new block has been written to the chain.
390 | It will also pass you the same response as in `chain_stats()`.
391 |
392 | Ex:
393 |
394 | ```js
395 | ibc.monitor_blockheight(my_callback);
396 | function my_callback(e, chainstats){
397 | console.log('got a new block!', chainstats);
398 | }
399 | ```
400 |
401 | ### ibc.get_transaction(udid, [callback])
402 | Get information about a particular transaction ID.
403 |
404 | Ex:
405 |
406 | ```js
407 | ibc.get_transaction('d30a1445-185f-4853-b4d6-ee7b4dfa5534', function(err, data){
408 | console.log('found trans', err, data);
409 | });
410 | ```
411 |
412 | ***
413 | ***
414 |
415 | ##Chaincode Functions
416 | - Chaincode functions are dependent on actually be found inside your Go chaincode
417 | - My advice is to build your chaincode off of the Marble Application one. This way you get the basic CRUD functions below:
418 |
419 | ### chaincode.deploy(func, args, [options], [enrollId], [callback])
420 | Deploy the chaincode.
421 | Call GoLang function named 'func' and feed it 'args'.
422 | Usually "args" is an array of strings.
423 | The `enrollId` parameter should be the desired secure context enrollId that has already been registered against the selected peer.
424 | If left `null` the SDK will use a known enrollId for the selected peer. (this is only relevant in a permissioned network)
425 |
426 | Options:
427 | - **save_path** = save the [Chaincode Summary File](#ccsf) to 'save_path'.
428 | - **delay_ms** = time in milliseconds to postpone the callback after deploy. Default is `40000`
429 |
430 | Ex:
431 |
432 | ```js
433 | chaincode.deploy('init', ['99'], {delay_ms: 60000}, cb_deployed);
434 | ```
435 |
436 | ### chaincode.query.CUSTOM_FUNCTION_NAME(args, [enrollId], [callback])
437 | Will invoke your Go function CUSTOM_FUNCTION_NAME and pass it `args`.
438 | Usually `args` is an array of strings.
439 | The `enrollId` parameter should be the desired secure context enrollId that has already been registered against the selected peer.
440 | If left `null` the SDK will use a known enrollId for the selected peer. (this is only relevant in a permissioned network)
441 |
442 | Ex:
443 |
444 | ```js
445 | chaincode.query.read(['abc'], function(err, data){
446 | console.log('read abc:', data, err);
447 | });
448 | ```
449 |
450 | ### chaincode.invoke.CUSTOM_FUNCTION_NAME(args, [enrollId], [callback])
451 | Will query your Go function CUSTOM_FUNCTION_NAME and pass it `args`.
452 | Usually `args` is an array of strings.
453 | The `enrollId` parameter should be the desired secure context enrollId that has already been registered against the selected peer.
454 | If left `null` the SDK will use a known enrollId for the selected peer. (this is only relevant in a permissioned network)
455 |
456 | Ex:
457 |
458 | ```js
459 | chaincode.invoke.init_marbles([args], function(err, data){
460 | console.log('create marble response:', data, err);
461 | });
462 | ```
463 |
464 | ### chaincode.query.read(name, [enrollId], [callback]) *depreciated 4/1/2016*
465 | *This function is only here to help people transition from ibc v0.0.x to v1.x.x.*
466 | *You should create your own read() function in your chaincode which will overwrite this prebuilt one.*
467 | *This function will put the `name` argument into `args[0]` and set `function` to `query`.*
468 | *These are passed to the chaincode function `Query(stub *shim.ChaincodeStub, function string, args []string)`.*
469 |
470 | Read variable named name from chaincode state.
471 | This will call the `Query()` function in the Go chaincode.
472 | The `enrollId` parameter should be the desired secure context enrollId that has already been registered against the selected peer.
473 | If left `null` the SDK will use a known enrollId for the selected peer. (this is only relevant in a permissioned network)
474 |
475 | ***
476 | ***
477 |
478 | ## Formats
479 | ### Chaincode Object
480 | This is the main guy.
481 | It is returned in the callback to load_chaincode() and contains all your cc functions + some of the setup/input data.
482 |
483 | ```js
484 | chaincode =
485 | {
486 | query: {
487 | CUSTOM_FUNCTION_NAME1: function(args, cb){etc...}; //call chaincode function and pass it args
488 | CUSTOM_FUNCTION_NAME2: function(args, cb){etc...};
489 | ^^ etc...
490 | }
491 | invoke: {
492 | CUSTOM_FUNCTION_NAME1: function(args, cb){etc...}; //call chaincode function and pass it args
493 | CUSTOM_FUNCTION_NAME2: function(args, cb){etc...};
494 | ^^ etc...
495 | }
496 | deploy: function(func, args, path, cb), //deploy loaded chaincode
497 | details:{ //input options get stored here, sometimes its handy
498 | deployed_name: '', //hash of deployed chaincode
499 | func: {
500 | invoke: [], //array of function names found
501 | query: [] //array of function names found
502 | },
503 | git_url: '',
504 | peers: [], //peer list provided in network()
505 | timestamp: 0, //utc unix timestamp in ms of parsing
506 | users: [], //users provided in load()
507 | unzip_dir: '',
508 | zip_url: '',
509 | }
510 | };
511 | ```
512 |
513 | ### Error Format
514 |
515 | ```js
516 | {
517 | name: "input error", //short name of error
518 | code: 400, //http error status code, integer
519 | details: {msg: "did not provide git_url"} //description of error, obj of unknown makeup
520 | };
521 | ```
522 |
523 | ### Chaincode Summary File
524 | This file is used internally when debugging.
525 | It is created in ibc.load_chaincode() and updated with chaincode.deploy().
526 | A copy can be saved elsewhere with ibc.save(path).
527 | I found it handy in niche cases, but it will probably be unhelpful to most developers.
528 |
529 | ```js
530 | {
531 | "details": {
532 | "deployed_name": "f6c084c42b3bde90c03f214ac6e0426e3e594807901fb1464287f2c3a18ade717bc495298958287594f81bb0d0cfdd3b4346d438d3b587d4fc73cf78ae8f7dfe",
533 | "func": {
534 | "invoke": ["init", "delete", "write", "init_marble", "set_user", "open_trade", "perform_trade"],
535 | },
536 | {
537 | "query": []
538 | },
539 | "git_url": 'https://github.com/ibm-blockchain/marbles-chaincode/part2_v1.0.0'
540 | "peers": [{
541 | "name": "vp1-xxx.xxx.xxx.xxx",
542 | "api_host": "xxx.xxx.xxx.xxx",
543 | "api_port": xxx,
544 | "id": "xxxxx_vp1",
545 | "tls": false,
546 | "enrollId": "user1"
547 | }],
548 | "timestamp": 1459779181971,
549 | "users": [{
550 | "enrollId": "xxx",
551 | "enrollSecret": "xxx"
552 | }],
553 | "unzip_dir": 'marbles-chaincode-master/part2_v1.0.0',
554 | "zip_url": 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip',
555 | "options": {}
556 | }
557 | }
558 | ```
559 |
560 | # FAQ
561 |
562 | *Do you have any examples that use this?*
563 |
564 | - Yes! Head over to the [Marbles Node.js Demo](https://github.com/IBM-Blockchain/marbles)
565 |
566 | *How exactly do I write chaincode?*
567 |
568 | - We have a "hello world" like tutorial for chaincode over at [Learn Chaincode](https://github.com/IBM-Blockchain/learn-chaincode)
569 |
570 | *I'm getting error code 2 in my deploy response?*
571 |
572 | - Your chaincode has build issues and is not compiling. Manually build it in your local machine to get details.
573 |
574 | *I'm getting error code 1!*
575 |
576 | - The shim version your chaincode import has is not the same as the shim the peer is running. ie you are probably running 'Hyperledger' peer code and sending it chaincode with a shim pointing to "OBC-Peer".
577 |
578 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /* global __dirname */
3 | /*******************************************************************************
4 | * Copyright (c) 2016 IBM Corp.
5 | *
6 | * All rights reserved.
7 | *
8 | *******************************************************************************/
9 |
10 | //Load modules
11 | var fs = require('fs');
12 | var path = require('path');
13 | var http = require('http');
14 | var https = require('https');
15 | var async = require('async');
16 | var rest = require(__dirname + '/lib/rest.js');
17 | var helper = require(__dirname + '/lib/helper.js');
18 | var AdmZip = require('adm-zip');
19 | var logger = {log: console.log, error: console.error, debug: console.log, warn: console.log};
20 |
21 | function ibc(log_outputs) {
22 | if(log_outputs && log_outputs.info) logger.log = log_outputs.info; //send normal logs here
23 | if(log_outputs && log_outputs.error) logger.error = log_outputs.error; //send error logs here
24 | if(log_outputs && log_outputs.warn) logger.warn = log_outputs.warn; //send warn logs here
25 | if(log_outputs && log_outputs.debug) logger.debug = log_outputs.debug; //send debug logs here
26 | }
27 | ibc.chaincode = {
28 | query: {},
29 | invoke: {},
30 | deploy: null,
31 | details:{
32 | deployed_name: '',
33 | func: {
34 | invoke: [],
35 | query: []
36 | },
37 | git_url: '',
38 | options: {},
39 | peers: [],
40 | timestamp: 0,
41 | users: [],
42 | unzip_dir: '',
43 | version: '',
44 | zip_url: '',
45 | }
46 | };
47 | ibc.selectedPeer = 0;
48 | ibc.q = []; //array of unix timestamps, 1 for each unsettled action
49 | ibc.lastPoll = 0; //unix timestamp of the last time we polled
50 | ibc.lastBlock = 0; //last blockheight found
51 | var tempDirectory = path.join(__dirname, './temp'); // =./temp - temp directory name
52 |
53 |
54 | // ============================================================================================================================
55 | // EXTERNAL - load() - wrapper on a standard startup flow.
56 | // 1. load network peer data
57 | // 2. register users with security (if present)
58 | // 3. load chaincode and parse
59 | // ============================================================================================================================
60 | ibc.prototype.load = function(options, cb){
61 | var errors = [];
62 | if(!options.network || !options.network.peers) errors.push('the option "network.peers" is required');
63 |
64 | if(!options.chaincode || !options.chaincode.zip_url) errors.push('the option "chaincode.zip_url" is required');
65 | if(!options.chaincode || !options.chaincode.unzip_dir) errors.push('the option "chaincode.unzip_dir" is required');
66 | if(!options.chaincode || !options.chaincode.git_url) errors.push('the option "chaincode.git_url" is required');
67 | if(errors.length > 0){ //check for input errors
68 | logger.error('! [ibc-js] Input Error - ibc.load()', errors);
69 | if(cb) cb(helper.eFmt('load() input error', 400, errors));
70 | return; //get out of dodge
71 | }
72 |
73 | ibc.chaincode = { //empty it all
74 | query: {
75 | read: read
76 | },
77 | invoke: {},
78 | deploy: null,
79 | details:{
80 | deployed_name: '',
81 | func: {
82 | invoke: [],
83 | query: []
84 | },
85 | git_url: '',
86 | options: options.network.options,
87 | peers: [],
88 | timestamp: 0,
89 | users: [],
90 | unzip_dir: '',
91 | version: '',
92 | zip_url: '',
93 | }
94 | };
95 |
96 | // Step 1
97 | ibc.prototype.network(options.network.peers, options.network.options);
98 |
99 | // Step 2 - optional - only for secure networks
100 | if(options.network.users && options.network.users.length > 0){
101 | ibc.chaincode.details.users = options.network.users;
102 | var arr = [];
103 | for(var i in ibc.chaincode.details.peers){
104 | arr.push(i); //build the list of indexes
105 | }
106 | async.each(arr, function(i, a_cb) {
107 | if(options.network.users[i]){ //make sure we still have a enrollId for this network
108 | var maxRetry = 2;
109 | if(options.network.options && options.network.options.maxRetry) maxRetry = options.network.options.maxRetry;
110 | ibc.prototype.register(i, options.network.users[i].enrollId, options.network.users[i].enrollSecret, maxRetry, a_cb);
111 | }
112 | else a_cb();
113 | }, function(err, data){
114 | if(err && cb) return cb(err); //error already formated
115 | else load_cc();
116 | });
117 | }
118 | else{
119 | ibc.chaincode.details.users = [];
120 | logger.log('[ibc-js] No membership users found, assuming this is a network w/o membership');
121 | load_cc();
122 | }
123 |
124 | // Step 3
125 | function load_cc(){
126 | ibc.prototype.load_chaincode(options.chaincode, cb); //download/parse and load chaincode
127 | }
128 | };
129 |
130 | // ============================================================================================================================
131 | // EXTERNAL - load_chaincode() - load the chaincode and parssssssssse
132 | // 0. Load the github or zip
133 | // 1. Unzip & scan directory for files
134 | // 2. Iter over go files
135 | // 2a. Find what shim version
136 | // 2b. Find the boundaries for Invoke() in the cc
137 | // 2c. Grab function names that need to be exported
138 | // 2d. Create JS invoke functions for golang functions
139 | // 2e. Find the boundaries for Query() in the cc
140 | // 2f. Grab function names that need to be exported
141 | // 2g. Create JS query functions for golang functions
142 | // 2h. Find the boundaries for Init() in the cc
143 | // 2i. Record function names that need to be exported
144 | // 3. Call callback()
145 | // ============================================================================================================================
146 | ibc.prototype.load_chaincode = function(options, cb) {
147 | var errors = [];
148 | if(!options.zip_url) errors.push('the option "zip_url" is required');
149 | if(!options.unzip_dir) errors.push('the option "unzip_dir" is required');
150 | if(!options.git_url) errors.push('the option "git_url" is required');
151 | if(errors.length > 0){ //check for input errors
152 | logger.error('! [ibc-js] Input Error - ibc.load_chaincode()', errors);
153 | if(cb) cb(helper.eFmt('load_chaincode() input error', 400, errors));
154 | return; //get out of dodge
155 | }
156 |
157 | var go_funcs = [], cc_suspects = [], cc_invocations = [], cc_queries = [], cc_inits = [];
158 | var found_query = false, found_invoke = false;
159 | var zip_dest = path.join(tempDirectory, '/file.zip'); // =./temp/file.zip
160 | var unzip_dest = path.join(tempDirectory, '/unzip'); // =./temp/unzip
161 | var unzip_cc_dest = path.join(unzip_dest, '/', options.unzip_dir); // =./temp/unzip/DIRECTORY
162 | ibc.chaincode.details.zip_url = options.zip_url;
163 | ibc.chaincode.details.unzip_dir = options.unzip_dir;
164 | ibc.chaincode.details.git_url = options.git_url;
165 | ibc.chaincode.details.deployed_name = options.deployed_name;
166 |
167 | if(!options.deployed_name || options.deployed_name === ''){ //lets clear and re-download
168 | ibc.prototype.clear(cb_ready);
169 | }
170 | else{
171 | cb_ready();
172 | }
173 |
174 | // check if we already have the chaincode in the local filesystem, else download it
175 | function cb_ready(){
176 | try{fs.mkdirSync(tempDirectory);}
177 | catch(e){ }
178 | fs.access(unzip_cc_dest, cb_file_exists); //check if files exist yet
179 | function cb_file_exists(e){
180 | if(e != null){
181 | download_it(options.zip_url); //nope, go download it
182 | }
183 | else{
184 | logger.log('[ibc-js] Found chaincode in local file system');
185 | fs.readdir(unzip_cc_dest, cb_got_names); //yeppers, go use it
186 | }
187 | }
188 | }
189 |
190 | // Step 0.
191 | function download_it(download_url){
192 | logger.log('[ibc-js] Downloading zip');
193 | var file = fs.createWriteStream(zip_dest);
194 | var handleResponse = function(response) { //download a .zip of the repo
195 | response.pipe(file);
196 | file.on('finish', function() {
197 | if(response.headers.status === '302 Found'){
198 | logger.log('redirect...', response.headers.location);
199 | file.close();
200 | download_it(response.headers.location);
201 | }
202 | else{
203 | file.close(cb_downloaded); //close() is async
204 | }
205 | });
206 | };
207 | var handleError = function(err) {
208 | logger.error('! [ibc-js] Download error');
209 | fs.unlink(zip_dest); //delete the file async
210 | if (cb) cb(helper.eFmt('doad_chaincode() download error', 500, err.message), ibc.chaincode);
211 | };
212 |
213 | var protocol = download_url.split('://')[0];
214 | if(protocol === 'https') { //choose http or https
215 | https.get(download_url, handleResponse).on('error', handleError);
216 | }
217 | else{
218 | http.get(download_url, handleResponse).on('error', handleError);
219 | }
220 | }
221 |
222 | // Step 1.
223 | function cb_downloaded(){
224 | logger.log('[ibc-js] Unzipping zip');
225 | try{
226 | var zip = new AdmZip(zip_dest); //unzip the zip we downloaded
227 | zip.extractAllTo(unzip_dest, /*overwrite*/true);
228 | }
229 | catch (err){
230 | return cb(helper.eFmt('download repo error', 400, err), null);
231 | }
232 | logger.log('[ibc-js] Unzip done');
233 | fs.readdir(unzip_cc_dest, cb_got_names);
234 | fs.unlink(zip_dest, function(err) {}); //remove zip file, never used again
235 | }
236 |
237 | // Step 2.
238 | function cb_got_names(err, obj){
239 | logger.log('[ibc-js] Scanning files', obj);
240 | var foundGo = false;
241 | if(err != null) logger.log('! [ibc-js] fs readdir Error', err);
242 | else{
243 | for(var i in obj){
244 | if(obj[i].indexOf('.go') >= 0){ //look for GoLang files
245 | if(!found_invoke || !found_query){
246 | foundGo = true;
247 | var file = fs.readFileSync(path.join(unzip_cc_dest, obj[i]), 'utf8');
248 |
249 | // Step 2a.
250 | ibc.chaincode.details.version = find_shim(file);
251 | if(ibc.chaincode.details.version !== ''){ //we can't search for functions until we identify the shim version
252 | parse_for_invoke(obj[i], file);
253 | parse_for_query(obj[i], file);
254 | parse_for_init(obj[i], file);
255 | }
256 | }
257 | }
258 | }
259 | }
260 |
261 | // done - look for errors/warnings
262 | var msg = '';
263 | if(!foundGo){ //error no go files
264 | msg = 'did not find any *.go files, cannot continue';
265 | logger.error('! [ibc-js] Error - ', msg);
266 | if(cb) return cb(helper.eFmt('load_chaincode() no chaincode', 400, msg), null);
267 | }
268 | else{
269 |
270 | if(!found_invoke){ //warning no run/invoke functions
271 | logger.warn('! [ibc-js] Warning - did not find any invoke functions in chaincode\'s "Invoke()", building a generic "invoke"');
272 | build_invoke_func('invoke'); //this will make chaincode.invoke.invokce(args)
273 | }
274 |
275 | if(!found_query){ //warning no query functions
276 | logger.warn('! [ibc-js] Warning - did not find any query functions in chaincode\'s "Query()", building a generic "query"');
277 | build_query_func('query'); //this will make chaincode.query.query(args)
278 | }
279 |
280 | // Step 3. success!
281 | logger.log('[ibc-js] load_chaincode() finished');
282 | ibc.chaincode.details.timestamp = Date.now();
283 | ibc.chaincode.deploy = deploy;
284 | if(cb) return cb(null, ibc.chaincode); //all done, send it to callback
285 | }
286 | }
287 |
288 | //regex to find the shim version for this chaincode
289 | function find_shim(file){
290 | var ret = '';
291 | if(file == null) logger.error('! [ibc-js] fs readfile Error');
292 | else{
293 | logger.log('[ibc-js] Parsing file for shim version');
294 |
295 | var shim_regex = /github.com\/\S+\/shim/g; //find chaincode's shim version
296 | var result = file.match(shim_regex);
297 | if(result[0]){
298 | logger.log('[ibc-js] Found shim version:', result[0]);
299 | ret = result[0];
300 | }
301 | }
302 | return ret;
303 | }
304 |
305 | //look for Invokes
306 | function parse_for_invoke(name, str){
307 | if(str == null) logger.error('! [ibc-js] fs readfile Error');
308 | else{
309 | logger.log('[ibc-js] Parsing file for invoke functions -', name);
310 |
311 | // Step 2a.
312 | var go_func_regex = /func\s+\(\w+\s+\*SimpleChaincode\)\s+(\w+)/g; //find chaincode's go lang functions
313 | var result;
314 | while ( (result = go_func_regex.exec(str)) ) {
315 | go_funcs.push({name: result[1], pos: result.index});
316 | }
317 | var i_start = 0;
318 | var i_stop = 0;
319 | var invokeFunctionName = 'Run'; //use Run for obc peer adn Invoke for hyperledger
320 | if(ibc.chaincode.details.version.indexOf('hyperledger/fabric/core/chaincode/shim') >= 0) invokeFunctionName = 'Invoke';
321 |
322 | for(var i in go_funcs){
323 | if(go_funcs[i].name === invokeFunctionName){
324 | i_start = go_funcs[i].pos; //find start and stop positions around the "Invoke()" function
325 | if(go_funcs[Number(i) + 1] == null) i_stop = i_start * 2; //invoke is the last function.. so uhhhh just make up a high number
326 | else i_stop = go_funcs[Number(i) + 1].pos;
327 | break;
328 | }
329 | }
330 |
331 | if(i_start > 0 && i_stop > 0){
332 | // Step 2c.
333 | var regex = /function\s+.=\s+["'](\w+)["']/g; //find the exposed chaincode functions in "Invoke()""
334 | var result2;
335 | while ( (result2 = regex.exec(str)) ) {
336 | cc_suspects.push({name: result2[1], index: result2.index}); //store this for future parsing like query & init
337 | if(result2.index > i_start && result2.index < i_stop){ //make sure its inside Invoke()
338 | cc_invocations.push(result2[1]); //build a list of function names
339 | }
340 | }
341 |
342 | if(cc_invocations.length > 0){
343 | found_invoke = true;
344 |
345 | // Step 2d.
346 | ibc.chaincode.details.func.invoke = [];
347 | for(i in cc_invocations){ //build the rest call for each function
348 | build_invoke_func(cc_invocations[i]);
349 | }
350 | }
351 | }
352 | }
353 | }
354 |
355 | //look for Queries
356 | function parse_for_query(name, str){
357 | if(str == null) logger.error('! [ibc-js] fs readfile Error');
358 | else{
359 | logger.log('[ibc-js] Parsing file for query functions -', name);
360 |
361 | // Step 2e.
362 | var q_start = 0;
363 | var q_stop = 0;
364 | for(var i in go_funcs){
365 | if(go_funcs[i].name === 'Query'){
366 | q_start = go_funcs[i].pos; //find start and stop positions around the "Query()" function
367 | if(go_funcs[Number(i) + 1] == null) q_stop = q_start * 2; //query is the last function.. so uhhhh just make up a high number
368 | else q_stop = go_funcs[Number(i) + 1].pos;
369 | break;
370 | }
371 | }
372 |
373 | if(q_start > 0 && q_stop > 0){
374 | // Step 2f.
375 | for(i in cc_suspects){
376 | if(cc_suspects[i].index > q_start && cc_suspects[i].index < q_stop){//make sure its inside Query()
377 | cc_queries.push(cc_suspects[i].name); //build a list of function names
378 | }
379 | }
380 |
381 | if(cc_queries.length > 0){
382 | found_query = true;
383 |
384 | // Step 2g.
385 | ibc.chaincode.details.func.query = [];
386 | for(i in cc_queries){ //build the rest call for each function
387 | build_query_func(cc_queries[i]);
388 | }
389 | }
390 | }
391 | }
392 | }
393 |
394 | //look for Inits
395 | function parse_for_init(name, str){
396 | if(str == null) logger.error('! [ibc-js] fs readfile Error');
397 | else{
398 | //logger.log('[ibc-js] Parsing file for init functions -', name);
399 |
400 | // Step 2h.
401 | var q_start = 0;
402 | var q_stop = 0;
403 | for(var i in go_funcs){
404 | if(go_funcs[i].name === 'Init'){
405 | q_start = go_funcs[i].pos; //find start and stop positions around the "Init()" function
406 | if(go_funcs[Number(i) + 1] == null) q_stop = q_start * 2; //init is the last function.. so uhhhh just make up a high number
407 | else q_stop = go_funcs[Number(i) + 1].pos;
408 | break;
409 | }
410 | }
411 |
412 | if(q_start > 0 && q_stop > 0){
413 | for(i in cc_suspects){
414 | if(cc_suspects[i].index > q_start && cc_suspects[i].index < q_stop){//make sure its inside Init()
415 | cc_inits.push(cc_suspects[i].name); //build a list of function names
416 | }
417 | }
418 |
419 | if(cc_inits.length > 0){
420 |
421 | // Step 2i.
422 | ibc.chaincode.details.func.init = [];
423 | for(i in cc_inits){ //no rest call to build, just remember it in 'details'
424 | ibc.chaincode.details.func.init.push(name);
425 | }
426 | }
427 | }
428 | }
429 | }
430 | };
431 |
432 | // ============================================================================================================================
433 | // EXTERNAL - network() - setup network configuration to hit a rest peer
434 | // ============================================================================================================================
435 | ibc.prototype.network = function(arrayPeers, options){
436 | var errors = [];
437 | ibc.chaincode.details.options = {quiet: true, timeout: 60000, tls: true}; //defaults
438 |
439 | if(!arrayPeers || arrayPeers.constructor !== Array) errors.push('network input arg should be array of peer objects');
440 |
441 | if(options){
442 | if(options.quiet === true || options.quiet === false) ibc.chaincode.details.options.quiet = options.quiet; //optional fields
443 | if(!isNaN(options.timeout)) ibc.chaincode.details.options.timeout = Number(options.timeout);
444 | if(options.tls === true || options.tls === false) ibc.chaincode.details.options.tls = options.tls;
445 | }
446 |
447 | for(var i in arrayPeers){ //check for errors in peers input obj
448 | if(!arrayPeers[i].id) errors.push('peer ' + i + ' is missing the field id');
449 | if(!arrayPeers[i].api_host) errors.push('peer ' + i + ' is missing the field api_host');
450 | if(options && options.tls === false){
451 | if(!arrayPeers[i].api_port) errors.push('peer ' + i + ' is missing the field api_port');
452 | }
453 | else{
454 | if(!arrayPeers[i].api_port_tls) errors.push('peer ' + i + ' is missing the field api_port_tls');
455 | }
456 | }
457 |
458 | if(errors.length > 0){ //check for input errors
459 | logger.error('! [ibc-js] Input Error - ibc.network()', errors);
460 | }
461 | else{
462 | ibc.chaincode.details.peers = [];
463 | for(i in arrayPeers){
464 | var pos = arrayPeers[i].id.indexOf('_') + 1;
465 | var temp = {
466 | name: arrayPeers[i].id.substring(pos) + '-' + arrayPeers[i].id.substring(0, 12) + '...:' + arrayPeers[i].api_port_tls,
467 | api_host: arrayPeers[i].api_host,
468 | api_port: arrayPeers[i].api_port,
469 | api_port_tls: arrayPeers[i].api_port_tls,
470 | id: arrayPeers[i].id,
471 | tls: ibc.chaincode.details.options.tls
472 | };
473 | if(options && options.tls === false){ //if not tls rebuild a few things
474 | temp.name = arrayPeers[i].id.substring(pos) + '-' + arrayPeers[i].id.substring(0, 12) + '...:' + arrayPeers[i].api_port;
475 | }
476 |
477 | logger.log('[ibc-js] Peer: ', temp.name); //print the friendly name
478 | ibc.chaincode.details.peers.push(temp);
479 | }
480 |
481 | rest.init({ //load default values for rest call to peer
482 | host: ibc.chaincode.details.peers[0].api_host,
483 | port: pick_port(0),
484 | headers: {
485 | 'Content-Type': 'application/json',
486 | 'Accept': 'application/json',
487 | },
488 | ssl: ibc.chaincode.details.peers[0].tls,
489 | timeout: ibc.chaincode.details.options.timeout,
490 | quiet: ibc.chaincode.details.options.quiet
491 | }, logger);
492 | }
493 | };
494 |
495 | //pick tls or non-tls port based on the tls setting
496 | function pick_port(pos){
497 | var port = ibc.chaincode.details.peers[pos].api_port_tls;
498 | if(ibc.chaincode.details.peers[pos].tls === false) port = ibc.chaincode.details.peers[pos].api_port;
499 | return port;
500 | }
501 |
502 |
503 | // ============================================================================================================================
504 | // EXTERNAL - switchPeer() - switch the default peer to hit
505 | // ============================================================================================================================
506 | ibc.prototype.switchPeer = function(index) {
507 | if(ibc.chaincode.details.peers[index]) {
508 | rest.init({ //load default values for rest call to peer
509 | host: ibc.chaincode.details.peers[index].api_host,
510 | port: pick_port(index),
511 | headers: {
512 | 'Content-Type': 'application/json',
513 | 'Accept': 'application/json',
514 | },
515 | ssl: ibc.chaincode.details.peers[index].tls,
516 | timeout: ibc.chaincode.details.options.timeout,
517 | quiet: ibc.chaincode.details.options.quiet
518 | });
519 | ibc.selectedPeer = index;
520 | return true;
521 | } else {
522 | return false;
523 | }
524 | };
525 |
526 | // ============================================================================================================================
527 | // EXTERNAL - save() - write chaincode details to a json file
528 | // ============================================================================================================================
529 | ibc.prototype.save = function(dir, cb){
530 | var errors = [];
531 | if(!dir) errors.push('the option "dir" is required');
532 | if(errors.length > 0){ //check for input errors
533 | logger.error('[ibc-js] Input Error - ibc.save()', errors);
534 | if(cb) cb(helper.eFmt('save() input error', 400, errors));
535 | }
536 | else{
537 | var fn = 'chaincode.json'; //default name
538 | if(ibc.chaincode.details.deployed_name) fn = ibc.chaincode.details.deployed_name + '.json';
539 | var dest = path.join(dir, fn);
540 | fs.writeFile(dest, JSON.stringify({details: ibc.chaincode.details}), function(e){
541 | if(e != null){
542 | logger.warn('[ibc-js] ibc.save() warning', e);
543 | if(cb) cb(helper.eFmt('save() fs write error', 500, e), null);
544 | }
545 | else {
546 | if(cb) cb(null, null);
547 | }
548 | });
549 | }
550 | };
551 |
552 | // ============================================================================================================================
553 | // EXTERNAL - clear() - clear the temp directory
554 | // ============================================================================================================================
555 | ibc.prototype.clear = function(cb){
556 | logger.log('[ibc-js] removing temp dir');
557 | helper.removeThing(tempDirectory, cb); //remove everything in this directory
558 | };
559 |
560 | //============================================================================================================================
561 | // EXTERNAL chain_stats() - get blockchain stats
562 | //============================================================================================================================
563 | ibc.prototype.chain_stats = function(cb){
564 | var options = {path: '/chain'}; //very simple API, get chainstats!
565 |
566 | rest.get(options, null, function(statusCode, data){
567 | if(statusCode != null){
568 | logger.error('[ibc-js] Chain Stats - failure:', statusCode, data);
569 | if(cb) cb(helper.eFmt('chain_stats() error', statusCode, data), null);
570 | }
571 | else{
572 | logger.log('[ibc-js] Chain Stats - success');
573 | if(cb) cb(null, data);
574 | }
575 | });
576 | };
577 |
578 | //============================================================================================================================
579 | // EXTERNAL block_stats() - get block meta data
580 | //============================================================================================================================
581 | ibc.prototype.block_stats = function(id, cb){
582 | var options = {path: '/chain/blocks/' + id}; //i think block IDs start at 0, height starts at 1, fyi
583 |
584 | rest.get(options, null, function(statusCode, data){
585 | if(statusCode != null){
586 | logger.error('[ibc-js] Block Stats ', id , '- failure:', statusCode);
587 | if(cb) cb(helper.eFmt('block_stats() error', statusCode, data), null);
588 | }
589 | else{
590 | logger.log('[ibc-js] Block Stats ', id , '- success');
591 | if(cb) cb(null, data);
592 | }
593 | });
594 | };
595 |
596 | //============================================================================================================================
597 | //read() - read generic variable from chaincode state - ! [legacy. do not use it anymore 4/1/2016]
598 | //============================================================================================================================
599 | function read(args, enrollId, cb){
600 | if(typeof enrollId === 'function'){ //if cb is in 2nd param use known enrollId
601 | cb = enrollId;
602 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
603 | }
604 | if(enrollId == null) { //if enrollId not provided, use known valid one
605 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
606 | }
607 |
608 | var options = {path: '/chaincode'};
609 | var body = {
610 | jsonrpc: '2.0',
611 | method: 'query',
612 | params: {
613 | type: 1,
614 | chaincodeID:{
615 | name: ibc.chaincode.details.deployed_name
616 | },
617 | ctorMsg: {
618 | function: 'query',
619 | args: args
620 | },
621 | secureContext: enrollId
622 | },
623 | id: Date.now()
624 | };
625 |
626 | rest.post(options, null, body, function(statusCode, data){
627 | if(statusCode != null){
628 | logger.error('[ibc-js] (Read) - failure:', statusCode);
629 | if(cb) cb(helper.eFmt('read() error', statusCode, data), null);
630 | }
631 | else{
632 | logger.log('[ibc-js] (Read) - success:', data);
633 | if(cb){
634 | if(data.error) cb(helper.eFmt('query() resp error', 400, data.error), null);
635 | else if(data.result) cb(null, data.result.message);
636 | else cb(null, data.OK);
637 | }
638 | }
639 | });
640 | }
641 |
642 | //============================================================================================================================
643 | // EXTERNAL - register() - register a enrollId with a peer (only for a blockchain network with membership)
644 | //============================================================================================================================
645 | ibc.prototype.register = function(index, enrollId, enrollSecret, maxRetry, cb) {
646 | register(index, enrollId, enrollSecret, maxRetry, 1, cb);
647 | };
648 |
649 | function register(index, enrollId, enrollSecret, maxRetry, attempt, cb){
650 | logger.log('[ibc-js] Registering ', ibc.chaincode.details.peers[index].name, ' w/enrollId - ' + enrollId);
651 | var options = {
652 | path: '/registrar',
653 | host: ibc.chaincode.details.peers[index].api_host,
654 | port: pick_port(index),
655 | ssl: ibc.chaincode.details.peers[index].tls
656 | };
657 |
658 | var body = {
659 | enrollId: enrollId,
660 | enrollSecret: enrollSecret
661 | };
662 | rest.post(options, null, body, function(statusCode, data){
663 | if(statusCode != null){
664 | logger.error('[ibc-js] Register - failure x' + attempt + ' :', enrollId, statusCode);
665 | if(attempt <= maxRetry){ //lets try again after a short delay, maybe the peer is still starting
666 | logger.log('[ibc-js] \tgoing to try to register again in 30 secs');
667 | setTimeout(function(){register(index, enrollId, enrollSecret, maxRetry, ++attempt, cb);}, 30000);
668 | }
669 | else{
670 | if(cb) cb(helper.eFmt('register() error', statusCode, data), null); //give up
671 | }
672 | }
673 | else {
674 | logger.log('[ibc-js] Registration success x' + attempt + ' :', enrollId);
675 | ibc.chaincode.details.peers[index].enrollId = enrollId; //remember a valid enrollId for this peer
676 | if(cb) cb(null, data);
677 | }
678 | });
679 | }
680 |
681 | //============================================================================================================================
682 | // EXTERNAL - unregister() - unregister a enrollId from a peer (only for a blockchain network with membership), enrollId can no longer make transactions
683 | //============================================================================================================================
684 | ibc.prototype.unregister = function(index, enrollId, cb) {
685 | logger.log('[ibc-js] Unregistering ', ibc.chaincode.details.peers[index].name, ' w/enrollId - ' + enrollId);
686 | var options = {
687 | path: '/registrar/' + enrollId,
688 | host: ibc.chaincode.details.peers[index].api_host,
689 | port: pick_port(index),
690 | ssl: ibc.chaincode.details.peers[index].tls
691 | };
692 |
693 | rest.delete(options, null, null, function(statusCode, data){
694 | if(statusCode != null){
695 | logger.log('[ibc-js] Unregistering - failure:', enrollId, statusCode);
696 | if(cb) cb(helper.eFmt('unregister() error', statusCode, data), null);
697 | }
698 | else {
699 | logger.log('[ibc-js] Unregistering success:', enrollId);
700 | ibc.chaincode.details.peers[index].enrollId = null; //unremember a valid enrollId for this peer
701 | if(cb) cb(null, data);
702 | }
703 | });
704 | };
705 |
706 | //============================================================================================================================
707 | // EXTERNAL - check_register() - check if a enrollId is registered or not with a peer
708 | //============================================================================================================================
709 | ibc.prototype.check_register = function(index, enrollId, cb) {
710 | logger.log('[ibc-js] Checking ', ibc.chaincode.details.peers[index].name, ' w/enrollId - ' + enrollId);
711 | var options = {
712 | path: '/registrar/' + enrollId,
713 | host: ibc.chaincode.details.peers[index].api_host,
714 | port: pick_port(index),
715 | ssl: ibc.chaincode.details.peers[index].tls
716 | };
717 |
718 | rest.get(options, null, function(statusCode, data){
719 | if(statusCode != null){
720 | logger.error('[ibc-js] Check Register - failure:', enrollId, statusCode);
721 | if(cb) cb(helper.eFmt('check_register() error', statusCode, data), null);
722 | }
723 | else{
724 | logger.log('[ibc-js] Check Register success:', enrollId);
725 | if(cb) cb(null, data);
726 | }
727 | });
728 | };
729 |
730 | //============================================================================================================================
731 | //deploy() - deploy chaincode and call a cc function
732 | //============================================================================================================================
733 | function deploy(func, args, deploy_options, enrollId, cb){
734 | if(typeof enrollId === 'function'){ //if cb is in 2nd param use known enrollId
735 | cb = enrollId;
736 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
737 | }
738 | if(enrollId == null) { //if enrollId not provided, use known valid one
739 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
740 | }
741 |
742 | logger.log('[ibc-js] Deploy Chaincode - Starting');
743 | logger.log('[ibc-js] \tfunction:', func, ', arg:', args);
744 | logger.log('\n\n\t Waiting...'); //this can take awhile
745 |
746 | var options = {}, body = {};
747 | options = {path: '/chaincode'};
748 | body = {
749 | jsonrpc: '2.0',
750 | method: 'deploy',
751 | params: {
752 | type: 1,
753 | chaincodeID:{
754 | path: ibc.chaincode.details.git_url
755 | },
756 | ctorMsg: {
757 | function: func,
758 | args: args
759 | },
760 | secureContext: enrollId
761 | },
762 | id: Date.now()
763 | };
764 |
765 | rest.post(options, null, body, function(statusCode, data){
766 | // ---- Failure ---- ///
767 | if(statusCode != null){
768 | logger.error('[ibc-js] deploy - failure:', statusCode);
769 | if(cb) cb(helper.eFmt('deploy() error', statusCode, data), null);
770 | }
771 |
772 | // ---- Success ---- //
773 | else{
774 | if(data.result && ibc.chaincode.details.version.indexOf('hyperledger/fabric/core/chaincode/shim') >= 0){//hyperledger response
775 | ibc.chaincode.details.deployed_name = data.result.message;
776 | }
777 | else ibc.chaincode.details.deployed_name = data.message; //obc-peer response
778 |
779 | if(!ibc.chaincode.details.deployed_name || ibc.chaincode.details.deployed_name.length < 32){
780 | ibc.chaincode.details.deployed_name = ''; //doesnt look right, let code below catch error
781 | }
782 |
783 | if(ibc.chaincode.details.deployed_name === ''){
784 | logger.error('\n\n\t deploy resp error - there is no chaincode hash name in response:', data);
785 | if(cb) cb(helper.eFmt('deploy() error no cc name', 502, data), null);
786 | }
787 | else{
788 | ibc.prototype.save(tempDirectory); //save it to known place so we remember the cc name
789 | if(deploy_options && deploy_options.save_path != null) { //save it to custom route
790 | ibc.prototype.save(deploy_options.save_path);
791 | }
792 |
793 | if(cb){
794 | var wait_ms = 45000; //default wait after deploy, peer may still be starting
795 | if(deploy_options && deploy_options.delay_ms && Number(deploy_options.delay_ms)) wait_ms = deploy_options.delay_ms;
796 | logger.log('\n\n\t deploy success [waiting another', (wait_ms / 1000) ,'seconds]');
797 | logger.log('\t', ibc.chaincode.details.deployed_name, '\n');
798 |
799 | setTimeout(function(){
800 | logger.log('[ibc-js] Deploy Chaincode - Complete');
801 | cb(null, data);
802 | }, wait_ms); //wait extra long, not always ready yet
803 | }
804 | }
805 | }
806 | });
807 | }
808 |
809 | //============================================================================================================================
810 | //heart_beat() - interval function to poll against blockchain height (has fast and slow mode)
811 | //============================================================================================================================
812 | var slow_mode = 10000;
813 | var fast_mode = 500;
814 | function heart_beat(){
815 | if(ibc.lastPoll + slow_mode < Date.now()){ //slow mode poll
816 | //logger.log('[ibc-js] Its been awhile, time to poll');
817 | ibc.lastPoll = Date.now();
818 | ibc.prototype.chain_stats(cb_got_stats);
819 | }
820 | else{
821 | for(var i in ibc.q){
822 | var elasped = Date.now() - ibc.q[i];
823 | if(elasped <= 3000){ //fresh unresolved action, fast mode!
824 | logger.log('[ibc-js] Unresolved action, must poll');
825 | ibc.lastPoll = Date.now();
826 | ibc.prototype.chain_stats(cb_got_stats);
827 | }
828 | else{
829 | //logger.log('[ibc-js] Expired, removing');
830 | ibc.q.pop(); //expired action, remove it
831 | }
832 | }
833 | }
834 | }
835 |
836 | function cb_got_stats(e, stats){
837 | if(e == null){
838 | if(stats && stats.height){
839 | if(ibc.lastBlock != stats.height) { //this is a new block!
840 | logger.log('[ibc-js] New block!', stats.height);
841 | ibc.lastBlock = stats.height;
842 | ibc.q.pop(); //action is resolved, remove
843 | if(ibc.monitorFunction) ibc.monitorFunction(stats); //call the user's callback
844 | }
845 | }
846 | }
847 | }
848 |
849 | //============================================================================================================================
850 | // EXTERNAL- monitor_blockheight() - exposed function that user can use to get callback when any new block is written to the chain
851 | //============================================================================================================================
852 | ibc.prototype.monitor_blockheight = function(cb) { //hook in your own function, triggers when chain grows
853 | setInterval(function(){heart_beat();}, fast_mode);
854 | ibc.monitorFunction = cb; //store it
855 | };
856 |
857 | //============================================================================================================================
858 | // EXTERNAL- get_transaction() - exposed function to find a transaction based on its UDID
859 | //============================================================================================================================
860 | ibc.prototype.get_transaction = function(udid, cb) {
861 | var options = {
862 | path: '/transactions/' + udid
863 | };
864 |
865 | rest.get(options, null, function(statusCode, data){
866 | if(statusCode != null){
867 | logger.error('[ibc-js] Get Transaction - failure:', statusCode);
868 | if(cb) cb(helper.eFmt('read() error', statusCode, data), null);
869 | }
870 | else{
871 | logger.log('[ibc-js] Get Transaction - success:', data);
872 | if(cb) cb(null, data);
873 | }
874 | });
875 | };
876 |
877 | //============================================================================================================================
878 | // Helper Functions()
879 | //============================================================================================================================
880 | //build_invoke_func() - create JS function that calls the custom goLang function in the chaincode
881 | //==================================================================
882 | function build_invoke_func(name){
883 | if(ibc.chaincode.invoke[name] != null){ //skip if already exists
884 | //logger.log('[ibc-js] \t skip, func', name, 'already exists');
885 | }
886 | else {
887 | logger.log('[ibc-js] Found cc invoke function: ', name);
888 | ibc.chaincode.details.func.invoke.push(name);
889 | ibc.chaincode.invoke[name] = function(args, enrollId, cb){ //create the function in the chaincode obj
890 | if(typeof enrollId === 'function'){ //if cb is in 2nd param use known enrollId
891 | cb = enrollId;
892 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
893 | }
894 | if(enrollId == null) { //if enrollId not provided, use known valid one
895 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
896 | }
897 |
898 | var options = {}, body = {};
899 | options = {path: '/chaincode'};
900 | body = {
901 | jsonrpc: '2.0',
902 | method: 'invoke',
903 | params: {
904 | type: 1,
905 | chaincodeID:{
906 | name: ibc.chaincode.details.deployed_name
907 | },
908 | ctorMsg: {
909 | function: name,
910 | args: args
911 | },
912 | secureContext: enrollId
913 | },
914 | id: Date.now()
915 | };
916 | rest.post(options, null, body, function(statusCode, data){
917 | if(statusCode != null){
918 | logger.error('[ibc-js]', name, ' - failure:', statusCode, data);
919 | if(cb) cb(helper.eFmt('invoke() error', statusCode, data), null);
920 | }
921 | else{
922 | logger.log('[ibc-js]', name, ' - success:', data);
923 | ibc.q.push(Date.now()); //new action, add it to queue
924 | if(cb) cb(null, data);
925 | }
926 | });
927 | };
928 | }
929 | }
930 |
931 | //==================================================================
932 | //build_query_func() - create JS function that calls the custom goLang function in the chaincode
933 | //==================================================================
934 | function build_query_func(name){
935 | if(ibc.chaincode.query[name] != null && name !== 'read'){ //skip if already exists
936 | //logger.log('[ibc-js] \t skip, func', name, 'already exists');
937 | }
938 | else {
939 | logger.log('[ibc-js] Found cc query function: ', name);
940 | ibc.chaincode.details.func.query.push(name);
941 | ibc.chaincode.query[name] = function(args, enrollId, cb){ //create the function in the chaincode obj
942 | if(typeof enrollId === 'function'){ //if cb is in 2nd param use known enrollId
943 | cb = enrollId;
944 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
945 | }
946 | if(enrollId == null) { //if enrollId not provided, use known valid one
947 | enrollId = ibc.chaincode.details.peers[ibc.selectedPeer].enrollId;
948 | }
949 |
950 | var options = {}, body = {};
951 |
952 | options = {path: '/chaincode'};
953 | body = {
954 | jsonrpc: '2.0',
955 | method: 'query',
956 | params: {
957 | type: 1,
958 | chaincodeID:{
959 | name: ibc.chaincode.details.deployed_name
960 | },
961 | ctorMsg: {
962 | function: name,
963 | args: args
964 | },
965 | secureContext: enrollId
966 | },
967 | id: Date.now()
968 | };
969 |
970 | rest.post(options, null, body, function(statusCode, data){
971 | if(statusCode != null){
972 | logger.error('[ibc-js]', name, ' - failure:', statusCode, data);
973 | if(cb) cb(helper.eFmt('query() error', statusCode, data), null);
974 | }
975 | else{
976 | logger.log('[ibc-js]', name, ' - success:', data);
977 | if(cb){
978 | if(data){
979 | if(data.error) cb(helper.eFmt('query() resp error', 400, data.error), null);
980 | else if(data.result) cb(null, data.result.message);
981 | else cb(null, data.OK);
982 | }
983 | else cb(helper.eFmt('query() resp error', 502, data), null); //something is wrong, response is not what we expect
984 | }
985 | }
986 | });
987 | };
988 | }
989 | }
990 |
991 | module.exports = ibc;
992 |
--------------------------------------------------------------------------------
/lib/helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /*******************************************************************************
3 | * Copyright (c) 2016 IBM Corp.
4 | *
5 | * All rights reserved.
6 | *
7 | *******************************************************************************/
8 | var fs = require('fs');
9 | var async = require('async');
10 | var path = require('path');
11 |
12 | //==================================================================
13 | //eFmt() - format errors
14 | //==================================================================
15 | module.exports.eFmt = function eFmt(name, code, details){ //my error format
16 | return {
17 | name: String(name), //error short name
18 | code: Number(code), //http code when applicable
19 | details: details //error description
20 | };
21 | };
22 |
23 |
24 | //==================================================================
25 | //filter_users() - return only client level enrollId - [1=client, 2=nvp, 4=vp, 8=auditor accurate as of 2/18]
26 | //==================================================================
27 | module.exports.filter_users = function(users){ //this is only needed in a permissioned network
28 | var valid_users = [];
29 | for(var i = 0; i < users.length; i++) {
30 | if(users[i].enrollId.indexOf('user_type1') === 0){ //type should be 1 for client
31 | valid_users.push(users[i]);
32 | }
33 | }
34 | return valid_users;
35 | };
36 |
37 | // ============================================================================================================================
38 | //removeThing() - clear the temp directory
39 | // ============================================================================================================================
40 | module.exports.removeThing = function(dir, cb){
41 | //console.log('!', dir);
42 | fs.readdir(dir, function (err, files) {
43 | if(err != null || !files || files.length === 0){
44 | cb();
45 | }
46 | else{
47 | async.each(files, function (file, cb) { //over each thing
48 | file = path.join(dir, file);
49 | fs.stat(file, function(err, stat) {
50 | if (err) {
51 | if(cb) cb(err);
52 | return;
53 | }
54 | if (stat.isDirectory()) {
55 | module.exports.removeThing(file, cb); //keep going
56 | }
57 | else {
58 | //console.log('!', dir);
59 | fs.unlink(file, function(err) {
60 | if (err) {
61 | if(cb) cb(err);
62 | return;
63 | }
64 | //console.log('good', dir);
65 | if(cb) cb();
66 | return;
67 | });
68 | }
69 | });
70 | }, function (err) {
71 | if(err){
72 | if(cb) cb(err);
73 | return;
74 | }
75 | fs.rmdir(dir, function (err) {
76 | if(cb) cb(err);
77 | return;
78 | });
79 | });
80 | }
81 | });
82 | };
--------------------------------------------------------------------------------
/lib/rest.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | /* global Buffer */
3 | /*******************************************************************************
4 | * Copyright (c) 2015 IBM Corp.
5 | *
6 | * All rights reserved.
7 | *
8 | * Contributors:
9 | * David Huffman - Initial implementation
10 | *******************************************************************************/
11 | /*
12 | Version: 0.7.3
13 | Updated: 11/03/2016
14 | -----------------------------------------------------------------
15 | Use: var rest = require('./rest');
16 | rest.init({quiet: false}); //set default values here for all calls of 'rest'
17 | var options = { //set options here for this call (overrides init)
18 | host: HOST_HERE,
19 | path: PATH HERE,
20 | headers: {"Accept": "application/json"}
21 | };
22 | rest.get(options, null, function(errCode, response){
23 | if(errCode != null) logger.log('Get - failure', errCode, response);
24 | else logger.log('Get - success', response);
25 | });
26 |
27 | -----------------------------------------------------------------
28 |
29 | Valid "options" values: (these are default ones that come from requests module)
30 | -----------------------------------------------------------------
31 | host: A domain name or IP address of the server to issue the request to. Defaults to 'localhost'.
32 | hostname: To support url.parse() hostname is preferred over host
33 | port: Port of remote server. Defaults to 80.
34 | localAddress: Local interface to bind for network connections.
35 | socketPath: Unix Domain Socket (use one of host:port or socketPath)
36 | method: A string specifying the HTTP request method. Defaults to 'GET'.
37 | path: Request path. Defaults to '/'. Should include query string if any. E.G. '/index.html?page=12'.
38 | An exception is thrown when the request path contains illegal characters. Currently, only spaces are rejected but that may change in the future.
39 | headers: An object containing request headers.
40 | auth: Basic authentication i.e. 'user:password' to compute an Authorization header.
41 | agent: Controls Agent behavior. When an Agent is used request will default to Connection: keep-alive. Possible values:
42 | undefined (default): use global Agent for this host and port.
43 | Agent object: explicitly use the passed in Agent.
44 | false: opts out of connection pooling with an Agent, defaults request to Connection: close.
45 | keepAlive: {Boolean} Keep sockets around in a pool to be used by other requests in the future. Default = false
46 | keepAliveMsecs: {Integer} When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive.
47 | Default = 1000. Only relevant if keepAlive is set to true.
48 |
49 | Plus my "options" values:
50 | -----------------------------------------------------------------
51 | quiet: If true will not print to logger. Defaults false.
52 | tls: Iff false will use http instead of https. Defaults true.
53 | timeout: Integer in milliseconds to time out requests. Defaults 20,000
54 | include_headers: If true the response argument will be {"response":, "headers":}
55 | */
56 |
57 | var https_mod = require('https');
58 | var http_mod = require('http');
59 | var querystring = require('querystring');
60 | var default_options = {
61 | quiet: false,
62 | tls: true,
63 | timeout: 20000,
64 | include_headers: false
65 | };
66 | var logger = {log: console.log, error: console.error, debug: console.log, warn: console.log};
67 |
68 | //is the obj empty or not
69 | function isEmpty(obj) {
70 | for(var prop in obj) {
71 | if(obj.hasOwnProperty(prop))
72 | return false;
73 | }
74 | return true;
75 | }
76 |
77 | //merge fields in obj B to obj A only if they don't exist in obj A
78 | function mergeBtoA(b, a){
79 | for(var i in b){
80 | if(a[i] === undefined) {
81 | a[i] = JSON.parse(JSON.stringify(b[i]));
82 | }
83 | }
84 | return a;
85 | }
86 |
87 | //main http request builder/handler/thingy
88 | function http_req(options, query_params, body, attempt, cb){
89 | var acceptJson = false, http, http_txt = '', request = null, formatted_body = null;
90 | var ids = 'abcdefghijkmnopqrstuvwxyz';
91 | var id = ids[Math.floor(Math.random() * ids.length)]; //random letter to help id calls when there are multiple rest calls
92 | var cb_fired = false;
93 |
94 | if(!attempt || isNaN(attempt)) attempt = 1; //defaults to attempt # 1
95 | options = mergeBtoA(default_options, options);
96 |
97 | // ----- Handle Call Back ----- //
98 | function call_cb(ret){
99 | if(cb_fired === false){ //only call cb once!
100 | cb_fired = true;
101 | if(options.include_headers) ret.msg = {response:ret.msg, headers: ret.headers};
102 | if(ret.code <= 399 && ret.code !== 302) ret.code = null;
103 | if(cb) cb(ret.code, ret.msg); //1st arg is error status code, null if no error code
104 | return;
105 | }
106 | }
107 |
108 | // ---- Pick HTTP vs HTTPS ---- //
109 | if(options.ssl === false || options.tls === false) {
110 | http = http_mod; //if options.tls === false use http
111 | http_txt = '[http ' + options.method + ' - ' + id + ']';
112 | }
113 | else{ //else use https
114 | http = https_mod;
115 | http_txt = '[https ' + options.method + ' - ' + id + ']';
116 | }
117 |
118 | if(!options.quiet) logger.debug(http_txt + ' ' + options.host + ':' + options.port);
119 | if(!options.quiet) logger.debug(http_txt + ' ' + options.path);
120 |
121 | // ---- Sanitize Inputs ---- //
122 | if(!options.headers) options.headers = {};
123 | for(var i in options.headers) { //convert all header keys to lower-case for easier parsing
124 | var temp = options.headers[i];
125 | delete options.headers[i];
126 | if(temp != null){
127 | options.headers[i.toLowerCase()] = temp;
128 | }
129 | }
130 |
131 | if(typeof body === 'object' && body != null){
132 | options.headers['content-type'] = 'application/json';
133 | formatted_body = JSON.stringify(body); //stringify body
134 | }
135 | else formatted_body = body;
136 |
137 | if(options.headers.accept && options.headers.accept.indexOf('json') >= 0) acceptJson = true;
138 | if(query_params && typeof query_params === 'object') options.path += '?' + querystring.stringify(query_params);
139 |
140 | if(formatted_body) options.headers['content-length'] = Buffer.byteLength(formatted_body);
141 | else if(options.headers['content-length']) delete options.headers['content-length']; //we don't need you
142 |
143 | if(!options.quiet && options.method.toLowerCase() !== 'get') logger.debug(' body:', formatted_body);
144 |
145 | // --------- Handle Request --------- //
146 | request = http.request(options, function(resp) {
147 | var str = '', chunks = 0;
148 | if(!options.quiet) logger.debug(http_txt + ' Status code: ' + resp.statusCode);
149 |
150 | resp.setEncoding('utf8');
151 | resp.on('data', function(chunk) { //merge chunks of request
152 | str += chunk;
153 | chunks++;
154 | });
155 | resp.on('end', function() { //wait for end before decision
156 | var ret = {
157 | code: resp.statusCode,
158 | headers: resp.headers,
159 | msg: str
160 | };
161 |
162 | // --------- Process Response - Debug Msgs --------- //
163 | if(resp.statusCode == 204){ //empty response, don't parse body
164 | if(!options.quiet) logger.debug(http_txt + ' Data: No Content');
165 | }
166 | else if(resp.statusCode === 302){ //redirect
167 | if(!options.quiet) logger.error(http_txt + ' Error - got a redirect, not what we want');
168 | }
169 | else if(resp.statusCode >= 200 && resp.statusCode <= 399){ //valid status codes
170 | if(acceptJson){
171 | try{
172 | ret.msg = JSON.parse(str); //all good [json resp]
173 | }
174 | catch(e){
175 | if(!options.quiet) logger.error(http_txt + ' Error - response is not JSON: ', str);
176 | ret.code = 500;
177 | ret.msg = 'Invalid JSON response: ' + str;
178 | }
179 | }
180 | else { //all good [not json resp]
181 | if(!options.quiet) logger.debug(http_txt + ' Data:', str);
182 | }
183 | }
184 | else { //invalid status codes
185 | if(!options.quiet) logger.error(http_txt + ' Error - status code: ' + resp.statusCode, str);
186 | if(acceptJson){
187 | try{
188 | ret.msg = JSON.parse(str); //attempt to parse error for JSON
189 | }
190 | catch(e){}
191 | }
192 | }
193 |
194 | // --------- Call CallBack --------- //
195 | return call_cb(ret);
196 | });
197 | });
198 |
199 | // --------- Handle Request Errors --------- //
200 | request.on('error', function(e) { //handle error event
201 | if(e.code === 'ECONNRESET' && attempt <= 3) { //try ECONNRESETs again
202 | if(cb_fired === false){
203 | logger.warn(http_txt + ' Warning - detected ECONNRESET, will try HTTP req again. attempt:' + attempt);
204 | attempt++;
205 | cb_fired = true; //set this just in case
206 | setTimeout(function(){ http_req(options, query_params, body, attempt, cb); }, 250 * Math.pow(2, attempt+1));
207 | }
208 | return;
209 | }
210 | else {
211 | if(!options.quiet) logger.error(http_txt + ' Error - unknown issue with request: ', e);//catch failed request (failed DNS lookup and such)
212 | return call_cb({code: 500, headers: null, msg: e});
213 | }
214 | });
215 |
216 | // --------- Handle Request Timeouts --------- //
217 | request.setTimeout(Number(options.timeout) || default_options.timeout);
218 | request.on('timeout', function(){ //handle time out events
219 | if(!options.quiet) logger.error(http_txt + ' Error - request timed out');
220 | return call_cb({code: 408, headers: null, msg: 'Request timed out'});
221 | });
222 |
223 | // ----- Body ----- //
224 | if(formatted_body && formatted_body !== '' && !isEmpty(formatted_body)){
225 | request.write(formatted_body);
226 | }
227 | request.end(); //send the request
228 | }
229 |
230 | //load new default option values
231 | module.exports.init = function(opt, log_outputs){
232 | for(var i in opt){
233 | default_options[i] = JSON.parse(JSON.stringify(opt[i]));
234 | }
235 |
236 | if(log_outputs && log_outputs.info) logger.log = log_outputs.info; //send normal logs here
237 | if(log_outputs && log_outputs.error) logger.error = log_outputs.error; //send error logs here
238 | if(log_outputs && log_outputs.warn) logger.warn = log_outputs.warn; //send warn logs here
239 | if(log_outputs && log_outputs.debug) logger.debug = log_outputs.debug; //send debug logs here
240 | };
241 |
242 | //http post
243 | module.exports.post = function (l_options, query_params, body, cb){
244 | l_options.method = 'POST';
245 | http_req(l_options, query_params, body, 1 , cb);
246 | };
247 |
248 | //http put
249 | module.exports.put = function (l_options, query_params, body, cb){
250 | l_options.method = 'PUT';
251 | http_req(l_options, query_params, body, 1 , cb);
252 | };
253 |
254 | //http delete
255 | module.exports.delete = function (l_options, query_params, body, cb){
256 | l_options.method = 'DELETE';
257 | http_req(l_options, query_params, body, 1 , cb);
258 | };
259 |
260 | //http get
261 | module.exports.get = function (l_options, query_params, cb){
262 | l_options.method = 'GET';
263 | http_req(l_options, query_params, null, 1 , cb);
264 | };
265 |
266 | //http head
267 | module.exports.head = function (l_options, query_params, cb){
268 | l_options.method = 'HEAD';
269 | http_req(l_options, query_params, null, 1 , cb);
270 | };
271 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibm-blockchain-js",
3 | "version": "1.3.2",
4 | "description": "A library for easily interacting with IBM Blockchain.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/IBM-Blockchain/ibm-blockchain-js.git"
12 | },
13 | "keywords": [
14 | "ibm",
15 | "blockchain",
16 | "obc",
17 | "peer",
18 | "node",
19 | "hyperledger"
20 | ],
21 | "dependencies": {
22 | "adm-zip": "0.4.4",
23 | "async": "1.5.0"
24 | },
25 | "devDependencies": {
26 | "tape": "latest"
27 | },
28 | "author": {
29 | "name": "David Huffman and Ben Smith"
30 | },
31 | "license": "Apache-2.0"
32 | }
33 |
--------------------------------------------------------------------------------
/test/ValidLoadTest.js:
--------------------------------------------------------------------------------
1 | // Function testing the SDK
2 | // Logging statement to terminal to indicate which test is running.
3 | console.log("Now starting LoadTest.js");
4 |
5 | // Starting out by requiring all dependancies
6 | var test = require('tape');
7 | var Ibc1 = require('..');
8 |
9 | // Then define new instances.
10 | var ibc = new Ibc1();
11 | var chaincode = {};
12 |
13 | // Define a flag which determines whether to use Valid or Invalid Options for the test.
14 | // V = Valid, I = Invalid
15 | var Flag = "I";
16 |
17 | // Define options for ibc.load_chancode, where to get the blockchain code
18 | // Made this an if/then flagged option to decide which option set to use.
19 | if (Flag == "V") {
20 | var options = {
21 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip',
22 | unzip_dir: 'marbles-chaincode-master/part2',
23 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2',
24 | deployed_name: null
25 | };
26 | }
27 | else {
28 | // Define some options with a bad zip_url to see how the sdk catches it.
29 | var options = {
30 | zip_url: 'https://github.com/ibm-lockchain/marbles-chaincode/archive/master.zip',
31 | unzip_dir: 'marbles-chaincode-master/part2',
32 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2',
33 | deployed_name: null
34 | };
35 | }
36 |
37 | // I need to create just one test, and run ibc.load inside of it.
38 | // I put the tests into an if/then with the Flag for options.
39 | test('Was the load_chaincode sucessful', function (t) {
40 | if (Flag == "V") {
41 | ibc.load_chaincode(options, function cb_ready(err, cc) {
42 | t.error(err, 'There were no errors');
43 | }); // End of the Valid Load Test
44 | }
45 | else {
46 | ibc.load_chaincode(options, function cb_ready(err, cc) {
47 | t.equal(err, 'Invalid or unsupported zip format. No END header found', 'The error message was unexpected.')
48 | })
49 | }
50 | t.end(); //End Testing
51 | });
--------------------------------------------------------------------------------
/test/cb_readyTest.js:
--------------------------------------------------------------------------------
1 | // Function testing the SDK
2 | // This file contains the most recent tests still being developed.
3 | console.log("Now starting cb_readyTeats.js");
4 |
5 | // Starting out by requiring all dependancies
6 | var test = require('tape');
7 | var Ibc1 = require('..');
8 |
9 | // Then define new instances that will be needed
10 | var ibc = new Ibc1();
11 | var chaincode = {};
12 |
13 | // configure ibc-js sdk by defining options
14 | var options = {
15 |
16 | // Set existant network credentials
17 | // Create a network on Bluemix Experimental BlockChain offering
18 | // Service Credentials are found under the Blockchain instance tab.
19 |
20 | network:{ peers: [{
21 | "api_host": "3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1-api.blockchain.ibm.com",
22 | "api_port": "80",
23 | "id": "3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1",
24 | "api_url": "http://3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1-api.blockchain.ibm.com:80"
25 | }],
26 |
27 | // For simplicity, I chose the first user on the list provided.
28 | users: [{
29 | "username": "user_type0_52737ec3c6",
30 | "secret": "4841d68d27",
31 | "enrollId":"user_type0_52737ec3c6",
32 | "enrollSecret":"4841d68d27"
33 | }] },
34 |
35 | // The chaincode version being tested here is the one deployed in Marbles2.
36 | chaincode:{
37 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip',
38 | unzip_dir: 'marbles-chaincode-master/part2',
39 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2'
40 | }
41 | };
42 |
43 | test('Was the load_chaincode sucessful', function (t) {
44 | // Load the Marbles2 chaincode, with defined options, and return call-back-when-ready function.
45 | ibc.load(options, cb_ready);
46 |
47 | // Define the call-back-when-ready function returned above
48 | // call-back-when-ready function has err
49 | function cb_ready(err, cc){
50 | //response has chaincode functions
51 |
52 | t.error(err, 'There were no errors');
53 |
54 | // if the deployed name is blank, then chaincode has not been deployed
55 | if(cc.details.deployed_name === ""){
56 | cc.deploy('init', ['99'], './cc_summaries', cb_deployed);
57 | function cb_deployed(err){
58 | t.error(err, 'There were no errors');
59 | console.log('sdk has deployed code and waited');
60 | };
61 | }
62 | else{
63 | console.log('chaincode summary file indicates chaincode has been previously deployed');
64 | };
65 | }
66 | t.end();
67 | });
68 |
--------------------------------------------------------------------------------
/test/sdkfunctiontest.js:
--------------------------------------------------------------------------------
1 | // Function testing the SDK
2 | // This file contains the most recent tests still being developed.
3 |
4 | console.log("Now starting SDKFunctiontest.js");
5 |
6 | // Starting out by requiring all dependancies
7 | var test = require('tape');
8 | var Ibc1 = require('..');
9 |
10 | // Then define new instances that will be needed
11 | var ibc = new Ibc1();
12 | var chaincode = {};
13 |
14 | // configure ibc-js sdk by defining options
15 | var options = {
16 |
17 | // Set existant network credentials
18 | // Create a network on Bluemix Experimental BlockChain offering
19 | // Service Credentials are found under the Blockchain instance tab.
20 |
21 | network:{
22 | peers: [{
23 | "api_host": "3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1-api.blockchain.ibm.com",
24 | "api_port": "80",
25 | "id": "3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1",
26 | "api_url": "http://3f3fa6c3-a8b4-48b2-95bc-63b5058fa333_vp1-api.blockchain.ibm.com:80"
27 | }],
28 |
29 | // For simplicity, I chose the first user on the list provided.
30 | users: [{
31 | "username": "user_type0_52737ec3c6",
32 | "secret": "4841d68d27"
33 | }] },
34 |
35 | // The chaincode version being tested here is the one deployed in Marbles2.
36 | chaincode:{
37 | zip_url: 'https://github.com/ibm-blockchain/marbles-chaincode/archive/master.zip',
38 | unzip_dir: 'marbles-chaincode-master/part2',
39 | git_url: 'https://github.com/ibm-blockchain/marbles-chaincode/part2'
40 | }
41 | };
42 |
43 |
44 | // Load the Marbles2 chaincode, with defined options, and return call-back-when-ready function.
45 | ibc.load(options, cb_ready);
46 |
47 | // Define the call-back-when-ready function returned above
48 | // call-back-when-ready function has err
49 | function cb_ready(err, cc){
50 | //response has chaincode functions
51 |
52 | // if the deployed name is blank, then chaincode has not been deployed
53 | if(cc.details.deployed_name === ""){
54 | cc.deploy('init', ['99'], './cc_summaries', cb_deployed);
55 | function cb_deployed(err){
56 | console.log('sdk has deployed code and waited');
57 | }
58 | }
59 | else{
60 | console.log('chaincode summary file indicates chaincode has been previously deployed');
61 | }
62 | };
63 |
64 | ibc.chain_stats([stats_callback]);
65 | function stats_callback(e, stats){
66 | console.log('got some stats', stats);
67 | }
68 |
69 |
--------------------------------------------------------------------------------