diff --git a/index.js b/index.js index 3068e591a3..a479013049 100644 --- a/index.js +++ b/index.js @@ -55,7 +55,7 @@ module.exports.getChain = function(chainName, create) { var chain = _chains[chainName]; if (!chain && create) { - chain = newChain(chainName); + chain = this.newChain(chainName); } return chain; diff --git a/lib/Chain.js b/lib/Chain.js index 2d34323993..fcbfdf1289 100644 --- a/lib/Chain.js +++ b/lib/Chain.js @@ -1,14 +1,14 @@ /* Copyright 2016 IBM All Rights Reserved. - Licensed under the Apache License, Version 2.0 (the "License"); + Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, + distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. @@ -23,6 +23,11 @@ var net = require('net'); var util = require('util'); var MemberServices = require('./impl/MemberServices.js'); var Member = require('./Member.js'); +var Orderer = require('./Orderer.js'); + +var debugModule = require('debug'); +var debug = debugModule('hfc'); +var isDebug = debugModule.enabled('hfc'); /** * The class representing a chain with which the client SDK interacts. @@ -36,12 +41,10 @@ var Chain = class { * is completely at the client application's discretion. */ constructor(name) { + debug('Chain.constructor'); // Name of the chain is only meaningful to the client this._name = name; - // The peers on this chain to which the client can connect - this._peers = []; // Peer[] - // Security enabled flag this._securityEnabled = true; @@ -106,7 +109,7 @@ var Chain = class { /** * Set the member services URL - * @param {string} url Member services URL of the form: "grpc://host:port" or "grpcs://host:port" + * @param {string} url Member services URL of the form: 'grpc://host:port' or 'grpcs://host:port' * @param {string} pem String value of the TLS certificate for the local client */ setMemberServicesUrl(url, pem) { @@ -126,6 +129,7 @@ var Chain = class { * @param [MemberService]{@link module:api.MemberService} an instance of the MemberServices class */ setMemberServices(memberServices) { + debug('Chain.setMemberServices -' + memberServices); this._memberServices = memberServices; if (memberServices instanceof MemberServices) { this.cryptoPrimitives = memberServices.getCrypto(); @@ -179,6 +183,7 @@ var Chain = class { * Set the key value store implementation. */ setKeyValueStore(keyValStore) { + debug('Chain.setKeyValueStore -' + keyValStore); this._keyValStore = keyValStore; } @@ -202,25 +207,28 @@ var Chain = class { * @returns Promise for the Member object */ getMember(name) { + debug('Chain.getMember - start - name:' + name); var self = this; return new Promise(function(resolve, reject) { if (!self._keyValStore) { + debug('Chain.getMember - reject -no key value store'); reject(new Error('No key value store was found. You must first call Chain.configureKeyValueStore or Chain.setKeyValueStore')); } if (!self._memberServices) { + debug('Chain.getMember - reject -no members services'); reject(new Error('No member services was found. You must first call Chain.configureMemberServices or Chain.setMemberServices')); } self._getMemberHelper(name).then( function(member) { - + debug('Chain.getMember - resolve -member found name:'+name); return resolve(member); } ).catch( function(err) { - + debug('Chain.getMember - reject - ERROR::'+err); reject(err); } @@ -245,26 +253,30 @@ var Chain = class { // If there are no errors and member is not found in the key value store, // return the new member. _getMemberHelper(name) { + debug('Chain._getMemberHelper - start name:'+name); var self = this; return new Promise(function(resolve, reject) { // Try to get the member state from the cache var member = self._members[name]; if (member) { + debug('Chain._getMemberHelper - resolve found in _members - name:'+name); return resolve(member); } // Create the member and try to restore it's state from the key value store (if found). member = new Member(name, self); + debug('Chain._getMemberHelper - create new member - will try to restoreState -name:'+name); member.restoreState() .then( function() { self._members[name] = member; - + debug('Chain._getMemberHelper - resolved restored name:'+name); return resolve(member); } ).catch( function(err) { + debug('Chain._getMemberHelper - reject - ERROR::'+err); reject(err); } ); @@ -274,21 +286,23 @@ var Chain = class { /** * Register a user or other member type with the chain. * @param registrationRequest Registration information. - * @returns Promise for a "true" status on successful registration + * @returns Promise for a 'true' status on successful registration */ register(registrationRequest) { - + debug('Chain.register - start registrationRequest:'+registrationRequest); var self = this; return new Promise(function(resolve, reject) { self.getMember(registrationRequest.enrollmentID) .then( - function(member) { + function(member) { member.register(registrationRequest); + debug('Chain.register - resolve registrationRequest:'+registrationRequest); return resolve(true); } ).catch( function(err) { + debug('Chain.register - reject - ERROR::'+err); reject(err); } ); @@ -303,6 +317,7 @@ var Chain = class { * @param cb The callback to return the user or other member. */ enroll(name, secret) { + debug('Chain.enroll - start name:'+name); var self = this; return new Promise(function(resolve, reject) { @@ -311,14 +326,17 @@ var Chain = class { .then( function(member) { _member = member; + debug('Chain.enroll - call member.enroll'); return _member.enroll(secret); } ).then( function() { + debug('Chain.enroll - resolved - member:'+name); return resolve(_member); } ).catch( function(err) { + debug('Chain.enroll - reject - ERROR::'+err); reject(err); } ); @@ -358,7 +376,40 @@ var Chain = class { ); }); } + + /** + * Set the orderer given an endpoint specification. + * Will replace the existing orderer if one exists. + * @param url The URL of the orderer. + * @param opts Optional GRPC options. + * @returns {Orderer} Returns the new Orderer. + */ + setOrderer(url, opts) { + debug('Chain.setOrderer - start url:'+url); + var orderer = new Orderer(url, this, opts); + this._orderer = orderer; + return orderer; + } + + /** + * Get the current orderer for this chain. + */ + getOrderer() { + return this._orderer; + } + + /** + * return a printable representation of this object + */ + toString() { + var state = { + name: this._name, + orderer: this._orderer ? this._orderer._url : 'N/A' + }; + + return JSON.stringify(state); + } + }; module.exports = Chain; - diff --git a/lib/Member.js b/lib/Member.js index 4f3c5862eb..9ab2802a3a 100644 --- a/lib/Member.js +++ b/lib/Member.js @@ -1,14 +1,14 @@ /* Copyright 2016 IBM All Rights Reserved. - Licensed under the Apache License, Version 2.0 (the "License"); + Licensed under the Apache License, Version 2.0 (the 'License'); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, + distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. @@ -27,6 +27,10 @@ var grpc = require('grpc'); var _ccProto = grpc.load(__dirname + '/protos/chaincode.proto').protos; var _fabricProto = grpc.load(__dirname + '/protos/fabric_next.proto').protos; +var debugModule = require('debug'); +var debug = debugModule('hfc'); +var isDebug = debugModule.enabled('hfc'); + /** * Represents an authenticated user of the application or an entity used by a Peer node. * A member can be in any of these following states: @@ -38,7 +42,7 @@ var _fabricProto = grpc.load(__dirname + '/protos/fabric_next.proto').protos; * with a one-time password, but it has not been enrolled * * - enrolled: the user has used the one-time password to exchange for an Enrollment Certificate (ECert) - * which can be used to identify him/herself to the member services + * which can be used to identify him/herself to the member services * * @class */ @@ -49,6 +53,7 @@ var Member = class { * @param cfg {string | RegistrationRequest} The member name or registration request. */ constructor(cfg, chain) { + debug('Member.constructor - '+chain); if (util.isString(cfg)) { this._name = cfg; this._roles = null; //string[] @@ -286,7 +291,7 @@ var Member = class { /** * Save the state of this member to the key value store. - * @returns Promise for a "true" upon successful save + * @returns Promise for a 'true' upon successful save */ saveState() { return this._keyValStore.setValue(this._keyValStoreName, this.toString()); @@ -294,7 +299,7 @@ var Member = class { /** * Restore the state of this member from the key value store (if found). If not found, do nothing. - * @returns Promise for a "true" upon successful restore + * @returns Promise for a 'true' upon successful restore */ restoreState() { var self = this; @@ -317,6 +322,32 @@ var Member = class { }); } + /** + * Sends the orderer an endorsed proposal. + * + * @param {Object} request An object containing the data: + * TODO explain data object + * @returns Promise for the sendTransaction + */ + sendTransaction(data) { + debug('Member.sendTransaction - start'); + // Verify that data is being passed in + if (!data) { + debug('Member.sendTransaction - input data missing'); + return Promise.reject(new Error('missing data in broadcast request')); + } + // verify that we have an orderer configured + if(!this._chain.getOrderer()) { + debug('Member.sendTransaction - no orderer defined'); + return Promise.reject(new Error('no Orderer defined')); + } + + debug('Member.sendTransaction - chain ::'+this._chain); + + var orderer = this._chain.getOrderer(); + return orderer.sendBroadcast(data); + } + /** * Sends a deployment proposal to an endorser. * @@ -364,12 +395,12 @@ var Member = class { let chaincodeDeploymentSpec = new _ccProto.ChaincodeDeploymentSpec(); chaincodeDeploymentSpec.setChaincodeSpec(ccSpec); - fs.readFile(targzFilePath, function(err, data) { - if(err) { - reject(new Error(util.format('Error reading deployment archive [%s]: %s', targzFilePath, err))); - } + fs.readFile(targzFilePath, function(err, data) { + if(err) { + reject(new Error(util.format('Error reading deployment archive [%s]: %s', targzFilePath, err))); + } - chaincodeDeploymentSpec.setCodePackage(data); + chaincodeDeploymentSpec.setCodePackage(data); let lcccSpec = { type: _ccProto.ChaincodeSpec.Type.GOLANG, @@ -394,7 +425,7 @@ var Member = class { let peer = new Peer(request.endorserUrl); return peer.sendProposal(proposal); }); - }, + }, function(err) { reject(err); } @@ -490,5 +521,3 @@ function packageChaincode(chaincodePath, fcn, args) { } module.exports = Member; - - diff --git a/test/unit/orderer-member-tests.js b/test/unit/orderer-member-tests.js new file mode 100644 index 0000000000..e93fd4010c --- /dev/null +++ b/test/unit/orderer-member-tests.js @@ -0,0 +1,335 @@ +/** + * Copyright 2016 IBM All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var tape = require('tape'); +var _test = require('tape-promise'); +var test = _test(tape); + +var hfc = require('../..'); +var util = require('util'); +var fs = require('fs'); + +var Orderer = require('../../lib/Orderer.js'); +var Member = require('../../lib/Member.js'); + +var keyValStorePath = '/tmp/keyValStore'; + +// +// Orderer via chain setOrderer/getOrderer +// +// Set the orderer URL through the chain setOrderer method. Verify that the +// orderer URL was set correctly through the getOrderer method. Repeat the +// process by updating the orderer URL to a different address. +// +test('\n\n** TEST ** orderer via chain setOrderer/getOrderer', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + try { + var order_address = 'grpc://localhost:5151'; + chain.setOrderer(order_address); + t.pass('Successfully set the new orderer URL'); + t.end(); + + var order = chain.getOrderer(); + if(order.getUrl() === order_address) { + t.pass('Successfully retrieved the new orderer URL from the chain'); + t.end(); + } + else { + t.fail('Failed to retieve the new orderer URL from the chain'); + t.end(); + } + + try { + var order_address2 = 'grpc://localhost:5152'; + chain.setOrderer(order_address2); + t.pass('Successfully updated the orderer URL'); + t.end(); + + var order2 = chain.getOrderer(); + if(order2.getUrl() === order_address2) { + t.pass('Successfully retrieved the upated orderer URL from the chain'); + t.end(); + } + else { + t.fail('Failed to retieve the updated orderer URL from the chain'); + t.end(); + } + } + catch(err2) { + t.fail('Failed to update the order URL ' + err); + t.end(); + } + } + catch(err) { + t.fail('Failed to set the new order URL ' + err); + t.end(); + } +}); + +// +// Orderer via chain set/get bad address +// +// Set the orderer URL to a bad address through the chain setOrderer method. +// Verify that an error is reported when trying to set a bad address. +// +test('\n\n** TEST ** orderer via chain set/get bad address', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + try { + var order_address = 'xxx'; + chain.setOrderer(order_address); + t.failed('Failed by setting the orderer to a bad address'); + t.end(); + } + catch(err) { + t.pass('Successfully caught setting of a bad address ' + err); + t.end(); + } +}); + +// +// Orderer via chain set/get empty address +// +// Set the orderer URL to an empty address through the chain setOrderer method. +// Verify that an error is reported when trying to set an empty address. +// +test('\n\n** TEST ** orderer via chain set/get empty address', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + try { + chain.setOrderer(); + t.failed('Failed by setting the orderer to a empty address'); + t.end(); + } + catch(err) { + t.pass('Successfully caught the setting of a empty address ' + err); + t.end(); + } +}); + +// +// Orderer via member missing orderer +// +// Attempt to send a request to the orderer with the sendTransaction method +// before the orderer URL was set. Verify that an error is reported when tying +// to send the request. +// +test('\n\n** TEST ** orderer via member missing orderer', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + + chain.setKeyValueStore(hfc.newKeyValueStore({ + path: keyValStorePath + })); + + chain.setMemberServicesUrl('grpc://localhost:7054'); + + chain.enroll('admin', 'Xurw3yU9zI0l') + .then( + function(admin) { + t.pass('Successfully enrolled user \'admin\''); + + // send to orderer + return admin.sendTransaction('data'); + }, + function(err) { + t.fail('Failed to enroll user \'admin\'. ' + err); + t.end(); + } + ).then( + function(status) { + console.log('Status: ' + status + ', type: (' + typeof status + ')'); + if (status === 0) { + t.fail('Successfully submitted request.'); + } else { + t.pass('Failed to submit. Error code: ' + status); + } + + t.end(); + }, + function(err) { + console.log('Error: ' + err); + t.pass('Failed to submit. Error code: ' + err); + t.end(); + } + ).catch(function(err) { + t.pass('Failed request. ' + err); + t.end(); + }); +}); + +// +// Orderer via member null data +// +// Attempt to send a request to the orderer with the sendTransaction method +// with the data set to null. Verify that an error is reported when tying +// to send null data. +// +test('\n\n** TEST ** orderer via member null data', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + + chain.setKeyValueStore(hfc.newKeyValueStore({ + path: keyValStorePath + })); + + chain.setMemberServicesUrl('grpc://localhost:7054'); + chain.setOrderer('grpc://localhost:5151'); + + chain.enroll('admin', 'Xurw3yU9zI0l') + .then( + function(admin) { + t.pass('Successfully enrolled user \'admin\''); + + // send to orderer + return admin.sendTransaction(null); + }, + function(err) { + t.fail('Failed to enroll user \'admin\'. ' + err); + t.end(); + } + ).then( + function(status) { + console.log('Status: ' + status + ', type: (' + typeof status + ')'); + if (status === 0) { + t.fail('Successfully submitted request.'); + } else { + t.pass('Failed to submit. Error code: ' + status); + } + + t.end(); + }, + function(err) { + console.log('Error: ' + err); + t.pass('Failed to submit. Error code: ' + err); + t.end(); + } + ).catch(function(err) { + t.pass('Failed request. ' + err); + t.end(); + }); +}); + +// +// Orderer via member bad orderer address +// +// Attempt to send a request to the orderer with the sendTransaction method +// with the orderer address set to a bad URL. Verify that an error is reported +// when tying to send the request. +// +test('\n\n** TEST ** orderer via member bad orderer address', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + + chain.setKeyValueStore(hfc.newKeyValueStore({ + path: keyValStorePath + })); + + chain.setMemberServicesUrl('grpc://localhost:7054'); + // Set bad orderer address here + chain.setOrderer('grpc://localhost:5199'); + + chain.enroll('admin', 'Xurw3yU9zI0l') + .then( + function(admin) { + t.pass('Successfully enrolled user \'admin\''); + + // send to orderer + return admin.sendTransaction('some data'); + }, + function(err) { + t.fail('Failed to enroll user \'admin\'. ' + err); + t.end(); + } + ).then( + function(status) { + console.log('Status: ' + status + ', type: (' + typeof status + ')'); + if (status === 0) { + t.fail('Successfully submitted request.'); + } else { + t.pass('Failed to submit. Error code: ' + status); + } + t.end(); + }, + function(err) { + t.pass('Failed to submit ::' + err); + t.end(); + } + ).catch(function(err) { + t.pass('Failed to submit orderer request. ' + err); + t.end(); + }); +}); + +// +// Orderer via member good data +// +// Attempt to send a request to the orderer with the sendTransaction method +// with the orderer address set to the correct URL and the data not being null. +// Verify that a success is returned when tying to send the request. +// +test('\n\n** TEST ** orderer via member good data', function(t) { + // + // Create and configure the test chain + // + var chain = hfc.getChain('testChain', true); + + chain.setKeyValueStore(hfc.newKeyValueStore({ + path: keyValStorePath + })); + + chain.setMemberServicesUrl('grpc://localhost:7054'); + chain.setOrderer('grpc://localhost:5151'); + + chain.enroll('admin', 'Xurw3yU9zI0l') + .then( + function(admin) { + t.pass('Successfully enrolled user \'admin\''); + + return admin.sendTransaction('some data'); + }, + function(err) { + t.fail('Failed to enroll user \'admin\'. ' + err); + t.end(); + } + ).then( + function(status) { + console.log('Status: ' + status + ', type: (' + typeof status + ')'); + if (status.Status === 'SUCCESS') { + t.pass('Successfully submitted request.'); + } else { + t.fail('Failed to submit. Error code: ' + status); + } + t.end(); + } + ).catch(function(err) { + t.fail('Failed to submit orderer request. ' + err); + t.end(); + }); +});