Skip to content
This repository was archived by the owner on Apr 22, 2025. It is now read-only.

Commit 6eefe64

Browse files
FABN-985: Use request txId in Channel.queryByChaincode
- In Channel.queryByChaincode(), use a txId property on the supplied request if present; otherwise create a new transaction ID as before. - Add some unit tests for queryByChaincode. Change-Id: I221405b1dfb6f20c25eed8e3533a7105af538e0d Signed-off-by: Mark S. Lewis <mark_lewis@uk.ibm.com>
1 parent d999468 commit 6eefe64

File tree

4 files changed

+162
-37
lines changed

4 files changed

+162
-37
lines changed

fabric-client/lib/Channel.js

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,7 +3119,7 @@ const Channel = class {
31193119
* object will be used.
31203120
* @property {string} chaincodeId - Required. The id of the chaincode to process
31213121
* the transaction proposal
3122-
* @property {object} [transientMap] - Optional. Object with String property names
3122+
* @property {object} transientMap - Optional. Object with String property names
31233123
* and Buffer property values that can be used by the chaincode but not
31243124
* saved in the ledger. Data such as cryptographic information for
31253125
* encryption can be passed to the chaincode using this technique. Data
@@ -3131,6 +3131,7 @@ const Channel = class {
31313131
* @property {string[]} args - An array of string arguments specific to the
31323132
* chaincode's 'Invoke' method
31333133
* @property {integer} request_timeout - The timeout value to use for this request
3134+
* @property {TransactionID} txId Optional. Transaction ID to use for the query.
31343135
*/
31353136

31363137
/**
@@ -3142,29 +3143,34 @@ const Channel = class {
31423143
* results in the byte array format and the caller will have to be able to decode
31433144
* these results.
31443145
*
3146+
* If the request contains a <code>txId</code> property, that transaction ID will be used, and its administrative
3147+
* privileges will apply. In this case the <code>useAdmin</code> parameter to this function will be ignored.
3148+
*
31453149
* @param {ChaincodeQueryRequest} request
31463150
* @param {boolean} useAdmin - Optional. Indicates that the admin credentials should be used in making
3147-
* this call
3151+
* this call. Ignored if the <code>request</code> contains a <code>txId</code> property.
31483152
* @returns {Promise} A Promise for an array of byte array results returned from the chaincode
31493153
* on all Endorsing Peers
31503154
* @example
31513155
* <caption>Get the list of query results returned by the chaincode</caption>
3152-
* channel.queryByChaincode(request)
3153-
* .then((response_payloads) => {
3154-
* for(let i = 0; i < response_payloads.length; i++) {
3155-
* console.log(util.format('Query result from peer [%s]: %s', i, response_payloads[i].toString('utf8')));
3156-
* }
3157-
* });
3156+
* const responsePayloads = await channel.queryByChaincode(request);
3157+
* for (let i = 0; i < responsePayloads.length; i++) {
3158+
* console.log(util.format('Query result from peer [%s]: %s', i, responsePayloads[i].toString('utf8')));
3159+
* }
31583160
*/
31593161
async queryByChaincode(request, useAdmin) {
31603162
logger.debug('queryByChaincode - start');
31613163
if (!request) {
31623164
throw new Error('Missing request object for this queryByChaincode call.');
31633165
}
31643166

3167+
if (request.txId) {
3168+
useAdmin = request.txId.isAdmin();
3169+
}
3170+
31653171
const targets = this._getTargets(request.targets, Constants.NetworkConfig.CHAINCODE_QUERY_ROLE);
31663172
const signer = this._clientContext._getSigningIdentity(useAdmin);
3167-
const txId = new TransactionID(signer, useAdmin);
3173+
const txId = request.txId || new TransactionID(signer, useAdmin);
31683174

31693175
// make a new request object so we can add in the txId and not change the user's
31703176
const query_request = {
@@ -3173,37 +3179,39 @@ const Channel = class {
31733179
fcn: request.fcn,
31743180
args: request.args,
31753181
transientMap: request.transientMap,
3176-
txId: txId,
3182+
txId,
31773183
signer: signer
31783184
};
31793185

3180-
let results = await Channel.sendTransactionProposal(query_request, this._name, this._clientContext, request.request_timeout);
3181-
const responses = results[0];
3186+
const proposalResults = await Channel.sendTransactionProposal(query_request, this._name, this._clientContext, request.request_timeout);
3187+
const responses = proposalResults[0];
31823188
logger.debug('queryByChaincode - results received');
3183-
if (responses && Array.isArray(responses)) {
3184-
results = [];
3185-
for (let i = 0; i < responses.length; i++) {
3186-
const response = responses[i];
3187-
if (response instanceof Error) {
3188-
results.push(response);
3189-
} else if (response.response && response.response.payload) {
3190-
if (response.response.status === 200) {
3191-
results.push(response.response.payload);
3189+
3190+
if (!responses || !Array.isArray(responses)) {
3191+
throw new Error('Payload results are missing from the chaincode query');
3192+
}
3193+
3194+
const results = [];
3195+
responses.forEach((response) => {
3196+
if (response instanceof Error) {
3197+
results.push(response);
3198+
} else if (response.response && response.response.payload) {
3199+
if (response.response.status === 200) {
3200+
results.push(response.response.payload);
3201+
} else {
3202+
if (response.response.message) {
3203+
results.push(new Error(response.response.message));
31923204
} else {
3193-
if (response.response.message) {
3194-
results.push(new Error(response.response.message));
3195-
} else {
3196-
results.push(new Error(response));
3197-
}
3205+
results.push(new Error(response));
31983206
}
3199-
} else {
3200-
logger.error('queryByChaincode - unknown or missing results in query ::' + results);
3201-
results.push(new Error(response));
32023207
}
3208+
} else {
3209+
logger.error('queryByChaincode - unknown or missing results in query ::' + results);
3210+
results.push(new Error(response));
32033211
}
3204-
return results;
3205-
}
3206-
throw new Error('Payload results are missing from the chaincode query');
3212+
});
3213+
3214+
return results;
32073215
}
32083216

32093217
/**

fabric-client/test/Channel.js

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const rewire = require('rewire');
2020
const chaiAsPromised = require('chai-as-promised');
2121
chai.use(chaiAsPromised);
2222
const expect = chai.expect;
23+
chai.should();
2324

2425
const Channel = require('fabric-client/lib/Channel');
2526
const ChannelRewire = rewire('fabric-client/lib/Channel');
@@ -89,12 +90,47 @@ describe('Channel', () => {
8990
sinon.restore();
9091
});
9192

93+
/**
94+
* Create a transaction success proposal response.
95+
* @param {Buffer} [payload] Transaction return value.
96+
* @returns {ProposalResponse} protobuff
97+
*/
98+
function createTransactionResponse(payload) {
99+
const proposalResponse = createProposalResponse(payload);
100+
101+
if (payload) {
102+
proposalResponse.response.payload = payload;
103+
}
104+
105+
return proposalResponse;
106+
}
107+
108+
/**
109+
* Create a transaction error proposal response.
110+
* @param {String} [message] Error message.
111+
* @returns {ProposalResponse} protobuff
112+
*/
113+
function createErrorResponse(message) {
114+
const proposalResponse = createProposalResponse(message, 500);
115+
116+
if (typeof message === 'string') {
117+
proposalResponse.response.message = Buffer.from(message);
118+
}
119+
120+
return proposalResponse;
121+
}
122+
92123
/**
93124
* Create a skeleton proposal response object.
94-
* @param {String} results value for the payload.extension.results field of the proposal response
125+
* @param {String} results value for the payload.extension.results fields of the proposal response.
126+
* @param {number} [status=200] status code for the response, where 200 is OK and 400+ is an error.
95127
* @returns {ProposalResponse} protobuff
96128
*/
97-
function createProposalResponse(results) {
129+
function createProposalResponse(results, status = 200) {
130+
if (typeof results !== 'string') {
131+
results = '';
132+
}
133+
98134
const extension = new proposalProto.ChaincodeAction();
99135
extension.response = new responseProto.Response();
100136
extension.results = Buffer.from(results);
@@ -109,7 +145,7 @@ describe('Channel', () => {
109145
endorsement.endorser = identity.toBuffer();
110146

111147
const response = new responseProto.Response();
112-
response.status = 200;
148+
response.status = status;
113149

114150
const proposalResponse = new responseProto.ProposalResponse();
115151
proposalResponse.response = response;
@@ -1190,7 +1226,88 @@ describe('Channel', () => {
11901226

11911227
describe('#buildEnvelope', () => {});
11921228

1193-
describe('#queryByChaincode', () => {});
1229+
describe('#queryByChaincode', () => {
1230+
const peer1Result = 'PEER1_RESULT';
1231+
const peer2Result = 'PEER2_RESULT';
1232+
let request;
1233+
let spySendTransactionProposal;
1234+
1235+
beforeEach(() => {
1236+
sinon.stub(peer1, 'sendProposal').resolves(createTransactionResponse(Buffer.from(peer1Result)));
1237+
sinon.stub(peer2, 'sendProposal').resolves(createTransactionResponse(Buffer.from(peer2Result)));
1238+
1239+
spySendTransactionProposal = sinon.spy(Channel, 'sendTransactionProposal');
1240+
1241+
request = {
1242+
targets: [peer1, peer2],
1243+
chaincodeId: 'chaincodeId',
1244+
fcn: 'fcn',
1245+
args: ['arg1', 'arg2']
1246+
};
1247+
});
1248+
1249+
it('uses supplied transaction ID', async () => {
1250+
const txId = client.newTransactionID();
1251+
request.txId = txId;
1252+
1253+
await channel.queryByChaincode(request);
1254+
1255+
sinon.assert.calledWith(spySendTransactionProposal, sinon.match.has('txId', txId));
1256+
});
1257+
1258+
it('creates a transaction ID if none supplied', async () => {
1259+
await channel.queryByChaincode(request);
1260+
sinon.assert.calledWith(spySendTransactionProposal, sinon.match.has('txId', sinon.match.instanceOf(TransactionID)));
1261+
});
1262+
1263+
it('returns valid peer response payloads', async () => {
1264+
const results = await channel.queryByChaincode(request);
1265+
1266+
const resultStrings = results.map((buffer) => buffer.toString());
1267+
expect(resultStrings).to.have.members([peer1Result, peer2Result]);
1268+
});
1269+
1270+
it('returns error peer response messages', async () => {
1271+
const errorMessage = 'ALL YOUR BASE ARE BELONG TO ME';
1272+
peer1.sendProposal.resolves(createErrorResponse(errorMessage));
1273+
request.targets = [peer1];
1274+
1275+
const results = await channel.queryByChaincode(request);
1276+
1277+
expect(results).to.have.lengthOf(1);
1278+
const result = results[0];
1279+
expect(result).to.be.an.instanceof(Error);
1280+
expect(result.message).to.equal(errorMessage);
1281+
});
1282+
1283+
it('returns error peer response without message', async () => {
1284+
peer1.sendProposal.resolves(createErrorResponse());
1285+
request.targets = [peer1];
1286+
1287+
const results = await channel.queryByChaincode(request);
1288+
1289+
expect(results).to.have.lengthOf(1);
1290+
const result = results[0];
1291+
expect(result).to.be.an.instanceof(Error);
1292+
});
1293+
1294+
it('returns peer invocation failures', async () => {
1295+
const peerError = new Error('peer invocation error');
1296+
peer1.sendProposal.rejects(peerError);
1297+
request.targets = [peer1];
1298+
1299+
const results = await channel.queryByChaincode(request);
1300+
1301+
expect(results).to.have.lengthOf(1);
1302+
const result = results[0];
1303+
expect(result).to.be.an.instanceof(Error);
1304+
expect(result.message).to.equal(peerError.message);
1305+
});
1306+
1307+
it('throws if no request supplied', async () => {
1308+
expect(channel.queryByChaincode()).to.be.rejectedWith('Missing request');
1309+
});
1310+
});
11941311

11951312
describe('#_getTargetForQuery', () => {});
11961313

fabric-client/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ declare namespace Client { // tslint:disable-line:no-namespace
481481
transientMap?: TransientMap;
482482
fcn?: string;
483483
args: string[];
484+
txId?: TransactionId;
484485
}
485486

486487
export interface KeyOpts {

test/integration/e2e/e2eUtils.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -782,7 +782,6 @@ function queryChaincode(org, version, targets, fcn, args, value, chaincodeId, t,
782782
// send query
783783
const request = {
784784
chaincodeId : chaincodeId,
785-
txId: tx_id,
786785
fcn: fcn,
787786
args: args,
788787
request_timeout: 3000

0 commit comments

Comments
 (0)