diff --git a/libraries/fabric-shim/lib/handler.js b/libraries/fabric-shim/lib/handler.js index 628ff68a..dc5c8406 100644 --- a/libraries/fabric-shim/lib/handler.js +++ b/libraries/fabric-shim/lib/handler.js @@ -277,6 +277,8 @@ class ChaincodeMessageHandler { constructor(stream, chaincode) { this._stream = stream; this.chaincode = chaincode; + this.usePeerGetMultipleKeys = false; + this.maxSizeGetMultipleKeys = 0; } // this is a long-running method that does not return until @@ -388,6 +390,65 @@ class ChaincodeMessageHandler { return await this._askPeerAndListen(msg, 'GetState'); } + async handleGetMultipleStates(collection, keys, channel_id, txId) { + if (keys.length === 0) { + return []; + } + + const responses = []; + + if (!this.usePeerGetMultipleKeys) { + for (const key of keys) { + const resp = await this.handleGetState(collection, key, channel_id, txId); + responses.push(resp); + } + return responses; + } + + let remainingKeys = [...keys]; + while (remainingKeys.length > this.maxSizeGetMultipleKeys) { + const batch = remainingKeys.slice(0, this.maxSizeGetMultipleKeys); + const resp = await this.handleOneSendGetMultipleStates(collection, batch, channel_id, txId); + responses.push(...resp); + remainingKeys = remainingKeys.slice(this.maxSizeGetMultipleKeys); + } + + if (remainingKeys.length > 0) { + const resp = await this.handleOneSendGetMultipleStates(collection, remainingKeys, channel_id, txId); + responses.push(...resp); + } + + return responses.map(r => (r.length === 0 ? null : r)); + } + + async handleOneSendGetMultipleStates(collection, keys, channel_id, txId) { + const msgPb = new peer.GetStateMultiple(); + msgPb.setCollection(collection); + msgPb.setKeysList(keys); + + const msg = mapToChaincodeMessage({ + type: peer.ChaincodeMessage.Type.GET_STATE_MULTIPLE, + payload: msgPb.serializeBinary(), + txid: txId, + channel_id: channel_id + }); + + logger.debug('handleOneSendGetMultipleStates - keys:', keys); + + const responseMsg = await this._askPeerAndListen(msg, 'GetMultipleStates'); + + if (responseMsg.getType() === peer.ChaincodeMessage.Type.RESPONSE) { + const result = peer.GetStateMultipleResult.deserializeBinary(responseMsg.getPayload()); + return result.getValuesList(); + } + + if (responseMsg.getType() === peer.ChaincodeMessage.Type.ERROR) { + throw new Error(Buffer.from(responseMsg.getPayload()).toString()); + } + + throw new Error(`Unexpected message type ${responseMsg.getType()} received`); + } + async handlePutState(collection, key, value, channel_id, txId) { const msgPb = new peer.PutState(); msgPb.setKey(key); diff --git a/libraries/fabric-shim/lib/stub.js b/libraries/fabric-shim/lib/stub.js index 1dae7847..8fabe209 100644 --- a/libraries/fabric-shim/lib/stub.js +++ b/libraries/fabric-shim/lib/stub.js @@ -477,6 +477,19 @@ class ChaincodeStub { return await this.handler.handleGetState(collection, key, this.channel_id, this.txId); } + /** + * Retrieves the current values of the state variables for the given keys + * @async + * @param {...string} keys State variable keys to retrieve from the state store + * @returns {Promise} Promise for the array of current values of the given keys + */ + async getMultipleStates(...keys) { + logger.debug('getMultipleStates called with keys:%o', keys); + // Access public data by setting the collection to empty string + const collection = ''; + return await this.handler.handleGetMultipleStates(collection, keys, this.channel_id, this.txId); + } + /** * Writes the state variable key of value value * to the state store. If the variable already exists, the value will be diff --git a/libraries/fabric-shim/test/unit/stub.js b/libraries/fabric-shim/test/unit/stub.js index b907fc29..b398c4de 100644 --- a/libraries/fabric-shim/test/unit/stub.js +++ b/libraries/fabric-shim/test/unit/stub.js @@ -612,6 +612,56 @@ describe('Stub', () => { }); }); + describe.only('getMultipleStates', () => { + it('should return handler.handleGetMultipleStates results', async () => { + const handleGetMultipleStatesStub = sinon.stub().resolves(['value1', 'value2', 'value3']); + + const stub = new Stub({ + handleGetMultipleStates: handleGetMultipleStatesStub + }, 'dummyChannelId', 'dummyTxid', chaincodeInput); + + const result = await stub.getMultipleStates('key1', 'key2', 'key3'); + expect(result).to.deep.equal(['value1', 'value2', 'value3']); + + expect(handleGetMultipleStatesStub.calledOnce).to.be.true; + expect(handleGetMultipleStatesStub.firstCall.args).to.deep.equal([ + '', + ['key1', 'key2', 'key3'], + 'dummyChannelId', + 'dummyTxid' + ]); + }); + + it('should return empty array if no keys passed', async () => { + const handleGetMultipleStatesStub = sinon.stub().resolves([]); + + const stub = new Stub({ + handleGetMultipleStates: handleGetMultipleStatesStub + }, 'dummyChannelId', 'dummyTxid', chaincodeInput); + + const result = await stub.getMultipleStates(); + expect(result).to.deep.equal([]); + + expect(handleGetMultipleStatesStub.calledOnce).to.be.true; + expect(handleGetMultipleStatesStub.firstCall.args).to.deep.equal([ + '', + [], + 'dummyChannelId', + 'dummyTxid' + ]); + }); + + it('should throw error if handler rejects', async () => { + const handleGetMultipleStatesStub = sinon.stub().rejects(new Error('Something gone wrong')); + + const stub = new Stub({ + handleGetMultipleStates: handleGetMultipleStatesStub + }, 'dummyChannelId', 'dummyTxid', chaincodeInput); + + await expect(stub.getMultipleStates('key1')).to.be.rejectedWith('Something gone wrong'); + }); + }); + describe('putState', () => { it ('should return handler.handlePutState', async () => { const handlePutStateStub = sinon.stub().resolves('some state');