From ab012696dba64a3550bc3c1bbe6dac6b40ef37d9 Mon Sep 17 00:00:00 2001 From: Matthew B White Date: Mon, 1 Oct 2018 11:20:43 +0100 Subject: [PATCH] [FABN-940] Contract Namespaces Change-Id: I6535d4695466847791458902cf24684d821d0f1b Signed-off-by: Matthew B White --- fabric-network/lib/contract.js | 17 +++++++---- fabric-network/lib/network.js | 12 ++++---- fabric-network/test/contract.js | 53 +++++++++++++++++++++++++++++++++ fabric-network/test/network.js | 17 ++++++++++- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/fabric-network/lib/contract.js b/fabric-network/lib/contract.js index fb80583c03..a907cceb8a 100644 --- a/fabric-network/lib/contract.js +++ b/fabric-network/lib/contract.js @@ -16,7 +16,7 @@ const util = require('util'); */ class Contract { - constructor(channel, chaincodeId, gateway, queryHandler, eventHandlerFactory) { + constructor(channel, chaincodeId, gateway, queryHandler, eventHandlerFactory, namespace='') { logger.debug('in Contract constructor'); this.channel = channel; @@ -24,6 +24,7 @@ class Contract { this.gateway = gateway; this.queryHandler = queryHandler; this.eventHandlerFactory = eventHandlerFactory; + this.namespace = namespace; } /** @@ -87,7 +88,10 @@ class Contract { async submitTransaction(transactionName, ...parameters) { logger.debug('in submitTransaction: ' + transactionName); - this._verifyTransactionDetails('submitTransaction', transactionName, parameters); + // form the transaction name with the namespace + const fullTxName = (this.namespace==='') ? transactionName : `${this.namespace}:${transactionName}`; + + this._verifyTransactionDetails('submitTransaction', fullTxName, parameters); const txId = this.gateway.getClient().newTransactionID(); // createTxEventHandler() will return null if no event handler is requested @@ -97,7 +101,7 @@ class Contract { const request = { chaincodeId: this.chaincodeId, txId, - fcn: transactionName, + fcn: fullTxName, args: parameters }; @@ -192,9 +196,12 @@ class Contract { * @returns {Buffer} Payload response from the transaction function */ async executeTransaction(transactionName, ...parameters) { - this._verifyTransactionDetails('executeTransaction', transactionName, parameters); + + // form the transaction name with the namespace + const fullTxName = (this.namespace==='') ? transactionName : `${this.namespace}:${transactionName}`; + this._verifyTransactionDetails('executeTransaction', fullTxName, parameters); const txId = this.gateway.getClient().newTransactionID(); - const result = await this.queryHandler.queryChaincode(this.chaincodeId, txId, transactionName, parameters); + const result = await this.queryHandler.queryChaincode(this.chaincodeId, txId, fullTxName, parameters); return result ? result : null; } } diff --git a/fabric-network/lib/network.js b/fabric-network/lib/network.js index c65b4d99fb..ac48cb0921 100644 --- a/fabric-network/lib/network.js +++ b/fabric-network/lib/network.js @@ -148,25 +148,27 @@ class Network { /** * Returns an instance of a contract (chaincode) on the current network * @param {string} chaincodeId the chaincode Identifier + * @param {string} [namespace] optional namespace for the contract * @returns {Contract} the contract * @api */ - getContract(chaincodeId) { + getContract(chaincodeId,namespace='') { logger.debug('in getContract'); if (!this.initialized) { throw new Error('Unable to get contract as network has failed to initialize'); } - - let contract = this.contracts.get(chaincodeId); + const key = `${chaincodeId}:${namespace}`; + let contract = this.contracts.get(key); if (!contract) { contract = new Contract( this.channel, chaincodeId, this.gateway, this.queryHandler, - this.eventHandlerManager + this.eventHandlerManager, + namespace ); - this.contracts.set(chaincodeId, contract); + this.contracts.set(key, contract); } return contract; } diff --git a/fabric-network/test/contract.js b/fabric-network/test/contract.js index cf11b4178c..b758021de5 100644 --- a/fabric-network/test/contract.js +++ b/fabric-network/test/contract.js @@ -333,6 +333,40 @@ describe('Contract', () => { .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); + }); + }); + }); describe('#executeTransaction', () => { @@ -360,5 +394,24 @@ describe('Contract', () => { .should.be.rejectedWith(/such error/); }); + + 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'); + + mockQueryHandler.queryChaincode.withArgs('someid', mockTransactionID, 'myfunc', ['arg1', 'arg2']).resolves(); + + await nscontract.executeTransaction('myfunc', 'arg1', 'arg2'); + sinon.assert.calledOnce(mockQueryHandler.queryChaincode); + sinon.assert.calledWith(mockQueryHandler.queryChaincode, + sinon.match.any, + sinon.match.any, + 'my.name.space:myfunc', + sinon.match.any); + }); }); }); diff --git a/fabric-network/test/network.js b/fabric-network/test/network.js index b3d9e6bd03..12345c2b57 100644 --- a/fabric-network/test/network.js +++ b/fabric-network/test/network.js @@ -220,7 +220,7 @@ describe('Network', () => { it('should return a cached contract object', () => { const mockContract = sinon.createStubInstance(Contract); - network.contracts.set('foo', mockContract); + network.contracts.set('foo:', mockContract); network.initialized = true; network.getContract('foo').should.equal(mockContract); }); @@ -231,6 +231,21 @@ describe('Network', () => { contract.should.be.instanceof(Contract); contract.chaincodeId.should.equal('bar'); }); + + it('should return a newly created contract, with namespace', () => { + const mockContract = sinon.createStubInstance(Contract); + network.contracts.set('foo:my.name.space', mockContract); + network.initialized = true; + network.getContract('foo','my.name.space').should.equal(mockContract); + }); + + it('should create a non-existent contract object with namespace', () => { + network.initialized = true; + const contract = network.getContract('bar','my.name.space'); + contract.should.be.instanceof(Contract); + contract.chaincodeId.should.equal('bar'); + contract.namespace.should.equal('my.name.space'); + }); }); describe('#_dispose', () => {