Skip to content

Commit

Permalink
[FABN-878] query for collection configuration
Browse files Browse the repository at this point in the history
node sdk support for retrieve collections config for an instantiated
chaincode.

add Channel#queryCollectionsConfig()

Change-Id: I4d2cc0cf9ec3c04d63b62d8081b694ad849fb4d6
Signed-off-by: zhaochy <zhaochy_2015@hotmail.com>
  • Loading branch information
zhaochy1990 committed Aug 21, 2018
1 parent f69b76a commit f70b744
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 2 deletions.
3 changes: 3 additions & 0 deletions build/tasks/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#
# SPDX-License-Identifier: Apache-2.0
*/

/* eslint-disable no-console */

'use strict';

const addsrc = require('gulp-add-src');
Expand Down
82 changes: 82 additions & 0 deletions fabric-client/lib/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const _mspPrincipalProto = grpc.load(__dirname + '/protos/msp/msp_principal.prot
const _identityProto = grpc.load(path.join(__dirname, '/protos/msp/identities.proto')).msp;
const _discoveryProto = grpc.load(__dirname + '/protos/discovery/protocol.proto').discovery;
const _gossipProto = grpc.load(__dirname + '/protos/gossip/message.proto').gossip;
const _collectionProto = grpc.load(__dirname + '/protos/common/collection.proto').common;

const ImplicitMetaPolicy_Rule = {0: 'ANY', 1: 'ALL', 2: 'MAJORITY'};

Expand Down Expand Up @@ -1991,6 +1992,58 @@ const Channel = class {
throw new Error('Payload results are missing from the query');
}

async queryCollectionsConfig(options, useAdmin) {
const method = 'queryCollectionsConfig';
logger.debug('%s - start. options:%j, useAdmin:%s', method, options, useAdmin);
if (!options || !options.chaincodeId || typeof options.chaincodeId !== 'string') {
throw new Error('Missing required argument \'options.chaincodeId\' or \'options.chaincodeId\' is not of type string');
}

const targets = this._getTargetForQuery(options.target);
const signer = this._clientContext._getSigningIdentity(useAdmin);
const txId = new TransactionID(signer, useAdmin);

const request = {
targets,
txId,
signer,
chaincodeId: Constants.LSCC,
fcn: 'GetCollectionsConfig',
args: [options.chaincodeId],
};

try {
const [responses] = await Channel.sendTransactionProposal(request, this._name, this._clientContext, null);
if (responses && Array.isArray(responses)) {
if (responses.length > 1) {
throw new Error('Too many results returned');
}
const [response] = responses;
if (response instanceof Error) {
throw response;
}
if (!response.response) {
throw new Error('Didn\'t receive a valid peer response');
}
logger.debug('%s - response status :: %d', method, response.response.status);

if (response.response.status !== 200) {
logger.debug('%s - response:%j', method, response);
if (response.response.message) {
throw new Error(response.response.message);
}
throw new Error('Failed to retrieve collections config from peer');
}
const queryResponse = decodeCollectionsConfig(response.response.payload);
logger.debug('%s - get %s collections for chaincode %s from peer', method, queryResponse.length, options.chaincodeId);
return queryResponse;
}
throw new Error('Failed to retrieve collections config from peer');
} catch (e) {
throw e;
}
}

/**
* @typedef {Object} ChaincodeInstantiateUpgradeRequest
* @property {Peer[]} targets - Optional. An array of endorsing
Expand Down Expand Up @@ -3313,4 +3366,33 @@ function decodeSignaturePolicy(identities) {
return results;
}

function decodeCollectionsConfig(payload) {
const configs = [];
const queryResponse = _collectionProto.CollectionConfigPackage.decode(payload);
queryResponse.config.forEach((config) => {
let collectionConfig = {
type: config.payload,
};
if (config.payload === 'static_collection_config') {
const { static_collection_config } = config;
const { signature_policy } = static_collection_config.member_orgs_policy;
const identities = decodeSignaturePolicy(signature_policy.identities);

// delete member_orgs_policy, and use policy to keep consistency with the format in collections-config.json
delete static_collection_config.member_orgs_policy;
static_collection_config.policy = {
identities: identities.map(i => JSON.parse(i)),
policy: signature_policy.rule.n_out_of,
};

static_collection_config.block_to_live = static_collection_config.block_to_live.toInt();
collectionConfig = Object.assign(collectionConfig, static_collection_config);
} else {
throw new Error(`Do not support collections config of type "${config.payload}"`);
}
configs.push(collectionConfig);
});
return configs;
}

module.exports = Channel;
1 change: 1 addition & 0 deletions test/integration/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ require('./e2e/instantiate-chaincode.js');
require('./e2e/invoke-transaction.js');
require('./e2e/query.js');
require('./e2e/private-data.js');
require('./e2e/getCollectionsConfig');
require('./e2e/upgrade.js');
77 changes: 75 additions & 2 deletions test/integration/e2e/e2eUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ function invokeChaincode(userOrg, version, chaincodeId, t, useStore, fcn, args,
let all_good = true;
for(const i in proposalResponses) {
let one_good = false;
let proposal_response = proposalResponses[i];
const proposal_response = proposalResponses[i];

if (expectedResult instanceof Error) {
t.true((proposal_response instanceof Error), 'proposal response should be an instance of error');
Expand All @@ -591,7 +591,7 @@ function invokeChaincode(userOrg, version, chaincodeId, t, useStore, fcn, args,
}

// check payload
let payload = proposal_response.response.payload.toString();
const payload = proposal_response.response.payload.toString();
// verify payload is equal to expectedResult
if (payload === expectedResult){
t.pass('transaction proposal payloads are valid');
Expand Down Expand Up @@ -914,3 +914,76 @@ function getTargetPeers(channel, targets) {
}
return targetPeers;
}

async function getCollectionsConfig(t, org, chaincodeId, targets) {
init();
const channel_name = Client.getConfigSetting('E2E_CONFIGTX_CHANNEL_NAME', testUtil.END2END.channel);

// this is a transaction, will just use org's identity to
// submit the request. intentionally we are using a different org
// than the one that submitted the "move" transaction, although either org
// should work properly
const client = new Client();
const channel = client.newChannel(channel_name);

const orgName = ORGS[org].name;
const cryptoSuite = Client.newCryptoSuite();
cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({path: testUtil.storePathForOrg(orgName)}));
client.setCryptoSuite(cryptoSuite);
let tlsInfo = null;

try {
const enrollment = await e2eUtils.tlsEnroll(org);
t.pass('Successfully retrieved TLS certificate');
tlsInfo = enrollment;
client.setTlsClientCertAndKey(tlsInfo.certificate, tlsInfo.key);
const store = await Client.newDefaultKeyValueStore({path: testUtil.storePathForOrg(orgName)});
client.setStateStore(store);

const admin = await testUtil.getSubmitter(client, t, org);
await client.setUserContext(admin);
t.pass('Successfully enrolled user \'admin\'');

// set up the channel to use each org's 'peer1' for
// both requests and events
for (const key in ORGS) {
if (ORGS.hasOwnProperty(key) && typeof ORGS[key].peer1 !== 'undefined') {
const data = fs.readFileSync(path.join(__dirname, ORGS[key].peer1['tls_cacerts']));
const peer = client.newPeer(
ORGS[key].peer1.requests,
{
pem: Buffer.from(data).toString(),
'ssl-target-name-override': ORGS[key].peer1['server-hostname']
});
channel.addPeer(peer);
}
}
// send query
const request = {
chaincodeId,
txId: client.newTransactionID(),
};

// find the peers that match the targets
if (targets && targets.length != 0) {
const targetPeers = getTargetPeers(channel, targets);
if (targetPeers.length < targets.length) {
t.fail('Failed to get all peers for targets: ' + targets);
} else {
request.targets = targetPeers;
}
}
try {
const resp = await channel.queryCollectionsConfig(request);
t.pass('Successfully retrieved collections config from peer');
return resp;
} catch (error) {
throw error;
}
} catch (error) {
t.fail(error.message);
throw error;
}
}

module.exports.getCollectionsConfig = getCollectionsConfig;
46 changes: 46 additions & 0 deletions test/integration/e2e/getCollectionsConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright 2018 IBM All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

const tape = require('tape');
const _test = require('tape-promise').default;
const test = _test(tape);

const e2eUtils = require('./e2eUtils.js');
const testUtil = require('../../unit/util.js');

const chaincodeId = testUtil.END2END.chaincodeIdPrivateData;

test('getCollectionsConfig from peer', async (t) => {
const targets = []; // empty array, meaning client will discover the peers
try {
const results = await e2eUtils.getCollectionsConfig(t, 'org1', chaincodeId, targets);
if (results) {
t.pass('Successfully query collections config');
}
else {
t.fail('Failed to query collections config');
t.end();
}
t.equal(results.length, 2, 'should exists two collections');

t.equal(results[0].type, 'static_collection_config');
t.equal(results[0].name, 'detailCol');
t.equal(results[0].required_peer_count, 0);
t.equal(results[0].maximum_peer_count, 1);
t.equal(results[0].block_to_live, 100);
t.deepEqual(results[0].policy.identities, [{ msp_identifier: 'Org1MSP', role: 'MEMBER' }, { msp_identifier: 'Org2MSP', role: 'MEMBER' }]);

t.equal(results[1].type, 'static_collection_config');
t.equal(results[1].name, 'sensitiveCol');
t.equal(results[1].required_peer_count, 0);
t.equal(results[1].maximum_peer_count, 1);
t.equal(results[1].block_to_live, 100);
t.deepEqual(results[1].policy.identities, [{ msp_identifier: 'Org1MSP', role: 'MEMBER' }]);
t.end();
} catch (err) {
t.fail('Failed to query chaincode on the channel. ' + err.stack ? err.stack : err);
}
});

0 comments on commit f70b744

Please sign in to comment.