diff --git a/fabric-network/index.js b/fabric-network/index.js index a286ae8617..4992bc183d 100644 --- a/fabric-network/index.js +++ b/fabric-network/index.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -module.exports.Network = require('./lib/network'); +module.exports.Gateway = require('./lib/gateway'); module.exports.InMemoryWallet = require('./lib/impl/wallet/inmemorywallet'); module.exports.X509WalletMixin = require('./lib/impl/wallet/x509walletmixin'); module.exports.FileSystemWallet = require('./lib/impl/wallet/filesystemwallet'); diff --git a/fabric-network/lib/channel.js b/fabric-network/lib/channel.js deleted file mode 100644 index d7b74c3039..0000000000 --- a/fabric-network/lib/channel.js +++ /dev/null @@ -1,200 +0,0 @@ -/** - * Copyright 2018 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; -const FabricConstants = require('fabric-client/lib/Constants'); -const Contract = require('./contract'); -const logger = require('./logger').getLogger('FabricNetwork.Channel'); -const EventHubFactory = require('./impl/event/eventhubfactory'); -const TransactionEventHandler = require('./impl/event/transactioneventhandler'); -const util = require('util'); - -class Channel { - - /** - * Channel constructor for internal use only - * @param {Network} network The owning network instance - * @param {Channel} channel The fabric-client channel instance - * @private - */ - constructor(network, channel) { - logger.debug('in Channel constructor'); - - this.network = network; - this.channel = channel; - - this.eventHandlerFactory = { - createTxEventHandler: () => null - }; - const createEventStrategyFn = network.getOptions().eventStrategy; - if (createEventStrategyFn) { - const self = this; - const eventHubFactory = new EventHubFactory(channel); - const mspId = network.getCurrentIdentity()._mspId; - const commitTimeout = network.getOptions().commitTimeout; - this.eventHandlerFactory.createTxEventHandler = (txId) => { - const eventStrategy = createEventStrategyFn(eventHubFactory, self, mspId); - return new TransactionEventHandler(txId, eventStrategy, { timeout: commitTimeout }); - }; - } - - this.contracts = new Map(); - this.initialized = false; - this.queryHandler; - } - - /** - * create a map of mspId's and the channel peers in those mspIds - * @private - * @memberof Network - */ - _mapPeersToMSPid() { - logger.debug('in _mapPeersToMSPid'); - - // TODO: assume 1-1 mapping of mspId to org as the node-sdk makes that assumption - // otherwise we would need to find the channel peer in the network config collection or however SD - // stores things - - const peerMap = new Map(); - const channelPeers = this.channel.getPeers(); - - // bug in service discovery, peers don't have the associated mspid - if (channelPeers.length > 0) { - for (const channelPeer of channelPeers) { - const mspId = channelPeer.getMspid(); - if (mspId) { - let peerList = peerMap.get(mspId); - if (!peerList) { - peerList = []; - peerMap.set(mspId, peerList); - } - peerList.push(channelPeer); - } - } - } - if (peerMap.size === 0) { - const msg = 'no suitable peers associated with mspIds were found'; - logger.error('_mapPeersToMSPid: ' + msg); - throw new Error(msg); - } - return peerMap; - } - - /** - * initialize the channel if it hasn't been done - * @private - */ - async _initializeInternalChannel() { - logger.debug('in _initializeInternalChannel'); - - //TODO: Should this work across all peers or just orgs peers ? - //TODO: should sort peer list to the identity org initializing the channel. - //TODO: Candidate to push to low level node-sdk. - - const ledgerPeers = this.channel.getPeers().filter((cPeer) => { - return cPeer.isInRole(FabricConstants.NetworkConfig.LEDGER_QUERY_ROLE); - }); - - if (ledgerPeers.length === 0) { - const msg = 'no suitable peers available to initialize from'; - logger.error('_initializeInternalChannel: ' + msg); - throw new Error(msg); - } - - let ledgerPeerIndex = 0; - let success = false; - - while (!success) { - try { - const initOptions = { - target: ledgerPeers[ledgerPeerIndex] - }; - - await this.channel.initialize(initOptions); - success = true; - } catch(error) { - if (ledgerPeerIndex >= ledgerPeers.length - 1) { - const msg = util.format('Unable to initialize channel. Attempted to contact %j Peers. Last error was %s', ledgerPeers.length, error); - logger.error('_initializeInternalChannel: ' + msg); - throw new Error(msg); - } - ledgerPeerIndex++; - } - } - } - - /** - * Initialize this channel instance - * @private - * @memberof Channel - */ - async _initialize() { - logger.debug('in initialize'); - - if (this.initialized) { - return; - } - - await this._initializeInternalChannel(); - this.peerMap = this._mapPeersToMSPid(); - this.queryHandler = await this.network._createQueryHandler(this.channel, this.peerMap); - this.initialized = true; - } - - getInternalChannel() { - logger.debug('in getInternalChannel'); - - return this.channel; - } - - getPeerMap() { - logger.debug('in getPeerMap'); - - return this.peerMap; - } - - /** - * Returns an instance of a contract (chaincode) on the current channel - * @param chaincodeId - * @returns {Contract} - * @api - */ - getContract(chaincodeId) { - logger.debug('in getContract'); - if (!this.initialized) { - throw new Error('Unable to get contract as channel has failed to initialize'); - } - - let contract = this.contracts.get(chaincodeId); - if (!contract) { - contract = new Contract( - this.channel, - chaincodeId, - this.network, - this.queryHandler, - this.eventHandlerFactory - ); - this.contracts.set(chaincodeId, contract); - } - return contract; - } - - _dispose() { - logger.debug('in _dispose'); - - // Danger as this cached in network, and also async so how would - // channel._dispose() followed by channel.initialize() be safe ? - // make this private is the safest option. - this.contracts.clear(); - if (this.queryHandler) { - this.queryHandler.dispose(); - } - this.initialized = false; - } - -} - -module.exports = Channel; diff --git a/fabric-network/lib/contract.js b/fabric-network/lib/contract.js index 2a667a226e..838e078d57 100644 --- a/fabric-network/lib/contract.js +++ b/fabric-network/lib/contract.js @@ -11,12 +11,12 @@ const util = require('util'); class Contract { - constructor(channel, chaincodeId, network, queryHandler, eventHandlerFactory) { + constructor(channel, chaincodeId, gateway, queryHandler, eventHandlerFactory) { logger.debug('in Contract constructor'); this.channel = channel; this.chaincodeId = chaincodeId; - this.network = network; + this.gateway = gateway; this.queryHandler = queryHandler; this.eventHandlerFactory = eventHandlerFactory; } @@ -89,7 +89,7 @@ class Contract { this._verifyTransactionDetails('submitTransaction', transactionName, parameters); - const txId = this.network.getClient().newTransactionID(); + const txId = this.gateway.getClient().newTransactionID(); // createTxEventHandler() will return null if no event handler is requested const eventHandler = this.eventHandlerFactory.createTxEventHandler(txId.getTransactionID()); @@ -190,7 +190,7 @@ class Contract { */ async executeTransaction(transactionName, ...parameters) { this._verifyTransactionDetails('executeTransaction', transactionName, parameters); - const txId = this.network.getClient().newTransactionID(); + const txId = this.gateway.getClient().newTransactionID(); const result = await this.queryHandler.queryChaincode(this.chaincodeId, txId, transactionName, parameters); return result ? result : null; } diff --git a/fabric-network/lib/eventstrategies.js b/fabric-network/lib/eventstrategies.js index 6490637584..59ba715448 100644 --- a/fabric-network/lib/eventstrategies.js +++ b/fabric-network/lib/eventstrategies.js @@ -9,29 +9,29 @@ const AllForTxStrategy = require('fabric-network/lib/impl/event/allfortxstrategy'); const AnyForTxStrategy = require('fabric-network/lib/impl/event/anyfortxstrategy'); -function MSPID_SCOPE_ALLFORTX(eventHubFactory, channel, mspId) { - const peers = channel.getPeerMap().get(mspId); +function MSPID_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { + const peers = network.getPeerMap().get(mspId); return new AllForTxStrategy(eventHubFactory, peers); } -function MSPID_SCOPE_ANYFORTX(eventHubFactory, channel, mspId) { - const peers = channel.getPeerMap().get(mspId); +function MSPID_SCOPE_ANYFORTX(eventHubFactory, network, mspId) { + const peers = network.getPeerMap().get(mspId); return new AnyForTxStrategy(eventHubFactory, peers); } -function CHANNEL_SCOPE_ALLFORTX(eventHubFactory, channel, mspId) { - const peers = channel.getInternalChannel().getPeers(); +function NETWORK_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { + const peers = network.getChannel().getPeers(); return new AllForTxStrategy(eventHubFactory, peers); } -function CHANNEL_SCOPE_ANYFORTX(eventHubFactory, channel, mspId) { - const peers = channel.getInternalChannel().getPeers(); +function NETWORK_SCOPE_ANYFORTX(eventHubFactory, network, mspId) { + const peers = network.getChannel().getPeers(); return new AnyForTxStrategy(eventHubFactory, peers); } module.exports = { MSPID_SCOPE_ALLFORTX, MSPID_SCOPE_ANYFORTX, - CHANNEL_SCOPE_ALLFORTX, - CHANNEL_SCOPE_ANYFORTX + NETWORK_SCOPE_ALLFORTX, + NETWORK_SCOPE_ANYFORTX }; diff --git a/fabric-network/lib/gateway.js b/fabric-network/lib/gateway.js new file mode 100644 index 0000000000..5b0e0c3c83 --- /dev/null +++ b/fabric-network/lib/gateway.js @@ -0,0 +1,201 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const Client = require('fabric-client'); + +const Network = require('./network'); +const EventStrategies = require('./eventstrategies'); + +const logger = require('./logger').getLogger('Gateway'); + +class Gateway { + + static _mergeOptions(defaultOptions, suppliedOptions) { + for (const prop in suppliedOptions) { + if (suppliedOptions[prop] instanceof Object && prop.endsWith('Options')) { + if (defaultOptions[prop] === undefined) { + defaultOptions[prop] = suppliedOptions[prop]; + } else { + Gateway._mergeOptions(defaultOptions[prop], suppliedOptions[prop]); + } + } else { + defaultOptions[prop] = suppliedOptions[prop]; + } + } + } + + /** + * Public constructor for Gateway object + */ + constructor() { + logger.debug('in Gateway constructor'); + this.client = null; + this.wallet = null; + this.networks = new Map(); + + // default options + this.options = { + commitTimeout: 300, // 5 minutes + eventStrategy: EventStrategies.MSPID_SCOPE_ALLFORTX, + queryHandler: './impl/query/defaultqueryhandler', + queryHandlerOptions: { + } + }; + } + + /** + * @typedef {Object} GatewayOptions + * @property {Wallet} wallet The identity wallet implementation for use with this Gateway instance + * @property {string} identity The identity in the wallet for all interactions on this Gateway instance + * @property {string} [clientTlsIdentity] the identity in the wallet to use as the client TLS identity + * @property {number} [commitTimeout = 300] The timout period in seconds to wait for commit notification to complete + * @property {*} [eventStrategy] Event handling strategy to identify successful transaction commits. A null value + * indicates that no event handling is desired. + */ + + /** + * Initialize the Gateway with a connection profile + * + * @param {Client | string} config The configuration for this Gateway which can come from a common connection + * profile or an existing fabric-client Client instance + * @see see {Client} + * @param {GatewayOptions} options specific options for creating this Gateway instance + * @memberof Gateway + */ + async initialize(config, options) { + const method = 'initialize'; + logger.debug('in %s', method); + + if (!options || !options.wallet) { + logger.error('%s - A wallet must be assigned to a Gateway instance', method); + throw new Error('A wallet must be assigned to a Gateway instance'); + } + + // if a different queryHandler was provided and it doesn't match the default + // delete the default queryHandlerOptions. + if (options.queryHandler && (this.options.queryHandler !== options.queryHandler)) { + delete this.options.queryHandlerOptions; + } + + Gateway._mergeOptions(this.options, options); + + if (!(config instanceof Client)) { + // still use a ccp for the discovery peer and ca information + logger.debug('%s - loading client from ccp', method); + this.client = Client.loadFromConfig(config); + } else { + // initialize from an existing Client object instance + logger.debug('%s - using existing client object', method); + this.client = config; + } + + // setup an initial identity for the Gateway + if (options.identity) { + logger.debug('%s - setting identity', method); + this.currentIdentity = await options.wallet.setUserContext(this.client, options.identity); + } + + if (options.clientTlsIdentity) { + const tlsIdentity = await options.wallet.export(options.clientTlsIdentity); + this.client.setTlsClientCertAndKey(tlsIdentity.certificate, tlsIdentity.privateKey); + } + + // load in the query handler plugin + if (this.options.queryHandler) { + logger.debug('%s - loading query handler: %s', method, this.options.queryHandler); + try { + this.queryHandlerClass = require(this.options.queryHandler); + } catch(error) { + logger.error('%s - unable to load provided query handler: %s. Error %O', method, this.options.queryHandler, error); + throw new Error(`unable to load provided query handler: ${this.options.queryHandler}. Error ${error}`); + } + } + } + + /** + * Get the current identity + * + * @returns {User} a fabric-client User instance of the current identity used by this Gateway + * @memberof Gateway + */ + getCurrentIdentity() { + logger.debug('in getCurrentIdentity'); + return this.currentIdentity; + } + + /** + * Get the underlying Client object instance + * + * @returns {Client} the underlying fabric-client Client instance + * @memberof Gateway + */ + getClient() { + logger.debug('in getClient'); + return this.client; + } + + /** + * Returns the set of options associated with the Gateway connection + * @returns {GatewayOptions} the Gateway options + * @memberof Gateway + */ + getOptions() { + logger.debug('in getOptions'); + return this.options; + } + + /** + * clean up this Gateway in prep for it to be discarded and garbage collected + * + * @memberof Gateway + */ + dispose() { + logger.debug('in cleanup'); + for (const network of this.networks.values()) { + network._dispose(); + } + this.networks.clear(); + } + + /** + * Returns an object representing the network + * @param networkName + * @returns {Promise} + * @memberof Gateway + */ + async getNetwork(networkName) { + logger.debug('in getNetwork'); + const existingNetwork = this.networks.get(networkName); + if (!existingNetwork) { + logger.debug('getNetwork: create network object and initialize'); + const channel = this.client.getChannel(networkName); + const newNetwork = new Network(this, channel); + await newNetwork._initialize(); + this.networks.set(networkName, newNetwork); + return newNetwork; + } + return existingNetwork; + } + + async _createQueryHandler(channel, peerMap) { + if (this.queryHandlerClass) { + const currentmspId = this.getCurrentIdentity()._mspId; + const queryHandler = new this.queryHandlerClass( + channel, + currentmspId, + peerMap, + this.options.queryHandlerOptions + ); + await queryHandler.initialize(); + return queryHandler; + } + return null; + } +} + +module.exports = Gateway; diff --git a/fabric-network/lib/network.js b/fabric-network/lib/network.js index 0e7e1e15cd..d505db4095 100644 --- a/fabric-network/lib/network.js +++ b/fabric-network/lib/network.js @@ -5,197 +5,196 @@ */ 'use strict'; - -const Client = require('fabric-client'); - -const Channel = require('./channel'); -const EventStrategies = require('./eventstrategies'); - +const FabricConstants = require('fabric-client/lib/Constants'); +const Contract = require('./contract'); const logger = require('./logger').getLogger('Network'); +const EventHubFactory = require('./impl/event/eventhubfactory'); +const TransactionEventHandler = require('./impl/event/transactioneventhandler'); +const util = require('util'); class Network { - static _mergeOptions(defaultOptions, suppliedOptions) { - for (const prop in suppliedOptions) { - if (suppliedOptions[prop] instanceof Object && prop.endsWith('Options')) { - if (defaultOptions[prop] === undefined) { - defaultOptions[prop] = suppliedOptions[prop]; - } else { - Network._mergeOptions(defaultOptions[prop], suppliedOptions[prop]); - } - } else { - defaultOptions[prop] = suppliedOptions[prop]; - } - } - } - /** - * Public constructor for Network object + * Network constructor for internal use only + * @param {Gateway} gateway The owning gateway instance + * @param {Channel} channel The fabric-client channel instance + * @private */ - constructor() { + constructor(gateway, channel) { logger.debug('in Network constructor'); - this.client = null; - this.wallet = null; - this.channels = new Map(); - - // default options - this.options = { - commitTimeout: 300, // 5 minutes - eventStrategy: EventStrategies.MSPID_SCOPE_ALLFORTX, - queryHandler: './impl/query/defaultqueryhandler', - queryHandlerOptions: { - } + + this.gateway = gateway; + this.channel = channel; + + this.eventHandlerFactory = { + createTxEventHandler: () => null }; - } + const createEventStrategyFn = gateway.getOptions().eventStrategy; + if (createEventStrategyFn) { + const self = this; + const eventHubFactory = new EventHubFactory(channel); + const mspId = gateway.getCurrentIdentity()._mspId; + const commitTimeout = gateway.getOptions().commitTimeout; + this.eventHandlerFactory.createTxEventHandler = (txId) => { + const eventStrategy = createEventStrategyFn(eventHubFactory, self, mspId); + return new TransactionEventHandler(txId, eventStrategy, { timeout: commitTimeout }); + }; + } - /** - * @typedef {Object} NetworkOptions - * @property {Wallet} wallet The identity wallet implementation for use with this network instance - * @property {string} identity The identity in the wallet for all interactions on this network instance - * @property {string} [clientTlsIdentity] the identity in the wallet to use as the client TLS identity - * @property {number} [commitTimeout = 300] The timout period in seconds to wait for commit notification to complete - * @property {*} [eventStrategy] Event handling strategy to identify successful transaction commits. A null value - * indicates that no event handling is desired. - */ + this.contracts = new Map(); + this.initialized = false; + this.queryHandler; + } /** - * Initialize the network with a connection profile - * - * @param {Client | string} config The configuration for this network which can come from a common connection - * profile or an existing fabric-client Client instance - * @see see {Client} - * @param {NetworkOptions} options specific options for creating this network instance + * create a map of mspId's and the network peers in those mspIds + * @private * @memberof Network */ - async initialize(config, options) { - const method = 'initialize'; - logger.debug('in %s', method); - - if (!options || !options.wallet) { - logger.error('%s - A wallet must be assigned to a Network instance', method); - throw new Error('A wallet must be assigned to a Network instance'); + _mapPeersToMSPid() { + logger.debug('in _mapPeersToMSPid'); + + // TODO: assume 1-1 mapping of mspId to org as the node-sdk makes that assumption + // otherwise we would need to find the channel peer in the network config collection or however SD + // stores things + + const peerMap = new Map(); + const channelPeers = this.channel.getPeers(); + + // bug in service discovery, peers don't have the associated mspid + if (channelPeers.length > 0) { + for (const channelPeer of channelPeers) { + const mspId = channelPeer.getMspid(); + if (mspId) { + let peerList = peerMap.get(mspId); + if (!peerList) { + peerList = []; + peerMap.set(mspId, peerList); + } + peerList.push(channelPeer); + } + } } - - // if a different queryHandler was provided and it doesn't match the default - // delete the default queryHandlerOptions. - if (options.queryHandler && (this.options.queryHandler !== options.queryHandler)) { - delete this.options.queryHandlerOptions; + if (peerMap.size === 0) { + const msg = 'no suitable peers associated with mspIds were found'; + logger.error('_mapPeersToMSPid: ' + msg); + throw new Error(msg); } + return peerMap; + } + + /** + * initialize the channel if it hasn't been done + * @private + */ + async _initializeInternalChannel() { + logger.debug('in _initializeInternalChannel'); - Network._mergeOptions(this.options, options); + //TODO: Should this work across all peers or just orgs peers ? + //TODO: should sort peer list to the identity org initializing the channel. + //TODO: Candidate to push to low level node-sdk. - if (!(config instanceof Client)) { - // still use a ccp for the discovery peer and ca information - logger.debug('%s - loading client from ccp', method); - this.client = Client.loadFromConfig(config); - } else { - // initialize from an existing Client object instance - logger.debug('%s - using existing client object', method); - this.client = config; - } + const ledgerPeers = this.channel.getPeers().filter((cPeer) => { + return cPeer.isInRole(FabricConstants.NetworkConfig.LEDGER_QUERY_ROLE); + }); - // setup an initial identity for the network - if (options.identity) { - logger.debug('%s - setting identity', method); - this.currentIdentity = await options.wallet.setUserContext(this.client, options.identity); + if (ledgerPeers.length === 0) { + const msg = 'no suitable peers available to initialize from'; + logger.error('_initializeInternalChannel: ' + msg); + throw new Error(msg); } - if (options.clientTlsIdentity) { - const tlsIdentity = await options.wallet.export(options.clientTlsIdentity); - this.client.setTlsClientCertAndKey(tlsIdentity.certificate, tlsIdentity.privateKey); - } + let ledgerPeerIndex = 0; + let success = false; - // load in the query handler plugin - if (this.options.queryHandler) { - logger.debug('%s - loading query handler: %s', method, this.options.queryHandler); + while (!success) { try { - this.queryHandlerClass = require(this.options.queryHandler); + const initOptions = { + target: ledgerPeers[ledgerPeerIndex] + }; + + await this.channel.initialize(initOptions); + success = true; } catch(error) { - logger.error('%s - unable to load provided query handler: %s. Error %O', method, this.options.queryHandler, error); - throw new Error(`unable to load provided query handler: ${this.options.queryHandler}. Error ${error}`); + if (ledgerPeerIndex >= ledgerPeers.length - 1) { + const msg = util.format('Unable to initialize channel. Attempted to contact %j Peers. Last error was %s', ledgerPeers.length, error); + logger.error('_initializeInternalChannel: ' + msg); + throw new Error(msg); + } + ledgerPeerIndex++; } } } /** - * Get the current identity - * - * @returns {User} a fabric-client User instance of the current identity used by this network - * @memberof Network - */ - getCurrentIdentity() { - logger.debug('in getCurrentIdentity'); - return this.currentIdentity; - } + * Initialize this network instance + * @private + * @memberof Network + */ + async _initialize() { + logger.debug('in initialize'); - /** - * Get the underlying Client object instance - * - * @returns {Client} the underlying fabric-client Client instance - * @memberof Network - */ - getClient() { - logger.debug('in getClient'); - return this.client; + if (this.initialized) { + return; + } + + await this._initializeInternalChannel(); + this.peerMap = this._mapPeersToMSPid(); + this.queryHandler = await this.gateway._createQueryHandler(this.channel, this.peerMap); + this.initialized = true; } - /** - * Returns the set of options associated with the network connection - * @returns {NetworkOptions} the network options - * @memberof Network - */ - getOptions() { - logger.debug('in getOptions'); - return this.options; + getChannel() { + logger.debug('in getChannel'); + + return this.channel; } - /** - * clean up this network in prep for it to be discarded and garbage collected - * - * @memberof Network - */ - dispose() { - logger.debug('in cleanup'); - for (const channel of this.channels.values()) { - channel._dispose(); - } - this.channels.clear(); + getPeerMap() { + logger.debug('in getPeerMap'); + + return this.peerMap; } /** - * Returns an object representing the channel - * @param channelName - * @returns {Promise} - * @memberof Network + * Returns an instance of a contract (chaincode) on the current network + * @param chaincodeId + * @returns {Contract} + * @api */ - async getChannel(channelName) { - logger.debug('in getChannel'); - const existingChannel = this.channels.get(channelName); - if (!existingChannel) { - logger.debug('getChannel: create channel object and initialize'); - const channel = this.client.getChannel(channelName); - const newChannel = new Channel(this, channel); - await newChannel._initialize(); - this.channels.set(channelName, newChannel); - return newChannel; + getContract(chaincodeId) { + logger.debug('in getContract'); + if (!this.initialized) { + throw new Error('Unable to get contract as network has failed to initialize'); } - return existingChannel; - } - async _createQueryHandler(channel, peerMap) { - if (this.queryHandlerClass) { - const currentmspId = this.getCurrentIdentity()._mspId; - const queryHandler = new this.queryHandlerClass( - channel, - currentmspId, - peerMap, - this.options.queryHandlerOptions + let contract = this.contracts.get(chaincodeId); + if (!contract) { + contract = new Contract( + this.channel, + chaincodeId, + this.gateway, + this.queryHandler, + this.eventHandlerFactory ); - await queryHandler.initialize(); - return queryHandler; + this.contracts.set(chaincodeId, contract); + } + return contract; + } + + _dispose() { + logger.debug('in _dispose'); + + // Danger as this cached in gateway, and also async so how would + // network._dispose() followed by network.initialize() be safe ? + // make this private is the safest option. + this.contracts.clear(); + if (this.queryHandler) { + this.queryHandler.dispose(); } - return null; + this.initialized = false; } + } module.exports = Network; diff --git a/fabric-network/test/channel.js b/fabric-network/test/channel.js deleted file mode 100644 index 3f2dd99480..0000000000 --- a/fabric-network/test/channel.js +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Copyright 2018 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; -const sinon = require('sinon'); -const rewire = require('rewire'); - -const InternalChannel = rewire('fabric-client/lib/Channel'); -const Peer = InternalChannel.__get__('ChannelPeer'); -const Client = require('fabric-client'); -const ChannelEventHub = Client.ChannelEventHub; -const TransactionID = require('fabric-client/lib/TransactionID.js'); -const FABRIC_CONSTANTS = require('fabric-client/lib/Constants'); - -const chai = require('chai'); -const should = chai.should(); -chai.use(require('chai-as-promised')); - -const Channel = require('../lib/channel'); -const Network = require('../lib/network'); -const Contract = require('../lib/contract'); -const EventStrategies = require('../lib/eventstrategies'); - -describe('Channel', () => { - const sandbox = sinon.createSandbox(); - - const mspId = 'MSP_ID'; - - let mockChannel, mockClient; - let mockPeer1, mockPeer2, mockPeer3; - let channel; - let mockTransactionID, mockNetwork; - - beforeEach(() => { - mockChannel = sinon.createStubInstance(InternalChannel); - mockClient = sinon.createStubInstance(Client); - mockTransactionID = sinon.createStubInstance(TransactionID); - mockTransactionID.getTransactionID.returns('00000000-0000-0000-0000-000000000000'); - mockClient.newTransactionID.returns(mockTransactionID); - - mockChannel.getName.returns('testchainid'); - const stubEventHub = sinon.createStubInstance(ChannelEventHub); - stubEventHub.isconnected.returns(true); - mockChannel.getChannelEventHub.returns(stubEventHub); - - mockPeer1 = sinon.createStubInstance(Peer); - mockPeer1.index = 1; // add these so that the mockPeers can be distiguished when used in WithArgs(). - mockPeer1.getName.returns('Peer1'); - - mockPeer2 = sinon.createStubInstance(Peer); - mockPeer2.index = 2; - mockPeer2.getName.returns('Peer2'); - - mockPeer3 = sinon.createStubInstance(Peer); - mockPeer3.index = 3; - mockPeer3.getName.returns('Peer3'); - - mockNetwork = sinon.createStubInstance(Network); - mockNetwork.getOptions.returns({ - useDiscovery: false, - commitTimeout: 300, - eventStrategy: EventStrategies.MSPID_SCOPE_ALLFORTX - }); - mockNetwork.getCurrentIdentity.returns({ - _mspId: mspId - }); - channel = new Channel(mockNetwork, mockChannel); - - }); - - afterEach(() => { - sandbox.restore(); - }); - - - describe('#_initializeInternalChannel', () => { - let peerArray; - let mockPeer4, mockPeer5; - beforeEach(() => { - mockPeer4 = sinon.createStubInstance(Peer); - mockPeer4.index = 4; - mockPeer5 = sinon.createStubInstance(Peer); - mockPeer5.index = 5; - - mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); - mockPeer2.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - mockPeer3.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); - mockPeer4.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); - mockPeer5.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; - mockChannel.getPeers.returns(peerArray); - }); - - it('should initialize the channel using the first peer', async () => { - mockChannel.initialize.resolves(); - await channel._initializeInternalChannel(); - sinon.assert.calledOnce(mockChannel.initialize); - }); - - it('should try other peers if initialization fails', async () => { - channel.initialized = false; - // create a real mock - mockChannel.initialize.onCall(0).rejects(new Error('connect failed')); - mockChannel.initialize.onCall(1).resolves(); - await channel._initializeInternalChannel(); - sinon.assert.calledTwice(mockChannel.initialize); - sinon.assert.calledWith(mockChannel.initialize.firstCall, {target: mockPeer1}); - sinon.assert.calledWith(mockChannel.initialize.secondCall, {target: mockPeer3}); - }); - - it('should fail if all peers fail', async () => { - channel.initialized = false; - mockChannel.initialize.onCall(0).rejects(new Error('connect failed')); - mockChannel.initialize.onCall(1).rejects(new Error('connect failed next')); - mockChannel.initialize.onCall(2).rejects(new Error('connect failed again')); - let error; - try { - await channel._initializeInternalChannel(); - } catch(_error) { - error = _error; - } - error.should.match(/connect failed again/); - sinon.assert.calledThrice(mockChannel.initialize); - sinon.assert.calledWith(mockChannel.initialize.firstCall, {target: mockPeer1}); - sinon.assert.calledWith(mockChannel.initialize.secondCall, {target: mockPeer3}); - sinon.assert.calledWith(mockChannel.initialize.thirdCall, {target: mockPeer4}); - }); - - it('should fail if there are no LEDGER_QUERY_ROLE peers', async () => { - channel.initialized = false; - mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - mockPeer2.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - mockPeer3.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - mockPeer4.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - mockPeer5.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); - peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; - mockChannel.getPeers.returns(peerArray); - return channel._initializeInternalChannel() - .should.be.rejectedWith(/no suitable peers available to initialize from/); - }); - }); - - describe('#initialize', () => { - it('should return with no action if already initialized', () => { - channel.initialized = true; - channel._initialize(); - }); - - it('should initialize the internal channels', async () => { - channel.initialized = false; - sandbox.stub(channel, '_initializeInternalChannel').returns(); - sandbox.stub(channel, '_mapPeersToMSPid').returns({}); - await channel._initialize(); - channel.initialized.should.equal(true); - }); - }); - - describe('#_mapPeersToMSPid', () => { - let peerArray; - let mockPeer4, mockPeer5; - beforeEach(() => { - mockPeer4 = sinon.createStubInstance(Peer); - mockPeer4.index = 4; - mockPeer5 = sinon.createStubInstance(Peer); - mockPeer5.index = 5; - - mockPeer1.getMspid.returns('MSP01'); - mockPeer2.getMspid.returns('MSP02'); - mockPeer3.getMspid.returns('MSP03'); - mockPeer4.getMspid.returns('MSP03'); // duplicate id - mockPeer5.getMspid.returns(); - peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; - mockChannel.getPeers.returns(peerArray); - }); - - it('should initialize the peer map', async () => { - const peermap = channel._mapPeersToMSPid(); - peermap.size.should.equal(3); - peermap.get('MSP01').should.deep.equal([mockPeer1]); - peermap.get('MSP02').should.deep.equal([mockPeer2]); - peermap.get('MSP03').should.deep.equal([mockPeer3, mockPeer4]); - }); - - it('should throw error if no peers associated with MSPID', async () => { - mockChannel.getPeers.returns([]); - (() => { - channel._mapPeersToMSPid(); - }).should.throw(/no suitable peers associated with mspIds were found/); - }); - }); - - describe('#getInternalChannel', () => { - it('should return the fabric-client channel object', () => { - channel.getInternalChannel().should.equal(mockChannel); - }); - }); - - describe('#getPeerMap', () => { - it('should return the peer map', () => { - const map = new Map(); - channel.peerMap = map; - channel.getPeerMap().should.equal(map); - }); - }); - - describe('#getContract', () => { - it('should throw an error if not initialized', () => { - channel.initialized = false; - (() => { - channel.getContract(); - }).should.throw(/Unable to get contract as channel has failed to initialize/); - }); - - it('should return a cached contract object', () => { - const mockContract = sinon.createStubInstance(Contract); - channel.contracts.set('foo', mockContract); - channel.initialized = true; - channel.getContract('foo').should.equal(mockContract); - }); - - it('should create a non-existent contract object', () => { - channel.initialized = true; - const contract = channel.getContract('bar'); - contract.should.be.instanceof(Contract); - contract.chaincodeId.should.equal('bar'); - }); - }); - - describe('#_dispose', () => { - it('should cleanup the channel object', () => { - const mockContract = sinon.createStubInstance(Contract); - channel.contracts.set('foo', mockContract); - channel.contracts.size.should.equal(1); - channel.initialized = true; - channel._dispose(); - channel.contracts.size.should.equal(0); - channel.initialized.should.equal(false); - }); - - it('should call dispose on the queryHandler if defined and work if no contracts have been got', () => { - const disposeStub = sinon.stub(); - channel.queryHandler = { - dispose: disposeStub - }; - channel._dispose(); - sinon.assert.calledOnce(disposeStub); - }); - }); - - describe('eventHandlerFactory', () => { - describe('#createTxEventHandler', () => { - const txId = 'TRANSACTION_ID'; - - async function initChannel() { - sandbox.stub(channel, '_initializeInternalChannel').returns(); - const peersByMspId = new Map(); - peersByMspId.set(mspId, [ mockPeer1 ]); - sandbox.stub(channel, '_mapPeersToMSPid').returns(peersByMspId); - await channel._initialize(); - } - - it('return an event handler object if event strategy set', async () => { - await initChannel(); - const eventHandler = channel.eventHandlerFactory.createTxEventHandler(txId); - eventHandler.should.be.an('Object'); - }); - - it('use commitTimeout option from network as timeout option for event handler', async () => { - await initChannel(); - const timeout = mockNetwork.getOptions().commitTimeout; - const eventHandler = channel.eventHandlerFactory.createTxEventHandler(txId); - eventHandler.options.timeout.should.equal(timeout); - }); - - it('return null if no event strategy set', async () => { - mockNetwork.getOptions.returns({ - useDiscovery: false, - commitTimeout: 300, - eventStrategy: null - }); - channel = new Channel(mockNetwork, mockChannel); - await initChannel(); - const eventHandler = channel.eventHandlerFactory.createTxEventHandler(txId); - should.equal(eventHandler, null); - }); - }); - }); -}); diff --git a/fabric-network/test/contract.js b/fabric-network/test/contract.js index 9c75d69985..5857011ec2 100644 --- a/fabric-network/test/contract.js +++ b/fabric-network/test/contract.js @@ -18,7 +18,7 @@ const should = chai.should(); chai.use(require('chai-as-promised')); const Contract = require('../lib/contract'); -const Network = require('../lib/network'); +const Gateway = require('../lib/gateway'); const QueryHandler = require('../lib/api/queryhandler'); const TransactionEventHandler = require('../lib/impl/event/transactioneventhandler'); @@ -27,7 +27,7 @@ describe('Contract', () => { const sandbox = sinon.createSandbox(); let clock; - let mockChannel, mockClient, mockUser, mockNetwork; + let mockChannel, mockClient, mockUser, mockGateway; let mockPeer1, mockPeer2, mockPeer3; let contract; let mockTransactionID; @@ -37,8 +37,8 @@ describe('Contract', () => { clock = sinon.useFakeTimers(); mockChannel = sinon.createStubInstance(Channel); mockClient = sinon.createStubInstance(Client); - mockNetwork = sinon.createStubInstance(Network); - mockNetwork.getClient.returns(mockClient); + mockGateway = sinon.createStubInstance(Gateway); + mockGateway.getClient.returns(mockClient); mockUser = sinon.createStubInstance(User); mockTransactionID = sinon.createStubInstance(TransactionID); mockTransactionID.getTransactionID.returns('00000000-0000-0000-0000-000000000000'); @@ -63,7 +63,7 @@ describe('Contract', () => { createTxEventHandler: () => stubEventHandler }; - contract = new Contract(mockChannel, 'someid', mockNetwork, mockQueryHandler, stubEventHandlerFactory); + contract = new Contract(mockChannel, 'someid', mockGateway, mockQueryHandler, stubEventHandlerFactory); }); afterEach(() => { diff --git a/fabric-network/test/eventstrategies.js b/fabric-network/test/eventstrategies.js index c7375235fb..e949166aeb 100644 --- a/fabric-network/test/eventstrategies.js +++ b/fabric-network/test/eventstrategies.js @@ -13,7 +13,7 @@ const should = chai.should(); const EventHubFactory = require('../lib/impl/event/eventhubfactory'); const ChannelEventHub = require('fabric-client').ChannelEventHub; -const Channel = require('../lib/channel'); +const Network = require('../lib/network'); const FabricChannel = require('fabric-client').Channel; // const ChannelPeer = require('fabric-client').ChannelPeer; const AllForTxStrategy = require('../lib/impl/event/allfortxstrategy'); @@ -26,7 +26,7 @@ describe('EventStrategies', () => { let stubEventHubFactory; let stubEventHub; - let stubChannel; + let stubNetwork; let stubPeer; beforeEach(() => { @@ -44,30 +44,30 @@ describe('EventStrategies', () => { const fabricChannel = sinon.createStubInstance(FabricChannel); fabricChannel.getPeers.returns([stubPeer]); - stubChannel = sinon.createStubInstance(Channel); + stubNetwork = sinon.createStubInstance(Network); const peerMap = new Map(); peerMap.set(mspId, [stubPeer]); - stubChannel.getPeerMap.returns(peerMap); - stubChannel.getInternalChannel.returns(fabricChannel); + stubNetwork.getPeerMap.returns(peerMap); + stubNetwork.getChannel.returns(fabricChannel); }); it('MSPID_SCOPE_ALLFORTX', () => { - const result = EventStrategies.MSPID_SCOPE_ALLFORTX(stubEventHubFactory, stubChannel, mspId); + const result = EventStrategies.MSPID_SCOPE_ALLFORTX(stubEventHubFactory, stubNetwork, mspId); result.should.be.an.instanceOf(AllForTxStrategy); }); it('MSPID_SCOPE_ANYFORTX', () => { - const result = EventStrategies.MSPID_SCOPE_ANYFORTX(stubEventHubFactory, stubChannel, mspId); + const result = EventStrategies.MSPID_SCOPE_ANYFORTX(stubEventHubFactory, stubNetwork, mspId); result.should.be.an.instanceOf(AnyForTxStrategy); }); - it('CHANNEL_SCOPE_ALLFORTX', () => { - const result = EventStrategies.CHANNEL_SCOPE_ALLFORTX(stubEventHubFactory, stubChannel, mspId); + it('NETWORK_SCOPE_ALLFORTX', () => { + const result = EventStrategies.NETWORK_SCOPE_ALLFORTX(stubEventHubFactory, stubNetwork, mspId); result.should.be.an.instanceOf(AllForTxStrategy); }); - it('CHANNEL_SCOPE_ANYFORTX', () => { - const result = EventStrategies.CHANNEL_SCOPE_ANYFORTX(stubEventHubFactory, stubChannel, mspId); + it('NETWORK_SCOPE_ANYFORTX', () => { + const result = EventStrategies.NETWORK_SCOPE_ANYFORTX(stubEventHubFactory, stubNetwork, mspId); result.should.be.an.instanceOf(AnyForTxStrategy); }); }); diff --git a/fabric-network/test/gateway.js b/fabric-network/test/gateway.js new file mode 100644 index 0000000000..470676c3ea --- /dev/null +++ b/fabric-network/test/gateway.js @@ -0,0 +1,439 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; +const sinon = require('sinon'); +const rewire = require('rewire'); + +const InternalChannel = rewire('fabric-client/lib/Channel'); +const Peer = InternalChannel.__get__('ChannelPeer'); +const FABRIC_CONSTANTS = require('fabric-client/lib/Constants'); + +const Client = require('fabric-client'); + +const chai = require('chai'); +const should = chai.should(); +chai.use(require('chai-as-promised')); + +const Network = require('../lib/network'); +const Gateway = require('../lib/gateway'); +const Wallet = require('../lib/api/wallet'); +const Mockery = require('mockery'); + + +describe('Gateway', () => { + const sandbox = sinon.createSandbox(); + + let mockClient; + let mockDefaultQueryHandler; + + before(() => { + Mockery.enable(); + mockDefaultQueryHandler = {query: 'mock'}; + Mockery.registerMock('./impl/query/defaultqueryhandler', mockDefaultQueryHandler); + }); + + after(() => { + Mockery.disable(); + }); + + beforeEach(() => { + mockClient = sinon.createStubInstance(Client); + }); + + afterEach(() => { + sandbox.restore(); + }); + + + describe('#_mergeOptions', () => { + let defaultOptions; + + beforeEach(() => { + defaultOptions = { + commitTimeout: 300 * 1000, + }; + }); + + it('should return the default options when there are no overrides', () => { + const overrideOptions = {}; + const expectedOptions = { + commitTimeout: 300 * 1000 + }; + Gateway._mergeOptions(defaultOptions, overrideOptions); + defaultOptions.should.deep.equal(expectedOptions); + }); + + it('should change a default option', () => { + const overrideOptions = { + commitTimeout: 1234 + }; + const expectedOptions = { + commitTimeout: 1234 + }; + Gateway._mergeOptions(defaultOptions, overrideOptions); + defaultOptions.should.deep.equal(expectedOptions); + }); + + it('should add a new option', () => { + const overrideOptions = { + useDiscovery: true + }; + const expectedOptions = { + commitTimeout: 300 * 1000, + useDiscovery: true + }; + Gateway._mergeOptions(defaultOptions, overrideOptions); + defaultOptions.should.deep.equal(expectedOptions); + }); + + it('should add option structures', () => { + const overrideOptions = { + identity: 'admin', + useDiscovery: true, + discoveryOptions: { + discoveryProtocol: 'grpc', + asLocalhost: true + } + }; + const expectedOptions = { + commitTimeout: 300 * 1000, + identity: 'admin', + useDiscovery: true, + discoveryOptions: { + discoveryProtocol: 'grpc', + asLocalhost: true + } + }; + Gateway._mergeOptions(defaultOptions, overrideOptions); + defaultOptions.should.deep.equal(expectedOptions); + }); + + it('should merge option structures', () => { + defaultOptions = { + commitTimeout: 300 * 1000, + identity: 'user', + useDiscovery: true, + discoveryOptions: { + discoveryProtocol: 'grpc', + asLocalhost: false + } + }; + const overrideOptions = { + identity: 'admin', + useDiscovery: true, + discoveryOptions: { + asLocalhost: true + } + }; + const expectedOptions = { + commitTimeout: 300 * 1000, + identity: 'admin', + useDiscovery: true, + discoveryOptions: { + discoveryProtocol: 'grpc', + asLocalhost: true + } + }; + Gateway._mergeOptions(defaultOptions, overrideOptions); + defaultOptions.should.deep.equal(expectedOptions); + }); + + }); + + describe('#constructor', () => { + it('should instantiate a Gateway object', () => { + const gateway = new Gateway(); + gateway.networks.should.be.instanceof(Map); + gateway.options.should.include({ + commitTimeout: 300, + queryHandler: './impl/query/defaultqueryhandler' + }); + }); + }); + + describe('#initialize', () => { + let gateway; + let mockWallet; + + beforeEach(() => { + gateway = new Gateway(); + mockWallet = sinon.createStubInstance(Wallet); + sandbox.stub(Client, 'loadFromConfig').withArgs('ccp').returns(mockClient); + mockWallet.setUserContext.withArgs(mockClient, 'admin').returns('foo'); + }); + + it('should fail without options supplied', () => { + return gateway.initialize() + .should.be.rejectedWith(/A wallet must be assigned to a Gateway instance/); + }); + + it('should fail without wallet option supplied', () => { + const options = { + identity: 'admin' + }; + return gateway.initialize('ccp', options) + .should.be.rejectedWith(/A wallet must be assigned to a Gateway instance/); + }); + + it('should initialize the gateway with default plugins', async () => { + const options = { + wallet: mockWallet, + }; + await gateway.initialize('ccp', options); + gateway.client.should.equal(mockClient); + should.not.exist(gateway.currentIdentity); + gateway.queryHandlerClass.should.equal(mockDefaultQueryHandler); + }); + + it('should initialize the gateway with identity', async () => { + const options = { + wallet: mockWallet, + identity: 'admin' + }; + await gateway.initialize('ccp', options); + gateway.client.should.equal(mockClient); + gateway.currentIdentity.should.equal('foo'); + }); + + it('should initialize the gateway with identity and set client tls crypto material', async () => { + mockWallet.export.withArgs('tlsId').resolves({certificate: 'acert', privateKey: 'akey'}); + + const options = { + wallet: mockWallet, + identity: 'admin', + clientTlsIdentity: 'tlsId' + }; + await gateway.initialize('ccp', options); + gateway.client.should.equal(mockClient); + gateway.currentIdentity.should.equal('foo'); + sinon.assert.calledOnce(mockClient.setTlsClientCertAndKey); + sinon.assert.calledWith(mockClient.setTlsClientCertAndKey, 'acert', 'akey'); + }); + + + it('should initialize from an existing client object', async () => { + const options = { + wallet: mockWallet, + identity: 'admin' + }; + await gateway.initialize(mockClient, options); + gateway.client.should.equal(mockClient); + gateway.currentIdentity.should.equal('foo'); + }); + + it ('should delete any default query handler options if the plugin doesn\'t match the default plugin', async () => { + gateway.options = { + commitTimeout: 300 * 1000, + queryHandler: './impl/query/defaultqueryhandler', + queryHandlerOptions: { + 'default1': 1, + 'default2': 2 + } + }; + + const otherQueryHandler = {query: 'other'}; + Mockery.registerMock('./impl/query/otherqueryhandler', otherQueryHandler); + + + const options = { + wallet: mockWallet, + identity: 'admin', + queryHandler: './impl/query/otherqueryhandler', + queryHandlerOptions: { + 'other1': 99 + } + }; + + await gateway.initialize('ccp', options); + gateway.options.should.deep.equal( + { + wallet: mockWallet, + identity: 'admin', + commitTimeout: 300 * 1000, + queryHandler: './impl/query/otherqueryhandler', + queryHandlerOptions: { + 'other1': 99 + } + } + ); + gateway.currentIdentity.should.equal('foo'); + gateway.queryHandlerClass.should.equal(otherQueryHandler); + }); + + it('should throw an error if it cannot load a query plugin', () => { + const options = { + wallet: mockWallet, + identity: 'admin', + queryHandler: './impl/query/noqueryhandler' + }; + return gateway.initialize('ccp', options) + .should.be.rejectedWith(/unable to load provided query handler: .\/impl\/query\/noqueryhandler/); + }); + + it('should not create a query handler if explicitly set to null', async () => { + const options = { + wallet: mockWallet, + queryHandler: null + }; + await gateway.initialize('ccp', options); + gateway.client.should.equal(mockClient); + should.equal(undefined, gateway.queryHandlerClass); + }); + + it('has default transaction event handling strategy if none specified', async () => { + const options = { + wallet: mockWallet + }; + await gateway.initialize('ccp', options); + gateway.options.eventStrategy.should.be.a('Function'); + }); + + it('allows transaction event handling strategy to be specified', async () => { + const stubStrategyFn = function stubStrategyFn() { }; + const options = { + wallet: mockWallet, + eventStrategy: stubStrategyFn + }; + await gateway.initialize('ccp', options); + gateway.options.eventStrategy.should.equal(stubStrategyFn); + }); + + it('allows null transaction event handling strategy to be set', async () => { + const options = { + wallet: mockWallet, + eventStrategy: null + }; + await gateway.initialize('ccp', options); + should.equal(gateway.options.eventStrategy, null); + }); + }); + + describe('#_createQueryHandler', () => { + let gateway; + beforeEach(() => { + gateway = new Gateway(); + }); + + it('should create a query handler if class defined', async () => { + const initStub = sinon.stub(); + const constructStub = sinon.stub(); + const mockClass = class MockClass { + constructor(...args) { + constructStub(...args); + this.initialize = initStub; + } + }; + + gateway.queryHandlerClass = mockClass; + + gateway.options.queryHandlerOptions = 'options'; + gateway.getCurrentIdentity = sinon.stub(); + gateway.getCurrentIdentity.returns({_mspId: 'anmsp'}); + const queryHandler = await gateway._createQueryHandler('channel', 'peerMap'); + queryHandler.should.be.instanceof(mockClass); + sinon.assert.calledOnce(constructStub); + sinon.assert.calledWith(constructStub, 'channel', 'anmsp', 'peerMap', 'options'); + sinon.assert.calledOnce(initStub); + + }); + + it('should do nothing if no class defined', async () => { + const queryHandler = await gateway._createQueryHandler('channel', 'peerMap'); + should.equal(null, queryHandler); + }); + }); + + describe('getters', () => { + let gateway; + let mockWallet; + + beforeEach(async () => { + gateway = new Gateway(); + mockWallet = sinon.createStubInstance(Wallet); + sandbox.stub(Client, 'loadFromConfig').withArgs('ccp').returns(mockClient); + mockWallet.setUserContext.withArgs(mockClient, 'admin').returns('foo'); + const options = { + wallet: mockWallet, + identity: 'admin' + }; + await gateway.initialize('ccp', options); + + }); + + describe('#getCurrentIdentity', () => { + it('should return the initialized identity', () => { + gateway.getCurrentIdentity().should.equal('foo'); + }); + }); + + describe('#getClient', () => { + it('should return the underlying client object', () => { + gateway.getClient().should.equal(mockClient); + }); + }); + + describe('#getOptions', () => { + it('should return the initialized options', () => { + const expectedOptions = { + commitTimeout: 300, + wallet: mockWallet, + identity: 'admin', + queryHandler: './impl/query/defaultqueryhandler' + }; + gateway.getOptions().should.include(expectedOptions); + }); + }); + }); + + describe('network interactions', () => { + let gateway; + let mockNetwork; + let mockInternalChannel; + + beforeEach(() => { + gateway = new Gateway(); + mockNetwork = sinon.createStubInstance(Network); + gateway.networks.set('foo', mockNetwork); + gateway.client = mockClient; + + mockInternalChannel = sinon.createStubInstance(InternalChannel); + const mockPeer1 = sinon.createStubInstance(Peer); + mockPeer1.index = 1; // add these so that the mockPeers can be distiguished when used in WithArgs(). + mockPeer1.getName.returns('Peer1'); + mockPeer1.getMspid.returns('MSP01'); + mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); + const peerArray = [mockPeer1]; + mockInternalChannel.getPeers.returns(peerArray); + }); + + describe('#getNetwork', () => { + it('should return a cached network object', () => { + gateway.getNetwork('foo').should.eventually.equal(mockNetwork); + }); + + it('should create a non-existent network object', async () => { + mockClient.getChannel.withArgs('bar').returns(mockInternalChannel); + gateway.getCurrentIdentity = sinon.stub().returns({ _mspId: 'MSP_ID' }); + + const network2 = await gateway.getNetwork('bar'); + network2.should.be.instanceof(Network); + network2.gateway.should.equal(gateway); + network2.channel.should.equal(mockInternalChannel); + gateway.networks.size.should.equal(2); + }); + }); + + describe('#dispose', () => { + it('should cleanup the gateway and its networks', () => { + gateway.networks.size.should.equal(1); + gateway.dispose(); + gateway.networks.size.should.equal(0); + }); + }); + }); + +}); diff --git a/fabric-network/test/network.js b/fabric-network/test/network.js index 017a8bcbea..534e4b1daa 100644 --- a/fabric-network/test/network.js +++ b/fabric-network/test/network.js @@ -10,38 +10,65 @@ const rewire = require('rewire'); const InternalChannel = rewire('fabric-client/lib/Channel'); const Peer = InternalChannel.__get__('ChannelPeer'); -const FABRIC_CONSTANTS = require('fabric-client/lib/Constants'); - const Client = require('fabric-client'); +const ChannelEventHub = Client.ChannelEventHub; +const TransactionID = require('fabric-client/lib/TransactionID.js'); +const FABRIC_CONSTANTS = require('fabric-client/lib/Constants'); const chai = require('chai'); const should = chai.should(); chai.use(require('chai-as-promised')); -const Channel = require('../lib/channel'); const Network = require('../lib/network'); -const Wallet = require('../lib/api/wallet'); -const Mockery = require('mockery'); - +const Gateway = require('../lib/gateway'); +const Contract = require('../lib/contract'); +const EventStrategies = require('../lib/eventstrategies'); describe('Network', () => { const sandbox = sinon.createSandbox(); - let mockClient; - let mockDefaultQueryHandler; - - before(() => { - Mockery.enable(); - mockDefaultQueryHandler = {query: 'mock'}; - Mockery.registerMock('./impl/query/defaultqueryhandler', mockDefaultQueryHandler); - }); + const mspId = 'MSP_ID'; - after(() => { - Mockery.disable(); - }); + let mockChannel, mockClient; + let mockPeer1, mockPeer2, mockPeer3; + let network; + let mockTransactionID, mockGateway; beforeEach(() => { + mockChannel = sinon.createStubInstance(InternalChannel); mockClient = sinon.createStubInstance(Client); + mockTransactionID = sinon.createStubInstance(TransactionID); + mockTransactionID.getTransactionID.returns('00000000-0000-0000-0000-000000000000'); + mockClient.newTransactionID.returns(mockTransactionID); + + mockChannel.getName.returns('testchainid'); + const stubEventHub = sinon.createStubInstance(ChannelEventHub); + stubEventHub.isconnected.returns(true); + mockChannel.getChannelEventHub.returns(stubEventHub); + + mockPeer1 = sinon.createStubInstance(Peer); + mockPeer1.index = 1; // add these so that the mockPeers can be distiguished when used in WithArgs(). + mockPeer1.getName.returns('Peer1'); + + mockPeer2 = sinon.createStubInstance(Peer); + mockPeer2.index = 2; + mockPeer2.getName.returns('Peer2'); + + mockPeer3 = sinon.createStubInstance(Peer); + mockPeer3.index = 3; + mockPeer3.getName.returns('Peer3'); + + mockGateway = sinon.createStubInstance(Gateway); + mockGateway.getOptions.returns({ + useDiscovery: false, + commitTimeout: 300, + eventStrategy: EventStrategies.MSPID_SCOPE_ALLFORTX + }); + mockGateway.getCurrentIdentity.returns({ + _mspId: mspId + }); + network = new Network(mockGateway, mockChannel); + }); afterEach(() => { @@ -49,391 +76,216 @@ describe('Network', () => { }); - describe('#_mergeOptions', () => { - let defaultOptions; - + describe('#_initializeInternalChannel', () => { + let peerArray; + let mockPeer4, mockPeer5; beforeEach(() => { - defaultOptions = { - commitTimeout: 300 * 1000, - }; - }); + mockPeer4 = sinon.createStubInstance(Peer); + mockPeer4.index = 4; + mockPeer5 = sinon.createStubInstance(Peer); + mockPeer5.index = 5; - it('should return the default options when there are no overrides', () => { - const overrideOptions = {}; - const expectedOptions = { - commitTimeout: 300 * 1000 - }; - Network._mergeOptions(defaultOptions, overrideOptions); - defaultOptions.should.deep.equal(expectedOptions); + mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); + mockPeer2.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + mockPeer3.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); + mockPeer4.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); + mockPeer5.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; + mockChannel.getPeers.returns(peerArray); }); - it('should change a default option', () => { - const overrideOptions = { - commitTimeout: 1234 - }; - const expectedOptions = { - commitTimeout: 1234 - }; - Network._mergeOptions(defaultOptions, overrideOptions); - defaultOptions.should.deep.equal(expectedOptions); + it('should initialize the network using the first peer', async () => { + mockChannel.initialize.resolves(); + await network._initializeInternalChannel(); + sinon.assert.calledOnce(mockChannel.initialize); }); - it('should add a new option', () => { - const overrideOptions = { - useDiscovery: true - }; - const expectedOptions = { - commitTimeout: 300 * 1000, - useDiscovery: true - }; - Network._mergeOptions(defaultOptions, overrideOptions); - defaultOptions.should.deep.equal(expectedOptions); + it('should try other peers if initialization fails', async () => { + network.initialized = false; + // create a real mock + mockChannel.initialize.onCall(0).rejects(new Error('connect failed')); + mockChannel.initialize.onCall(1).resolves(); + await network._initializeInternalChannel(); + sinon.assert.calledTwice(mockChannel.initialize); + sinon.assert.calledWith(mockChannel.initialize.firstCall, {target: mockPeer1}); + sinon.assert.calledWith(mockChannel.initialize.secondCall, {target: mockPeer3}); }); - it('should add option structures', () => { - const overrideOptions = { - identity: 'admin', - useDiscovery: true, - discoveryOptions: { - discoveryProtocol: 'grpc', - asLocalhost: true - } - }; - const expectedOptions = { - commitTimeout: 300 * 1000, - identity: 'admin', - useDiscovery: true, - discoveryOptions: { - discoveryProtocol: 'grpc', - asLocalhost: true - } - }; - Network._mergeOptions(defaultOptions, overrideOptions); - defaultOptions.should.deep.equal(expectedOptions); + it('should fail if all peers fail', async () => { + network.initialized = false; + mockChannel.initialize.onCall(0).rejects(new Error('connect failed')); + mockChannel.initialize.onCall(1).rejects(new Error('connect failed next')); + mockChannel.initialize.onCall(2).rejects(new Error('connect failed again')); + let error; + try { + await network._initializeInternalChannel(); + } catch(_error) { + error = _error; + } + error.should.match(/connect failed again/); + sinon.assert.calledThrice(mockChannel.initialize); + sinon.assert.calledWith(mockChannel.initialize.firstCall, {target: mockPeer1}); + sinon.assert.calledWith(mockChannel.initialize.secondCall, {target: mockPeer3}); + sinon.assert.calledWith(mockChannel.initialize.thirdCall, {target: mockPeer4}); }); - it('should merge option structures', () => { - defaultOptions = { - commitTimeout: 300 * 1000, - identity: 'user', - useDiscovery: true, - discoveryOptions: { - discoveryProtocol: 'grpc', - asLocalhost: false - } - }; - const overrideOptions = { - identity: 'admin', - useDiscovery: true, - discoveryOptions: { - asLocalhost: true - } - }; - const expectedOptions = { - commitTimeout: 300 * 1000, - identity: 'admin', - useDiscovery: true, - discoveryOptions: { - discoveryProtocol: 'grpc', - asLocalhost: true - } - }; - Network._mergeOptions(defaultOptions, overrideOptions); - defaultOptions.should.deep.equal(expectedOptions); - }); - - }); - - describe('#constructor', () => { - it('should instantiate a Network object', () => { - const network = new Network(); - network.channels.should.be.instanceof(Map); - network.options.should.include({ - commitTimeout: 300, - queryHandler: './impl/query/defaultqueryhandler' - }); + it('should fail if there are no LEDGER_QUERY_ROLE peers', async () => { + network.initialized = false; + mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + mockPeer2.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + mockPeer3.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + mockPeer4.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + mockPeer5.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(false); + peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; + mockChannel.getPeers.returns(peerArray); + return network._initializeInternalChannel() + .should.be.rejectedWith(/no suitable peers available to initialize from/); }); }); describe('#initialize', () => { - let network; - let mockWallet; - - beforeEach(() => { - network = new Network(); - mockWallet = sinon.createStubInstance(Wallet); - sandbox.stub(Client, 'loadFromConfig').withArgs('ccp').returns(mockClient); - mockWallet.setUserContext.withArgs(mockClient, 'admin').returns('foo'); + it('should return with no action if already initialized', () => { + network.initialized = true; + network._initialize(); }); - it('should fail without options supplied', () => { - return network.initialize() - .should.be.rejectedWith(/A wallet must be assigned to a Network instance/); - }); - - it('should fail without wallet option supplied', () => { - const options = { - identity: 'admin' - }; - return network.initialize('ccp', options) - .should.be.rejectedWith(/A wallet must be assigned to a Network instance/); + it('should initialize the internal channels', async () => { + network.initialized = false; + sandbox.stub(network, '_initializeInternalChannel').returns(); + sandbox.stub(network, '_mapPeersToMSPid').returns({}); + await network._initialize(); + network.initialized.should.equal(true); }); + }); - it('should initialize the network with default plugins', async () => { - const options = { - wallet: mockWallet, - }; - await network.initialize('ccp', options); - network.client.should.equal(mockClient); - should.not.exist(network.currentIdentity); - network.queryHandlerClass.should.equal(mockDefaultQueryHandler); - }); + describe('#_mapPeersToMSPid', () => { + let peerArray; + let mockPeer4, mockPeer5; + beforeEach(() => { + mockPeer4 = sinon.createStubInstance(Peer); + mockPeer4.index = 4; + mockPeer5 = sinon.createStubInstance(Peer); + mockPeer5.index = 5; - it('should initialize the network with identity', async () => { - const options = { - wallet: mockWallet, - identity: 'admin' - }; - await network.initialize('ccp', options); - network.client.should.equal(mockClient); - network.currentIdentity.should.equal('foo'); + mockPeer1.getMspid.returns('MSP01'); + mockPeer2.getMspid.returns('MSP02'); + mockPeer3.getMspid.returns('MSP03'); + mockPeer4.getMspid.returns('MSP03'); // duplicate id + mockPeer5.getMspid.returns(); + peerArray = [mockPeer1, mockPeer2, mockPeer3, mockPeer4, mockPeer5]; + mockChannel.getPeers.returns(peerArray); }); - it('should initialize the network with identity and set client tls crypto material', async () => { - mockWallet.export.withArgs('tlsId').resolves({certificate: 'acert', privateKey: 'akey'}); - - const options = { - wallet: mockWallet, - identity: 'admin', - clientTlsIdentity: 'tlsId' - }; - await network.initialize('ccp', options); - network.client.should.equal(mockClient); - network.currentIdentity.should.equal('foo'); - sinon.assert.calledOnce(mockClient.setTlsClientCertAndKey); - sinon.assert.calledWith(mockClient.setTlsClientCertAndKey, 'acert', 'akey'); + it('should initialize the peer map', async () => { + const peermap = network._mapPeersToMSPid(); + peermap.size.should.equal(3); + peermap.get('MSP01').should.deep.equal([mockPeer1]); + peermap.get('MSP02').should.deep.equal([mockPeer2]); + peermap.get('MSP03').should.deep.equal([mockPeer3, mockPeer4]); }); - - it('should initialize from an existing client object', async () => { - const options = { - wallet: mockWallet, - identity: 'admin' - }; - await network.initialize(mockClient, options); - network.client.should.equal(mockClient); - network.currentIdentity.should.equal('foo'); - }); - - it ('should delete any default query handler options if the plugin doesn\'t match the default plugin', async () => { - network.options = { - commitTimeout: 300 * 1000, - queryHandler: './impl/query/defaultqueryhandler', - queryHandlerOptions: { - 'default1': 1, - 'default2': 2 - } - }; - - const otherQueryHandler = {query: 'other'}; - Mockery.registerMock('./impl/query/otherqueryhandler', otherQueryHandler); - - - const options = { - wallet: mockWallet, - identity: 'admin', - queryHandler: './impl/query/otherqueryhandler', - queryHandlerOptions: { - 'other1': 99 - } - }; - - await network.initialize('ccp', options); - network.options.should.deep.equal( - { - wallet: mockWallet, - identity: 'admin', - commitTimeout: 300 * 1000, - queryHandler: './impl/query/otherqueryhandler', - queryHandlerOptions: { - 'other1': 99 - } - } - ); - network.currentIdentity.should.equal('foo'); - network.queryHandlerClass.should.equal(otherQueryHandler); + it('should throw error if no peers associated with MSPID', async () => { + mockChannel.getPeers.returns([]); + (() => { + network._mapPeersToMSPid(); + }).should.throw(/no suitable peers associated with mspIds were found/); }); + }); - it('should throw an error if it cannot load a query plugin', () => { - const options = { - wallet: mockWallet, - identity: 'admin', - queryHandler: './impl/query/noqueryhandler' - }; - return network.initialize('ccp', options) - .should.be.rejectedWith(/unable to load provided query handler: .\/impl\/query\/noqueryhandler/); + describe('#getChannel', () => { + it('should return the fabric-client channel object', () => { + network.getChannel().should.equal(mockChannel); }); + }); - it('should not create a query handler if explicitly set to null', async () => { - const options = { - wallet: mockWallet, - queryHandler: null - }; - await network.initialize('ccp', options); - network.client.should.equal(mockClient); - should.equal(undefined, network.queryHandlerClass); + describe('#getPeerMap', () => { + it('should return the peer map', () => { + const map = new Map(); + network.peerMap = map; + network.getPeerMap().should.equal(map); }); + }); - it('has default transaction event handling strategy if none specified', async () => { - const options = { - wallet: mockWallet - }; - await network.initialize('ccp', options); - network.options.eventStrategy.should.be.a('Function'); + describe('#getContract', () => { + it('should throw an error if not initialized', () => { + network.initialized = false; + (() => { + network.getContract(); + }).should.throw(/Unable to get contract as network has failed to initialize/); }); - it('allows transaction event handling strategy to be specified', async () => { - const stubStrategyFn = function stubStrategyFn() { }; - const options = { - wallet: mockWallet, - eventStrategy: stubStrategyFn - }; - await network.initialize('ccp', options); - network.options.eventStrategy.should.equal(stubStrategyFn); + it('should return a cached contract object', () => { + const mockContract = sinon.createStubInstance(Contract); + network.contracts.set('foo', mockContract); + network.initialized = true; + network.getContract('foo').should.equal(mockContract); }); - it('allows null transaction event handling strategy to be set', async () => { - const options = { - wallet: mockWallet, - eventStrategy: null - }; - await network.initialize('ccp', options); - should.equal(network.options.eventStrategy, null); + it('should create a non-existent contract object', () => { + network.initialized = true; + const contract = network.getContract('bar'); + contract.should.be.instanceof(Contract); + contract.chaincodeId.should.equal('bar'); }); }); - describe('#_createQueryHandler', () => { - let network; - beforeEach(() => { - network = new Network(); + describe('#_dispose', () => { + it('should cleanup the network object', () => { + const mockContract = sinon.createStubInstance(Contract); + network.contracts.set('foo', mockContract); + network.contracts.size.should.equal(1); + network.initialized = true; + network._dispose(); + network.contracts.size.should.equal(0); + network.initialized.should.equal(false); }); - it('should create a query handler if class defined', async () => { - const initStub = sinon.stub(); - const constructStub = sinon.stub(); - const mockClass = class MockClass { - constructor(...args) { - constructStub(...args); - this.initialize = initStub; - } + it('should call dispose on the queryHandler if defined and work if no contracts have been got', () => { + const disposeStub = sinon.stub(); + network.queryHandler = { + dispose: disposeStub }; - - network.queryHandlerClass = mockClass; - - network.options.queryHandlerOptions = 'options'; - network.getCurrentIdentity = sinon.stub(); - network.getCurrentIdentity.returns({_mspId: 'anmsp'}); - const queryHandler = await network._createQueryHandler('channel', 'peerMap'); - queryHandler.should.be.instanceof(mockClass); - sinon.assert.calledOnce(constructStub); - sinon.assert.calledWith(constructStub, 'channel', 'anmsp', 'peerMap', 'options'); - sinon.assert.calledOnce(initStub); - - }); - - it('should do nothing if no class defined', async () => { - const queryHandler = await network._createQueryHandler('channel', 'peerMap'); - should.equal(null, queryHandler); + network._dispose(); + sinon.assert.calledOnce(disposeStub); }); }); - describe('getters', () => { - let network; - let mockWallet; - - beforeEach(async () => { - network = new Network(); - mockWallet = sinon.createStubInstance(Wallet); - sandbox.stub(Client, 'loadFromConfig').withArgs('ccp').returns(mockClient); - mockWallet.setUserContext.withArgs(mockClient, 'admin').returns('foo'); - const options = { - wallet: mockWallet, - identity: 'admin' - }; - await network.initialize('ccp', options); - - }); - - describe('#getCurrentIdentity', () => { - it('should return the initialized identity', () => { - network.getCurrentIdentity().should.equal('foo'); + describe('eventHandlerFactory', () => { + describe('#createTxEventHandler', () => { + const txId = 'TRANSACTION_ID'; + + async function initNetwork() { + sandbox.stub(network, '_initializeInternalChannel').returns(); + const peersByMspId = new Map(); + peersByMspId.set(mspId, [ mockPeer1 ]); + sandbox.stub(network, '_mapPeersToMSPid').returns(peersByMspId); + await network._initialize(); + } + + it('return an event handler object if event strategy set', async () => { + await initNetwork(); + const eventHandler = network.eventHandlerFactory.createTxEventHandler(txId); + eventHandler.should.be.an('Object'); }); - }); - describe('#getClient', () => { - it('should return the underlying client object', () => { - network.getClient().should.equal(mockClient); + it('use commitTimeout option from gateway as timeout option for event handler', async () => { + await initNetwork(); + const timeout = mockGateway.getOptions().commitTimeout; + const eventHandler = network.eventHandlerFactory.createTxEventHandler(txId); + eventHandler.options.timeout.should.equal(timeout); }); - }); - describe('#getOptions', () => { - it('should return the initialized options', () => { - const expectedOptions = { + it('return null if no event strategy set', async () => { + mockGateway.getOptions.returns({ + useDiscovery: false, commitTimeout: 300, - wallet: mockWallet, - identity: 'admin', - queryHandler: './impl/query/defaultqueryhandler' - }; - network.getOptions().should.include(expectedOptions); - }); - }); - }); - - describe('channel interactions', () => { - let network; - let mockChannel; - let mockInternalChannel; - - beforeEach(() => { - network = new Network(); - mockChannel = sinon.createStubInstance(Channel); - network.channels.set('foo', mockChannel); - network.client = mockClient; - - mockInternalChannel = sinon.createStubInstance(InternalChannel); - const mockPeer1 = sinon.createStubInstance(Peer); - mockPeer1.index = 1; // add these so that the mockPeers can be distiguished when used in WithArgs(). - mockPeer1.getName.returns('Peer1'); - mockPeer1.getMspid.returns('MSP01'); - mockPeer1.isInRole.withArgs(FABRIC_CONSTANTS.NetworkConfig.LEDGER_QUERY_ROLE).returns(true); - const peerArray = [mockPeer1]; - mockInternalChannel.getPeers.returns(peerArray); - }); - - describe('#getChannel', () => { - it('should return a cached channel object', () => { - network.getChannel('foo').should.eventually.equal(mockChannel); - }); - - it('should create a non-existent channel object', async () => { - mockClient.getChannel.withArgs('bar').returns(mockInternalChannel); - network.getCurrentIdentity = sinon.stub().returns({ _mspId: 'MSP_ID' }); - - const channel2 = await network.getChannel('bar'); - channel2.should.be.instanceof(Channel); - channel2.network.should.equal(network); - channel2.channel.should.equal(mockInternalChannel); - network.channels.size.should.equal(2); - }); - }); - - describe('#dispose', () => { - it('should cleanup the network and its channels', () => { - network.channels.size.should.equal(1); - network.dispose(); - network.channels.size.should.equal(0); + eventStrategy: null + }); + network = new Network(mockGateway, mockChannel); + await initNetwork(); + const eventHandler = network.eventHandlerFactory.createTxEventHandler(txId); + should.equal(eventHandler, null); }); }); }); - }); diff --git a/fabric-network/types/index.d.ts b/fabric-network/types/index.d.ts index 412e99cedc..178d589eac 100644 --- a/fabric-network/types/index.d.ts +++ b/fabric-network/types/index.d.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { User } from 'fabric-client'; +import { User, Channel } from 'fabric-client'; import Client = require('fabric-client'); @@ -19,25 +19,20 @@ export interface InitOptions { clientTlsIdentity?: string; } -export class Network { +export class Gateway { constructor(); initialize(ccp: string | Client, options?: InitOptions): Promise; getCurrentIdentity(): User; getClient(): Client; getOptions(): InitOptions; - getChannel(channelName: string): Promise; + getNetwork(channelName: string): Promise; dispose(): void; } -// put into it's own separate namespace to avoid a clash with fabric-client Channel -declare namespace FabricNetwork { - export class Channel { - getInternalChannel(): Client.Channel; - getPeerMap(): Map; - getContract(chaincodeId: string): Contract; - // will be coming - // getEventHubs(): ChannelEventHub[]; - } +export class Network { + getChannel(): Channel; + getPeerMap(): Map; + getContract(chaincodeId: string): Contract; } export class Contract { diff --git a/test/integration/network-e2e/invoke.js b/test/integration/network-e2e/invoke.js index fd90207cde..40ab3749b0 100644 --- a/test/integration/network-e2e/invoke.js +++ b/test/integration/network-e2e/invoke.js @@ -11,7 +11,7 @@ const tape = require('tape'); const _test = require('tape-promise').default; const test = _test(tape); -const {Network, InMemoryWallet, FileSystemWallet, X509WalletMixin, EventStrategies} = require('../../../fabric-network/index.js'); +const {Gateway, InMemoryWallet, FileSystemWallet, X509WalletMixin, EventStrategies} = require('../../../fabric-network/index.js'); const fs = require('fs-extra'); const os = require('os'); const path = require('path'); @@ -39,15 +39,15 @@ async function tlsSetup() { await inMemoryWallet.import('tlsId', X509WalletMixin.createIdentity('org1', tlsInfo.certificate, tlsInfo.key)); } -async function createContract(t, networkOptions) { - const network = new Network(); - await network.initialize(JSON.parse(ccp.toString()), networkOptions); - t.pass('Initialized the network'); +async function createContract(t, gatewayOptions) { + const gateway = new Gateway(); + await gateway.initialize(JSON.parse(ccp.toString()), gatewayOptions); + t.pass('Initialized the gateway'); - const channel = await network.getChannel(channelName); - t.pass('Initialized the channel, ' + channelName); + const network = await gateway.getNetwork(channelName); + t.pass('Initialized the network, ' + channelName); - const contract = await channel.getContract(chaincodeId); + const contract = await network.getContract(chaincodeId); t.pass('Got the contract, about to submit "move" transaction'); return contract; @@ -147,7 +147,7 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using t.end(); }); -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and CHANNEL_SCOPE_ALLFORTX event strategy *****\n\n', async (t) => { +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ALLFORTX event strategy *****\n\n', async (t) => { try { await inMemoryIdentitySetup(); await tlsSetup(); @@ -156,7 +156,7 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using wallet: inMemoryWallet, identity: 'User1@org1.example.com', clientTlsIdentity: 'tlsId', - eventStrategy: EventStrategies.CHANNEL_SCOPE_ALLFORTX + eventStrategy: EventStrategies.NETWORK_SCOPE_ALLFORTX }); const response = await contract.submitTransaction('move', 'a', 'b','100'); @@ -175,7 +175,7 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using t.end(); }); -test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and CHANNEL_SCOPE_ANYFORTX event strategy *****\n\n', async (t) => { +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and NETWORK_SCOPE_ANYFORTX event strategy *****\n\n', async (t) => { try { await inMemoryIdentitySetup(); await tlsSetup(); @@ -184,7 +184,7 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using wallet: inMemoryWallet, identity: 'User1@org1.example.com', clientTlsIdentity: 'tlsId', - eventStrategy: EventStrategies.CHANNEL_SCOPE_ANYFORTX + eventStrategy: EventStrategies.NETWORK_SCOPE_ANYFORTX }); const response = await contract.submitTransaction('move', 'a', 'b','100'); @@ -250,22 +250,22 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using - const network = new Network(); + const gateway = new Gateway(); const ccp = fs.readFileSync(fixtures + '/network.json'); - await network.initialize(JSON.parse(ccp.toString()), { + await gateway.initialize(JSON.parse(ccp.toString()), { wallet: fileSystemWallet, identity: identityLabel, clientTlsIdentity: 'tlsId' }); - t.pass('Initialized the network'); + t.pass('Initialized the gateway'); - const channel = await network.getChannel(channelName); + const network = await gateway.getNetwork(channelName); t.pass('Initialized the channel, ' + channelName); - const contract = await channel.getContract(chaincodeId); + const contract = await network.getContract(chaincodeId); t.pass('Got the contract, about to submit "move" transaction'); diff --git a/test/integration/network-e2e/query.js b/test/integration/network-e2e/query.js index b36e60de62..401f105d04 100644 --- a/test/integration/network-e2e/query.js +++ b/test/integration/network-e2e/query.js @@ -11,7 +11,7 @@ const tape = require('tape'); const _test = require('tape-promise').default; const test = _test(tape); -const {Network, FileSystemWallet, X509WalletMixin} = require('../../../fabric-network/index.js'); +const {Gateway, FileSystemWallet, X509WalletMixin} = require('../../../fabric-network/index.js'); const fs = require('fs-extra'); const os = require('os'); const path = require('path'); @@ -43,20 +43,20 @@ test('\n\n***** Network End-to-end flow: execute transaction to get information await fileSystemWallet.import('tlsId', X509WalletMixin.createIdentity('org1', tlsInfo.certificate, tlsInfo.key)); - const network = new Network(); + const gateway = new Gateway(); const ccp = fs.readFileSync(fixtures + '/network.json'); let ccpObject = JSON.parse(ccp.toString()); - await network.initialize(ccpObject, { + await gateway.initialize(ccpObject, { wallet: fileSystemWallet, identity: identityLabel, clientTlsIdentity: 'tlsId' }); - t.pass('Initialized the network'); + t.pass('Initialized the gateway'); - const channel = await network.getChannel(channelName); + const channel = await gateway.getNetwork(channelName); t.pass('Initialized the channel, ' + channelName); diff --git a/test/typescript/network.ts b/test/typescript/network.ts index 7711481926..7360e0d4e3 100644 --- a/test/typescript/network.ts +++ b/test/typescript/network.ts @@ -8,11 +8,11 @@ import { FileSystemWallet, InMemoryWallet, Contract, - FabricNetwork, + Network, Identity, IdentityInformation, InitOptions, - Network, + Gateway, Wallet, X509Identity, X509WalletMixin @@ -47,7 +47,7 @@ import { const idList: IdentityInformation[] = await inMemoryWallet.list(); - const network: Network = new Network(); + const gateway: Gateway = new Gateway(); const opt1: InitOptions = { wallet: inMemoryWallet, @@ -56,36 +56,36 @@ import { commitTimeout: 1000 }; - await network.initialize('accp', opt1); + await gateway.initialize('accp', opt1); - const network2: Network = new Network(); + const gateway2: Gateway = new Gateway(); const client: Client = new Client(); const opt2: InitOptions = { wallet: fileSystemWallet, identity: 'anod' }; - await network.initialize(client, opt2); + await gateway.initialize(client, opt2); - const channel: FabricNetwork.Channel = await network.getChannel('a channel'); - const contract: Contract = await channel.getContract('chaincode'); + const network: Network = await gateway.getNetwork('a channel'); + const contract: Contract = await network.getContract('chaincode'); let response: Buffer = await contract.submitTransaction('move', 'a', 'b','100'); response = await contract.executeTransaction('move', 'a', 'b','100'); - const aClient: Client = network.getClient(); - const user: User = network.getCurrentIdentity(); - const opt3: InitOptions = network.getOptions(); + const aClient: Client = gateway.getClient(); + const user: User = gateway.getCurrentIdentity(); + const opt3: InitOptions = gateway.getOptions(); - const internalChannel: Channel = channel.getInternalChannel(); - const peerMap: Map = channel.getPeerMap(); + const internalChannel: Channel = network.getChannel(); + const peerMap: Map = network.getPeerMap(); const deleteDone: Promise = inMemoryWallet.delete('User1@org1.example.com') await deleteDone; await fileSystemWallet.delete('User1@org1.example.com'); - network.dispose(); - network2.dispose(); + gateway.dispose(); + gateway2.dispose(); })();