From 19b6086ec74a65c371742118b61d12cb28ecbf46 Mon Sep 17 00:00:00 2001 From: Bret Harrison Date: Mon, 10 Sep 2018 13:55:32 -0400 Subject: [PATCH] FABN-843 NodeSDK - Discovery using collections Allow user to specify collection names along with the chaincode name. Change-Id: I93afd84266c091ca01e23b46b1129bd2a9a15e1f Signed-off-by: Bret Harrison --- fabric-client/lib/Channel.js | 16 ++-- .../lib/impl/DiscoveryEndorsementHandler.js | 2 +- test/integration/discovery.js | 86 +++++++++++++------ test/unit/channel.js | 26 ++++-- 4 files changed, 93 insertions(+), 37 deletions(-) diff --git a/fabric-client/lib/Channel.js b/fabric-client/lib/Channel.js index 09c5f86385..982b3da618 100755 --- a/fabric-client/lib/Channel.js +++ b/fabric-client/lib/Channel.js @@ -565,7 +565,9 @@ const Channel = class { /** * @typedef {Object} DiscoveryChaincodeInterest - * @property {DiscoveryChaincodeCall[]} chaincodes + * @property {DiscoveryChaincodeCall[]} chaincodes The chaincodes names and collections + * that will be sent to the discovery service to calculate an endorsement + * plan. */ /** @@ -1537,15 +1539,16 @@ const Channel = class { } /* internal method - * takes a single string that represents a chaincode name and builds a JSON + * takes a single string that represents a chaincode and optional array of strings + * that represent collections and builds a JSON * object that may be used as input to building of the GRPC objects to send * to the discovery service. */ - _buildDiscoveryInterest(name) { + _buildDiscoveryInterest(name, collections) { logger.debug('_buildDiscoveryInterest - name %s', name); const interest = {}; interest.chaincodes = []; - const chaincodes = this._buildDiscoveryChaincodeCall(name); + const chaincodes = this._buildDiscoveryChaincodeCall(name, collections); interest.chaincodes.push(chaincodes); return interest; @@ -1562,15 +1565,14 @@ const Channel = class { chaincode_call.name = name; if(collection_names) { if(Array.isArray(collection_names)) { - const collection_names = []; + chaincode_call.collection_names = []; collection_names.map(name =>{ if(typeof name === 'string') { - collection_names.push(name); + chaincode_call.collection_names.push(name); } else { throw Error('The collection name must be a string'); } }); - chaincode_call.collection_names = collection_names; } else { throw Error('Collections names must be an array of strings'); } diff --git a/fabric-client/lib/impl/DiscoveryEndorsementHandler.js b/fabric-client/lib/impl/DiscoveryEndorsementHandler.js index 3b970c5ad3..bcfe34cc16 100644 --- a/fabric-client/lib/impl/DiscoveryEndorsementHandler.js +++ b/fabric-client/lib/impl/DiscoveryEndorsementHandler.js @@ -225,7 +225,7 @@ class DiscoveryEndorsementHandler extends api.EndorsementHandler { plan.endorsements[peer_info.name] = {}; plan.endorsements[peer_info.name].endorsement = error; plan.endorsements[peer_info.name].success = false; - logger.error('%s - endorsement failed - %s', method, error.toString()); + logger.warn('%s - endorsement failed - %s', method, error.toString()); } } else { logger.debug('%s - peer %s not assigned to this channel', method, peer_info.name); diff --git a/test/integration/discovery.js b/test/integration/discovery.js index 456765ae68..0cb5c6d581 100644 --- a/test/integration/discovery.js +++ b/test/integration/discovery.js @@ -108,10 +108,10 @@ test('\n\n***** D I S C O V E R Y *****\n\n', async function(t) { t.equals(results.peers_by_org.Org1MSP.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking peer chaincode name'); t.equals(results.peers_by_org.Org1MSP.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); if(results.endorsement_plans[0].groups.G0) { - t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org2.example.com:8051', 'Checking peer endpoint'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking peer ledger_height'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking peer chaincode name'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org1.example.com:7051', 'Checking plan peer endpoint'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking plan peer ledger_height'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking plan peer chaincode name'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking plan peer chaincode version'); t.equals(results.endorsement_plans[0].layouts[0].G0, 1, 'Checking layout quantities_by_group'); } else { t.fail('MISSING group results'); @@ -136,10 +136,10 @@ test('\n\n***** D I S C O V E R Y *****\n\n', async function(t) { t.equals(results.peers_by_org.Org1MSP.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); if(results.endorsement_plans[0].groups.G0) { t.equals(results.endorsement_plans[0].chaincode, first_chaincode_name, 'Checking plan id'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org2.example.com:8051', 'Checking peer endpoint'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking peer ledger_height'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking peer chaincode name'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org1.example.com:7051', 'Checking plan peer endpoint'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking plan peer ledger_height'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking plan peer chaincode name'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking plan peer chaincode version'); t.equals(results.endorsement_plans[0].layouts[0].G0, 1, 'Checking layout quantities_by_group'); } else { t.fail('MISSING group results'); @@ -161,10 +161,10 @@ test('\n\n***** D I S C O V E R Y *****\n\n', async function(t) { t.equals(results.peers_by_org.Org1MSP.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking peer chaincode name'); t.equals(results.peers_by_org.Org1MSP.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); if(results.endorsement_plans[0].groups.G0) { - t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org2.example.com:8051', 'Checking peer endpoint'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking peer ledger_height'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking peer chaincode name'); - t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking peer chaincode version'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].endpoint, 'peer0.org1.example.com:7051', 'Checking plan peer endpoint'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].ledger_height.low, ledger_height, 'Checking plan peer ledger_height'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].name, first_chaincode_name, 'Checking plan peer chaincode name'); + t.equals(results.endorsement_plans[0].groups.G0.peers[0].chaincodes[0].version, first_chaincode_ver, 'Checking plan peer chaincode version'); t.equals(results.endorsement_plans[0].layouts[0].G0, 1, 'Checking layout quantities_by_group'); } else { t.fail('MISSING group results'); @@ -269,6 +269,27 @@ test('\n\n***** D I S C O V E R Y *****\n\n', async function(t) { t.fail('Failed to have received a good chaincode to chaincode endorsement ::'+error); } + const collections_request = { + chaincodeId : first_chaincode_name, + fcn: 'call', + args: [second_chaincode_name, 'move', 'a', 'b','100'], + txId: client_org1.newTransactionID(true), + endorsement_hint: { chaincodes: [ + {name: first_chaincode_name, collection_names: ['detailCol', 'sensitiveCol']}, + {name: second_chaincode_name, collection_names: ['detailCol', 'sensitiveCol']} + ]} + }; + try { + const results = await channel_org1.sendTransactionProposal(collections_request); + if(testUtil.checkGoodResults(t, results)) { + t.pass('Successfully endorsed chaincode to chaincode with collections'); + } else { + t.fail('Failed to endorse using a chaincode to chaincode call with collections'); + } + } catch(error) { + t.fail('Failed to have received a good chaincode to chaincode endorsement with collections::'+error); + } + t.pass('End discovery testing'); t.end(); }); @@ -305,6 +326,19 @@ async function startChaincode(t, client, channel, orderer, peers, chaincode_id, try { const tx_id = client.newTransactionID(true); + const policy = { + identities: [ + { role: { name: 'member', mspId: 'Org1MSP' }}, + { role: { name: 'member', mspId: 'Org2MSP' }} + ], + policy: { + '1-of': [ + { 'signed-by': 0}, + { 'signed-by': 1} + ] + } + }; + // send proposal to endorser const proposal_request = { targets: peers, @@ -318,19 +352,23 @@ async function startChaincode(t, client, channel, orderer, peers, chaincode_id, // 'if signed by org1 admin, then that's the only signature required, // but if that signature is missing, then the policy can also be fulfilled // when members (non-admin) from both orgs signed' - 'endorsement-policy': { - identities: [ - { role: { name: 'member', mspId: 'Org1MSP' }}, - { role: { name: 'member', mspId: 'Org2MSP' }}, - { role: { name: 'admin', mspId: 'Org1MSP' }} - ], - policy: { - '1-of': [ - { 'signed-by': 1} - ] + 'endorsement-policy': policy, + 'collections-config': [ + { + 'name': 'detailCol', + 'policy': policy, + 'requiredPeerCount': 0, + 'maxPeerCount': 1, + 'blockToLive': 100 + }, + { + 'name': 'sensitiveCol', + 'policy': policy, + 'requiredPeerCount': 0, + 'maxPeerCount': 1, + 'blockToLive': 100 } - }, - 'collections-config': null + ] }; const proposal_results = await channel.sendInstantiateProposal(proposal_request, 10*60*1000); diff --git a/test/unit/channel.js b/test/unit/channel.js index f912266a41..baafa39a93 100644 --- a/test/unit/channel.js +++ b/test/unit/channel.js @@ -1239,15 +1239,31 @@ test('\n\n ** Channel Discovery tests **\n\n', async (t) => { added = channel._merge_hints(endorsement_hint_2); t.equal(added,true,'Check that the new endorsement hint will be added'); - const plan_id = JSON.stringify(endorsement_hint_2); - const check_endorsement_hint = channel._discovery_interests.get(plan_id); - t.equals(check_endorsement_hint.chaincodes[0].name, 'somechaincode2', 'checking that the name is correct'); + const plan_id_2 = JSON.stringify(endorsement_hint_2); + const check_endorsement_hint_2 = channel._discovery_interests.get(plan_id_2); + t.equals(check_endorsement_hint_2.chaincodes[0].name, 'somechaincode2', 'checking that the name is correct'); channel._last_discover_timestamp = Date.now(); - channel._discovery_results = {endorsement_plans:[{plan_id: plan_id}]}; + channel._discovery_results = {endorsement_plans:[{plan_id: plan_id_2}]}; const plan = await channel.getEndorsementPlan(endorsement_hint_2); - t.equals(plan.plan_id, plan_id, 'Check the name of endorsement plan retrieved'); + t.equals(plan.plan_id, plan_id_2, 'Check the name of endorsement plan retrieved'); + + const endorsement_hint_3 = channel._buildDiscoveryInterest('somechaincode3', ['collection1', 'collection2', 'collection3']); + added = channel._merge_hints(endorsement_hint_3); + t.equal(added,true,'Check that the new endorsement hint will be added'); + + const plan_id_3 = JSON.stringify(endorsement_hint_3); + const check_endorsement_hint_3 = channel._discovery_interests.get(plan_id_3); + t.equals(check_endorsement_hint_3.chaincodes[0].name, 'somechaincode3', 'checking that the name is correct'); + t.equals(check_endorsement_hint_3.chaincodes[0].collection_names[2], 'collection3', 'checking that the collection is correct'); + + const proto_interest = channel._buildProtoChaincodeInterest(endorsement_hint_3); + const proto_chaincodes = proto_interest.getChaincodes(); + const proto_chaincode = proto_chaincodes[0]; + t.equals(proto_chaincode.getName(), 'somechaincode3', 'Checking the protobuf name of the chaincode'); + const proto_collections = proto_chaincode.getCollectionNames(); + t.equals(proto_collections[2], 'collection3', 'Checking that the collection name is correct'); } catch(error) { t.fail(error);