From 792d86f0c4464d2e51cf576674cf3617dafad4eb Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Tue, 9 Oct 2018 17:06:29 +0100 Subject: [PATCH] FABN-865: Plug-in event handlers User plug-in point by passing their own factory function as the 'strategy' option on Gateway creation. Sample user implementation included in fabric-network integration tests. Change-Id: I961b3e09dbfbc17d92b9acef22593388611bf166 Signed-off-by: Mark S. Lewis --- fabric-network/lib/contract.js | 25 +- fabric-network/lib/gateway.js | 39 ++- .../lib/impl/event/abstracteventstrategy.js | 2 +- .../impl/event/defaulteventhandlermanager.js | 64 ----- .../event/defaulteventhandlerstrategies.js | 37 ++- .../lib/impl/event/eventhubfactory.js | 2 +- fabric-network/lib/impl/wallet/basewallet.js | 8 +- fabric-network/lib/network.js | 42 +-- fabric-network/test/contract.js | 239 ++++++------------ fabric-network/test/gateway.js | 15 +- .../impl/event/defaulteventhandlermanager.js | 101 -------- .../event/defaulteventhandlerstrategies.js | 85 ++++--- .../test/impl/event/eventhubfactory.js | 2 +- .../impl/event/transactioneventhandler.js | 7 +- fabric-network/test/impl/wallet/basewallet.js | 8 +- .../test/impl/wallet/inmemorywallet.js | 4 +- fabric-network/test/network.js | 56 +--- test/integration/network-e2e/invoke.js | 235 ++++++++++++++--- .../sample-transaction-event-handler-2.js | 158 ++++++++++++ .../sample-transaction-event-handler-3.js | 86 +++++++ .../sample-transaction-event-handler.js | 238 +++++++++++++++++ test/typescript/network.ts | 3 +- 22 files changed, 929 insertions(+), 527 deletions(-) delete mode 100644 fabric-network/lib/impl/event/defaulteventhandlermanager.js delete mode 100644 fabric-network/test/impl/event/defaulteventhandlermanager.js create mode 100644 test/integration/network-e2e/sample-transaction-event-handler-2.js create mode 100644 test/integration/network-e2e/sample-transaction-event-handler-3.js create mode 100644 test/integration/network-e2e/sample-transaction-event-handler.js diff --git a/fabric-network/lib/contract.js b/fabric-network/lib/contract.js index a907cceb8a..f84dcc81d0 100644 --- a/fabric-network/lib/contract.js +++ b/fabric-network/lib/contract.js @@ -16,14 +16,15 @@ const util = require('util'); */ class Contract { - constructor(channel, chaincodeId, gateway, queryHandler, eventHandlerFactory, namespace='') { + constructor(network, chaincodeId, gateway, queryHandler, namespace='') { logger.debug('in Contract constructor'); - this.channel = channel; + this.network = network; + this.channel = network.getChannel(); this.chaincodeId = chaincodeId; this.gateway = gateway; this.queryHandler = queryHandler; - this.eventHandlerFactory = eventHandlerFactory; + this.eventHandlerOptions = gateway.getOptions().eventHandlerOptions; this.namespace = namespace; } @@ -94,8 +95,7 @@ class Contract { this._verifyTransactionDetails('submitTransaction', fullTxName, parameters); const txId = this.gateway.getClient().newTransactionID(); - // createTxEventHandler() will return null if no event handler is requested - const eventHandler = this.eventHandlerFactory ? this.eventHandlerFactory.createTxEventHandler(txId.getTransactionID()) : null; + const eventHandler = this._createTxEventHandler(txId.getTransactionID()); // Submit the transaction to the endorsers. const request = { @@ -138,6 +138,21 @@ class Contract { return result; } + /** + * Create an appropriate transaction event handler, if one is configured. + * @private + * @param {String} transactionId The transation ID to listen for. + * @returns {TransactionEventHandler|null} A transactionevent handler, or null if non is configured. + */ + _createTxEventHandler(transactionId) { + const eventHandlerFactoryFn = this.eventHandlerOptions.strategy; + if (eventHandlerFactoryFn) { + return eventHandlerFactoryFn(transactionId, this.network, this.eventHandlerOptions); + } else { + return null; + } + } + /** * Verify the supplied transaction details. * @private diff --git a/fabric-network/lib/gateway.js b/fabric-network/lib/gateway.js index 3a20846573..339ef9da40 100644 --- a/fabric-network/lib/gateway.js +++ b/fabric-network/lib/gateway.js @@ -63,19 +63,38 @@ class Gateway { /** * @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 {DefaultEventHandlerOptions|Object} [eventHandlerOptions] This defines options for the inbuilt default - * event handler capability + * @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 {DefaultEventHandlerOptions} [eventHandlerOptions] This defines options for the inbuilt default + * event handler capability. */ /** * @typedef {Object} DefaultEventHandlerOptions - * @property {number} [commitTimeout = 300] The timeout period in seconds to wait for commit notification to complete - * @property {MSPID_SCOPE_ALLFORTX|MSPID_SCOPE_ANYFORTX|NETWORK_SCOPE_ALLFORTX|NETWORK_SCOPE_ANYFORTX} [strategy] Event - * handling strategy to identify successful transaction commits. A null value - * indicates that no event handling is desired. The default is {@link MSPID_SCOPE_ALLFORTX}. + * @property {number} [commitTimeout = 300] The timeout period in seconds to wait for commit notification to + * complete. + * @property {?TxEventHandlerFactory} [strategy = MSPID_SCOPE_ALLFORTX] Event handling strategy to identify + * successful transaction commits. A null value indicates that no event handling is desired. The default is + * {@link MSPID_SCOPE_ALLFORTX}. + */ + + /** + * @typedef {Function} TxEventHandlerFactory + * @param {String} transactionId The transaction ID for which the handler should listen. + * @param {Network} network The network on which this transaction is being submitted. + * @returns {TxEventHandler} A transaction event handler. + */ + + /** + * @typedef {Object} TxEventHandler + * @property {Function} startLstening Async function that resolves when the handler has started listening for + * transaction commit events. Called after the transaction proposal has been accepted and prior to submission of + * the transaction to the orderer. + * @property {Function} waitForEvents Async function that resolves (or rejects) when suitable transaction + * commit events have been received. Called after submission of the transaction to the orderer. + * @property {Function} cancelListening Cancel listening. Called if submission of the transaction to the orderer + * fails. */ /** @@ -215,7 +234,7 @@ class Gateway { async _createQueryHandler(channel, peerMap) { if (this.queryHandlerClass) { - const currentmspId = this.getCurrentIdentity()._mspId; + const currentmspId = this.getCurrentIdentity().getIdentity().getMSPId(); const queryHandler = new this.queryHandlerClass( channel, currentmspId, diff --git a/fabric-network/lib/impl/event/abstracteventstrategy.js b/fabric-network/lib/impl/event/abstracteventstrategy.js index d941a05c3d..b869c970c7 100644 --- a/fabric-network/lib/impl/event/abstracteventstrategy.js +++ b/fabric-network/lib/impl/event/abstracteventstrategy.js @@ -42,7 +42,7 @@ class AbstractEventStrategy { * Called by event handler to obtain the event hubs to which it should listen. Gives an opportunity for * the strategy to store information on the events it expects to receive for later use in event handling. * @async - * @returns ChannelEventHubs[] connected event hubs. + * @returns {ChannelEventHubs[]} connected event hubs. * @throws {Error} if the connected event hubs do not satisfy the strategy. */ async getConnectedEventHubs() { diff --git a/fabric-network/lib/impl/event/defaulteventhandlermanager.js b/fabric-network/lib/impl/event/defaulteventhandlermanager.js deleted file mode 100644 index 95fea79f21..0000000000 --- a/fabric-network/lib/impl/event/defaulteventhandlermanager.js +++ /dev/null @@ -1,64 +0,0 @@ -/** -* Copyright 2018 IBM All Rights Reserved. -* -* SPDX-License-Identifier: Apache-2.0 -*/ - -'use strict'; - -const EventHandlerStrategies = require('./defaulteventhandlerstrategies'); -const TransactionEventHandler = require('./transactioneventhandler'); -const EventHubFactory = require('./eventhubfactory'); -const logger = require('fabric-network/lib/logger').getLogger('DefaultEventHandlerManager'); - -class DefaultEventHandlerManager { - /** - * @typedef {Object} EventHandlerOptions - * @property {Function} [strategy = EventHandlerStrategies.MSPID_SCOPE_ALLFORTX] Event strategy factory. - * @property {Number} [commitTimeout = 0] Number of seconds to wait for transaction completion. A value of zero - * indicates that the handler should wait indefinitely. - */ - - /** - * Constructor. - * @param {Network} network Network on which events will be processed. - * @param {String} mspId Member Services Provider identifier. - * @param {EventHandlerOptions} options Additional options for event handling behaviour. - */ - constructor(network, mspId, options) { - this.network = network; - this.eventHubFactory = new EventHubFactory(network.getChannel()); - this.mspId = mspId; - - const defaultOptions = { - strategy: EventHandlerStrategies.MSPID_SCOPE_ALLFORTX - }; - this.options = Object.assign(defaultOptions, options); - - logger.debug('constructor: mspId = %s, options = %O', mspId, this.options); - } - - async initialize() { - const strategy = this.options.strategy(this.eventHubFactory, this.network, this.mspId); - try { - await strategy.getConnectedEventHubs(); - } catch (error) { - logger.debug('initialize:', error); - } - } - - /** - * create an Tx Event handler for the specific txid - * - * @param {String} txid - * @returns The transaction event handler - * @memberof DefaultEventHandlerFactory - */ - createTxEventHandler(txid) { - logger.debug('createTxEventHandler: txid = %s', txid); - const strategy = this.options.strategy(this.eventHubFactory, this.network, this.mspId); - return new TransactionEventHandler(txid, strategy, this.options); - } -} - -module.exports = DefaultEventHandlerManager; diff --git a/fabric-network/lib/impl/event/defaulteventhandlerstrategies.js b/fabric-network/lib/impl/event/defaulteventhandlerstrategies.js index f94ae8db17..b1b57a92a5 100644 --- a/fabric-network/lib/impl/event/defaulteventhandlerstrategies.js +++ b/fabric-network/lib/impl/event/defaulteventhandlerstrategies.js @@ -8,6 +8,17 @@ const AllForTxStrategy = require('fabric-network/lib/impl/event/allfortxstrategy'); const AnyForTxStrategy = require('fabric-network/lib/impl/event/anyfortxstrategy'); +const TransactionEventHandler = require('fabric-network/lib/impl/event/transactioneventhandler'); + +function getOrganizationEventHubs(network) { + const peers = network.getChannel().getPeersForOrg(); + return network.getEventHubFactory().getEventHubs(peers); +} + +function getNetworkEventHubs(network) { + const peers = network.getChannel().getPeers(); + return network.getEventHubFactory().getEventHubs(peers); +} /** * Default event handler strategy.
@@ -16,9 +27,9 @@ const AnyForTxStrategy = require('fabric-network/lib/impl/event/anyfortxstrategy * until all of the events to be received from * all of the event hubs that are still connected (minimum 1). */ -function MSPID_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { - const peers = network.getPeerMap().get(mspId); - return new AllForTxStrategy(eventHubFactory.getEventHubs(peers)); +function MSPID_SCOPE_ALLFORTX(transactionId, network, options) { + const eventStrategy = new AllForTxStrategy(getOrganizationEventHubs(network)); + return new TransactionEventHandler(transactionId, eventStrategy, options); } /** @@ -27,9 +38,9 @@ function MSPID_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { * The [submitTransaction]{@link Contract#submitTransaction} method will wait * until the first event from any of the event hubs that are still connected. */ -function MSPID_SCOPE_ANYFORTX(eventHubFactory, network, mspId) { - const peers = network.getPeerMap().get(mspId); - return new AnyForTxStrategy(eventHubFactory.getEventHubs(peers)); +function MSPID_SCOPE_ANYFORTX(transactionId, network, options) { + const eventStrategy = new AnyForTxStrategy(getOrganizationEventHubs(network)); + return new TransactionEventHandler(transactionId, eventStrategy, options); } /** @@ -39,10 +50,9 @@ function MSPID_SCOPE_ANYFORTX(eventHubFactory, network, mspId) { * until all of the events to be received from * all of the event hubs that are still connected (minimum 1). */ -//eslint-disable-next-line no-unused-vars -function NETWORK_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { - const peers = network.getChannel().getPeers(); - return new AllForTxStrategy(eventHubFactory.getEventHubs(peers)); +function NETWORK_SCOPE_ALLFORTX(transactionId, network, options) { + const eventStrategy = new AllForTxStrategy(getNetworkEventHubs(network)); + return new TransactionEventHandler(transactionId, eventStrategy, options); } /** @@ -51,10 +61,9 @@ function NETWORK_SCOPE_ALLFORTX(eventHubFactory, network, mspId) { * The [submitTransaction]{@link Contract#submitTransaction} method will wait * until the first event from any of the event hubs that are still connected. */ -//eslint-disable-next-line no-unused-vars -function NETWORK_SCOPE_ANYFORTX(eventHubFactory, network, mspId) { - const peers = network.getChannel().getPeers(); - return new AnyForTxStrategy(eventHubFactory.getEventHubs(peers)); +function NETWORK_SCOPE_ANYFORTX(transactionId, network, options) { + const eventStrategy = new AnyForTxStrategy(getNetworkEventHubs(network)); + return new TransactionEventHandler(transactionId, eventStrategy, options); } module.exports = { diff --git a/fabric-network/lib/impl/event/eventhubfactory.js b/fabric-network/lib/impl/event/eventhubfactory.js index 582cbea08d..f69ef926ba 100644 --- a/fabric-network/lib/impl/event/eventhubfactory.js +++ b/fabric-network/lib/impl/event/eventhubfactory.js @@ -32,7 +32,7 @@ class EventHubFactory { /** * Gets event hubs for all specified peers. Where possible, the event hubs will be connected. * @async - * @param {ChannelPeer} peers Peers for which event hubs should be connected. + * @param {ChannelPeer[]} peers Peers for which event hubs should be connected. * @returns {ChannelEventHub[]} Event hubs, which may or may not have successfully connected. */ async getEventHubs(peers) { diff --git a/fabric-network/lib/impl/wallet/basewallet.js b/fabric-network/lib/impl/wallet/basewallet.js index 36d285f7fe..b9ca8e43b2 100644 --- a/fabric-network/lib/impl/wallet/basewallet.js +++ b/fabric-network/lib/impl/wallet/basewallet.js @@ -86,11 +86,11 @@ class BaseWallet extends Wallet { // a mixin can override the getCryptoSuite //======================================== - async getStateStore(label) { + async getStateStore(label) { // eslint-disable-line no-unused-vars throw new Error('Not implemented'); } - async getCryptoSuite(label) { + async getCryptoSuite(label) { // eslint-disable-line no-unused-vars throw new Error('Not implemented'); } @@ -173,11 +173,11 @@ class BaseWallet extends Wallet { //========================================================= - async delete(label) { + async delete(label) { // eslint-disable-line no-unused-vars throw new Error('Not implemented'); } - async exists(label) { + async exists(label) { // eslint-disable-line no-unused-vars throw new Error('Not implemented'); } diff --git a/fabric-network/lib/network.js b/fabric-network/lib/network.js index 95d98f4cbb..6670dfd28a 100644 --- a/fabric-network/lib/network.js +++ b/fabric-network/lib/network.js @@ -7,8 +7,9 @@ 'use strict'; const FabricConstants = require('fabric-client/lib/Constants'); const Contract = require('./contract'); +const EventHubFactory = require('fabric-network/lib/impl/event/eventhubfactory'); + const logger = require('./logger').getLogger('Network'); -const DefaultEventHandlerManager = require('./impl/event/defaulteventhandlermanager'); const util = require('util'); /** @@ -31,6 +32,7 @@ class Network { this.gateway = gateway; this.channel = channel; this.contracts = new Map(); + this.eventHubFactory = new EventHubFactory(channel); this.initialized = false; } @@ -127,9 +129,8 @@ class Network { } await this._initializeInternalChannel(); - this.peerMap = this._mapPeersToMSPid(); - this.eventHandlerManager = await this._createEventHandlerManager(); - this.queryHandler = await this.gateway._createQueryHandler(this.channel, this.peerMap); + const peerMap = this._mapPeersToMSPid(); + this.queryHandler = await this.gateway._createQueryHandler(this.channel, peerMap); this.initialized = true; } @@ -139,12 +140,6 @@ class Network { return this.channel; } - getPeerMap() { - logger.debug('in getPeerMap'); - - return this.peerMap; - } - /** * Returns an instance of a contract (chaincode) on the current network * @param {string} chaincodeId the chaincode Identifier @@ -161,11 +156,10 @@ class Network { let contract = this.contracts.get(key); if (!contract) { contract = new Contract( - this.channel, + this, chaincodeId, this.gateway, this.queryHandler, - this.eventHandlerManager, namespace ); this.contracts.set(key, contract); @@ -173,22 +167,6 @@ class Network { return contract; } - async _createEventHandlerManager() { - const createEventStrategyFn = this.gateway.getOptions().eventHandlerOptions.strategy; - if (createEventStrategyFn) { - const currentmspId = this.gateway.getCurrentIdentity()._mspId; - const eventHandlerManager = new DefaultEventHandlerManager( - this, - currentmspId, - this.gateway.getOptions().eventHandlerOptions - ); - await eventHandlerManager.initialize(); - return eventHandlerManager; - } - return null; - - } - _dispose() { logger.debug('in _dispose'); @@ -206,6 +184,14 @@ class Network { this.initialized = false; } + /** + * Get the event hub factory for this network. + * @private + * @returns {EventHubFactory} An event hub factory. + */ + getEventHubFactory() { + return this.eventHubFactory; + } } module.exports = Network; diff --git a/fabric-network/test/contract.js b/fabric-network/test/contract.js index 4176abd119..026282339b 100644 --- a/fabric-network/test/contract.js +++ b/fabric-network/test/contract.js @@ -11,7 +11,6 @@ const Channel = require('fabric-client/lib/Channel'); const Peer = require('fabric-client/lib/Peer'); const Client = require('fabric-client'); const TransactionID = require('fabric-client/lib/TransactionID.js'); -const User = require('fabric-client/lib/User.js'); const chai = require('chai'); const should = chai.should(); @@ -19,14 +18,16 @@ chai.use(require('chai-as-promised')); const Contract = require('../lib/contract'); const Gateway = require('../lib/gateway'); +const Network = require('fabric-network/lib/network'); const QueryHandler = require('../lib/api/queryhandler'); const TransactionEventHandler = require('../lib/impl/event/transactioneventhandler'); describe('Contract', () => { let clock; + let network; - let mockChannel, mockClient, mockUser, mockGateway; + let mockChannel, mockClient, mockGateway; let mockPeer1, mockPeer2, mockPeer3; let contract; let mockTransactionID; @@ -34,11 +35,22 @@ describe('Contract', () => { beforeEach(() => { clock = sinon.useFakeTimers(); + + const stubEventHandler = sinon.createStubInstance(TransactionEventHandler); + mockChannel = sinon.createStubInstance(Channel); mockClient = sinon.createStubInstance(Client); + mockGateway = sinon.createStubInstance(Gateway); mockGateway.getClient.returns(mockClient); - mockUser = sinon.createStubInstance(User); + mockGateway.getOptions.returns({ + eventHandlerOptions: { + strategy: () => stubEventHandler + } + }); + + network = new Network(mockGateway, mockChannel); + mockTransactionID = sinon.createStubInstance(TransactionID); mockTransactionID.getTransactionID.returns('00000000-0000-0000-0000-000000000000'); mockClient.newTransactionID.returns(mockTransactionID); @@ -57,12 +69,7 @@ describe('Contract', () => { mockPeer3.getName.returns('Peer3'); mockQueryHandler = sinon.createStubInstance(QueryHandler); - const stubEventHandler = sinon.createStubInstance(TransactionEventHandler); - const stubEventHandlerFactory = { - createTxEventHandler: () => stubEventHandler - }; - - contract = new Contract(mockChannel, 'someid', mockGateway, mockQueryHandler, stubEventHandlerFactory); + contract = new Contract(network, 'someid', mockGateway, mockQueryHandler); }); afterEach(() => { @@ -141,15 +148,23 @@ describe('Contract', () => { }); describe('#submitTransaction', () => { - const validResponses = [{ - response: { - status: 200 - } - }]; - + const expectedResult = 'hello world'; beforeEach(() => { - sinon.stub(contract, '_validatePeerResponses').returns({validResponses: validResponses}); + const proposalResponses = [{ + response: { + status: 200, + payload: expectedResult + } + }]; + const proposal = { proposal: 'i do' }; + const header = { header: 'gooooal' }; + mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); + // This is the commit proposal and response (from the orderer). + const response = { + status: 'SUCCESS' + }; + mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); }); it('should throw if functionName not specified', () => { @@ -163,7 +178,7 @@ describe('Contract', () => { .should.be.rejectedWith('Transaction parameters must be strings: 3.142, null'); }); - it('should submit an invoke request to the chaincode which does not return data', () => { + it('should return null if transaction response contains no response payload', async () => { const proposalResponses = [{ response: { status: 200 @@ -177,116 +192,48 @@ describe('Contract', () => { status: 'SUCCESS' }; mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); - return contract.submitTransaction('myfunc', 'arg1', 'arg2') - .then((result) => { - should.equal(result, null); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, { - chaincodeId: 'someid', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'] - }); - sinon.assert.calledOnce(mockChannel.sendTransaction); - }); + const result = await contract.submitTransaction('myfunc', 'arg1', 'arg2'); + should.equal(result, null); }); - it('should submit an invoke request to the chaincode which does not return data', () => { - const proposalResponses = [{ - response: { - status: 200 + it('should return transaction response payload', async () => { + mockGateway.getOptions.returns({ + eventHandlerOptions: { + strategy: null } - }]; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - // This is the commit proposal and response (from the orderer). - const response = { - status: 'SUCCESS' - }; - mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); - return contract.submitTransaction('myfunc', 'arg1', 'arg2') - .then((result) => { - should.equal(result, null); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, { - chaincodeId: 'someid', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'] - }); - sinon.assert.calledOnce(mockChannel.sendTransaction); - }); + }); + contract = new Contract(network, 'someid', mockGateway, mockQueryHandler); + + const result = await contract.submitTransaction('myfunc', 'arg1', 'arg2'); + result.should.equal(expectedResult); }); - it('should submit an invoke request to the chaincode with no event handler', () => { - const proposalResponses = [{ - response: { - status: 200, - payload: 'hello world' + it('should get expected result with no event handler', async () => { + mockGateway.getOptions.returns({ + eventHandlerOptions: { + strategy: null } - }]; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - contract._validatePeerResponses.returns({validResponses: proposalResponses}); - // This is the commit proposal and response (from the orderer). - const response = { - status: 'SUCCESS' - }; - mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); - contract.eventHandlerFactory = null; - return contract.submitTransaction('myfunc', 'arg1', 'arg2') - .then((result) => { - result.should.equal('hello world'); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, { - chaincodeId: 'someid', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'] - }); - sinon.assert.calledOnce(mockChannel.sendTransaction); - }); + }); + contract = new Contract(network, 'someid', mockGateway, mockQueryHandler); + + const result = await contract.submitTransaction('myfunc', 'arg1', 'arg2'); + result.should.equal(expectedResult); }); - it('should submit an invoke request to the chaincode', () => { - const proposalResponses = [{ - response: { - status: 200 - } - }]; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - // This is the commit proposal and response (from the orderer). - const response = { - status: 'SUCCESS' - }; - mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); - return contract.submitTransaction('myfunc', 'arg1', 'arg2') - .then((result) => { - should.equal(result, null); - sinon.assert.calledOnce(mockClient.newTransactionID); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, { - chaincodeId: 'someid', - txId: mockTransactionID, - fcn: 'myfunc', - args: ['arg1', 'arg2'] - }); - sinon.assert.calledOnce(mockChannel.sendTransaction); - }); + it('should send transaction proposal with supplied function and argument values', async () => { + await contract.submitTransaction('myfunc', 'arg1', 'arg2'); + sinon.assert.calledWith(mockChannel.sendTransactionProposal, { + chaincodeId: 'someid', + txId: mockTransactionID, + fcn: 'myfunc', + args: ['arg1', 'arg2'] + }); }); it('should throw if transaction proposals were not valid', () => { - const proposalResponses = []; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; const errorResp = new Error('an error'); - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - contract._validatePeerResponses.withArgs(proposalResponses).throws(errorResp); + sinon.stub(contract, '_validatePeerResponses').throws(errorResp); return contract.submitTransaction('myfunc', 'arg1', 'arg2') .should.be.rejectedWith(/an error/); }); @@ -307,63 +254,30 @@ describe('Contract', () => { const header = { header: 'gooooal' }; mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - // Remove the stubbing of _validatePeerResponses in beforeEach() - contract._validatePeerResponses.restore(); - return contract.submitTransaction('myfunc', 'arg1', 'arg2') .should.be.rejectedWith(/No valid responses from any peers/); }); it('should throw an error if the orderer responds with an error', () => { - const proposalResponses = [{ - response: { - status: 200 - } - }]; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - // This is the commit proposal and response (from the orderer). const response = { status: 'FAILURE' }; - mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); + mockChannel.sendTransaction.withArgs(sinon.match.any).resolves(response); return contract.submitTransaction('myfunc', 'arg1', 'arg2') .should.be.rejectedWith(/Failed to send/); }); - it('should preprend the namespace if one has been given',()=>{ - const stubEventHandler = sinon.createStubInstance(TransactionEventHandler); - const stubEventHandlerFactory = { - createTxEventHandler: () => stubEventHandler - }; - const nscontract = new Contract(mockChannel, 'someid', mockGateway, mockQueryHandler, stubEventHandlerFactory,'my.name.space'); - const proposalResponses = [{ - response: { - status: 200 - } - }]; - const proposal = { proposal: 'i do' }; - const header = { header: 'gooooal' }; - mockChannel.sendTransactionProposal.resolves([ proposalResponses, proposal, header ]); - // This is the commit proposal and response (from the orderer). - const response = { - status: 'SUCCESS' - }; - mockChannel.sendTransaction.withArgs({ proposalResponses: proposalResponses, proposal: proposal }).resolves(response); - return nscontract.submitTransaction('myfunc', 'arg1', 'arg2') - .then((result) => { - should.equal(result, null); - sinon.assert.calledOnce(mockClient.newTransactionID); - sinon.assert.calledOnce(mockChannel.sendTransactionProposal); - sinon.assert.calledWith(mockChannel.sendTransactionProposal, { - chaincodeId: 'someid', - txId: mockTransactionID, - fcn: 'my.name.space:myfunc', - args: ['arg1', 'arg2'] - }); - sinon.assert.calledOnce(mockChannel.sendTransaction); - }); + it('should preprend the namespace to transaction proposal if one has been given', async () => { + const nscontract = new Contract(network, 'someid', mockGateway, mockQueryHandler, 'my.name.space'); + + await nscontract.submitTransaction('myfunc', 'arg1', 'arg2'); + + sinon.assert.calledWith(mockChannel.sendTransactionProposal, { + chaincodeId: 'someid', + txId: mockTransactionID, + fcn: 'my.name.space:myfunc', + args: ['arg1', 'arg2'] + }); }); }); @@ -395,12 +309,7 @@ describe('Contract', () => { }); it('should query chaincode with namespace added to the function', async () => { - - const stubEventHandler = sinon.createStubInstance(TransactionEventHandler); - const stubEventHandlerFactory = { - createTxEventHandler: () => stubEventHandler - }; - const nscontract = new Contract(mockChannel, 'someid', mockGateway, mockQueryHandler, stubEventHandlerFactory,'my.name.space'); + const nscontract = new Contract(network, 'someid', mockGateway, mockQueryHandler, 'my.name.space'); mockQueryHandler.queryChaincode.withArgs('someid', mockTransactionID, 'myfunc', ['arg1', 'arg2']).resolves(); diff --git a/fabric-network/test/gateway.js b/fabric-network/test/gateway.js index 398c362be6..f73c8d8165 100644 --- a/fabric-network/test/gateway.js +++ b/fabric-network/test/gateway.js @@ -13,6 +13,8 @@ const Peer = InternalChannel.__get__('ChannelPeer'); const FABRIC_CONSTANTS = require('fabric-client/lib/Constants'); const Client = require('fabric-client'); +const User = require('fabric-client/lib/User'); +const { Identity } = require('fabric-client/lib/msp/identity'); const chai = require('chai'); const should = chai.should(); @@ -22,8 +24,6 @@ const Network = require('../lib/network'); const Gateway = require('../lib/gateway'); const Wallet = require('../lib/api/wallet'); const Mockery = require('mockery'); -const EventStrategies = require('../lib/impl/event/defaulteventhandlerstrategies'); - describe('Gateway', () => { let mockClient; @@ -331,10 +331,17 @@ describe('Gateway', () => { gateway.queryHandlerClass = mockClass; + const stubIdentity = sinon.createStubInstance(Identity); + stubIdentity.getMSPId.returns('anmsp'); + + const stubUser = sinon.createStubInstance(User); + stubUser.getIdentity.returns(stubIdentity); + gateway.options.queryHandlerOptions = 'options'; - gateway.getCurrentIdentity = sinon.stub(); - gateway.getCurrentIdentity.returns({_mspId: 'anmsp'}); + sinon.stub(gateway, 'getCurrentIdentity').returns(stubUser); + const queryHandler = await gateway._createQueryHandler('channel', 'peerMap'); + queryHandler.should.be.instanceof(mockClass); sinon.assert.calledOnce(constructStub); sinon.assert.calledWith(constructStub, 'channel', 'anmsp', 'peerMap', 'options'); diff --git a/fabric-network/test/impl/event/defaulteventhandlermanager.js b/fabric-network/test/impl/event/defaulteventhandlermanager.js deleted file mode 100644 index 92f77decb5..0000000000 --- a/fabric-network/test/impl/event/defaulteventhandlermanager.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2018 IBM All Rights Reserved. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict'; - -const chai = require('chai'); -chai.use(require('chai-as-promised')); -const expect = chai.expect; -const sinon = require('sinon'); - -const DefaultEventHandlerManager = require('../../../lib/impl/event/defaulteventhandlermanager'); -const EventHandlerStrategies = require('../../../lib/impl/event/defaulteventhandlerstrategies'); -const EventHubFactory = require('fabric-network/lib/impl/event/eventhubfactory'); -const TransactionEventHandler = require('fabric-network/lib/impl/event/transactioneventhandler'); -const Network = require('fabric-network/lib/network'); -const Channel = require('fabric-client/lib/Channel'); - -describe('DefaultEventHandlerManager', () => { - const mspId = 'MSP_ID'; - - let stubNetwork; - let fakeStrategyFactory; - let stubStrategy; - - beforeEach(() => { - const stubChannel = sinon.createStubInstance(Channel); - - stubNetwork = sinon.createStubInstance(Network); - stubNetwork.getChannel.returns(stubChannel); - - stubStrategy = { - getConnectedEventHubs: sinon.stub() - }; - stubStrategy.getConnectedEventHubs.resolves([]); - - fakeStrategyFactory = sinon.stub(); - fakeStrategyFactory.withArgs(sinon.match.instanceOf(EventHubFactory), sinon.match(stubNetwork), sinon.match(mspId)).returns(stubStrategy); - }); - - afterEach(() => { - sinon.restore(); - }); - - describe('#constructor', () => { - it('has a default strategy if no options supplied', () => { - const manager = new DefaultEventHandlerManager(stubNetwork, mspId, {}); - expect(manager.options.strategy).to.equal(EventHandlerStrategies.MSPID_SCOPE_ALLFORTX); - }); - - it('allows a strategy to be specified', () => { - const options = { - strategy: EventHandlerStrategies.MSPID_SCOPE_ANYFORTX - }; - const manager = new DefaultEventHandlerManager(stubNetwork, mspId, options); - expect(manager.options.strategy).to.equal(EventHandlerStrategies.MSPID_SCOPE_ANYFORTX); - }); - }); - - describe('#initialize', () => { - let manager; - - beforeEach(() => { - const options = { strategy: fakeStrategyFactory }; - manager = new DefaultEventHandlerManager(stubNetwork, mspId, options); - }); - - it('gets event hubs from strategy', () => { - manager.initialize(); - sinon.assert.calledOnce(stubStrategy.getConnectedEventHubs); - }); - - it('does not reject if getting event hubs from strategy errors', () => { - stubStrategy.getConnectedEventHubs.rejects(); - return expect(manager.initialize()).to.be.fulfilled; - }); - }); - - describe('#createTxEventHandler', () => { - it('returns a transaction event handler', () => { - const options = { strategy: fakeStrategyFactory }; - const manager = new DefaultEventHandlerManager(stubNetwork, mspId, options); - - const result = manager.createTxEventHandler('txId'); - - expect(result).to.be.an.instanceof(TransactionEventHandler); - }); - - it('creates a new strategy instance on each call', () => { - const options = { strategy: fakeStrategyFactory }; - const manager = new DefaultEventHandlerManager(stubNetwork, mspId, options); - - manager.createTxEventHandler('txId'); - manager.createTxEventHandler('txId'); - - sinon.assert.calledTwice(fakeStrategyFactory); - }); - }); -}); diff --git a/fabric-network/test/impl/event/defaulteventhandlerstrategies.js b/fabric-network/test/impl/event/defaulteventhandlerstrategies.js index 0866e871c5..65d7d141ef 100644 --- a/fabric-network/test/impl/event/defaulteventhandlerstrategies.js +++ b/fabric-network/test/impl/event/defaulteventhandlerstrategies.js @@ -13,63 +13,80 @@ const expect = chai.expect; const EventHubFactory = require('fabric-network/lib/impl/event/eventhubfactory'); const ChannelEventHub = require('fabric-client').ChannelEventHub; const Network = require('fabric-network/lib/network'); -const FabricChannel = require('fabric-client').Channel; +const Channel = require('fabric-client').Channel; const AllForTxStrategy = require('fabric-network/lib/impl/event/allfortxstrategy'); const AnyForTxStrategy = require('fabric-network/lib/impl/event/anyfortxstrategy'); +const TransactionEventHandler = require('fabric-network/lib/impl/event/transactioneventhandler'); const EventStrategies = require('fabric-network/lib/impl/event/defaulteventhandlerstrategies'); describe('DefaultEventHandlerStrategies', () => { - const mspId = 'MSP_ID'; - - let stubEventHubFactory; - let stubEventHub; + const transactionId = 'TRANSACTION_ID'; + const expectedStrategyTypes = { + 'MSPID_SCOPE_ALLFORTX': AllForTxStrategy, + 'MSPID_SCOPE_ANYFORTX': AnyForTxStrategy, + 'NETWORK_SCOPE_ALLFORTX': AllForTxStrategy, + 'NETWORK_SCOPE_ANYFORTX': AnyForTxStrategy + }; + const strategyNames = Object.keys(expectedStrategyTypes); + + let options; let stubNetwork; - let stubPeer; beforeEach(() => { - stubEventHub = sinon.createStubInstance(ChannelEventHub); - stubEventHub.isconnected.returns(true); - - stubEventHubFactory = sinon.createStubInstance(EventHubFactory); - stubEventHubFactory.getEventHubs.resolves([stubEventHub]); + options = { + commitTimeout: 418, + banana: 'man' + }; - stubPeer = { + const stubPeer = { _stubInfo: 'peer', getName: function() { return 'peer'; } }; - const fabricChannel = sinon.createStubInstance(FabricChannel); - fabricChannel.getPeers.returns([stubPeer]); + const stubEventHub = sinon.createStubInstance(ChannelEventHub); + stubEventHub.isconnected.returns(true); + + const stubEventHubFactory = sinon.createStubInstance(EventHubFactory); + stubEventHubFactory.getEventHubs.withArgs([stubPeer]).resolves([stubEventHub]); + + const channel = sinon.createStubInstance(Channel); + channel.getPeers.returns([stubPeer]); + channel.getPeersForOrg.returns([stubPeer]); stubNetwork = sinon.createStubInstance(Network); - const peerMap = new Map(); - peerMap.set(mspId, [stubPeer]); - stubNetwork.getPeerMap.returns(peerMap); - stubNetwork.getChannel.returns(fabricChannel); + stubNetwork.getChannel.returns(channel); + stubNetwork.getEventHubFactory.returns(stubEventHubFactory); }); afterEach(() => { sinon.restore(); }); - it('MSPID_SCOPE_ALLFORTX', () => { - const result = EventStrategies.MSPID_SCOPE_ALLFORTX(stubEventHubFactory, stubNetwork, mspId); - expect(result).to.be.an.instanceOf(AllForTxStrategy); - }); + strategyNames.forEach((strategyName) => describe(strategyName, () => { + const createTxEventHandler = EventStrategies[strategyName]; - it('MSPID_SCOPE_ANYFORTX', () => { - const result = EventStrategies.MSPID_SCOPE_ANYFORTX(stubEventHubFactory, stubNetwork, mspId); - expect(result).to.be.an.instanceOf(AnyForTxStrategy); - }); + let eventHandler; - it('NETWORK_SCOPE_ALLFORTX', () => { - const result = EventStrategies.NETWORK_SCOPE_ALLFORTX(stubEventHubFactory, stubNetwork, mspId); - expect(result).to.be.an.instanceOf(AllForTxStrategy); - }); + beforeEach(() => { + eventHandler = createTxEventHandler(transactionId, stubNetwork, options); + }); - it('NETWORK_SCOPE_ANYFORTX', () => { - const result = EventStrategies.NETWORK_SCOPE_ANYFORTX(stubEventHubFactory, stubNetwork, mspId); - expect(result).to.be.an.instanceOf(AnyForTxStrategy); - }); + it('Returns a TransactionEventHandler', () => { + expect(eventHandler).to.be.an.instanceOf(TransactionEventHandler); + }); + + it('Sets transaction ID on event handler', () => { + expect(eventHandler.transactionId).to.equal(transactionId); + }); + + it('Sets options on event handler', () => { + expect(eventHandler.options).to.include(options); + }); + + it('Sets correct strategy on event handler', () => { + const expectedType = expectedStrategyTypes[strategyName]; + expect(eventHandler.strategy).to.be.an.instanceOf(expectedType); + }); + })); }); diff --git a/fabric-network/test/impl/event/eventhubfactory.js b/fabric-network/test/impl/event/eventhubfactory.js index 3ed700563d..0ae8f4c76e 100644 --- a/fabric-network/test/impl/event/eventhubfactory.js +++ b/fabric-network/test/impl/event/eventhubfactory.js @@ -45,7 +45,7 @@ describe('EventHubFactory', () => { stubEventHub2._stubInfo = 'eventHub2'; stubEventHub2.getName.returns('eventHub2'); // Fake a connection success event - stubEventHub2.registerBlockEvent.callsFake((block, error) => { + stubEventHub2.registerBlockEvent.callsFake((block, error) => { // eslint-disable-line no-unused-vars Promise.resolve().then(block); return 2; }); diff --git a/fabric-network/test/impl/event/transactioneventhandler.js b/fabric-network/test/impl/event/transactioneventhandler.js index 76a5faca4f..a61a757b8b 100644 --- a/fabric-network/test/impl/event/transactioneventhandler.js +++ b/fabric-network/test/impl/event/transactioneventhandler.js @@ -14,7 +14,6 @@ const sinon = require('sinon'); const ChannelEventHub = require('fabric-client').ChannelEventHub; const TransactionEventHandler = require('fabric-network/lib/impl/event/transactioneventhandler'); -const DefaultEventHandlerManager = require('fabric-network/lib/impl/event/defaulteventhandlermanager'); describe('TransactionEventHandler', () => { const transactionId = 'TRANSACTION_ID'; @@ -119,7 +118,7 @@ describe('TransactionEventHandler', () => { }); it('succeeds when strategy calls success function after event received', async () => { - stubStrategy.eventReceived = ((successFn, failFn) => successFn()); + stubStrategy.eventReceived = ((successFn, failFn) => successFn()); // eslint-disable-line no-unused-vars await handler.startListening(); stubEventHub._onEventFn(transactionId, 'VALID'); @@ -136,7 +135,7 @@ describe('TransactionEventHandler', () => { }); it('succeeds when strategy calls success function after error received', async () => { - stubStrategy.errorReceived = ((successFn, failFn) => successFn()); + stubStrategy.errorReceived = ((successFn, failFn) => successFn()); // eslint-disable-line no-unused-vars await handler.startListening(); stubEventHub._onErrorFn(new Error('EVENT_HUB_ERROR')); @@ -183,7 +182,7 @@ describe('TransactionEventHandler', () => { }); it('does not timeout if timeout set to zero', async () => { - stubStrategy.eventReceived = ((successFn, failFn) => successFn()); + stubStrategy.eventReceived = ((successFn, failFn) => successFn()); // eslint-disable-line no-unused-vars const options = { commitTimeout: 0 }; handler = new TransactionEventHandler(transactionId, stubStrategy, options); diff --git a/fabric-network/test/impl/wallet/basewallet.js b/fabric-network/test/impl/wallet/basewallet.js index 4daacb0838..b3ae653e5b 100644 --- a/fabric-network/test/impl/wallet/basewallet.js +++ b/fabric-network/test/impl/wallet/basewallet.js @@ -26,12 +26,6 @@ describe('BaseWallet', () => { }); }); - describe('#setUserContext', () => { - const wallet = new BaseWallet(); - - - }); - describe('Unimplemented methods', () => { const wallet = new BaseWallet(); @@ -62,4 +56,4 @@ describe('BaseWallet', () => { }); -}); \ No newline at end of file +}); diff --git a/fabric-network/test/impl/wallet/inmemorywallet.js b/fabric-network/test/impl/wallet/inmemorywallet.js index 1dcf32e3d7..f7633f1b70 100644 --- a/fabric-network/test/impl/wallet/inmemorywallet.js +++ b/fabric-network/test/impl/wallet/inmemorywallet.js @@ -216,8 +216,8 @@ mb3MM6J+V7kciO3hSyP5OJSBPWGlsjxQj2m55aFutmlleVfr6YiaLnYd it('#getValue', async () => { await store.setValue('user1', 'val1'); - const value = await store.getValue('user1'); - const value2 = await store.getValue('user3'); + await store.getValue('user1'); + await store.getValue('user3'); }); }); }); diff --git a/fabric-network/test/network.js b/fabric-network/test/network.js index 5a891f3b7b..591c02ee2b 100644 --- a/fabric-network/test/network.js +++ b/fabric-network/test/network.js @@ -12,11 +12,12 @@ const InternalChannel = rewire('fabric-client/lib/Channel'); const Peer = InternalChannel.__get__('ChannelPeer'); const Client = require('fabric-client'); const ChannelEventHub = Client.ChannelEventHub; +const EventHubFactory = require('fabric-network/lib/impl/event/eventhubfactory'); 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.should(); chai.use(require('chai-as-promised')); const Network = require('../lib/network'); @@ -64,9 +65,7 @@ describe('Network', () => { strategy: EventStrategies.MSPID_SCOPE_ALLFORTX } }); - mockGateway.getCurrentIdentity.returns({ - _mspId: mspId - }); + network = new Network(mockGateway, mockChannel); }); @@ -200,14 +199,6 @@ describe('Network', () => { }); }); - describe('#getPeerMap', () => { - it('should return the peer map', () => { - const map = new Map(); - network.peerMap = map; - network.getPeerMap().should.equal(map); - }); - }); - describe('#getContract', () => { it('should throw an error if not initialized', () => { network.initialized = false; @@ -272,43 +263,10 @@ describe('Network', () => { }); }); - describe('eventHandlerManager', () => { - describe('#createTxEventHandler', () => { - const txId = 'TRANSACTION_ID'; - - async function initNetwork() { - sinon.stub(network, '_initializeInternalChannel').returns(); - const peersByMspId = new Map(); - peersByMspId.set(mspId, [ mockPeer1 ]); - sinon.stub(network, '_mapPeersToMSPid').returns(peersByMspId); - await network._initialize(); - } - - it('return an event handler object if event strategy set', async () => { - await initNetwork(); - const eventHandler = network.eventHandlerManager.createTxEventHandler(txId); - eventHandler.should.be.an('Object'); - }); - - it('use commitTimeout option from gateway as timeout option for event handler', async () => { - await initNetwork(); - const timeout = mockGateway.getOptions().eventHandlerOptions.commitTimeout; - const eventHandler = network.eventHandlerManager.createTxEventHandler(txId); - eventHandler.options.commitTimeout.should.equal(timeout); - }); - - it('return null if no event strategy set', async () => { - mockGateway.getOptions.returns({ - useDiscovery: false, - eventHandlerOptions: { - commitTimeout: 300, - strategy: null - } - }); - network = new Network(mockGateway, mockChannel); - await initNetwork(); - should.equal(network.eventHandlerManager, null); - }); + describe('#getEventHubFactory', () => { + it('Returns an EventHubFactory', () => { + const result = network.getEventHubFactory(); + result.should.be.an.instanceOf(EventHubFactory); }); }); }); diff --git a/test/integration/network-e2e/invoke.js b/test/integration/network-e2e/invoke.js index d47ed7c52f..86a3d23a16 100644 --- a/test/integration/network-e2e/invoke.js +++ b/test/integration/network-e2e/invoke.js @@ -12,6 +12,9 @@ const tape = require('tape'); const _test = require('tape-promise').default; const test = _test(tape); const {Gateway, CouchDBWallet, InMemoryWallet, FileSystemWallet, X509WalletMixin, DefaultEventHandlerStrategies} = require('../../../fabric-network/index.js'); +const { sampleEventStrategy, SampleEventHandlerManager } = require('./sample-transaction-event-handler'); // eslint-disable-line no-unused-vars +const { sampleEventStrategy: sampleEventStrategy2 } = require('./sample-transaction-event-handler-2'); +const { sampleEventStrategy: sampleEventStrategy3 } = require('./sample-transaction-event-handler-3'); const fs = require('fs-extra'); const os = require('os'); const path = require('path'); @@ -52,12 +55,12 @@ async function createContract(t, gateway, gatewayOptions) { return contract; } -const getEventHubForOrg = async (gateway, orgMSP) => { - // bit horrible until we provide a proper api to get the underlying event hubs +async function getFirstEventHubForOrg(gateway, orgMSP) { const network = await gateway.getNetwork(channelName); - const orgpeer = network.getPeerMap().get(orgMSP)[0]; - return network.getChannel().getChannelEventHub(orgpeer.getName()); -}; + const channel = network.getChannel(); + const orgPeer = channel.getPeersForOrg(orgMSP)[0]; + return channel.getChannelEventHub(orgPeer.getName()); +} test('\n\n***** Network End-to-end flow: import identity into wallet *****\n\n', async (t) => { await inMemoryIdentitySetup(); @@ -86,11 +89,12 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using }); // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); - // initialize eventFired to 0 - let eventFired = 0; + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; // have to register for all transaction events (a new feature in 1.3) as // there is no way to know what the initial transaction id is @@ -140,11 +144,12 @@ test('\n\n***** Network End-to-end flow: invoke multiple transactions to move mo }); // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); - // initialize eventFired to 0 - let eventFired = 0; + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; // have to register for all transaction events (a new feature in 1.3) as // there is no way to know what the initial transaction id is @@ -220,11 +225,12 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using }); // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); - // initialize eventFired to 0 - let eventFired = 0; + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; // have to register for all transaction events (a new feature in 1.3) as // there is no way to know what the initial transaction id is @@ -272,12 +278,13 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using }); // Obtain an event hub that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - const org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; - // initialize eventFired to 0 - let eventFired = 0; // have to register for all transaction events (a new feature in 1.3) as // there is no way to know what the initial transaction id is org1EventHub.registerTxEvent('all', (txId, code) => { @@ -326,12 +333,13 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using }); // Obtain the event hubs that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); - // initialize eventFired to 0 - let org1EventFired = 0; - let org2EventFired = 0; + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let org1EventFired = -1; + let org2EventFired = -1; org1EventHub.registerTxEvent('all', (txId, code) => { if (code === 'VALID') { org1EventFired++; @@ -387,12 +395,13 @@ test('\n\n***** Network End-to-end flow: invoke transaction to move money using }); // Obtain the event hubs that that will be used by the underlying implementation - org1EventHub = await getEventHubForOrg(gateway, 'Org1MSP'); - org2EventHub = await getEventHubForOrg(gateway, 'Org2MSP'); + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); - // initialize eventFired to 0 - let org1EventFired = 0; - let org2EventFired = 0; + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let org1EventFired = -1; + let org2EventFired = -1; org1EventHub.registerTxEvent('all', (txId, code) => { if (code === 'VALID') { @@ -435,6 +444,170 @@ 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 plug-in event strategy *****\n\n', async (t) => { + const gateway = new Gateway(); + let org1EventHub; + + try { + await inMemoryIdentitySetup(); + await tlsSetup(); + + // const eventHandlerManager = new SampleEventHandlerManager(); // --- With event handler manager + const contract = await createContract(t, gateway, { + wallet: inMemoryWallet, + identity: 'User1@org1.example.com', + clientTlsIdentity: 'tlsId', + eventHandlerOptions: { + strategy: sampleEventStrategy // --- Without event handler manager + // strategy: eventHandlerManager.createTransactionEventHandler // --- With event handler manager + } + }); + + // Obtain an event hub that that will be used by the underlying implementation + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); + + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; + + // have to register for all transaction events (a new feature in 1.3) as + // there is no way to know what the initial transaction id is + org1EventHub.registerTxEvent('all', (txId, code) => { + if (code === 'VALID') { + eventFired++; + } + }, () => {}); + + const response = await contract.submitTransaction('move', 'a', 'b','100'); + + t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); + t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); + const expectedResult = 'move succeed'; + if(response.toString() === expectedResult){ + t.pass('Successfully invoked transaction chaincode on channel'); + } + else { + t.fail('Unexpected response from transaction chaincode: ' + response); + } + } catch(err) { + t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); + } finally { + gateway.disconnect(); + t.false(org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); + } + + t.end(); +}); + +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and plug-in event strategy 2 *****\n\n', async (t) => { + const gateway = new Gateway(); + let org1EventHub; + + try { + await inMemoryIdentitySetup(); + await tlsSetup(); + + const contract = await createContract(t, gateway, { + wallet: inMemoryWallet, + identity: 'User1@org1.example.com', + clientTlsIdentity: 'tlsId', + eventHandlerOptions: { + strategy: sampleEventStrategy2 + } + }); + + // Obtain an event hub that that will be used by the underlying implementation + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); + + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; + + // have to register for all transaction events (a new feature in 1.3) as + // there is no way to know what the initial transaction id is + org1EventHub.registerTxEvent('all', (txId, code) => { + if (code === 'VALID') { + eventFired++; + } + }, () => {}); + + const response = await contract.submitTransaction('move', 'a', 'b','100'); + + t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); + t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); + const expectedResult = 'move succeed'; + if(response.toString() === expectedResult){ + t.pass('Successfully invoked transaction chaincode on channel'); + } + else { + t.fail('Unexpected response from transaction chaincode: ' + response); + } + } catch(err) { + t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); + } finally { + gateway.disconnect(); + t.false(org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); + } + + t.end(); +}); + +test('\n\n***** Network End-to-end flow: invoke transaction to move money using in memory wallet and plug-in event strategy 3 *****\n\n', async (t) => { + const gateway = new Gateway(); + let org1EventHub; + + try { + await inMemoryIdentitySetup(); + await tlsSetup(); + + const contract = await createContract(t, gateway, { + wallet: inMemoryWallet, + identity: 'User1@org1.example.com', + clientTlsIdentity: 'tlsId', + eventHandlerOptions: { + strategy: sampleEventStrategy3 + } + }); + + // Obtain an event hub that that will be used by the underlying implementation + org1EventHub = await getFirstEventHubForOrg(gateway, 'Org1MSP'); + const org2EventHub = await getFirstEventHubForOrg(gateway, 'Org2MSP'); + + // Initialize eventFired to -1 since the event hub connection event will happen during + // the first call to submitTransaction() after the network is created + let eventFired = -1; + + // have to register for all transaction events (a new feature in 1.3) as + // there is no way to know what the initial transaction id is + org1EventHub.registerTxEvent('all', (txId, code) => { + if (code === 'VALID') { + eventFired++; + } + }, () => {}); + + const response = await contract.submitTransaction('move', 'a', 'b','100'); + + t.false(org2EventHub.isconnected(), 'org2 event hub correctly not connected'); + t.equal(eventFired, 1, 'single event for org1 correctly unblocked submitTransaction'); + const expectedResult = 'move succeed'; + if(response.toString() === expectedResult){ + t.pass('Successfully invoked transaction chaincode on channel'); + } + else { + t.fail('Unexpected response from transaction chaincode: ' + response); + } + } catch(err) { + t.fail('Failed to invoke transaction chaincode on channel. ' + err.stack ? err.stack : err); + } finally { + gateway.disconnect(); + t.false(org1EventHub.isconnected(), 'org1 event hub correctly been disconnected'); + } + + t.end(); +}); + test('\n\n***** Network End-to-end flow: handle transaction error *****\n\n', async (t) => { const gateway = new Gateway(); diff --git a/test/integration/network-e2e/sample-transaction-event-handler-2.js b/test/integration/network-e2e/sample-transaction-event-handler-2.js new file mode 100644 index 0000000000..b742e8e7e1 --- /dev/null +++ b/test/integration/network-e2e/sample-transaction-event-handler-2.js @@ -0,0 +1,158 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +//--- Plug-in event handler sample where we provide an event hub factory to help user obtain and connect event hubs + +/** + * Handler that listens for commit events for a specific transaction from a set of event hubs. + * A new instance of this class should be created to handle each transaction as it maintains state + * related to events for a given transaction. + * @class + */ +class SampleTransactionEventHandler { + /** + * Constructor. + * @param {String} transactionId Transaction ID for which events will be received. + * @param {Promise.ChannelEventHub[]} eventHubsPromise Connected event hubs from which events will be received. + * @param {Object} [options] Additional configuration options. + * @param {Number} [options.commitTimeout] Time in seconds to wait for commit events to be reveived. + */ + constructor(transactionId, eventHubsPromise, options) { + this.transactionId = transactionId; + this.eventHubsPromise = eventHubsPromise; + + const defaultOptions = { + commitTimeout: 120 // 2 minutes + }; + this.options = Object.assign(defaultOptions, options); + + this.eventHubs = []; + + this.notificationPromise = new Promise((resolve, reject) => { + this._txResolve = resolve; + this._txReject = reject; + }); + } + + /** + * Called to initiate listening for transaction events. + * @async + * @throws {Error} if not in a state where the handling strategy can be satified and the transaction should + * be aborted. For example, if insufficient event hubs could be connected. + */ + async startListening() { + this.eventHubs = await this._getConnectedEventHubs(); + if (this.eventHubs.length > 0) { + this.eventCounts = { + expected: this.eventHubs.length, + received: 0 + }; + this._registerTxEventListeners(); + this._setListenTimeout(); + } else { + // Assume success if unable to listen for events + this._txResolve(); + } + } + + /** + * Wait until enough events have been received from the event hubs to satisfy the event handling strategy. + * @async + * @throws {Error} if the transaction commit is not successful within the timeout period. + */ + async waitForEvents() { + await this.notificationPromise; + } + + /** + * Cancel listening for events. + */ + cancelListening() { + clearTimeout(this.timeoutHandler); + this.eventHubs.forEach((eventHub) => { + eventHub.unregisterTxEvent(this.transactionId); + }); + } + + async _getConnectedEventHubs() { + const eventHubs = await this.eventHubsPromise; + return eventHubs.filter((eventHub) => eventHub.isconnected()); + } + + _registerTxEventListeners() { + for (const eventHub of this.eventHubs) { + eventHub.registerTxEvent(this.transactionId, + (txId, code) => this._onEvent(eventHub, txId, code), + (err) => this._onError(eventHub, err)); + } + } + + _onEvent(eventHub, txId, code) { + eventHub.unregisterTxEvent(this.transactionId); + if (code !== 'VALID') { + // Peer has rejected the transaction so stop listening with a failure + const message = `Peer ${eventHub.getPeerAddr()} has rejected transaction ${txId} with code ${code}`; + this._fail(new Error(message)); + } else { + // -------------------------------------------------------------- + // Handle processing of successful transaction commit events here + // -------------------------------------------------------------- + this._responseReceived(); + } + } + + _onError(eventHub, err) { // eslint-disable-line no-unused-vars + eventHub.unregisterTxEvent(this.transactionId); + // -------------------------------------------------- + // Handle processing of event hub disconnections here + // -------------------------------------------------- + this._responseReceived(); + } + + /** + * Simple event handling logic that is satisfied once all of the event hubs have either responded with valid + * events or disconnected. + */ + _responseReceived() { + this.eventCounts.received++; + if (this.eventCounts.received === this.eventCounts.expected) { + this._success(); + } + } + + _setListenTimeout() { + if (this.options.commitTimeout > 0) { + return; + } + + this.timeoutHandler = setTimeout(() => { + this._fail(new Error(`Timeout waiting for commit events for transaction ID ${this.transactionId}`)); + }, this.options.commitTimeout * 1000); + } + + _fail(error) { + this.cancelListening(); + this._txReject(error); + } + + _success() { + this.cancelListening(); + this._txResolve(); + } +} + +function createTransactionEventHandler(transactionId, network) { + const channel = network.getChannel(); + const peers = channel.getPeersForOrg(); + const eventHubsPromise = network.getEventHubFactory().getEventHubs(peers); + return new SampleTransactionEventHandler(transactionId, eventHubsPromise); +} + +module.exports = { + sampleEventStrategy: createTransactionEventHandler +}; diff --git a/test/integration/network-e2e/sample-transaction-event-handler-3.js b/test/integration/network-e2e/sample-transaction-event-handler-3.js new file mode 100644 index 0000000000..6638201167 --- /dev/null +++ b/test/integration/network-e2e/sample-transaction-event-handler-3.js @@ -0,0 +1,86 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +//--- Plug-in event handler sample where we provide both an event handler factory and our transaction event handler. +//--- The user just implements a plug-in strategy for the event handler, which exposes an additional API. + +const TransactionEventHandler = require('fabric-network/lib/impl/event/transactioneventhandler'); + +class SampleEventHandlerStrategy { + /** + * Constructor. + * @param {Promise.ChannelEventHub[]} eventHubsPromise Promise to event hubs for which to process events. + */ + constructor(eventHubsPromise) { + this.eventHubsPromise = eventHubsPromise; + } + + /** + * Called by event handler to obtain the event hubs to which it should listen. Gives an opportunity for + * the strategy to store information on the events it expects to receive for later use in event handling. + * @async + * @returns {ChannelEventHubs[]} connected event hubs. + * @throws {Error} if the connected event hubs do not satisfy the strategy. + */ + async getConnectedEventHubs() { + const eventHubs = await this.eventHubsPromise; + const connectedEventHubs = eventHubs.filter((eventHub) => eventHub.isconnected()); + + this.eventCounts = { + expected: connectedEventHubs.length, + received: 0 + }; + + return connectedEventHubs; + } + + /** + * Called when an event is received. + * @param {Function} successFn Callback function to invoke if this event satisfies the strategy. + * @param {Function} failFn Callback function to invoke if this event fails the strategy. + */ + eventReceived(successFn, failFn) { + this._responseReceived(successFn, failFn); + } + + /** + * Called when an error is received. + * @param {Function} successFn Callback function to invoke if this error satisfies the strategy. + * @param {Function} failFn Callback function to invoke if this error fails the strategy. + */ + errorReceived(successFn, failFn) { + this._responseReceived(successFn, failFn); + } + + /** + * Simple event handling logic that is satisfied once all of the event hubs have either responded with valid + * events or disconnected. + * @param {Function} successFn Callback function to invoke if the strategy is successful. + * @param {Function} failFn Callback function to invoke if the strategy fails. + */ + _responseReceived(successFn, failFn) { // eslint-disable-line no-unused-vars + this.eventCounts.received++; + if (this.eventCounts.received === this.eventCounts.expected) { + successFn(); + } + } +} + +function createTransactionEventHandler(transactionId, network) { + const peers = network.getChannel().getPeersForOrg(); + const eventHubsPromise = network.getEventHubFactory().getEventHubs(peers); + const strategy = new SampleEventHandlerStrategy(eventHubsPromise); + const options = { + commitTimeout: 120 + }; + return new TransactionEventHandler(transactionId, strategy, options); +} + +module.exports = { + sampleEventStrategy: createTransactionEventHandler +}; diff --git a/test/integration/network-e2e/sample-transaction-event-handler.js b/test/integration/network-e2e/sample-transaction-event-handler.js new file mode 100644 index 0000000000..10a17c4ae1 --- /dev/null +++ b/test/integration/network-e2e/sample-transaction-event-handler.js @@ -0,0 +1,238 @@ +/** + * Copyright 2018 IBM All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +//--- Plug-in event handler sample where the user takes full responsibility for event hub connection and event handling + +/** + * Handler that listens for commit events for a specific transaction from a set of event hubs. + * A new instance of this class should be created to handle each transaction as it maintains state + * related to events for a given transaction. + * @class + */ +class SampleTransactionEventHandler { + /** + * Constructor. + * @param {String} transactionId Transaction ID for which events will be received. + * @param {Promise.ChannelEventHub[]} eventHubsPromise Connected event hubs from which events will be received. + * @param {Object} [options] Additional configuration options. + * @param {Number} [options.commitTimeout] Time in seconds to wait for commit events to be reveived. + */ + constructor(transactionId, eventHubsPromise, options) { + this.transactionId = transactionId; + this.eventHubsPromise = eventHubsPromise; + + const defaultOptions = { + commitTimeout: 120 // 2 minutes + }; + this.options = Object.assign(defaultOptions, options); + + this.eventHubs = []; + + this.notificationPromise = new Promise((resolve, reject) => { + this._txResolve = resolve; + this._txReject = reject; + }); + } + + /** + * Called to initiate listening for transaction events. + * @async + * @throws {Error} if not in a state where the handling strategy can be satified and the transaction should + * be aborted. For example, if insufficient event hubs could be connected. + */ + async startListening() { + this.eventHubs = await this.eventHubsPromise; + if (this.eventHubs.length > 0) { + this.eventCounts = { + expected: this.eventHubs.length, + received: 0 + }; + this._registerTxEventListeners(); + this._setListenTimeout(); + } else { + // Assume success if unable to listen for events + this._txResolve(); + } + } + + /** + * Wait until enough events have been received from the event hubs to satisfy the event handling strategy. + * @async + * @throws {Error} if the transaction commit is not successful within the timeout period. + */ + async waitForEvents() { + await this.notificationPromise; + } + + /** + * Cancel listening for events. + */ + cancelListening() { + clearTimeout(this.timeoutHandler); + this.eventHubs.forEach((eventHub) => { + eventHub.unregisterTxEvent(this.transactionId); + }); + } + + _registerTxEventListeners() { + for (const eventHub of this.eventHubs) { + eventHub.registerTxEvent(this.transactionId, + (txId, code) => this._onEvent(eventHub, txId, code), + (err) => this._onError(eventHub, err)); + } + } + + _onEvent(eventHub, txId, code) { + eventHub.unregisterTxEvent(this.transactionId); + if (code !== 'VALID') { + // Peer has rejected the transaction so stop listening with a failure + const message = `Peer ${eventHub.getPeerAddr()} has rejected transaction ${txId} with code ${code}`; + this._fail(new Error(message)); + } else { + // -------------------------------------------------------------- + // Handle processing of successful transaction commit events here + // -------------------------------------------------------------- + this._responseReceived(); + } + } + + _onError(eventHub, err) { // eslint-disable-line no-unused-vars + eventHub.unregisterTxEvent(this.transactionId); + // -------------------------------------------------- + // Handle processing of event hub disconnections here + // -------------------------------------------------- + this._responseReceived(); + } + + /** + * Simple event handling logic that is satisfied once all of the event hubs have either responded with valid + * events or disconnected. + */ + _responseReceived() { + this.eventCounts.received++; + if (this.eventCounts.received === this.eventCounts.expected) { + this._success(); + } + } + + _setListenTimeout() { + if (this.options.commitTimeout > 0) { + return; + } + + this.timeoutHandler = setTimeout(() => { + this._fail(new Error(`Timeout waiting for commit events for transaction ID ${this.transactionId}`)); + }, this.options.commitTimeout * 1000); + } + + _fail(error) { + this.cancelListening(); + this._txReject(error); + } + + _success() { + this.cancelListening(); + this._txResolve(); + } +} + +/** + * Factory for obtaining event hubs for peers on a given channel. + * Reuses event hubs cached by the channel; the channel will deal with their cleanup. + * Where possible, ensures that event hubs are connected. + * @class + */ +class SampleEventHubFactory { + /** + * Constructor. + * @param {Channel} channel Channel used to create event hubs. + */ + constructor(channel) { + this.channel = channel; + } + + /** + * Gets event hubs that can be connected for all specified peers. + * @async + * @param {ChannelPeer[]} peers Peers for which event hubs should be connected. + */ + async getConnectedEventHubs(peers) { + const eventHubs = await this.getEventHubs(peers); + return eventHubs.filter((eventHub) => eventHub.isconnected()); + } + + /** + * Gets event hubs for all specified peers. Where possible, the event hubs will be connected. + * @async + * @param {ChannelPeer[]} peers Peers for which event hubs should be connected. + * @returns {ChannelEventHub[]} Event hubs, which may or may not have successfully connected. + */ + async getEventHubs(peers) { + // Get event hubs in parallel as each may take some time + const eventHubPromises = peers.map((peer) => this.getEventHub(peer)); + return Promise.all(eventHubPromises); + } + + /** + * Get the event hub for a specific peer. Where possible, the event hub will be connected. + * @private + * @async + * @param {ChannelPeer} peer Peer for which the event hub should be connected. + * @returns {ChannelEventHub} Event hub, which may or may not have successfully connected. + */ + async getEventHub(peer) { + const eventHub = this.channel.getChannelEventHub(peer.getName()); + if (!eventHub.isconnected()) { + await this.connectEventHub(eventHub); + } + return eventHub; + } + + /** + * Attempt to connect a given event hub. Resolves successfully regardless of whether or the event hub connection + * was successful or failed. + * @private + * @async + * @param {ChannelEventHub} eventHub An event hub. + */ + async connectEventHub(eventHub) { + const connectPromise = new Promise((resolve) => { + const regId = eventHub.registerBlockEvent( + () => { + eventHub.unregisterBlockEvent(regId); + resolve(); + }, + () => { + eventHub.unregisterBlockEvent(regId); + resolve(); + } + ); + }); + eventHub.connect(); + await connectPromise; + } +} + +function createTransactionEventHandler(transactionId, network) { + const channel = network.getChannel(); + const peers = channel.getPeersForOrg(); + const eventHubFactory = new SampleEventHubFactory(channel); + const eventHubsPromise = eventHubFactory.getConnectedEventHubs(peers); + return new SampleTransactionEventHandler(transactionId, eventHubsPromise); +} + +class SampleEventHandlerManager { + createTransactionEventHandler(transactionId, network) { + return createTransactionEventHandler(transactionId, network); + } +} + +module.exports = { + sampleEventStrategy: createTransactionEventHandler, + SampleEventHandlerManager +}; diff --git a/test/typescript/network.ts b/test/typescript/network.ts index e97b723c02..c0d79ddea4 100644 --- a/test/typescript/network.ts +++ b/test/typescript/network.ts @@ -96,8 +96,7 @@ import { const user: User = gateway.getCurrentIdentity(); const opt3: InitOptions = gateway.getOptions(); - const internalChannel: Channel = network.getChannel(); - const peerMap: Map = network.getPeerMap(); + const internalChannel: Channel = network.getChannel(); const deleteDone: Promise = inMemoryWallet.delete('User1@org1.example.com') await deleteDone;