From 9043fd07af367861c4d743d9ba551491dd473753 Mon Sep 17 00:00:00 2001 From: Dave Kelsey <25582377+davidkel@users.noreply.github.com> Date: Fri, 25 Feb 2022 14:05:26 +0000 Subject: [PATCH] Remove the legacy fabric connectors (#1235) This is mainly a deletion exercise with the following code changes 1. simplication of which connector to load 2. only create a connector instance if required for example flow-only-start and flow-only-end don't need to have a connector created Signed-off-by: D Co-authored-by: D --- .../caliper-core/lib/common/config/Config.js | 1 - .../lib/common/config/default.yaml | 2 - .../lib/manager/caliper-engine.js | 6 +- .../lib/FabricConnectorFactory.js | 73 +- .../caliper-fabric/lib/configValidator.js | 661 --- .../ConnectorConfiguration.js | 1 + .../connector-versions/v1/fabric-gateway.js | 1720 -------- .../lib/connector-versions/v1/fabric.js | 2115 --------- .../connector-versions/v2/fabric-gateway.js | 645 --- .../connector-versions/v2/registrarHelper.js | 236 - packages/caliper-fabric/lib/fabricNetwork.js | 951 ----- .../test/FabricConnectorFactory.js | 72 +- .../caliper-fabric/test/configValidator.js | 3788 ----------------- packages/caliper-fabric/test/fabricNetwork.js | 131 - .../fabric_tests/phase1/networkconfig.yaml | 2 +- .../phase2/networkconfig-legacy.yaml | 162 - .../phase3/networkconfig-legacy.yaml | 158 - .../phase4/networkconfig-legacy.yaml | 150 - .../phase5/networkconfig-legacy.yaml | 150 - .../phase6/networkconfig-legacy.yaml | 150 - .../fabric_tests/phase7/networkconfig.yaml | 2 +- .../fabric_tests/run.sh | 36 +- .../fabric/myWorkspace/ccp-org1.yaml | 79 + .../fabric/myWorkspace/ccp-org2.yaml | 79 + .../fabric/myWorkspace/networkconfig.yaml | 154 +- 25 files changed, 244 insertions(+), 11280 deletions(-) delete mode 100644 packages/caliper-fabric/lib/configValidator.js delete mode 100644 packages/caliper-fabric/lib/connector-versions/v1/fabric-gateway.js delete mode 100644 packages/caliper-fabric/lib/connector-versions/v1/fabric.js delete mode 100644 packages/caliper-fabric/lib/connector-versions/v2/fabric-gateway.js delete mode 100644 packages/caliper-fabric/lib/connector-versions/v2/registrarHelper.js delete mode 100644 packages/caliper-fabric/lib/fabricNetwork.js delete mode 100644 packages/caliper-fabric/test/configValidator.js delete mode 100644 packages/caliper-fabric/test/fabricNetwork.js delete mode 100644 packages/caliper-tests-integration/fabric_tests/phase2/networkconfig-legacy.yaml delete mode 100644 packages/caliper-tests-integration/fabric_tests/phase3/networkconfig-legacy.yaml delete mode 100644 packages/caliper-tests-integration/fabric_tests/phase4/networkconfig-legacy.yaml delete mode 100644 packages/caliper-tests-integration/fabric_tests/phase5/networkconfig-legacy.yaml delete mode 100644 packages/caliper-tests-integration/fabric_tests/phase6/networkconfig-legacy.yaml create mode 100644 packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org1.yaml create mode 100644 packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org2.yaml diff --git a/packages/caliper-core/lib/common/config/Config.js b/packages/caliper-core/lib/common/config/Config.js index 282e4d1f5..5232280ed 100644 --- a/packages/caliper-core/lib/common/config/Config.js +++ b/packages/caliper-core/lib/common/config/Config.js @@ -160,7 +160,6 @@ const keys = { CountQueryAsLoad: 'caliper-fabric-countqueryasload', SkipCreateChannelPrefix: 'caliper-fabric-skipcreatechannel-', Gateway: { - Discovery: 'caliper-fabric-gateway-discovery', Enabled: 'caliper-fabric-gateway-enabled', EventStrategy: 'caliper-fabric-gateway-eventstrategy', LocalHost: 'caliper-fabric-gateway-localhost', diff --git a/packages/caliper-core/lib/common/config/default.yaml b/packages/caliper-core/lib/common/config/default.yaml index a2af74f6d..18728d01f 100644 --- a/packages/caliper-core/lib/common/config/default.yaml +++ b/packages/caliper-core/lib/common/config/default.yaml @@ -226,8 +226,6 @@ caliper: enabled: false # Indicates whether to use the localhost default within the Fabric Gateway API localhost: true - # Indicates whether to use the Fabric discovery mechanism (via Gateway API) - discovery: false # Which event strategy to use eventstrategy: msp_any # Which query strategy to use diff --git a/packages/caliper-core/lib/manager/caliper-engine.js b/packages/caliper-core/lib/manager/caliper-engine.js index 4b2006704..4cf9b447f 100644 --- a/packages/caliper-core/lib/manager/caliper-engine.js +++ b/packages/caliper-core/lib/manager/caliper-engine.js @@ -90,7 +90,6 @@ class CaliperEngine { BenchValidator.validateObject(this.benchmarkConfig); logger.info('Starting benchmark flow'); - let connector = await this.adapterFactory(-1); try { @@ -101,10 +100,13 @@ class CaliperEngine { await this._executeCommand('start', 0); } + let connector; + // Conditional network initialization if (!flowOpts.performInit) { logger.info('Skipping initialization phase due to benchmark flow conditioning'); } else { + connector = connector ? connector : await this.adapterFactory(-1); let initStartTime = Date.now(); try { await connector.init(); @@ -123,6 +125,7 @@ class CaliperEngine { if (!flowOpts.performInstall) { logger.info('Skipping install smart contract phase due to benchmark flow conditioning'); } else { + connector = connector ? connector : await this.adapterFactory(-1); let installStartTime = Date.now(); try { await connector.installSmartContract(); @@ -143,6 +146,7 @@ class CaliperEngine { } else { let numberSet = this.benchmarkConfig.test && this.benchmarkConfig.test.workers && this.benchmarkConfig.test.workers.number; let numberOfWorkers = numberSet ? this.benchmarkConfig.test.workers.number : 1; + connector = connector ? connector : await this.adapterFactory(-1); let workerArguments = await connector.prepareWorkerArguments(numberOfWorkers); const roundOrchestrator = new RoundOrchestrator(this.benchmarkConfig, this.networkConfig, workerArguments); diff --git a/packages/caliper-fabric/lib/FabricConnectorFactory.js b/packages/caliper-fabric/lib/FabricConnectorFactory.js index 2b9986f94..a6b707e8e 100644 --- a/packages/caliper-fabric/lib/FabricConnectorFactory.js +++ b/packages/caliper-fabric/lib/FabricConnectorFactory.js @@ -16,14 +16,9 @@ const { CaliperUtils, ConfigUtil } = require('@hyperledger/caliper-core'); const ConnectorConfigurationFactory = require('./connector-configuration/ConnectorConfigurationFactory'); -const ConfigValidator = require('./configValidator.js'); -const Logger = CaliperUtils.getLogger('fabric-connector'); +const Logger = CaliperUtils.getLogger('FabricConnectorFactory'); const semver = require('semver'); -const LEGACY_V1_NODE_CONNECTOR = './connector-versions/v1/fabric.js'; -const LEGACY_V1_GATEWAY_CONNECTOR = './connector-versions/v1/fabric-gateway.js'; -const LEGACY_V2_GATEWAY_CONNECTOR = './connector-versions/v2/fabric-gateway.js'; - const NEW_V1_NODE_CONNECTOR = './connector-versions/v1/FabricNonGateway.js'; const NEW_V1_GATEWAY_CONNECTOR = './connector-versions/v1/FabricGateway.js'; const NEW_V1_WALLET_FACADE_FACTORY = './connector-versions/v1/WalletFacadeFactory.js'; @@ -35,41 +30,29 @@ const NEW_V2_WALLET_FACADE_FACTORY = './connector-versions/v2/WalletFacadeFactor * @returns {string} version */ const _determineInstalledNodeSDKVersion = () => { - let version; + + // Caliper can only work if you use bind and it will pull in fabric network even for non gateway 1.4 if (CaliperUtils.moduleIsInstalled('fabric-network')) { const packageVersion = require('fabric-network/package').version; - version = semver.coerce(packageVersion); - } else if (CaliperUtils.moduleIsInstalled('fabric-client')) { - const packageVersion = require('fabric-client/package').version; - version = semver.coerce(packageVersion); + return semver.coerce(packageVersion); } else { - const msg = 'Unable to detect required Fabric binding packages'; - throw new Error(msg); + throw new Error('Unable to detect required Fabric binding packages'); } - return version; }; -const _loadAppropriateConnectorClass = (installedNodeSDKVersion, useGateway, useLegacyVersion) => { +const _loadAppropriateConnectorClass = (installedNodeSDKVersion, useGateway) => { let connectorPath; let walletFacadeFactoryPath; if (semver.satisfies(installedNodeSDKVersion, '=1.x')) { if (!useGateway) { - if (useLegacyVersion) { - connectorPath = LEGACY_V1_NODE_CONNECTOR; - } else { - connectorPath = NEW_V1_NODE_CONNECTOR; - walletFacadeFactoryPath = NEW_V1_WALLET_FACADE_FACTORY; - } + connectorPath = NEW_V1_NODE_CONNECTOR; + walletFacadeFactoryPath = NEW_V1_WALLET_FACADE_FACTORY; } else { // gateway with default event handlers appears in SDK > 1.4.2 if (semver.satisfies(installedNodeSDKVersion, '>=1.4.2')) { - if (useLegacyVersion) { - connectorPath = LEGACY_V1_GATEWAY_CONNECTOR; - } else { - connectorPath = NEW_V1_GATEWAY_CONNECTOR; - walletFacadeFactoryPath = NEW_V1_WALLET_FACADE_FACTORY; - } + connectorPath = NEW_V1_GATEWAY_CONNECTOR; + walletFacadeFactoryPath = NEW_V1_WALLET_FACADE_FACTORY; } else { throw new Error('Caliper currently only supports Fabric gateway based operation using Fabric-SDK 1.4.2 and higher. Please retry with a different SDK binding'); } @@ -78,12 +61,8 @@ const _loadAppropriateConnectorClass = (installedNodeSDKVersion, useGateway, use if (!useGateway) { throw new Error(`Caliper currently only supports gateway based operation using the ${installedNodeSDKVersion} Fabric-SDK. Please retry with the gateway flag`); } else { - if (useLegacyVersion) { - connectorPath = LEGACY_V2_GATEWAY_CONNECTOR; - } else { - connectorPath = NEW_V2_GATEWAY_CONNECTOR; - walletFacadeFactoryPath = NEW_V2_WALLET_FACADE_FACTORY; - } + connectorPath = NEW_V2_GATEWAY_CONNECTOR; + walletFacadeFactoryPath = NEW_V2_WALLET_FACADE_FACTORY; } } else { throw new Error(`Installed SDK version ${installedNodeSDKVersion} did not match any compatible Fabric connectors`); @@ -108,35 +87,21 @@ const connectorFactory = async (workerIndex) => { const connectorConfigurationFile = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); const loadedConnectorConfiguration = CaliperUtils.parseYaml(connectorConfigurationFile); - const legacyVersion = loadedConnectorConfiguration.version === '1.0'; + if (loadedConnectorConfiguration.version === '1.0') { + throw new Error('Network configuration version 1.0 is not supported anymore, please use version 2'); + } - if (!legacyVersion && !semver.satisfies(loadedConnectorConfiguration.version, '=2.0')) { + if (!semver.satisfies(loadedConnectorConfiguration.version, '=2.0')) { throw new Error(`Unknown network configuration version ${loadedConnectorConfiguration.version} specified`); } const installedNodeSDKVersion = _determineInstalledNodeSDKVersion(); const useGateway = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.Enabled, false); - const useDiscovery = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.Discovery, false); Logger.info(`Initializing ${useGateway ? 'gateway' : 'standard' } connector compatible with installed SDK: ${installedNodeSDKVersion}`); - const {fabricConnectorClass, walletFacadeFactoryClass} = _loadAppropriateConnectorClass(installedNodeSDKVersion, useGateway, legacyVersion); - - let fabricConnector; - - if (legacyVersion) { - ConfigValidator.validateNetwork(loadedConnectorConfiguration, CaliperUtils.getFlowOptions(), useDiscovery, useGateway); - - fabricConnector = new fabricConnectorClass(loadedConnectorConfiguration, workerIndex, 'fabric'); - if (workerIndex > -1) { - // These connectors must have init called for both masters and workers - // but for masters it will have already been called - await fabricConnector.init(true); - } - } else { - // use new connectors - const connectorConfiguration = await new ConnectorConfigurationFactory().create(connectorConfigurationFile, new walletFacadeFactoryClass()); - fabricConnector = new fabricConnectorClass(connectorConfiguration, workerIndex, 'fabric'); - } + const {fabricConnectorClass, walletFacadeFactoryClass} = _loadAppropriateConnectorClass(installedNodeSDKVersion, useGateway); + const connectorConfiguration = await new ConnectorConfigurationFactory().create(connectorConfigurationFile, new walletFacadeFactoryClass()); + const fabricConnector = new fabricConnectorClass(connectorConfiguration, workerIndex, 'fabric'); return fabricConnector; }; diff --git a/packages/caliper-fabric/lib/configValidator.js b/packages/caliper-fabric/lib/configValidator.js deleted file mode 100644 index c20d8eab0..000000000 --- a/packages/caliper-fabric/lib/configValidator.js +++ /dev/null @@ -1,661 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const CaliperUtils = require('@hyperledger/caliper-core').CaliperUtils; -const Logger = CaliperUtils.getLogger('config-validator'); - -const j = require('@hapi/joi'); - -/** - * Utility class for the declarative validation of Fabric network configuration objects. - */ -class ConfigValidator { - /** - * Validates the entire network configuration object. - * @param {object} config The network configuration object. - * @param {object} flowOptions Contains the flow control options for Caliper. - * @param {boolean} discovery Indicates whether discovery is configured or not. - * @param {boolean} gateway Indicates whether gateway mode is configured or not. - */ - static validateNetwork(config, flowOptions, discovery, gateway) { - Logger.debug('Entering validateNetwork'); - - // Current limitation is that default Caliper transactions do not work with discovery, since full network knowledge is required; it is required to use a gateway - if (discovery && !gateway) { - throw new Error('Use of discovery is only supported through a gateway transaction'); - } - - // Not possible to use discovery to perform admin operations (init/install) since full knowledge is required - if (discovery && (flowOptions.performInit || flowOptions.performInstall)) { - throw new Error('Use of service discovery is only valid with a `caliper-flow-only-test` flag'); - } - - // registrar requirement removed if only-test - let requireRegistrar = 'required'; - if (flowOptions.performTest && (!flowOptions.performInit && !flowOptions.performInstall)) { - requireRegistrar = 'optional'; - } - - let tls; // undefined => we don't know yet - // the TLS setting might not be known after the individual section if they are missing - // the first existing node will determine its value, and after that every node is validated against that value - // see the lines: "tls = ... || nodeUrl.startsWith(...);" - - // can't validate mutual TLS now - ConfigValidator._validateTopLevel(config, flowOptions, discovery, tls); - - // validate CA section - let cas = []; - if (config.certificateAuthorities) { - cas = Object.keys(config.certificateAuthorities); - for (const ca of cas) { - try { - ConfigValidator.validateCertificateAuthority(config.certificateAuthorities[ca], tls, requireRegistrar); - tls = (tls || false) || config.certificateAuthorities[ca].url.startsWith('https://'); - } catch (err) { - throw new Error(`Invalid "${ca}" CA configuration: ${err.message}`); - } - } - } - - // validate orderer section - let orderers = []; - if (config.orderers) { - orderers = Object.keys(config.orderers); - for (const orderer of orderers) { - try { - ConfigValidator.validateOrderer(config.orderers[orderer], tls); - tls = (tls || false) || config.orderers[orderer].url.startsWith('grpcs://'); - } catch (err) { - throw new Error(`Invalid "${orderer}" orderer configuration: ${err.message}`); - } - } - } - - // validate peer section - let peers = []; - if (config.peers) { - let eventUrl; - peers = Object.keys(config.peers); - for (const peer of peers) { - try { - ConfigValidator.validatePeer(config.peers[peer], tls, eventUrl); - tls = (tls || false) || config.peers[peer].url.startsWith('grpcs://'); - eventUrl = !!config.peers[peer].eventUrl; // the first peer will decide it - } catch (err) { - throw new Error(`Invalid "${peer}" peer configuration: ${err.message}`); - } - } - } - - // validate organization section - let orgs = []; - const mspIds = []; - if (config.organizations) { - orgs = Object.keys(config.organizations); - for (const org of orgs) { - try { - ConfigValidator.validateOrganization(config.organizations[org], peers, cas); - mspIds.push(config.organizations[org].mspid); - } catch (err) { - throw new Error(`Invalid "${org}" organization configuration: ${err.message}`); - } - } - } - - // validate organizationWallets section - if (config.organizationWallets) { - try { - ConfigValidator.validateOrganizationWallets(config.organizationWallets, orgs); - } catch (err) { - throw new Error(`Invalid organizationWallets configuration : ${err.message}`); - } - } - - // validate client section - if (config.clients) { - const clients = Object.keys(config.clients); - for (const client of clients) { - try { - const hasOrgWallet = config.hasOwnProperty('organizationWallets') && Object.keys(config.organizationWallets).includes(config.clients[client].client.organization); - ConfigValidator.validateClient(config.clients[client], orgs, hasOrgWallet); - } catch (err) { - throw new Error(`Invalid "${client}" client configuration: ${err.message}`); - } - } - } - - // validate channels section - if (config.channels) { - const channels = Object.keys(config.channels); - const takenContractIds = []; - for (const channel of channels) { - try { - ConfigValidator.validateChannel(config.channels[channel], orderers, peers, mspIds, takenContractIds, flowOptions, discovery); - takenContractIds.push(config.channels[channel].contracts.map(cc => cc.contractID || cc.id)); - } catch (err) { - throw new Error(`Invalid "${channel}" channel configuration: ${err.message}`); - } - } - } - - // now we can validate mutual TLS - ConfigValidator._validateTopLevel(config, flowOptions, discovery, tls); - Logger.debug('Exiting validateNetwork'); - } - - /** - * Validates the top-level properties of the configuration. - * @param {object} config The network configuration object. - * @param {object} flowOptions Contains the flow control options for Caliper. - * @param {boolean} discovery Indicates whether discovery is configured or not. - * @param {boolean} tls Indicates whether TLS is enabled or known at this point. - * @private - */ - static _validateTopLevel(config, flowOptions, discovery, tls) { - Logger.debug(`Entering _validateTopLevel with discovery "${discovery}" and tls "${tls}"`); - // some utility vars for the sake of readability - const onlyScript = !flowOptions.performInit && !flowOptions.performInstall && !flowOptions.performTest; - - // to dynamically call the modifier functions - const scriptModif = onlyScript ? 'optional' : 'required'; - const ordererModif = (onlyScript || discovery) ? 'optional' : 'required'; - - // if server TLS is explicitly disabled, can't enable mutual TLS - const mutualTlsValid = tls === undefined ? [ true, false ] : (tls ? [ true, false ] : [ false ]); - - const schema = j.object().keys({ - // simple attributes - name: j.string().min(1).required(), - version: j.string().valid('1.0').required(), - 'mutual-tls': j.boolean().valid(mutualTlsValid).optional(), - caliper: j.object().keys({ - blockchain: j.string().valid('fabric').required(), - command: j.object().keys({ - start: j.string().min(1).optional(), - end: j.string().min(1).optional() - }).or('start', 'end').optional(), - }).required(), - info: j.object().optional(), - // organizationWallets is an optional array of wallet objects to be used by clients - organizationWallets: j.object().optional(), - // complicated parts with custom keys - clients: j.object()[scriptModif](), // only required for full workflow - channels: j.object()[scriptModif](), // only required for full workflow - organizations: j.object()[scriptModif](), // only required for full workflow - orderers: j.object()[ordererModif](), // only required for full workflow without discovery - peers: j.object()[scriptModif](), // only required for full workflow - certificateAuthorities: j.object().optional() - }); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateNetwork'); - } - - /** - * Validates the given channel configuration object - * @param {object} config The configuration object. - * @param {string[]} validOrderers The array of valid orderer names. - * @param {string[]} validPeers The array of valid peer names. - * @param {string[]} validMspIds The array of valid MSP IDs. - * @param {string[]} takenContractIds The array of invalid/taken contract IDs. - * @param {object} flowOptions Contains the flow control options for Caliper. - * @param {boolean} discovery Indicates whether discovery is configured or not. - */ - static validateChannel(config, validOrderers, validPeers, validMspIds, takenContractIds, flowOptions, discovery) { - Logger.debug('Entering validateChannel'); - // ugly hack, but there are too many declarative conditional modifiers otherwise - const created = typeof config.created === 'boolean' ? config.created : false; - const binary = !!config.configBinary; - const def = !!config.definition; - const ordererModif = discovery ? 'optional' : 'required'; - const peerModif = discovery ? 'optional' : 'required'; - - let binaryModif; - let defModif; - let needXor = false; - let needOptionalXor = false; - - if (!created) { - if (def) { - defModif = 'required'; // definition takes precedence - binaryModif = 'forbidden'; // if it's not specified, this won't matter - } else if (binary) { // && !def - binaryModif = 'required'; - defModif = 'forbidden'; // doesn't matter, it's not specified - } else { - // nothing is specified, so make them optional, but require one - defModif = 'optional'; - binaryModif = 'optional'; - needXor = true; - } - } else { - // nothing is required, but keep the oxor rule if both is specified - defModif = 'optional'; - binaryModif = 'optional'; - needOptionalXor = true; - } - - const createPeersSchema = () => { - const peersSchema = {}; - for (const peer of validPeers) { - peersSchema[peer] = j.object().keys({ - endorsingPeer: j.boolean().optional(), - chaincodeQuery: j.boolean().optional(), - ledgerQuery: j.boolean().optional(), - eventSource: j.boolean().optional(), - }).optional(); - } - - return peersSchema; - }; - - const createEndorsementPolicySchema = () => { - // recursive schema of "X-of" objects - // array element objects either have a "signed-by" key, or a recursive "X-of" - const policySchema = j.array().sparse(false).min(1).items(j.object().min(1) - .pattern(/^signed-by$/, j.number().integer().min(0)) - .pattern(/^[1-9]\d*-of$/, - j.lazy(() => policySchema).description('Policy schema')) - ); - - return j.object().keys({ - identities: j.array().sparse(false).items(j.object().keys({ - role: j.object().keys({ - name: j.string().valid('member', 'admin').required(), - mspId: j.string().valid(validMspIds).required() - }).required() - })).unique().required(), - - // at the top level, allow exactly one "[integer>0]-of" key - // the schema of that top level key will be recursive - policy: j.object().pattern(/^[1-9]\d*-of$/, policySchema).length(1).required() - }); - }; - - const contractIdComparator = (a, b) => { - if (a.contractID) { - if (b.contractID) { - return a.contractID === b.contractID; - } - - return a.contractID === b.id; - } else { - if (b.contractID) { - return a.id === b.contractID; - } - - return a.id === b.id; - } - }; - - const collectionsConfigObjectSchema = j.array().sparse(false).min(1).items(j.object().keys({ - name: j.string().min(1).required(), - policy: createEndorsementPolicySchema().required(), - requiredPeerCount: j.number().integer().min(0).max(j.ref('maxPeerCount')).required(), - maxPeerCount: j.number().integer().min(j.ref('requiredPeerCount')).required(), - blockToLive: j.number().integer().min(0).required() - })).unique('name'); - - let schema = j.object().keys({ - created: j.boolean().optional(), - - configBinary: j.string().min(1)[binaryModif](), - - definition: j.object().keys({ - capabilities: j.array().sparse(false).required(), - consortium: j.string().min(1).required(), - msps: j.array().sparse(false).items(j.string().valid(validMspIds)).unique().required(), - version: j.number().integer().min(0).required() - })[defModif](), - - orderers: j.array().sparse(false).items(j.string().valid(validOrderers)).unique()[ordererModif](), - peers: j.object().keys(createPeersSchema())[peerModif](), - - // leave this embedded, so the validation error messages are more meaningful - contracts: j.array().sparse(false).items(j.object().keys({ - id: j.string().min(1).required(), - version: j.string().min(1).required(), - contractID: j.string().min(1).disallow(takenContractIds).optional(), - - language: j.string().valid('golang', 'node', 'java').optional(), - path: j.string().min(1).optional(), - metadataPath: j.string().min(1).optional(), - init: j.array().sparse(false).items(j.string()).optional(), - function: j.string().optional(), - // every key must be a string - initTransientMap: j.object().pattern(j.string(), j.string()).optional(), - - 'collections-config': j.alternatives().try(j.string().min(1), collectionsConfigObjectSchema).optional(), - - 'endorsement-policy': createEndorsementPolicySchema().optional(), - targetPeers: j.array().sparse(false).min(1).unique().items(j.string().valid(validPeers)).optional() - }) // constraints for the contract properties - .with('metadataPath', 'path') // if metadataPath is provided, installation needs the path - .with('path', 'language') // if path is provided, installation needs the language - // the following properties indicate instantiation, which needs the language property - .with('init', 'language') - .with('function', 'language') - .with('initTransientMap', 'language') - .with('collections-config', 'language') - .with('endorsement-policy', 'language') - ).unique(contractIdComparator).required() // for the contracts collection - }); - - if (needXor) { - schema = schema.xor('configBinary', 'definition'); - } else if (needOptionalXor) { - schema = schema.oxor('configBinary', 'definition'); - } - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateChannel'); - } - - /** - * Validates the given CA configuration object. - * @param {object} config The configuration object. - * @param {boolean} tls Indicates whether TLS is enabled or known at this point. - * @param {string} requireRegistrar Indicates whether a registrar is optional or required. - */ - static validateCertificateAuthority(config, tls, requireRegistrar) { - Logger.debug('Entering validateCertificateAuthority'); - const urlRegex = tls === undefined ? /^(https|http):\/\// : (tls ? /^https:\/\// : /^http:\/\//); - - const schema = j.object().keys({ - caName: j.string().optional(), - url: j.string().uri().regex(urlRegex).required(), - - httpOptions: j.object().optional(), - - // required when using https - tlsCACerts: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path').when('url', { - is: j.string().regex(/^https:\/\//), - then: j.required(), - otherwise: j.forbidden() - }), - - registrar: j.array().items(j.object().keys({ - enrollId: j.string().min(1).required(), - enrollSecret: j.string().min(1).required() - })).min(1).sparse(false).unique('enrollId')[requireRegistrar]() - }); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateCertificateAuthority'); - } - - /** - * Validates the given peer configuration object. - * @param {object} config The configuration object. - * @param {boolean} tls Indicates whether TLS is enabled or known at this point. - * @param {boolean} eventUrl Indicates whether other peers specified event URLs or not. - */ - static validatePeer(config, tls, eventUrl) { - Logger.debug(`Entering validatePeer with tls "${tls}" and eventUrl "${eventUrl}"`); - const urlRegex = tls === undefined ? /^(grpcs|grpc):\/\// : (tls ? /^grpcs:\/\// : /^grpc:\/\//); - const eventModif = eventUrl === undefined ? 'optional' : (eventUrl ? 'required' : 'forbidden'); - - const schema = j.object().keys({ - url: j.string().uri().regex(urlRegex).required(), - // match the protocol of the base "url" - // NOTE: Fabric v1.0.0 can only be detected through the presence of "eventUrl" - eventUrl: j.string().uri().when('url', { - is: j.string().regex(/^grpcs:\/\//), - then: j.string().regex(/^grpcs:\/\//), - otherwise: j.string().regex(/^grpc:\/\//) - })[eventModif](), - grpcOptions: j.object().optional(), - - // required when using https - tlsCACerts: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path').when('url', { - is: j.string().regex(/^grpcs:\/\//), - then: j.required(), - otherwise: j.forbidden() - }) - }); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validatePeer'); - } - - /** - * Validates the given orderer configuration object. - * @param {object} config The configuration object. - * @param {boolean} tls Indicates whether TLS is enabled or known at this point. - */ - static validateOrderer(config, tls) { - Logger.debug(`Entering validateOrderer with tls "${tls}"`); - const urlRegex = tls === undefined ? /^(grpcs|grpc):\/\// : (tls ? /^grpcs:\/\// : /^grpc:\/\//); - - const schema = j.object().keys({ - url: j.string().uri().regex(urlRegex).required(), - grpcOptions: j.object().optional(), - - // required when using https - tlsCACerts: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path').when('url', { - is: j.string().regex(/^grpcs:\/\//), - then: j.required(), - otherwise: j.forbidden() - }) - }); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateOrderer'); - } - - /** - * Validates the given organization configuration object. - * @param {object} config The configuration object. - * @param {string[]} validPeers The array of valid peer names. - * @param {string[]} validCAs The array of valid CA names. - */ - static validateOrganization(config, validPeers, validCAs) { - Logger.debug('Entering validateOrganization'); - const schema = j.object().keys({ - mspid: j.string().min(1).required(), - // optional: to include orderer admin clients, and orderer org must be added, which doesn't have peers - peers: j.array().items(j.string().valid(validPeers)) - .min(1).sparse(false).unique().optional(), - certificateAuthorities: j.array().items(j.string().valid(validCAs)) - .min(1).sparse(false).unique().optional(), - - // admin client for orgs are optional - adminPrivateKey: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path').optional(), - - // admin client for orgs are optional - signedCert: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path').optional(), - }).and('adminPrivateKey', 'signedCert'); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateOrganization'); - } - - /** - * Validates the given organization wallet configuration object. - * @param {object} config The configuration object. - * @param {string[]} validOrgs The valid organizations - */ - static validateOrganizationWallets(config, validOrgs) { - Logger.debug('Entering validateOrganizationWallets'); - - const options = { - abortEarly: false, - allowUnknown: false - }; - - const organizationWallets = Object.keys(config); - for (const organizationWallet of organizationWallets) { - // Valid key - let schema = j.string().valid(validOrgs); - let result = j.validate(organizationWallet, schema, options); - if (result.error) { - throw result.error; - } - - // Valid object - const walletObject = config[organizationWallet]; - schema = j.object().keys({ - path: j.string().min(1).required(), - }); - - result = j.validate(walletObject, schema, options); - if (result.error) { - throw result.error; - } - } - - Logger.debug('Exiting validateOrganizationWallets'); - } - - /** - * Validates the given client configuration object. - * @param {object} config The configuration object. - * @param {string[]} validOrgs The array of valid organization names. - * @param {boolean} hasOrgWallet flag for presence of wallet for client organization - */ - static validateClient(config, validOrgs, hasOrgWallet) { - Logger.debug(`Entering validateClient with validOrgs ${JSON.stringify(validOrgs)} and hasOrgWallet "${hasOrgWallet}"`); - const walletModif = hasOrgWallet ? 'forbidden' : 'optional'; - const credModif = hasOrgWallet ? 'forbidden' : 'required'; - - let clientSchema = j.object().keys({ - organization: j.string().valid(validOrgs).required(), - // this part is implementation-specific - credentialStore: j.object().keys({ - path: j.string().min(1).required(), - cryptoStore: j.object().keys({ - path: j.string().min(1).required(), - }).required(), - })[credModif](), - - clientPrivateKey: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path')[walletModif](), - - clientSignedCert: j.object().keys({ - pem: j.string().min(1).optional(), - path: j.string().min(1).optional() - }).xor('pem', 'path')[walletModif](), - - affiliation: j.string().min(1)[walletModif](), - attributes: j.array().items(j.object().keys({ - name: j.string().min(1).required(), - value: j.string().required(), - ecert: j.boolean().optional() - })).min(1).sparse(false).unique('name')[walletModif](), - - enrollmentSecret: j.string().min(1)[walletModif](), - - connection: j.object().keys({ - timeout: j.object().keys({ - peer: j.object().keys({ - endorser: j.number().positive().optional(), - eventHub: j.number().positive().optional(), - eventReg: j.number().positive().optional() - }).or('endorser', 'eventHub', 'eventReg').optional(), - orderer: j.number().positive().optional() - }).or('peer', 'orderer').required() - }).optional() - }); - - if (!hasOrgWallet) { - // additional constraints for the different "client init" methods without wallet - // 1) registration and enrollment specified, attributes require affiliation - // 2) static credentials provided, must be set together - // 3) only a single method can be specified (enrollment-only has no additional constraints) - clientSchema = clientSchema - .with('attributes', 'affiliation') // 1) - // static init/loading - .and('clientPrivateKey', 'clientSignedCert') // 2) - .xor('affiliation', 'enrollmentSecret', 'clientSignedCert'); // 3) - } - - const schema = j.object().keys({ - client: clientSchema.required() - }); - - const options = { - abortEarly: false, - allowUnknown: false - }; - const result = j.validate(config, schema, options); - if (result.error) { - throw result.error; - } - Logger.debug('Exiting validateClient'); - } -} - -module.exports = ConfigValidator; diff --git a/packages/caliper-fabric/lib/connector-configuration/ConnectorConfiguration.js b/packages/caliper-fabric/lib/connector-configuration/ConnectorConfiguration.js index 55e49e563..523d58b22 100644 --- a/packages/caliper-fabric/lib/connector-configuration/ConnectorConfiguration.js +++ b/packages/caliper-fabric/lib/connector-configuration/ConnectorConfiguration.js @@ -367,6 +367,7 @@ class ConnectorConfiguration { async _createDefaultInvokerCache() { for (const organization of this.getOrganizations()) { const aliasNames = await this.identityManager.getAliasNamesForOrganization(organization); + if (aliasNames.length > 0) { this.defaultInvokerMap.set(organization, aliasNames[0]); if (!this.defaultInvokerForDefaultOrganization) { diff --git a/packages/caliper-fabric/lib/connector-versions/v1/fabric-gateway.js b/packages/caliper-fabric/lib/connector-versions/v1/fabric-gateway.js deleted file mode 100644 index 50fcb012c..000000000 --- a/packages/caliper-fabric/lib/connector-versions/v1/fabric-gateway.js +++ /dev/null @@ -1,1720 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const FabricClient = require('fabric-client'); -const { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, FileSystemWallet, Gateway, InMemoryWallet, X509WalletMixin } = require('fabric-network'); -const { google, common } = require('fabric-protos'); -const { ConnectorBase, CaliperUtils, TxStatus, ConfigUtil } = require('@hyperledger/caliper-core'); -const FabricNetwork = require('../../fabricNetwork.js'); - -const fs = require('fs'); -const semver = require('semver'); - -const logger = CaliperUtils.getLogger('connectors/v1/fabric-gateway'); - -const EventStrategies = { - msp_all : DefaultEventHandlerStrategies.MSPID_SCOPE_ALLFORTX, - msp_any : DefaultEventHandlerStrategies.MSPID_SCOPE_ANYFORTX, - network_all : DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX, - network_any : DefaultEventHandlerStrategies.NETWORK_SCOPE_ANYFORTX, -}; - -const QueryStrategies = { - msp_single : DefaultQueryHandlerStrategies.MSPID_SCOPE_SINGLE, - msp_round_robin : DefaultQueryHandlerStrategies.MSPID_SCOPE_ROUND_ROBIN, -}; - -////////////////////// -// TYPE DEFINITIONS // -////////////////////// - -/** - * @typedef {Object} ContractInvokeSettings - * - * @property {string} contractId Required. The name/ID of the contract whose function - * should be invoked. - * @property {string} contractVersion Required. The version of the contract whose function - * should be invoked. - * @property {string} contractFunction Required. The name of the function that should be - * invoked in the contract. - * @property {string[]} [contractArguments] Optional. The list of {string} arguments that should - * be passed to the contract. - * @property {Map} [transientMap] Optional. The transient map that should be - * passed to the contract. - * @property {string} invokerIdentity Required. The name of the client who should invoke the - * contract. If an admin is needed, use the organization name prefixed with a # symbol. - * @property {string} channel Required. The name of the channel whose contract should be invoked. - * @property {string[]} [targetPeers] Optional. An array of endorsing - * peer names as the targets of the invoke. When this - * parameter is omitted the target list will include the endorsing peers assigned - * to the target contract, or if it is also omitted, to the channel. - * @property {string[]} [targetOrganizations] Optional. An array of endorsing - * organizations as the targets of the invoke. If both targetPeers and targetOrganizations - * are specified then targetPeers will take precedence - * @property {string} [orderer] Optional. The name of the orderer to whom the request should - * be submitted. If omitted, then the first orderer node of the channel will be used. - */ - -/** - * @typedef {Object} ContractQuerySettings - * - * @property {string} contractId Required. The name/ID of the contract whose function - * should be invoked. - * @property {string} contractVersion Required. The version of the contract whose function - * should be invoked. - * @property {string} contractFunction Required. The name of the function that should be - * invoked in the contract. - * @property {string[]} [contractArguments] Optional. The list of {string} arguments that should - * be passed to the contract. - * @property {Map} [transientMap] Optional. The transient map that should be - * passed to the contract. - * @property {string} invokerIdentity Required. The name of the client who should invoke the - * contract. If an admin is needed, use the organization name prefixed with a # symbol. - * @property {string} channel Required. The name of the channel whose contract should be invoked. - * @property {boolean} [countAsLoad] Optional. Indicates whether to count this query as workload. - */ - -///////////////////////////// -// END OF TYPE DEFINITIONS // -///////////////////////////// - -/** - * Extends {BlockchainConnector} for a Fabric backend, utilizing the SDK Common Connection Profile. - * - * @property {Version} version Contains the version information about the used Fabric SDK. - * @property {Map} clientProfiles Contains the initialized and user-specific SDK client profiles - * for each defined user. Maps the custom user names to the Client instances. - * @property {Map} adminProfiles Contains the initialized and admin-specific SDK client profiles - * for each defined admin. Maps the custom organization names to the Client instances - * (since only one admin per org is supported). - * @property {Map} registrarProfiles Contains the initialized and registrar-specific SDK client - * profiles for each defined registrar. Maps the custom organization names to the Client instances - * (since only one registrar per org is supported). - * @property {EventSource[]} eventSources Collection of potential event sources to listen to for transaction confirmation events. - * @property {number} workerIndex The index of the client process using the adapter that is set in the constructor - * @property {number} txIndex A counter for keeping track of the index of the currently submitted transaction. - * @property {FabricNetwork} networkUtil Utility object containing easy-to-query information about the topology - * and settings of the network. - * @property {Map>>} randomTargetPeerCache Contains the target peers of contracts - * grouped by channels and organizations: Channel -> Contract -> Org -> Peers - * @property {Map} channelEventSourcesCache Contains the list of event sources for every channel. - * @property {Map} randomTargetOrdererCache Contains the list of target orderers of channels. - * @property {string} defaultInvoker The name of the client to use if an invoker is not specified. - * @property {number} configSmallestTimeout The timeout value to use when the user-provided timeout is too small. - * @property {number} configSleepAfterCreateChannel The sleep duration in milliseconds after creating the channels. - * @property {number} configSleepAfterJoinChannel The sleep duration in milliseconds after joining the channels. - * @property {number} configSleepAfterInstantiateContract The sleep duration in milliseconds after instantiating the contracts. - * @property {boolean} configVerifyProposalResponse Indicates whether to verify the proposal responses of the endorsers. - * @property {boolean} configVerifyReadWriteSets Indicates whether to verify the matching of the returned read-write sets. - * @property {number} configLatencyThreshold The network latency threshold to use for calculating the final commit time of transactions. - * @property {boolean} configOverwriteGopath Indicates whether GOPATH should be set to the Caliper root directory. - * @property {number} configContractInstantiateTimeout The timeout in milliseconds for the contract instantiation endorsement. - * @property {number} configContractInstantiateEventTimeout The timeout in milliseconds for receiving the contract instantiation event. - * @property {number} configDefaultTimeout The default timeout in milliseconds to use for invoke/query transactions. - * @property {boolean} configCountQueryAsLoad Indicates whether queries should be counted as workload. - * @property {boolean} configLocalHost Indicates whether to use the localhost default within the Fabric Gateway API - * @property {boolean} configDiscovery Indicates whether to use discovery within the Fabric Gateway API - */ -class LegacyV1FabricGateway extends ConnectorBase { - /** - * Initializes the Fabric adapter. - * @param {object} networkObject The parsed network configuration. - * @param {number} workerIndex the worker index - * @param {string} bcType The target SUT type - */ - constructor(networkObject, workerIndex, bcType) { - super(workerIndex, bcType); - this.version = require('fabric-client/package').version; - - // clone the object to prevent modification by other objects - this.network = CaliperUtils.parseYamlString(CaliperUtils.stringifyYaml(networkObject)); - - this.clientProfiles = new Map(); - this.adminProfiles = new Map(); - this.registrarProfiles = new Map(); - this.txIndex = -1; - this.orgWallets = new Map(); - this.userContracts = new Map(); - this.userGateways = new Map(); - this.peerCache = new Map(); - this.context = undefined; - - // this value is hardcoded, if it's used, that means that the provided timeouts are not sufficient - this.configSmallestTimeout = 1000; - - this.configSleepAfterCreateChannel = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.CreateChannel, 5000); - this.configSleepAfterJoinChannel = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.JoinChannel, 3000); - this.configSleepAfterInstantiateContract = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.InstantiateContract, 5000); - this.configVerifyProposalResponse = ConfigUtil.get(ConfigUtil.keys.Fabric.Verify.ProposalResponse, true); - this.configVerifyReadWriteSets = ConfigUtil.get(ConfigUtil.keys.Fabric.Verify.ReadWriteSets, true); - this.configLatencyThreshold = ConfigUtil.get(ConfigUtil.keys.Fabric.LatencyThreshold, 1.0); - this.configOverwriteGopath = ConfigUtil.get(ConfigUtil.keys.Fabric.OverwriteGopath, true); - this.configContractInstantiateTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.ContractInstantiate, 300000); - this.configContractInstantiateEventTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.ContractInstantiateEvent, 300000); - this.configDefaultTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.InvokeOrQuery, 60); - this.configCountQueryAsLoad = ConfigUtil.get(ConfigUtil.keys.Fabric.CountQueryAsLoad, true); - - // Gateway connector - this.configLocalHost = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.LocalHost, true); - this.configDiscovery = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.Discovery, false); - this.eventStrategy = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.EventStrategy, 'msp_all'); - this.queryStrategy = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.QueryStrategy, 'msp_single'); - - this.networkUtil = new FabricNetwork(this.network); - this.defaultInvoker = Array.from(this.networkUtil.getClients())[0]; - - this._prepareOrgWallets(); - } - - //////////////////////////////// - // INTERNAL UTILITY FUNCTIONS // - //////////////////////////////// - - /** - * Assembles the event sources based on explicitly given target peers. - * @param {string} channel The name of channel containing the target peers. Doesn't matter if peer-level event service is used in compatibility mode. - * @param {string[]} targetPeers The list of peers to connect to. - * @return {EventSource[]} The list of event sources. - * @private - */ - _assembleTargetEventSources(channel, targetPeers) { - const eventSources = []; - - for (const peer of targetPeers) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - const eventHub = admin.getChannel(channel, true).newChannelEventHub(peer); - - eventSources.push({ - channel: [channel], // unused during contract instantiation - peer: peer, - eventHub: eventHub - }); - } - - return eventSources; - } - - /** - * Creates the specified channels if necessary. - * @return {boolean} True, if at least one channel was created. Otherwise, false. - * @private - * @async - */ - async _createChannels() { - const channels = this.networkUtil.getChannels(); - let channelCreated = false; - - for (const channel of channels) { - const channelObject = this.networkUtil.getNetworkObject().channels[channel]; - - if (CaliperUtils.checkProperty(channelObject, 'created') && channelObject.created) { - logger.info(`Channel '${channel}' is configured as created, skipping creation`); - continue; - } - - if (ConfigUtil.get(ConfigUtil.keys.Fabric.SkipCreateChannelPrefix + channel, false)) { - logger.info(`Creation of Channel '${channel}' is configured to skip`); - continue; - } - - channelCreated = true; - - let configUpdate; - if (CaliperUtils.checkProperty(channelObject, 'configBinary')) { - logger.info(`Channel '${channel}' definiton being retrieved from file`); - configUpdate = this._getChannelConfigFromFile(channelObject, channel); - } - else { - logger.info(`Channel '${channel}' definiton being generated from description`); - const channelTx = this._createChannelTxEnvelope(channelObject.definition, channel); - const payload = common.Payload.decode(channelTx.getPayload().toBuffer()); - const configtx = common.ConfigUpdateEnvelope.decode(payload.getData().toBuffer()); - configUpdate = configtx.getConfigUpdate().toBuffer(); - } - - // NOTE: without knowing the system channel policies, signing with every org admin is a safe bet - const orgs = this.networkUtil.getOrganizationsOfChannel(channel); - let admin; // declared here to keep the admin of the last org of the channel - const signatures = []; - for (const org of orgs) { - admin = this.adminProfiles.get(org); - try { - signatures.push(admin.signChannelConfig(configUpdate)); - } catch (err) { - throw new Error(`${org}'s admin couldn't sign the configuration update of Channel '${channel}': ${err.message}`); - } - } - - const txId = admin.newTransactionID(true); - const request = { - config: configUpdate, - signatures: signatures, - name: channel, - txId: txId - }; - - try { - /** @link{BroadcastResponse} */ - const broadcastResponse = await admin.createChannel(request); - - CaliperUtils.assertDefined(broadcastResponse, `The returned broadcast response for creating Channel '${channel}' is undefined`); - CaliperUtils.assertProperty(broadcastResponse, 'broadcastResponse', 'status'); - - if (broadcastResponse.status !== 'SUCCESS') { - throw new Error(`Orderer response indicated unsuccessful Channel '${channel}' creation: ${broadcastResponse.status}`); - } - } catch (err) { - throw new Error(`Couldn't create Channel '${channel}': ${err.message}`); - } - - logger.info(`Channel '${channel}' successfully created`); - } - - return channelCreated; - } - - /** - * Creates and sets a User object as the context based on the provided identity information. - * @param {Client} profile The Client object whose user context must be set. - * @param {string} org The name of the user's organization. - * @param {string} userName The name of the user. - * @param {{privateKeyPEM: Buffer, signedCertPEM: Buffer}} cryptoContent The object containing the signing key and cert in PEM format. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @returns {FabricClient.User} The User object created - * @private - * @async - */ - async _createUser(profile, org, userName, cryptoContent, profileName) { - // set the user explicitly based on its crypto materials - // createUser also sets the user context - try { - return await profile.createUser({ - username: userName, - mspid: this.networkUtil.getMspIdOfOrganization(org), - cryptoContent: cryptoContent, - skipPersistence: true - }); - } catch (err) { - throw new Error(`Couldn't create ${profileName || ''} user object: ${err.message}`); - } - } - - /** - * Enrolls the given user through its corresponding CA. - * @param {Client} profile The Client object whose user must be enrolled. - * @param {string} id The enrollment ID. - * @param {string} secret The enrollment secret. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise<{key: ECDSA_KEY, certificate: string}>} The resulting private key and certificate. - * @private - * @async - */ - async _enrollUser(profile, id, secret, profileName) { - // this call will throw an error if the CA configuration is not found - // this error should propagate up - const ca = profile.getCertificateAuthority(); - try { - return await ca.enroll({ - enrollmentID: id, - enrollmentSecret: secret - }); - } catch (err) { - throw new Error(`Couldn't enroll ${profileName || 'user'}: ${err.message}`); - } - } - - /** - * Populate an envelope with a channel creation transaction - * @param {object} channelObject The channel configuration object. - * @param {string} channelName The name of the channel. - * @return {Buffer} The extracted channel configuration bytes. - * @private - */ - _createChannelTxEnvelope(channelObject, channelName) { - // Versioning - const readVersion = 0; - const writeVersion = 0; - const appVersion = 1; - const policyVersion = 0; - - // Build the readSet - const readValues = {}; - readValues.Consortium = new common.ConfigValue(); - - const readAppGroup = {}; - for (const mspId of channelObject.msps) { - readAppGroup[mspId] = new common.ConfigGroup(); - } - const readGroups = {}; - readGroups.Application = new common.ConfigGroup({ groups: readAppGroup }); - - const readSet = new common.ConfigGroup({ version: readVersion, groups: readGroups, values: readValues }); - - // Build the writeSet (based on consortium name and passed Capabiliites) - const modPolicy = 'Admins'; - const writeValues = {}; - - const consortium = new common.Consortium({ name: channelObject.consortium }); - writeValues.Consortium = new common.ConfigValue({ version: writeVersion, value: consortium.toBuffer() }); - - if (channelObject.capabilities) { - const capabilities = this._populateCapabilities(channelObject.capabilities); - writeValues.Capabilities = new common.ConfigValue({ version: writeVersion, value: capabilities.toBuffer(), mod_policy: modPolicy }); - } - - // Write Policy - const writePolicies = this._generateWritePolicy(policyVersion, modPolicy); - - // Write Application Groups - const writeAppGroup = {}; - for (const mspId of channelObject.msps) { - writeAppGroup[mspId] = new common.ConfigGroup(); - } - - const writeGroups = {}; - writeGroups.Application = new common.ConfigGroup({ version: appVersion, groups: writeAppGroup, policies: writePolicies, mod_policy: modPolicy }); - - const writeSet = new common.ConfigGroup({ version: writeVersion, groups: writeGroups, values: writeValues }); - - // Now create the configUpdate and configUpdateEnv - const configUpdate = new common.ConfigUpdate({ channel_id: channelName, read_set: readSet, write_set: writeSet}); - const configUpdateEnv= new common.ConfigUpdateEnvelope({ config_update: configUpdate.toBuffer(), signatures: [] }); - - // Channel header - const channelTimestamp = new google.protobuf.Timestamp({ seconds: Date.now()/1000, nanos: 0 }); // Date.now() is millis since 1970 epoch, we need seconds - const channelEpoch = 0; - const chHeader = new common.ChannelHeader({ type: common.HeaderType.CONFIG_UPDATE, version: channelObject.version, timestamp: channelTimestamp, channel_id: channelName, epoch: channelEpoch }); - - // Common header - const header = new common.Header({ channel_header: chHeader.toBuffer() }); - - // Form the payload header/data - const payload = new common.Payload({ header: header, data: configUpdateEnv.toBuffer() }); - - // Form and return the envelope - const envelope = new common.Envelope({ payload: payload.toBuffer() }); - return envelope; - } - - /** - * Populate a Capabilities protobuf - * @param {Array} applicationCapabilities the application capability keys - * @returns {common.Capabilities} Capabilities in a protobuf - */ - _populateCapabilities(applicationCapabilities) { - const capabilities = {}; - for (const capability of applicationCapabilities) { - capabilities[capability] = new common.Capability(); - } - return new common.Capabilities({ capabilities: capabilities }); - } - - /** - * Form a populated Policy protobuf that contains an ImplicitMetaPolicy - * @param {String} subPolicyName the sub policy name - * @param {common.Policy.PolicyType} rule the rule type - * @returns {common.Policy} the policy protobuf - */ - _makeImplicitMetaPolicy(subPolicyName, rule){ - const metaPolicy = new common.ImplicitMetaPolicy({ sub_policy: subPolicyName, rule: rule }); - const policy= new common.Policy({ type: common.Policy.PolicyType.IMPLICIT_META, value: metaPolicy.toBuffer() }); - return policy; - } - - /** - * Generate a write policy - * @param {number} version the policy version - * @param {string} modPolicy the modification policy - * @returns {Object} an object of Admin/Reader/Writer keys mapping to populated ConfigPolicy protobufs - */ - _generateWritePolicy(version, modPolicy) { - // Write Policy - const writePolicies = {}; - // admins - const adminsPolicy = this._makeImplicitMetaPolicy('Admins', common.ImplicitMetaPolicy.Rule.MAJORITY); // majority - writePolicies.Admins = new common.ConfigPolicy({ version: version, policy: adminsPolicy, mod_policy: modPolicy }); - // Readers - const readersPolicy = this._makeImplicitMetaPolicy('Readers', common.ImplicitMetaPolicy.Rule.ANY); // Any - writePolicies.Readers = new common.ConfigPolicy({ version: version, policy: readersPolicy, mod_policy: modPolicy }); - // Writers - const writersPolicy = this._makeImplicitMetaPolicy('Writers', common.ImplicitMetaPolicy.Rule.ANY); // Any - writePolicies.Writers = new common.ConfigPolicy({ version: version, policy: writersPolicy, mod_policy: modPolicy }); - return writePolicies; - } - - /** - * Extracts the channel configuration from the configured file. - * @param {object} channelObject The channel configuration object. - * @param {string} channelName The name of the channel. - * @return {Buffer} The extracted channel configuration bytes. - * @private - */ - _getChannelConfigFromFile(channelObject, channelName) { - // extracting the config from the binary file - const binaryPath = CaliperUtils.resolvePath(channelObject.configBinary); - let envelopeBytes; - - try { - envelopeBytes = fs.readFileSync(binaryPath); - } catch (err) { - throw new Error(`Couldn't read configuration binary for ${channelName}: ${err.message}`); - } - - try { - return new FabricClient().extractChannelConfig(envelopeBytes); - } catch (err) { - throw new Error(`Couldn't extract configuration object for ${channelName}: ${err.message}`); - } - } - - /** - * Checks whether the user materials are already persisted in the local store and sets the user context if found. - * @param {Client} profile The Client object to fill with the User instance. - * @param {string} userName The name of the user to check and load. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise} The loaded User object - * @private - * @async - */ - async _getUserContext(profile, userName, profileName) { - // Check whether the materials are already saved - // getUserContext automatically sets the user if found - try { - return await profile.getUserContext(userName, true); - } catch (err) { - throw new Error(`Couldn't check whether ${profileName || 'the user'}'s materials are available locally: ${err.message}`); - } - } - - /** - * Initializes the admins of the organizations. These are required for administrative actions such as channel creation and installing contracts. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeAdmins(workerInit) { - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - // Admin names are assumed to be of the form `admin.${org}` - const adminName = `admin.${org}`; - // build the common part of the profile - const adminProfile = await this._prepareClientProfile(org, undefined, `${org}'s admin`); - - // Check for required crypto material, and conditionally create new (admin) user - let cryptoContent; - const usesOrgWallets = this.networkUtil.usesOrganizationWallets(); - if (!usesOrgWallets){ - // Check if the materials already exist locally in file system key-value stores, or retrieve from network configuration - const admin = await this._getUserContext(adminProfile, adminName, `${org}'s admin`); - if (admin) { - this.adminProfiles.set(org, adminProfile); - - if (this.networkUtil.isMutualTlsEnabled()) { - this._setTlsAdminCertAndKey(org); - } - - if (!workerInit) { - logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); - } - - // Persist to InMemory wallet - await this._addToOrgWallet(org, admin.getIdentity()._certificate, admin.getSigningIdentity()._signer._key.toBytes(), adminName); - continue; - } else { - cryptoContent = this.networkUtil.getAdminCryptoContentOfOrganization(org); - } - } else { - // If a file wallet is provided, it is expected that *all* required identities are provided - // Admin is a super-user identity, and is consequently optional - const orgWallet = this.orgWallets.get(org); - const hasAdmin = await orgWallet.exists(adminName); - if (!hasAdmin) { - logger.info(`No ${adminName} found in wallet - unable to perform admin options`); - continue; - } - - logger.info(`Retrieving credentials for ${adminName} from wallet`); - const identity = await orgWallet.export(adminName); - // Identity {type: string, mspId: string, privateKeyPEM: string, signedCertPEM: string} - cryptoContent = { - privateKeyPEM: identity.privateKey, - signedCertPEM: identity.certificate - }; - } - - // Need to create the admin user - const adminUser = await this._createUser(adminProfile, org, adminName, cryptoContent,`${org}'s admin`); - this.adminProfiles.set(org, adminProfile); - - if (this.networkUtil.isMutualTlsEnabled()) { - this._setTlsAdminCertAndKey(org); - } - - if (!usesOrgWallets) { - // Persist to InMemory wallet - await this._addToOrgWallet(org, adminUser.getIdentity()._certificate, adminUser.getSigningIdentity()._signer._key.toBytes(), adminName); - } - - logger.info(`${org}'s admin's materials are successfully loaded`); - } - } - - /** - * Initializes the registrars of the organizations. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeRegistrars(workerInit) { - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - - // providing registrar information is optional and only needed for user registration and enrollment - if (this.networkUtil.usesOrganizationWallets()) { - logger.info('skipping registrar initialization due to presence of organization wallets'); - continue; - } - const registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); - if (!registrarInfo) { - if (!workerInit) { - logger.warn(`${org}'s registrar information not provided.`); - } - continue; - } - - // build the common part of the profile - const registrarProfile = await this._prepareClientProfile(org, undefined, 'registrar'); - // check if the materials already exist locally in the file system key-value stores - const registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); - - if (registrar) { - if (!workerInit) { - logger.warn(`${org}'s registrar's materials found locally in file system key-value stores. Make sure it is the right one!`); - } - this.registrarProfiles.set(org, registrarProfile); - continue; - } - - // set the registrar identity as the current user context - await this._setUserContextByEnrollment(registrarProfile, registrarInfo.enrollId, - registrarInfo.enrollSecret, `${org}'s registrar`); - - this.registrarProfiles.set(org, registrarProfile); - if (!workerInit) { - logger.info(`${org}'s registrar enrolled successfully`); - } - } - } - - /** - * Registers and enrolls the specified users if necessary. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeUsers(workerInit) { - const clients = this.networkUtil.getClients(); - - for (const client of clients) { - const org = this.networkUtil.getOrganizationOfClient(client); - - // If using organization wallets, client must already exist within org wallet. - if (this.networkUtil.usesOrganizationWallets()){ - if (this.orgWallets.has(org)) { - const orgWallet = this.orgWallets.get(org); - const identityExists = await orgWallet.exists(client); - if (!identityExists) { - logger.error(`Identity for client ${client} not present within wallet for organization ${org}; will attempt to register and enrol client`); - } else { - logger.info(`Detected identity for client ${client} within wallet for organization ${org}`); - continue; - } - } else { - logger.error(`No wallet listed for organization ${org}; will attempt to register and enrol client`); - } - } - - // No wallet provided, need to create the identities based on the connection profile - const clientProfile = await this._prepareClientProfile(org, client, client); - this.clientProfiles.set(client, clientProfile); - - // check if the materials already exist locally in the file system key-value stores - let user = await this._getUserContext(clientProfile, client, client); - if (user) { - if (this.networkUtil.isMutualTlsEnabled()) { - // "retrieve" and set the deserialized cert and key - clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); - } - - if (!workerInit) { - logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); - } - - // Add identity to wallet - await this._addToOrgWallet(org, user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes(), client); - continue; - } - - // Need to look at registering and enrolling each named client, using its organization's CA based on network configuration file - const cryptoContent = this.networkUtil.getClientCryptoContent(client); - if (cryptoContent) { - // the client is already enrolled, just create and persist the User object - user = await this._createUser(clientProfile, org, client, cryptoContent, client); - if (this.networkUtil.isMutualTlsEnabled()) { - // the materials are included in the configuration file - const crypto = this.networkUtil.getClientCryptoContent(client); - clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); - } - - if (!workerInit) { - logger.info(`${client}'s materials are successfully loaded`); - } - - // Persist in InMemory wallet if not using file based wallet - await this._addToOrgWallet(org, user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes(), client); - continue; - } - - // The user needs to be enrolled or even registered - - // if the enrollment ID and secret is provided, then enroll the already registered user - const enrollmentSecret = this.networkUtil.getClientEnrollmentSecret(client); - if (enrollmentSecret) { - const enrollment = await this._enrollUser(clientProfile, client, enrollmentSecret, client); - - // create the new user based on the retrieved materials - user = await this._createUser(clientProfile, org, client, - { - privateKeyPEM: enrollment.key.toBytes(), - signedCertPEM: Buffer.from(enrollment.certificate) - }, client); - - if (this.networkUtil.isMutualTlsEnabled()) { - // set the received cert and key for mutual TLS - clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); - } - - if (!workerInit) { - logger.info(`${client} successfully enrolled`); - } - - // Add identity to org wallet - await this._addToOrgWallet(org, user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes(), client); - continue; - } - - // Otherwise, register then enroll the user - let secret; - try { - const registrarProfile = this.registrarProfiles.get(org); - - if (!registrarProfile) { - throw new Error(`Registrar identity is not provided for ${org}`); - } - - const registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); - const registrar = await registrarProfile.getUserContext(registrarInfo.enrollId, true); - // this call will throw an error if the CA configuration is not found - // this error should propagate up - const ca = clientProfile.getCertificateAuthority(); - const userAffiliation = this.networkUtil.getAffiliationOfUser(client); - const affService = ca.newAffiliationService(); - let affiliationExists = false; - try { - await affService.getOne(userAffiliation, registrar); - affiliationExists = true; - } catch (err) { - if (!workerInit) { - logger.info(`${userAffiliation} affiliation doesn't exists`); - } - } - - if (!affiliationExists) { - await affService.create({name: userAffiliation, force: true}, registrar); - if (!workerInit) { - logger.info(`${userAffiliation} affiliation added`); - } - } - - const attributes = this.networkUtil.getAttributesOfUser(client); - attributes.push({name: 'hf.Registrar.Roles', value: 'client'}); - - secret = await ca.register({ - enrollmentID: client, - affiliation: userAffiliation, - role: 'client', - attrs: attributes - }, registrar); - } catch (err) { - throw new Error(`Couldn't register ${client}: ${err.message}`); - } - - if (!workerInit) { - logger.info(`${client} successfully registered`); - } - - const enrollment = await this._enrollUser(clientProfile, client, secret, client); - - // create the new user based on the retrieved materials - user = await this._createUser(clientProfile, org, client, - {privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: Buffer.from(enrollment.certificate)}, client); - - if (this.networkUtil.isMutualTlsEnabled()) { - // set the received cert and key for mutual TLS - clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); - //this._setTlsClientCertAndKey(client); - } - - if (!workerInit) { - logger.info(`${client} successfully enrolled`); - } - - // Add identity to org wallet - await this._addToOrgWallet(org, user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes(), client); - } - } - - /** - * Add a user to the wallet under a provided name - * @param {string} org, the organization name - * @param {string} certificate the user certificate - * @param {string} key the private key matching the certificate - * @param {string} name the name to store the User as within the wallet - * @async - */ - async _addToOrgWallet(org, certificate, key, name) { - const identity = X509WalletMixin.createIdentity(this.networkUtil.getMspIdOfOrganization(org), certificate, key); - logger.info(`Adding identity for ${name} to wallet for organization ${org}`); - const orgWallet = this.orgWallets.get(org); - await orgWallet.import(name, identity); - logger.info(`Identity ${name} created and imported to wallet for organization ${org}`); - } - - /** - * Extract and persist Contracts from Gateway Networks for identities listed within wallets - * @async - */ - async _initializeContracts() { - // Use organization wallets, but we use gateways associated with client identities to submit transactions - // by creation and use of a userContracts map - - for (const walletOrg of this.orgWallets.keys()) { - logger.info(`Retrieving and persisting contract map for organization ${walletOrg}`); - const orgWallet = this.orgWallets.get(walletOrg); - - // Prepare client contracts based on wallet identities only - const walletInfoList = await orgWallet.list(); - for (const info of walletInfoList) { - logger.info(`Retrieving and persisting contract map for identity ${info.label}`); - // Retrieve - const contractMap = await this._retrieveContractMapForIdentity(info.label, orgWallet); - // Persist - this.userContracts.set(info.label, contractMap); - } - } - } - - /** - * Retrieve all Contracts accessible for the passed unique identity - * @param {string} identity, the unique identity that maps to the client name - * @param {FileSystemWallet | InMemoryWallet} wallet, the wallet that holds the passed identity name - * @returns {Map} A map of all Contracts retrieved from the client Gateway - * @async - */ - async _retrieveContractMapForIdentity(identity, wallet) { - - // Retrieve the gateway for the passed user. The gateway object is persisted for easier cleanup. - // - userName must match that created for wallet userId in init phases - const gateway = await this._retrieveUserGateway(identity, wallet); - this.userGateways.set(identity, gateway); - - // Work on all channels to build a contract map - logger.info(`Generating contract map for user ${identity}`); - const contractMap = new Map(); - const channels = this.networkUtil.getChannels(); - for (const channel of channels) { - // retrieve the channel network - const network = await gateway.getNetwork(channel); - // Work on all contracts/smart contracts in the channel - const contracts = this.networkUtil.getContractsOfChannel(channel); - for (const contract of contracts) { - const networkContract = await network.getContract(contract.id); - contractMap.set(`${channel}_${contract.id}`, networkContract); - } - } - - return contractMap; - } - - /** - * Retrieve a Gateway object for the passed userId - * @param {string} identity string user id to use as the identity - * @param {FileSystemWallet | InMemoryWallet} wallet, the wallet that holds the passed identity name - * @returns {FabricNet.Gateway} a gateway object for the passed user identity - * @async - */ - async _retrieveUserGateway(identity, wallet) { - // Build options for the connection - const opts = { - identity, - wallet, - discovery: { - asLocalhost: this.configLocalHost, - enabled: this.configDiscovery - }, - eventHandlerOptions: { - commitTimeout: this.configDefaultTimeout, - strategy: EventStrategies[this.eventStrategy] - }, - queryHandlerOptions: { - requestTimeout: this.configDefaultTimeout, - strategy: QueryStrategies[this.queryStrategy] - } - }; - - // Optional on mutual auth - if (this.networkUtil.isMutualTlsEnabled()) { - opts.clientTlsIdentity = identity; - } - - // Retrieve gateway using ccp and options - const gateway = new Gateway(); - - logger.info(`Connecting user with identity ${identity} to a Network Gateway`); - await gateway.connect(this.networkUtil.getNetworkObject(), opts); - - // return the gateway object - return gateway; - } - - /** - * Install the specified contracts to their target peers. - * @private - * @async - */ - async _installContracts() { - if (this.configOverwriteGopath) { - process.env.GOPATH = CaliperUtils.resolvePath('.'); - } - - const errors = []; - - const channels = this.networkUtil.getChannels(); - for (const channel of channels) { - logger.info(`Installing contracts for ${channel}...`); - - // proceed cc by cc for the channel - const contractInfos = this.networkUtil.getContractsOfChannel(channel); - for (const contractInfo of contractInfos) { - const ccObject = this.networkUtil.getNetworkObject().channels[channel].contracts.find( - cc => cc.id === contractInfo.id && cc.version === contractInfo.version); - - const targetPeers = this.networkUtil.getTargetPeersOfContractOfChannel(contractInfo, channel); - if (targetPeers.size < 1) { - logger.info(`No target peers are defined for ${contractInfo.id}@${contractInfo.version} on ${channel}, skipping it`); - continue; - } - - // find the peers that don't have the cc installed - const installTargets = []; - - for (const peer of targetPeers) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - try { - /** {@link ChaincodeQueryResponse} */ - const resp = await admin.queryInstalledChaincodes(peer, true); - if (resp.chaincodes.some(cc => cc.name === contractInfo.id && cc.version === contractInfo.version)) { - logger.info(`${contractInfo.id}@${contractInfo.version} is already installed on ${peer}`); - continue; - } - - installTargets.push(peer); - } catch (err) { - errors.push(new Error(`Couldn't query installed contracts on ${peer}: ${err.message}`)); - } - } - - if (errors.length > 0) { - let errorMsg = `Could not query whether ${contractInfo.id}@${contractInfo.version} is installed on some peers of ${channel}:`; - for (const err of errors) { - errorMsg += `\n\t- ${err.message}`; - } - - logger.error(errorMsg); - throw new Error(`Could not query whether ${contractInfo.id}@${contractInfo.version} is installed on some peers of ${channel}`); - } - - // cc is installed on every target peer in the channel - if (installTargets.length < 1) { - continue; - } - - // install contracts org by org - const orgs = this.networkUtil.getOrganizationsOfChannel(channel); - for (const org of orgs) { - const peersOfOrg = this.networkUtil.getPeersOfOrganization(org); - // selecting the target peers for this org - const orgPeerTargets = installTargets.filter(p => peersOfOrg.has(p)); - - // cc is installed on every target peer of the org in the channel - if (orgPeerTargets.length < 1) { - continue; - } - - const admin = this.adminProfiles.get(org); - - const txId = admin.newTransactionID(true); - /** @{ChaincodeInstallRequest} */ - const request = { - targets: orgPeerTargets, - chaincodePath: ccObject.language === 'golang' ? ccObject.path : CaliperUtils.resolvePath(ccObject.path), - chaincodeId: ccObject.id, - chaincodeVersion: ccObject.version, - chaincodeType: ccObject.language, - txId: txId - }; - - // metadata (like CouchDB indices) are only supported since Fabric v1.1 - if (CaliperUtils.checkProperty(ccObject, 'metadataPath')) { - request.metadataPath = CaliperUtils.resolvePath(ccObject.metadataPath); - } - - // install to necessary peers of org and process the results - try { - /** @link{ProposalResponseObject} */ - const propRespObject = await admin.installChaincode(request); - CaliperUtils.assertDefined(propRespObject); - - /** Array of @link{ProposalResponse} objects */ - const proposalResponses = propRespObject[0]; - CaliperUtils.assertDefined(proposalResponses); - - proposalResponses.forEach((propResponse, index) => { - if (propResponse instanceof Error) { - const errMsg = `Install proposal error for ${contractInfo.id}@${contractInfo.version} on ${orgPeerTargets[index]}: ${propResponse.message}`; - errors.push(new Error(errMsg)); - return; - } - - /** @link{ProposalResponse} */ - CaliperUtils.assertProperty(propResponse, 'propResponse', 'response'); - - /** @link{ResponseObject} */ - const response = propResponse.response; - CaliperUtils.assertProperty(response, 'response', 'status'); - - if (response.status !== 200) { - const errMsg = `Unsuccessful install status for ${contractInfo.id}@${contractInfo.version} on ${orgPeerTargets[index]}: ${propResponse.response.message}`; - errors.push(new Error(errMsg)); - } - }); - } catch (err) { - throw new Error(`Couldn't install ${contractInfo.id}@${contractInfo.version} on peers ${orgPeerTargets.toString()}: ${err.message}`); - } - - // there were some install errors, proceed to the other orgs to gather more information - if (errors.length > 0) { - continue; - } - - logger.info(`${contractInfo.id}@${contractInfo.version} successfully installed on ${org}'s peers: ${orgPeerTargets.toString()}`); - } - - if (errors.length > 0) { - let errorMsg = `Could not install ${contractInfo.id}@${contractInfo.version} on some peers of ${channel}:`; - for (const err of errors) { - errorMsg += `\n\t- ${err.message}`; - } - - logger.error(errorMsg); - throw new Error(`Could not install ${contractInfo.id}@${contractInfo.version} on some peers of ${channel}`); - } - } - } - } - - /** - * Instantiates the contracts on their channels. - * @return {boolean} True, if at least one contract was instantiated. Otherwise, false. - * @private - * @async - */ - async _instantiateContracts() { - const channels = this.networkUtil.getChannels(); - let contractInstantiated = false; - - // contracts needs to be installed channel by channel - for (const channel of channels) { - const contractInfos = this.networkUtil.getContractsOfChannel(channel); - - for (const contractInfo of contractInfos) { - logger.info(`Instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}. This might take some time...`); - - const ccObject = this.networkUtil.getNetworkObject().channels[channel].contracts.find( - cc => cc.id === contractInfo.id && cc.version === contractInfo.version); - - // check contract language - // only golang, node and java are supported - if (!['golang', 'node', 'java'].includes(ccObject.language)) { - throw new Error(`${contractInfo.id}@${contractInfo.version} in ${channel}: unknown contract type ${ccObject.language}`); - } - - const targetPeers = Array.from(this.networkUtil.getTargetPeersOfContractOfChannel(contractInfo, channel)); - if (targetPeers.length < 1) { - logger.info(`No target peers are defined for ${contractInfo.id}@${contractInfo.version} in ${channel}, skipping it`); - continue; - } - - // select a target peer for the contract to see if it's instantiated - // these are the same as the install targets, so if one of the peers has already instantiated the contract, - // then the other targets also had done the same - const org = this.networkUtil.getOrganizationOfPeer(targetPeers[0]); - const admin = this.adminProfiles.get(org); - - /** @link{ChaincodeQueryResponse} */ - let queryResponse; - try { - queryResponse = await admin.getChannel(channel, true).queryInstantiatedChaincodes(targetPeers[0], true); - } catch (err) { - throw new Error(`Couldn't query whether ${contractInfo.id}@${contractInfo.version} is instantiated on ${targetPeers[0]}: ${err.message}`); - } - - CaliperUtils.assertDefined(queryResponse); - CaliperUtils.assertProperty(queryResponse, 'queryResponse', 'chaincodes'); - - if (queryResponse.chaincodes.some( - cc => cc.name === contractInfo.id && cc.version === contractInfo.version)) { - logger.info(`${contractInfo.id}@${contractInfo.version} is already instantiated in ${channel}`); - continue; - } - - contractInstantiated = true; - - const txId = admin.newTransactionID(true); - /** @link{ChaincodeInstantiateUpgradeRequest} */ - const request = { - targets: targetPeers, - chaincodeId: ccObject.id, - chaincodeVersion: ccObject.version, - chaincodeType: ccObject.language, - args: ccObject.init || [], - fcn: ccObject.function || 'init', - 'endorsement-policy': ccObject['endorsement-policy'] || - this.networkUtil.getDefaultEndorsementPolicy(channel, { id: ccObject.id, version: ccObject.version }), - transientMap: this.networkUtil.getTransientMapOfContractOfChannel(contractInfo, channel), - txId: txId - }; - - // check private collection configuration - if (CaliperUtils.checkProperty(ccObject, 'collections-config')) { - request['collections-config'] = ccObject['collections-config']; - } - - /** @link{ProposalResponseObject} */ - let response; - try { - response = await admin.getChannel(channel, true).sendInstantiateProposal(request, this.configContractInstantiateTimeout); - } catch (err) { - throw new Error(`Couldn't endorse ${contractInfo.id}@${contractInfo.version} in ${channel} on peers [${targetPeers.toString()}]: ${err.message}`); - } - - CaliperUtils.assertDefined(response); - - /** @link{Array} */ - const proposalResponses = response[0]; - /** @link{Proposal} */ - const proposal = response[1]; - CaliperUtils.assertDefined(proposalResponses); - CaliperUtils.assertDefined(proposal); - - // check each response - proposalResponses.forEach((propResp, index) => { - CaliperUtils.assertDefined(propResp); - // an Error is returned for a rejected proposal - if (propResp instanceof Error) { - throw new Error(`Invalid endorsement for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${targetPeers[index]}: ${propResp.message}`); - } else if (propResp.response.status !== 200) { - throw new Error(`Invalid endorsement for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${targetPeers[index]}: status code ${propResp.response.status}`); - } - }); - - // connect to every event source of every org in the channel - const eventSources = this._assembleTargetEventSources(channel, targetPeers); - const eventPromises = []; - - try { - // NOTE: everything is resolved, errors are signaled through an Error object - // this makes error handling and reporting easier - eventSources.forEach((es) => { - const promise = new Promise((resolve) => { - const timeoutHandle = setTimeout(() => { - // unregister manually - es.eventHub.unregisterTxEvent(txId.getTransactionID(), false); - resolve(new Error(`Commit timeout for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${es.peer}`)); - }, this.configContractInstantiateEventTimeout); - - es.eventHub.registerTxEvent(txId.getTransactionID(), (tx, code) => { - clearTimeout(timeoutHandle); - if (code !== 'VALID') { - resolve(new Error(`Invalid commit code for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${es.peer}: ${code}`)); - } else { - resolve(code); - } - }, /* Error handler */ (err) => { - clearTimeout(timeoutHandle); - resolve(new Error(`Event hub error from ${es.peer} during instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${err.message}`)); - }); - - es.eventHub.connect(); - }); - - eventPromises.push(promise); - }); - - /** @link{TransactionRequest} */ - const ordererRequest = { - txId: txId, - proposalResponses: proposalResponses, - proposal: proposal - }; - - /** @link{BroadcastResponse} */ - let broadcastResponse; - try { - broadcastResponse = await admin.getChannel(channel, true).sendTransaction(ordererRequest); - } catch (err) { - throw new Error(`Orderer error for instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${err.message}`); - } - - CaliperUtils.assertDefined(broadcastResponse); - CaliperUtils.assertProperty(broadcastResponse, 'broadcastResponse', 'status'); - - if (broadcastResponse.status !== 'SUCCESS') { - throw new Error(`Orderer error for instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${broadcastResponse.status}`); - } - - // since every event promise is resolved, this shouldn't throw an error - const eventResults = await Promise.all(eventPromises); - - // if we received an error, propagate it - if (eventResults.some(er => er instanceof Error)) { - let errMsg = `The following errors occured while instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}:`; - let err; // keep the last error - for (const eventResult of eventResults) { - if (eventResult instanceof Error) { - err = eventResult; - errMsg += `\n\t- ${eventResult.message}`; - } - } - - logger.error(errMsg); - throw err; - } - - logger.info(`Successfully instantiated ${contractInfo.id}@${contractInfo.version} in ${channel}`); - } finally { - eventSources.forEach(es => { - if (es.eventHub.isconnected()) { - es.eventHub.disconnect(); - } - }); - } - } - } - - return contractInstantiated; - } - - /** - * Joins the peers to the specified channels is necessary. - * @return {boolean} True, if at least one peer joined a channel. Otherwise, false. - * @private - * @async - */ - async _joinChannels() { - const channels = this.networkUtil.getChannels(); - let channelJoined = false; - const errors = []; - - for (const channelName of channels) { - let genesisBlock = null; - const orgs = this.networkUtil.getOrganizationsOfChannel(channelName); - - for (const org of orgs) { - const admin = this.adminProfiles.get(org); - const channelObject = admin.getChannel(channelName, true); - - const peers = this.networkUtil.getPeersOfOrganizationAndChannel(org, channelName); - const peersToJoin = []; - - for (const peer of peers) { - try { - /** {@link ChannelQueryResponse} */ - const resp = await admin.queryChannels(peer, true); - if (resp.channels.some(ch => ch.channel_id === channelName)) { - logger.info(`${peer} has already joined ${channelName}`); - continue; - } - - peersToJoin.push(peer); - } catch (err) { - errors.push(new Error(`Couldn't query ${channelName} information from ${peer}: ${err.message}`)); - } - } - - if (errors.length > 0) { - let errMsg = `The following errors occurred while querying ${channelName} information from ${org}'s peers:`; - for (const err of errors) { - errMsg += `\n\t- ${err.message}`; - } - - logger.error(errMsg); - throw new Error(`Couldn't query ${channelName} information from ${org}'s peers`); - } - - // all target peers of the org have already joined the channel - if (peersToJoin.length < 1) { - continue; - } - - channelJoined = true; - - // only retrieve the genesis block once, and "cache" it - if (genesisBlock === null) { - try { - const genesisTxId = admin.newTransactionID(true); - /** @link{OrdererRequest} */ - const genesisRequest = { - txId: genesisTxId - }; - genesisBlock = await channelObject.getGenesisBlock(genesisRequest); - } catch (err) { - throw new Error(`Couldn't retrieve the genesis block for ${channelName}: ${err.message}`); - } - } - - const joinTxId = admin.newTransactionID(true); - const joinRequest = { - block: genesisBlock, - txId: joinTxId, - targets: peersToJoin - }; - - try { - /**{@link ProposalResponse} array*/ - const joinRespArray = await channelObject.joinChannel(joinRequest); - CaliperUtils.assertDefined(joinRespArray); - - // Some errors are returned as Error instances, some as error messages - joinRespArray.forEach((propResponse, index) => { - if (propResponse instanceof Error) { - errors.push(new Error(`${peersToJoin[index]} could not join ${channelName}: ${propResponse.message}`)); - } else if (propResponse.response.status !== 200) { - errors.push(new Error(`${peersToJoin[index]} could not join ${channelName}: ${propResponse.response.message}`)); - } - }); - } catch (err) { - throw new Error(`Couldn't join peers ${peersToJoin.toString()} to ${channelName}: ${err.message}`); - } - - if (errors.length > 0) { - let errMsg = `The following errors occurred while ${org}'s peers tried to join ${channelName}:`; - for (const err of errors) { - errMsg += `\n\t- ${err.message}`; - } - - logger.error(errMsg); - throw new Error(`${org}'s peers couldn't join ${channelName}`); - } - - logger.info(`${org}'s peers successfully joined ${channelName}: ${peersToJoin}`); - } - } - - return channelJoined; - } - - /** - * Initialize channel objects for use in peer targeting. Requires user gateways to have been - * formed in advance. - */ - async _initializePeerCache() { - - for (const userName of this.userGateways.keys()) { - const gateway = this.userGateways.get(userName); - // Loop over known channel names - const channelNames = this.networkUtil.getChannels(); - for (const channelName of channelNames) { - const network = await gateway.getNetwork(channelName); - const channel = network.getChannel(); - // Add all peers - for (const peerObject of channel.getChannelPeers()) { - this.peerCache.set(peerObject.getName(), peerObject); - } - } - } - } - - /** - * Conditionally initializes the organization wallet map depending on network configuration - * @private - */ - _prepareOrgWallets() { - if (this.networkUtil.usesOrganizationWallets()) { - logger.info('Using defined organization file system wallets'); - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const fileWalletPath = this.networkUtil.getWalletPathForOrganization(org); - if (fileWalletPath) { - const wallet = new FileSystemWallet(fileWalletPath); - this.orgWallets.set(org, wallet); - } else { - logger.warn(`No defined organization wallet for org ${org}`); - } - } - } else { - logger.info('Creating new InMemoryWallets for organizations'); - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const wallet = new InMemoryWallet(); - this.orgWallets.set(org, wallet); - } - } - } - - /** - * Partially assembles a Client object containing general network information. - * @param {string} org The name of the organization the client belongs to. Mandatory, if the client name is omitted. - * @param {string} clientName The name of the client to base the profile on. - * If omitted, then the first client of the organization will be used. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise} The partially assembled Client object. - * @private - * @async - */ - async _prepareClientProfile(org, clientName, profileName) { - let client = clientName; - if (!client) { - CaliperUtils.assertDefined(org); - // base it on the first client connection profile of the org - const clients = this.networkUtil.getClientsOfOrganization(org); - - // NOTE: this assumes at least one client per org, which is reasonable, the clients will interact with the network - if (clients.size < 1) { - throw new Error(`At least one client specification for ${org} is needed to initialize the ${profileName || 'profile'}`); - } - - client = Array.from(clients)[0]; - } - - // load the general network data from a clone of the network object - // NOTE: if we provide a common object instead, the Client class will use it directly, - // and it will be overwritten when loading the next client - const profile = FabricClient.loadFromConfig(this.networkUtil.getNewNetworkObject()); - profile.loadFromConfig({ - version: '1.0', - client: this.networkUtil.getClientObject(client) - }); - - if (!this.networkUtil.usesOrganizationWallets()) { - try { - await profile.initCredentialStores(); - } catch (err) { - throw new Error(`Couldn't initialize the credential stores for ${org}'s ${profileName || 'profile'}: ${err.message}`); - } - } - - return profile; - } - - /** - * Sets the mutual TLS for the admin of the given organization. - * @param {string} org The name of the organization. - * @private - */ - _setTlsAdminCertAndKey(org) { - const profile = this.adminProfiles.get(org); - const crypto = this.networkUtil.getAdminCryptoContentOfOrganization(org); - profile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); - } - - /** - * Tries to set the given identity as the current user context for the given profile. Enrolls it if needed and can. - * @param {Client} profile The Client object whose user context must be set. - * @param {string} userName The name of the user. - * @param {string} password The password for the user. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @private - * @async - */ - async _setUserContextByEnrollment(profile, userName, password, profileName) { - try { - // automatically tries to enroll the given identity with the CA (must be registered) - await profile.setUserContext({ - username: userName, - password: password - }, false); - } catch (err) { - throw new Error(`Couldn't enroll ${profileName || 'the user'} or set it as user context: ${err.message}`); - } - } - - /** - * Perform a transaction using a Gateway contract - * @param {ContractInvokeSettings | ContractQuerySettings} invokeSettings The settings associated with the transaction submission. - * @param {boolean} isSubmit boolean flag to indicate if the transaction is a submit or evaluate - * @return {Promise} The result and stats of the transaction invocation. - * @async - */ - async _performGatewayTransaction(invokeSettings, isSubmit) { - // Retrieve the existing contract and a client - const smartContract = await this._getUserContract(invokeSettings.invokerIdentity, invokeSettings.channel, invokeSettings.contractId); - - // Create a transaction - const transaction = smartContract.createTransaction(invokeSettings.contractFunction); - - // Build the Caliper TxStatus - const invokeStatus = new TxStatus(transaction.getTransactionID()); - - // Add transient data if present - // - passed as key value pairing such as {"hello":"world"} - if (invokeSettings.transientMap) { - const transientData = {}; - const keys = Array.from(Object.keys(invokeSettings.transientMap)); - keys.forEach((key) => { - transientData[key] = Buffer.from(invokeSettings.transientMap[key]); - }); - transaction.setTransient(transientData); - } - - // Set endorsing peers if passed as a string array - if (invokeSettings.targetPeers) { - // Retrieved cached peer objects - const targetPeerObjects = []; - for (const name of invokeSettings.targetPeers) { - const peer = this.peerCache.get(name); - if (peer) { - targetPeerObjects.push(peer); - } - } - // Set the peer objects in the transaction - if (targetPeerObjects.length > 0) { - transaction.setEndorsingPeers(targetPeerObjects); - } - } else if (invokeSettings.targetOrganizations) { - if (Array.isArray(invokeSettings.targetOrganizations) && invokeSettings.targetOrganizations.length > 0) { - transaction.setEndorsingOrganizations(...invokeSettings.targetOrganizations); - } else { - logger.warn(`${invokeSettings.targetOrganizations} is not a populated array, no orgs targetted`); - } - } - - try { - let result; - if (isSubmit) { - invokeStatus.Set('request_type', 'transaction'); - invokeStatus.Set('time_create', Date.now()); - result = await transaction.submit(...invokeSettings.contractArguments); - } else { - if (invokeSettings.targetPeers || invokeSettings.targetOrganizations) { - logger.warn('targetPeers or targetOrganizations options are not valid for query requests'); - } - invokeStatus.Set('request_type', 'query'); - invokeStatus.Set('time_create', Date.now()); - result = await transaction.evaluate(...invokeSettings.contractArguments); - } - invokeStatus.result = result; - invokeStatus.verified = true; - invokeStatus.SetStatusSuccess(); - return invokeStatus; - } catch (err) { - logger.error(`Failed to perform ${isSubmit ? 'submit' : 'query' } transaction [${invokeSettings.contractFunction}] using arguments [${invokeSettings.contractArguments}], with error: ${err.stack ? err.stack : err}`); - invokeStatus.SetStatusFail(); - invokeStatus.result = []; - return invokeStatus; - } - } - - /** - * Get the named contract for a named user - * @param {string} invokerIdentity the user identity for interacting with the contract - * @param {string} channelName the channel name the contract exists on - * @param {string} contractId the name of the contract to return - * @returns {FabricNetworkAPI.Contract} A contract that may be used to submit or evaluate transactions - * @async - */ - async _getUserContract(invokerIdentity, channelName, contractId) { - - // Determine the invoking user for this transaction - let userName; - if (invokerIdentity.startsWith('#')) { - userName = invokerIdentity.substring(1); - } else { - userName = invokerIdentity; - } - - const contractSet = this.userContracts.get(userName); - - // If no contract set found, there is a user configuration/test specification error, so it should terminate - if (!contractSet) { - throw Error(`No contracts for Invoker ${userName} found!`); - } - - // Retrieve the named Network Contract for the invoking user from the Map - const contract = contractSet.get(`${channelName}_${contractId}`); - - // If no contract found, there is a user configuration/test specification error, so it should terminate - if (!contract) { - throw Error(`Unable to find specified contract ${contractId} on channel ${channelName}!`); - } - - return contract; - } - - ////////////////////////// - // PUBLIC API FUNCTIONS // - ////////////////////////// - - /** - * Prepares the adapter by either: - * - building a gateway object linked to a wallet ID - * - loading user data and connection to the event hubs. - * - * @param {Number} roundIndex The zero-based round index of the test. - * @param {Array} args Unused. - * @return {Promise<{networkInfo : FabricNetwork, eventSources: EventSource[]}>} Returns the network utility object. - * @async - */ - async getContext(roundIndex, args) { - // Reset counter for new test round - this.txIndex = -1; - - // Build Gateway Network Contracts for possible users and return the network object - // - within submit/evaluate, a contract will be used for a nominated user - await this._initializeContracts(); - - // - use gateways to build a peer cache if the version supports it - if (semver.satisfies(semver.coerce(this.version), '>=1.4.5')) { - await this._initializePeerCache(); - } else { - logger.warn(`Bound SDK ${this.version} is unable to use target peers; to enable target peer nomination for a gateway transaction, bind Caliper to Fabric 1.4.5 and above`); - } - - // We are done - return the networkUtil object - this.context = { - networkInfo: this.networkUtil, - clientIdx: this.workerIndex - }; - - return this.context; - } - - /** - * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and contracts. - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @async - */ - async init(workerInit = false) { - const tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' - : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); - logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}`); - - await this._initializeRegistrars(workerInit); - await this._initializeAdmins(workerInit); - await this._initializeUsers(workerInit); - this.initPhaseCompleted = true; - - if (!workerInit) { - if (await this._createChannels()) { - logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterCreateChannel); - } - - if (await this._joinChannels()) { - logger.info(`Sleeping ${this.configSleepAfterJoinChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterJoinChannel); - } - } - } - - /** - * Installs and initializes the specified contracts. - * @async - */ - async installSmartContract() { - // With flow conditioning, this phase is conditionally required - if (!this.initPhaseCompleted ) { - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); - } - - await this._installContracts(); - if (await this._instantiateContracts()) { - logger.info(`Sleeping ${this.configSleepAfterInstantiateContract / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterInstantiateContract); - } - } - - /** - * Send a single request to the backing SUT. - * @param {FabricRequestSettings} request The request object. - */ - async _sendSingleRequest(request) { - if (!request.hasOwnProperty('channel')) { - const contractDetails = this.networkUtil.getContractDetails(request.contractId); - if (!contractDetails) { - throw new Error(`Could not find details for contract ID ${request.contractId}`); - } - request.channel = contractDetails.channel; - request.contractId = contractDetails.id; - request.contractVersion = contractDetails.version; - } - - if (!request.invokerIdentity) { - request.invokerIdentity = this.defaultInvoker; - } - - return this._performGatewayTransaction(request, request.readOnly === undefined || !request.readOnly); - } - - /** - * Releases the resources of the adapter. - * - * @async - */ - async releaseContext() { - // Disconnect from all persisted user gateways - for (const userName of this.userGateways.keys()) { - const gateway = this.userGateways.get(userName); - logger.info(`disconnecting gateway for user ${userName}`); - gateway.disconnect(); - } - - // Clear peer cache - this.peerCache.clear(); - this.context = undefined; - } -} - -module.exports = LegacyV1FabricGateway; diff --git a/packages/caliper-fabric/lib/connector-versions/v1/fabric.js b/packages/caliper-fabric/lib/connector-versions/v1/fabric.js deleted file mode 100644 index 3bd6c6942..000000000 --- a/packages/caliper-fabric/lib/connector-versions/v1/fabric.js +++ /dev/null @@ -1,2115 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const FabricClient = require('fabric-client'); -const FabricConstants = require('fabric-client/lib/Constants'); -const {google, common} = require('fabric-protos'); -const {ConnectorBase, CaliperUtils, TxStatus, Version, ConfigUtil} = require('@hyperledger/caliper-core'); -const FabricNetwork = require('../../fabricNetwork.js'); - -const fs = require('fs'); - -const logger = CaliperUtils.getLogger('connectors/v1/fabric'); - -////////////////////// -// TYPE DEFINITIONS // -////////////////////// - -/** - * @typedef {Object} EventSource - * - * @property {string[]} channel The list of channels this event source listens on. Only meaningful for Fabric v1.0. - * @property {string} peer The name of the peer the event source connects to. - * @property {EventHub|ChannelEventHub} eventHub The event hub object representing the connection. - */ - -///////////////////////////// -// END OF TYPE DEFINITIONS // -///////////////////////////// - -/** - * Extends {BlockchainConnector} for a Fabric backend, utilizing the SDK's Common Connection Profile. - * - * @property {Version} version Contains the version information about the used Fabric SDK. - * @property {Map} clientProfiles Contains the initialized and user-specific SDK client profiles - * for each defined user. Maps the custom user names to the Client instances. - * @property {Map} adminProfiles Contains the initialized and admin-specific SDK client profiles - * for each defined admin. Maps the custom organization names to the Client instances - * (since only one admin per org is supported). - * @property {Map} registrarProfiles Contains the initialized and registrar-specific SDK client - * profiles for each defined registrar. Maps the custom organization names to the Client instances - * (since only one registrar per org is supported). - * @property {EventSource[]} eventSources Collection of potential event sources to listen to for transaction confirmation events. - * @property {number} workerIndex The index of the client process using the adapter that is set in the constructor - * @property {number} txIndex A counter for keeping track of the index of the currently submitted transaction. - * @property {FabricNetwork} networkUtil Utility object containing easy-to-query information about the topology - * and settings of the network. - * @property {Map>>} randomTargetPeerCache Contains the target peers of contracts - * grouped by channels and organizations: Channel -> Contract -> Org -> Peers - * @property {Map} channelEventSourcesCache Contains the list of event sources for every channel. - * @property {Map} randomTargetOrdererCache Contains the list of target orderers of channels. - * @property {string} defaultInvoker The name of the client to use if an invoker is not specified. - * @property {number} configSmallestTimeout The timeout value to use when the user-provided timeout is too small. - * @property {number} configSleepAfterCreateChannel The sleep duration in milliseconds after creating the channels. - * @property {number} configSleepAfterJoinChannel The sleep duration in milliseconds after joining the channels. - * @property {number} configSleepAfterInstantiateContract The sleep duration in milliseconds after instantiating the contracts. - * @property {boolean} configVerifyProposalResponse Indicates whether to verify the proposal responses of the endorsers. - * @property {boolean} configVerifyReadWriteSets Indicates whether to verify the matching of the returned read-write sets. - * @property {number} configLatencyThreshold The network latency threshold to use for calculating the final commit time of transactions. - * @property {boolean} configOverwriteGopath Indicates whether GOPATH should be set to the Caliper root directory. - * @property {number} configContractInstantiateTimeout The timeout in milliseconds for the contract instantiation endorsement. - * @property {number} configContractInstantiateEventTimeout The timeout in milliseconds for receiving the contract instantion event. - * @property {number} configDefaultTimeout The default timeout in milliseconds to use for invoke/query transactions. - * @property {string} configClientBasedLoadBalancing The value indicating the type of automatic load balancing to use. - * @property {boolean} configCountQueryAsLoad Indicates whether queries should be counted as workload. - */ -class LegacyV1Fabric extends ConnectorBase { - /** - * Initializes the Fabric adapter. - * @param {object} networkObject The parsed network configuration. - * @param {number} workerIndex the worker index - * @param {string} bcType The target SUT type - */ - constructor(networkObject, workerIndex, bcType) { - super(workerIndex, bcType); - this.version = new Version(require('fabric-client/package').version); - - // clone the object to prevent modification by other objects - this.network = CaliperUtils.parseYamlString(CaliperUtils.stringifyYaml(networkObject)); - - this.clientProfiles = new Map(); - this.adminProfiles = new Map(); - this.registrarProfiles = new Map(); - this.eventSources = []; - this.txIndex = -1; - this.randomTargetPeerCache = new Map(); - this.channelEventSourcesCache = new Map(); - this.randomTargetOrdererCache = new Map(); - this.userContracts = new Map(); - this.context = undefined; - - // this value is hardcoded, if it's used, that means that the provided timeouts are not sufficient - this.configSmallestTimeout = 1000; - - this.configSleepAfterCreateChannel = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.CreateChannel, 5000); - this.configSleepAfterJoinChannel = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.JoinChannel, 3000); - this.configSleepAfterInstantiateContract = ConfigUtil.get(ConfigUtil.keys.Fabric.SleepAfter.InstantiateContract, 5000); - this.configVerifyProposalResponse = ConfigUtil.get(ConfigUtil.keys.Fabric.Verify.ProposalResponse, true); - this.configVerifyReadWriteSets = ConfigUtil.get(ConfigUtil.keys.Fabric.Verify.ReadWriteSets, true); - this.configLatencyThreshold = ConfigUtil.get(ConfigUtil.keys.Fabric.LatencyThreshold, 1.0); - this.configOverwriteGopath = ConfigUtil.get(ConfigUtil.keys.Fabric.OverwriteGopath, true); - this.configContractInstantiateTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.ContractInstantiate, 300000); - this.configContractInstantiateEventTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.ContractInstantiateEvent, 300000); - this.configDefaultTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.InvokeOrQuery, 60); - this.configClientBasedLoadBalancing = ConfigUtil.get(ConfigUtil.keys.Fabric.LoadBalancing, 'client') === 'client'; - this.configCountQueryAsLoad = ConfigUtil.get(ConfigUtil.keys.Fabric.CountQueryAsLoad, true); - - this.networkUtil = new FabricNetwork(this.network); - this.defaultInvoker = Array.from(this.networkUtil.getClients())[0]; - - // NOTE: regardless of the version of the Fabric backend, the SDK must be at least v1.1.0 in order to - // use the common connection profile feature - if (this.version.lessThan('1.1.0')) { - throw new Error(`Fabric SDK ${this.version.toString()} is not supported, use at least version 1.1.0`); - } - - if (this.networkUtil.isInCompatibilityMode() && this.version.greaterThan('1.1.0')) { - throw new Error(`Fabric 1.0 compatibility mode is detected, but SDK version ${this.version.toString()} is used`); - } - - this._prepareCaches(); - } - - ////////////////////////// - // PUBLIC API FUNCTIONS // - ////////////////////////// - - /** - * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and contracts. - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @async - */ - async init(workerInit = false) { - const tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' - : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); - const compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; - logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); - - // TODO: only required for specific scenarios, means network config has to have more info defined - await this._initializeRegistrars(workerInit); - await this._initializeAdmins(workerInit); - await this._initializeUsers(workerInit); - this.initPhaseCompleted = true; - - if (!workerInit) { - if (await this._createChannels()) { - logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterCreateChannel); - } - - if (await this._joinChannels()) { - logger.info(`Sleeping ${this.configSleepAfterJoinChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterJoinChannel); - } - } - } - - /** - * Prepares the adapter by loading user data and connection to the event hubs. - * - * @param {Number} roundIndex The zero-based round index of the test. - * @param {Array} args Unused. - * @return {Promise<{networkInfo : FabricNetwork, eventSources: EventSource[]}>} Returns the network utility object. - * @async - */ - async getContext(roundIndex, args) { - // Reset counter for new test round - this.txIndex = -1; - - if (!this.context) { - this.context = { - networkInfo: this.networkUtil, - clientIdx: this.workerIndex - }; - await this._initializeChannelsAndEventHubs(); - } - - return this.context; - } - - /** - * Releases the resources of the adapter. - * @async - */ - async releaseContext() { - this.eventSources.forEach((es) => { - if (es.eventHub.isconnected()) { - es.eventHub.disconnect(); - } - }); - - this.eventSources = []; - this.context = undefined; - } - - /** - * Installs and initializes the specified contracts. - * @async - */ - async installSmartContract() { - // With flow conditioning, this phase is conditionally required - if (!this.initPhaseCompleted ) { - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); - } - - await this._installContracts(); - if (await this._instantiateContracts()) { - logger.info(`Sleeping ${this.configSleepAfterInstantiateContract / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterInstantiateContract); - } - } - - //////////////////////////////// - // INTERNAL UTILITY FUNCTIONS // - //////////////////////////////// - - /** - * Assembles the event sources based on explicitly given target peers. - * @param {string} channel The name of channel containing the target peers. Doesn't matter if peer-level event service is used in compatibility mode. - * @param {string[]} targetPeers The list of peers to connect to. - * @return {EventSource[]} The list of event sources. - * @private - */ - _assembleTargetEventSources(channel, targetPeers) { - const eventSources = []; - if (this.networkUtil.isInCompatibilityMode()) { - // NOTE: for old event hubs we have a single connection to every peer set as an event source - const EventHub = require('fabric-client/lib/EventHub.js'); - - for (const peer of targetPeers) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - const eventHub = new EventHub(admin); - eventHub.setPeerAddr(this.networkUtil.getPeerEventUrl(peer), - this.networkUtil.getGrpcOptionsOfPeer(peer)); - - eventSources.push({ - channel: [channel], // unused during contract instantiation - peer: peer, - eventHub: eventHub - }); - } - } else { - for (const peer of targetPeers) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - const eventHub = admin.getChannel(channel, true).newChannelEventHub(peer); - - eventSources.push({ - channel: [channel], // unused during contract instantiation - peer: peer, - eventHub: eventHub - }); - } - } - - return eventSources; - } - - /** - * Collects all peers that are endorsing peers belonging to a set of organizations - * @param {*} channel The name of the channel - * @param {string[]} endorsingOrgs An array of the required orgs to target - * @returns {ChannelPeer[]} Array containing the set of peers - */ - _getEndorsingPeersForOrgs(channel, endorsingOrgs) { - const channelPeers = channel.getChannelPeers(); - const filteredPeers = channelPeers.filter((channelPeer) => { - return channelPeer.isInRole(FabricConstants.NetworkConfig.ENDORSING_PEER_ROLE) && - endorsingOrgs.some((org) => channelPeer.isInOrg(org)); - }); - return filteredPeers; - } - - /** - * Assembles random target peers for the channel from every organization that has the contract deployed. - * @param {string} channel The name of the channel. - * @param {string} contractId The name/ID of the contract. - * @param {string} contractVersion The version of the contract. - * @returns {string[]} Array containing a random peer from each needed organization. - * @private - */ - _assembleRandomTargetPeers(channel, contractId, contractVersion) { - const targets = []; - const contractOrgs = this.randomTargetPeerCache.get(channel).get(`${contractId}@${contractVersion}`); - - for (const entries of contractOrgs.entries()) { - const peers = entries[1]; - - // represents the load balancing mechanism - const loadBalancingCounter = this.configClientBasedLoadBalancing ? this.workerIndex : this.txIndex; - targets.push(peers[loadBalancingCounter % peers.length]); - } - - return targets; - } - - /** - * Creates the specified channels if necessary. - * @return {boolean} True, if at least one channel was created. Otherwise, false. - * @private - * @async - */ - async _createChannels() { - const channels = this.networkUtil.getChannels(); - let channelCreated = false; - - for (const channel of channels) { - const channelObject = this.networkUtil.getNetworkObject().channels[channel]; - - if (CaliperUtils.checkProperty(channelObject, 'created') && channelObject.created) { - logger.info(`Channel '${channel}' is configured as created, skipping creation`); - continue; - } - - if (ConfigUtil.get(ConfigUtil.keys.Fabric.SkipCreateChannelPrefix + channel, false)) { - logger.info(`Creation of Channel '${channel}' is configured to skip`); - continue; - } - - channelCreated = true; - - let configUpdate; - if (CaliperUtils.checkProperty(channelObject, 'configBinary')) { - logger.info(`Channel '${channel}' definiton being retrieved from file`); - configUpdate = this._getChannelConfigFromFile(channelObject, channel); - } - else { - logger.info(`Channel '${channel}' definiton being generated from description`); - const channelTx = this._createChannelTxEnvelope(channelObject.definition, channel); - const payload = common.Payload.decode(channelTx.getPayload().toBuffer()); - const configtx = common.ConfigUpdateEnvelope.decode(payload.getData().toBuffer()); - configUpdate = configtx.getConfigUpdate().toBuffer(); - } - - // NOTE: without knowing the system channel policies, signing with every org admin is a safe bet - const orgs = this.networkUtil.getOrganizationsOfChannel(channel); - let admin; // declared here to keep the admin of the last org of the channel - const signatures = []; - for (const org of orgs) { - admin = this.adminProfiles.get(org); - try { - signatures.push(admin.signChannelConfig(configUpdate)); - } catch (err) { - throw new Error(`${org}'s admin couldn't sign the configuration update of Channel '${channel}': ${err.message}`); - } - } - - const txId = admin.newTransactionID(true); - const request = { - config: configUpdate, - signatures: signatures, - name: channel, - txId: txId - }; - - try { - /** @link{BroadcastResponse} */ - const broadcastResponse = await admin.createChannel(request); - - CaliperUtils.assertDefined(broadcastResponse, `The returned broadcast response for creating Channel '${channel}' is undefined`); - CaliperUtils.assertProperty(broadcastResponse, 'broadcastResponse', 'status'); - - if (broadcastResponse.status !== 'SUCCESS') { - throw new Error(`Orderer response indicated unsuccessful Channel '${channel}' creation: ${broadcastResponse.status}`); - } - } catch (err) { - throw new Error(`Couldn't create Channel '${channel}': ${err.message}`); - } - - logger.info(`Channel '${channel}' successfully created`); - } - - return channelCreated; - } - - /** - * - * @param {EventSource} eventSource The event source to use for registering the Tx event. - * @param {string} txId The transaction ID. - * @param {TxStatus} invokeStatus The transaction status object. - * @param {number} startTime The epoch of the transaction start time. - * @param {number} timeout The timeout for the transaction life-cycle. - * @return {Promise<{successful: boolean, message: string, time: number}>} The details of the event notification. - * @private - */ - _createEventRegistrationPromise(eventSource, txId, invokeStatus, startTime, timeout) { - return new Promise(resolve => { - const handle = setTimeout(() => { - // give the other event hub connections a chance - // to verify the Tx status, so resolve the promise - - eventSource.eventHub.unregisterTxEvent(txId); - - const time = Date.now(); - invokeStatus.Set(`commit_timeout_${eventSource.peer}`, 'TIMEOUT'); - - // resolve the failed transaction with the current time and error message - resolve({ - successful: false, - message: `Commit timeout on ${eventSource.peer} for transaction ${txId}`, - time: time - }); - }, this._getRemainingTimeout(startTime, timeout)); - - eventSource.eventHub.registerTxEvent(txId, (tx, code) => { - clearTimeout(handle); - const time = Date.now(); - eventSource.eventHub.unregisterTxEvent(txId); - - // either explicit invalid event or valid event, verified in both cases by at least one peer - // TODO: what about when a transient error occurred on a peer? - invokeStatus.SetVerification(true); - - if (code !== 'VALID') { - invokeStatus.Set(`commit_error_${eventSource.peer}`, code); - - resolve({ - successful: false, - message: `Commit error on ${eventSource.peer} with code ${code}`, - time: time - }); - } else { - invokeStatus.Set(`commit_success_${eventSource.peer}`, time); - resolve({ - successful: true, - message: 'undefined', - time: time - }); - } - }, (err) => { - clearTimeout(handle); - eventSource.eventHub.unregisterTxEvent(txId); - const time = Date.now(); - - // we don't know what happened, but give the other event hub connections a chance - // to verify the Tx status, so resolve this promise - invokeStatus.Set(`event_hub_error_${eventSource.peer}`, err.message); - - resolve({ - successful: false, - message: `Event hub error on ${eventSource.peer}: ${err.message}`, - time: time - }); - }); - }); - } - - /** - * Creates and sets a User object as the context based on the provided identity information. - * @param {Client} profile The Client object whose user context must be set. - * @param {string} org The name of the user's organization. - * @param {string} userName The name of the user. - * @param {{privateKeyPEM: Buffer, signedCertPEM: Buffer}} cryptoContent The object containing the signing key and cert in PEM format. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @returns {FabricClient.User} The User object created - * @private - * @async - */ - async _createUser(profile, org, userName, cryptoContent, profileName) { - // set the user explicitly based on its crypto materials - // createUser also sets the user context - try { - return await profile.createUser({ - username: userName, - mspid: this.networkUtil.getMspIdOfOrganization(org), - cryptoContent: cryptoContent, - skipPersistence: true - }); - } catch (err) { - throw new Error(`Couldn't create ${profileName || ''} user object: ${err.message}`); - } - } - - /** - * Enrolls the given user through its corresponding CA. - * @param {Client} profile The Client object whose user must be enrolled. - * @param {string} id The enrollment ID. - * @param {string} secret The enrollment secret. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise<{key: ECDSA_KEY, certificate: string}>} The resulting private key and certificate. - * @private - * @async - */ - async _enrollUser(profile, id, secret, profileName) { - // this call will throw an error if the CA configuration is not found - // this error should propagate up - const ca = profile.getCertificateAuthority(); - try { - return await ca.enroll({ - enrollmentID: id, - enrollmentSecret: secret - }); - } catch (err) { - throw new Error(`Couldn't enroll ${profileName || 'user'}: ${err.message}`); - } - } - - /** - * Populate an envelope with a channel creation transaction - * @param {object} channelObject The channel configuration object. - * @param {string} channelName The name of the channel. - * @return {Buffer} The extracted channel configuration bytes. - * @private - */ - _createChannelTxEnvelope(channelObject, channelName) { - // Versioning - const readVersion = 0; - const writeVersion = 0; - const appVersion = 1; - const policyVersion = 0; - - // Build the readSet - const readValues = {}; - readValues.Consortium = new common.ConfigValue(); - - const readAppGroup = {}; - for (const mspId of channelObject.msps) { - readAppGroup[mspId] = new common.ConfigGroup(); - } - const readGroups = {}; - readGroups.Application = new common.ConfigGroup({ groups: readAppGroup }); - - const readSet = new common.ConfigGroup({ version: readVersion, groups: readGroups, values: readValues }); - - // Build the writeSet (based on consortium name and passed Capabiliites) - const modPolicy = 'Admins'; - const writeValues = {}; - - const consortium = new common.Consortium({ name: channelObject.consortium }); - writeValues.Consortium = new common.ConfigValue({ version: writeVersion, value: consortium.toBuffer() }); - - if (channelObject.capabilities) { - const capabilities = this._populateCapabilities(channelObject.capabilities); - writeValues.Capabilities = new common.ConfigValue({ version: writeVersion, value: capabilities.toBuffer(), mod_policy: modPolicy }); - } - - // Write Policy - const writePolicies = this._generateWritePolicy(policyVersion, modPolicy); - - // Write Application Groups - const writeAppGroup = {}; - for (const mspId of channelObject.msps) { - writeAppGroup[mspId] = new common.ConfigGroup(); - } - - const writeGroups = {}; - writeGroups.Application = new common.ConfigGroup({ version: appVersion, groups: writeAppGroup, policies: writePolicies, mod_policy: modPolicy }); - - const writeSet = new common.ConfigGroup({ version: writeVersion, groups: writeGroups, values: writeValues }); - - // Now create the configUpdate and configUpdateEnv - const configUpdate = new common.ConfigUpdate({ channel_id: channelName, read_set: readSet, write_set: writeSet}); - const configUpdateEnv= new common.ConfigUpdateEnvelope({ config_update: configUpdate.toBuffer(), signatures: [] }); - - // Channel header - const channelTimestamp = new google.protobuf.Timestamp({ seconds: Date.now()/1000, nanos: 0 }); // Date.now() is millis since 1970 epoch, we need seconds - const channelEpoch = 0; - const chHeader = new common.ChannelHeader({ type: common.HeaderType.CONFIG_UPDATE, version: channelObject.version, timestamp: channelTimestamp, channel_id: channelName, epoch: channelEpoch }); - - // Common header - const header = new common.Header({ channel_header: chHeader.toBuffer() }); - - // Form the payload header/data - const payload = new common.Payload({ header: header, data: configUpdateEnv.toBuffer() }); - - // Form and return the envelope - const envelope = new common.Envelope({ payload: payload.toBuffer() }); - return envelope; - } - - /** - * Populate a Capabilities protobuf - * @param {Array} applicationCapabilities the application capability keys - * @returns {common.Capabilities} Capabilities in a protobuff - */ - _populateCapabilities(applicationCapabilities) { - const capabilities = {}; - for (const capability of applicationCapabilities) { - capabilities[capability] = new common.Capability(); - } - return new common.Capabilities({ capabilities: capabilities }); - } - - /** - * Form a populted Poicy protobuf that contains an ImplicitMetaPolicy - * @param {String} subPolicyName the sub policy name - * @param {common.Policy.PolicyType} rule the rule type - * @returns {common.Policy} the policy protobuf - */ - _makeImplicitMetaPolicy(subPolicyName, rule){ - const metaPolicy = new common.ImplicitMetaPolicy({ sub_policy: subPolicyName, rule: rule }); - const policy= new common.Policy({ type: common.Policy.PolicyType.IMPLICIT_META, value: metaPolicy.toBuffer() }); - return policy; - } - - /** - * Generate a write policy - * @param {number} version the policy version - * @param {string} modPolicy the modification policy - * @returns {Object} an object of Admin/Reader/Writer keys mapping to populated ConfigPolicy protobuffs - */ - _generateWritePolicy(version, modPolicy) { - // Write Policy - const writePolicies = {}; - // admins - const adminsPolicy = this._makeImplicitMetaPolicy('Admins', common.ImplicitMetaPolicy.Rule.MAJORITY); // majority - writePolicies.Admins = new common.ConfigPolicy({ version: version, policy: adminsPolicy, mod_policy: modPolicy }); - // Readers - const readersPolicy = this._makeImplicitMetaPolicy('Readers', common.ImplicitMetaPolicy.Rule.ANY); // Any - writePolicies.Readers = new common.ConfigPolicy({ version: version, policy: readersPolicy, mod_policy: modPolicy }); - // Writers - const writersPolicy = this._makeImplicitMetaPolicy('Writers', common.ImplicitMetaPolicy.Rule.ANY); // Any - writePolicies.Writers = new common.ConfigPolicy({ version: version, policy: writersPolicy, mod_policy: modPolicy }); - return writePolicies; - } - - /** - * Extracts the channel configuration from the configured file. - * @param {object} channelObject The channel configuration object. - * @param {string} channelName The name of the channel. - * @return {Buffer} The extracted channel configuration bytes. - * @private - */ - _getChannelConfigFromFile(channelObject, channelName) { - // extracting the config from the binary file - const binaryPath = CaliperUtils.resolvePath(channelObject.configBinary); - let envelopeBytes; - - try { - envelopeBytes = fs.readFileSync(binaryPath); - } catch (err) { - throw new Error(`Couldn't read configuration binary for ${channelName}: ${err.message}`); - } - - try { - return new FabricClient().extractChannelConfig(envelopeBytes); - } catch (err) { - throw new Error(`Couldn't extract configuration object for ${channelName}: ${err.message}`); - } - } - - /** - * Gets a random target orderer for the given channel. - * @param {string} channel The name of the channel. - * @return {string} The name of the target orderer. - * @private - */ - _getRandomTargetOrderer(channel) { - const orderers = this.randomTargetOrdererCache.get(channel); - - // represents the load balancing mechanism - const loadBalancingCounter = this.configClientBasedLoadBalancing ? this.workerIndex : this.txIndex; - - return orderers[loadBalancingCounter % orderers.length]; - } - - /** - * Calculates the remaining time to timeout based on the original timeout and a starting time. - * @param {number} start The epoch of the start time in ms. - * @param {number} original The original timeout in ms. - * @returns {number} The remaining time until the timeout in ms. - * @private - */ - _getRemainingTimeout(start, original) { - let newTimeout = original - (Date.now() - start); - if (newTimeout < this.configSmallestTimeout) { - logger.warn(`Timeout is too small, default value of ${this.configSmallestTimeout}ms is used instead`); - newTimeout = this.configSmallestTimeout; - } - - return newTimeout; - } - - /** - * Checks whether the user materials are already persisted in the local store and sets the user context if found. - * @param {Client} profile The Client object to fill with the User instance. - * @param {string} userName The name of the user to check and load. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise} The loaded User object - * @private - * @async - */ - async _getUserContext(profile, userName, profileName) { - // Check whether the materials are already saved - // getUserContext automatically sets the user if found - try { - return await profile.getUserContext(userName, true); - } catch (err) { - throw new Error(`Couldn't check whether ${profileName || 'the user'}'s materials are available locally: ${err.message}`); - } - } - - /** - * Initializes the admins of the organizations. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeAdmins(workerInit) { - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const adminName = `admin.${org}`; - // build the common part of the profile - const adminProfile = await this._prepareClientProfile(org, undefined, `${org}'s admin`); - - // Check if the materials already exist locally in file system key-value stores. - const admin = await this._getUserContext(adminProfile, adminName, `${org}'s admin`); - if (admin) { - this.adminProfiles.set(org, adminProfile); - - if (this.networkUtil.isMutualTlsEnabled()) { - this._setTlsAdminCertAndKey(org); - } - - if (!workerInit) { - logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); - } - - continue; - } - - // Set the admin explicitly based on its crypto materials either provided in the connection profile - const cryptoContent = this.networkUtil.getAdminCryptoContentOfOrganization(org); - await this._createUser(adminProfile, org, adminName, cryptoContent,`${org}'s admin`); - - this.adminProfiles.set(org, adminProfile); - - if (this.networkUtil.isMutualTlsEnabled()) { - this._setTlsAdminCertAndKey(org); - } - - logger.info(`${org}'s admin's materials are successfully loaded`); - } - } - - /** - * Initializes the given channel of every client profile to be able to verify proposal responses. - * @param {Map} profiles The collection of client profiles. - * @param {string} channel The name of the channel to initialize. - * @param {boolean} admin Indicates whether the profiles are admin profiles. - * @private - * @async - */ - async _initializeChannel(profiles, channel, admin) { - // initialize the channel for every client profile from the local config - for (const profile of profiles.entries()) { - const profileOrg = admin ? profile[0] : this.networkUtil.getOrganizationOfClient(profile[0]); - const channelOrgs = this.networkUtil.getOrganizationsOfChannel(channel); - - // skip init for profiles whose org is a not a member of the channel - if (!channelOrgs.has(profileOrg)) { - continue; - } - - const ch = profile[1].getChannel(channel, false); - if (ch) { - try { - await ch.initialize(); - } catch (err) { - logger.error(`Couldn't initialize ${channel} for ${profile[0]}: ${err.message}`); - throw err; - } - } - } - } - - /** - * Initializes the registrars of the organizations. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeRegistrars(workerInit) { - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - - // providing registrar information is optional and only needed for user registration and enrollment - const registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); - if (!registrarInfo) { - if (!workerInit) { - logger.warn(`${org}'s registrar information not provided.`); - } - continue; - } - - // build the common part of the profile - const registrarProfile = await this._prepareClientProfile(org, undefined, 'registrar'); - // check if the materials already exist locally in the file system key-value stores - const registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); - - if (registrar) { - if (!workerInit) { - logger.warn(`${org}'s registrar's materials found locally in file system key-value stores. Make sure it is the right one!`); - } - this.registrarProfiles.set(org, registrarProfile); - continue; - } - - // set the registrar identity as the current user context - await this._setUserContextByEnrollment(registrarProfile, registrarInfo.enrollId, - registrarInfo.enrollSecret, `${org}'s registrar`); - - this.registrarProfiles.set(org, registrarProfile); - if (!workerInit) { - logger.info(`${org}'s registrar enrolled successfully`); - } - } - } - - /** - * Registers and enrolls the specified users if necessary. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeUsers(workerInit) { - const clients = this.networkUtil.getClients(); - - // register and enroll each client with its organization's CA - for (const client of clients) { - const org = this.networkUtil.getOrganizationOfClient(client); - - // create the profile based on the connection profile - const clientProfile = await this._prepareClientProfile(org, client, client); - this.clientProfiles.set(client, clientProfile); - - // check if the materials already exist locally in the file system key-value stores - const user = await this._getUserContext(clientProfile, client, client); - if (user) { - if (this.networkUtil.isMutualTlsEnabled()) { - // "retrieve" and set the deserialized cert and key - clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); - } - - if (!workerInit) { - logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); - } - - continue; - } - - const cryptoContent = this.networkUtil.getClientCryptoContent(client); - - if (cryptoContent) { - // the client is already enrolled, just create and persist the User object - await this._createUser(clientProfile, org, client, cryptoContent, client); - if (this.networkUtil.isMutualTlsEnabled()) { - // the materials are included in the configuration file - const crypto = this.networkUtil.getClientCryptoContent(client); - clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); - } - - if (!workerInit) { - logger.info(`${client}'s materials are successfully loaded`); - } - - continue; - } - - // The user needs to be enrolled or even registered - - // if the enrollment ID and secret is provided, then enroll the already registered user - const enrollmentSecret = this.networkUtil.getClientEnrollmentSecret(client); - if (enrollmentSecret) { - const enrollment = await this._enrollUser(clientProfile, client, enrollmentSecret, client); - - // create the new user based on the retrieved materials - await this._createUser(clientProfile, org, client, - { - privateKeyPEM: enrollment.key.toBytes(), - signedCertPEM: Buffer.from(enrollment.certificate) - }, client); - - if (this.networkUtil.isMutualTlsEnabled()) { - // set the received cert and key for mutual TLS - clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); - } - - if (!workerInit) { - logger.info(`${client} successfully enrolled`); - } - - continue; - } - - // Otherwise, register then enroll the user - let secret; - try { - const registrarProfile = this.registrarProfiles.get(org); - - if (!registrarProfile) { - throw new Error(`Registrar identity is not provided for ${org}`); - } - - const registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); - const registrar = await registrarProfile.getUserContext(registrarInfo.enrollId, true); - // this call will throw an error if the CA configuration is not found - // this error should propagate up - const ca = clientProfile.getCertificateAuthority(); - const userAffiliation = this.networkUtil.getAffiliationOfUser(client); - - // if not in compatibility mode (i.e., at least SDK v1.1), check whether the affiliation is already registered or not - if (!this.networkUtil.isInCompatibilityMode()) { - const affService = ca.newAffiliationService(); - let affiliationExists = false; - try { - await affService.getOne(userAffiliation, registrar); - affiliationExists = true; - } catch (err) { - if (!workerInit) { - logger.info(`${userAffiliation} affiliation doesn't exists`); - } - } - - if (!affiliationExists) { - await affService.create({name: userAffiliation, force: true}, registrar); - if (!workerInit) { - logger.info(`${userAffiliation} affiliation added`); - } - } - } - - const attributes = this.networkUtil.getAttributesOfUser(client); - attributes.push({name: 'hf.Registrar.Roles', value: 'client'}); - - secret = await ca.register({ - enrollmentID: client, - affiliation: userAffiliation, - role: 'client', - attrs: attributes - }, registrar); - } catch (err) { - throw new Error(`Couldn't register ${client}: ${err.message}`); - } - - if (!workerInit) { - logger.info(`${client} successfully registered`); - } - - const enrollment = await this._enrollUser(clientProfile, client, secret, client); - - // create the new user based on the retrieved materials - await this._createUser(clientProfile, org, client, - {privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: Buffer.from(enrollment.certificate)}, client); - - if (this.networkUtil.isMutualTlsEnabled()) { - // set the received cert and key for mutual TLS - clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); - //this._setTlsClientCertAndKey(client); - } - - if (!workerInit) { - logger.info(`${client} successfully enrolled`); - } - } - } - - /** - * Install the specified contracts to their target peers. - * @private - * @async - */ - async _installContracts() { - if (this.configOverwriteGopath) { - process.env.GOPATH = CaliperUtils.resolvePath('.'); - } - - const errors = []; - - const channels = this.networkUtil.getChannels(); - for (const channel of channels) { - logger.info(`Installing contracts for ${channel}...`); - - // proceed cc by cc for the channel - const contractInfos = this.networkUtil.getContractsOfChannel(channel); - for (const contractInfo of contractInfos) { - const ccObject = this.networkUtil.getNetworkObject().channels[channel].contracts.find( - cc => cc.id === contractInfo.id && cc.version === contractInfo.version); - - const targetPeers = this.networkUtil.getTargetPeersOfContractOfChannel(contractInfo, channel); - if (targetPeers.size < 1) { - logger.info(`No target peers are defined for ${contractInfo.id}@${contractInfo.version} on ${channel}, skipping it`); - continue; - } - - // find the peers that don't have the cc installed - const installTargets = []; - - for (const peer of targetPeers) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - try { - /** {@link ChaincodeQueryResponse} */ - const resp = await admin.queryInstalledChaincodes(peer, true); - if (resp.chaincodes.some(cc => cc.name === contractInfo.id && cc.version === contractInfo.version)) { - logger.info(`${contractInfo.id}@${contractInfo.version} is already installed on ${peer}`); - continue; - } - - installTargets.push(peer); - } catch (err) { - errors.push(new Error(`Couldn't query installed contracts on ${peer}: ${err.message}`)); - } - } - - if (errors.length > 0) { - let errorMsg = `Could not query whether ${contractInfo.id}@${contractInfo.version} is installed on some peers of ${channel}:`; - for (const err of errors) { - errorMsg += `\n\t- ${err.message}`; - } - - logger.error(errorMsg); - throw new Error(`Could not query whether ${contractInfo.id}@${contractInfo.version} is installed on some peers of ${channel}`); - } - - // cc is installed on every target peer in the channel - if (installTargets.length < 1) { - continue; - } - - // install contracts org by org - const orgs = this.networkUtil.getOrganizationsOfChannel(channel); - for (const org of orgs) { - const peersOfOrg = this.networkUtil.getPeersOfOrganization(org); - // selecting the target peers for this org - const orgPeerTargets = installTargets.filter(p => peersOfOrg.has(p)); - - // cc is installed on every target peer of the org in the channel - if (orgPeerTargets.length < 1) { - continue; - } - - const admin = this.adminProfiles.get(org); - - const txId = admin.newTransactionID(true); - /** @{ChaincodeInstallRequest} */ - const request = { - targets: orgPeerTargets, - chaincodePath: ccObject.language === 'golang' ? ccObject.path : CaliperUtils.resolvePath(ccObject.path), - chaincodeId: ccObject.id, - chaincodeVersion: ccObject.version, - chaincodeType: ccObject.language, - txId: txId - }; - - // metadata (like CouchDB indices) are only supported since Fabric v1.1 - if (CaliperUtils.checkProperty(ccObject, 'metadataPath')) { - if (!this.networkUtil.isInCompatibilityMode()) { - request.metadataPath = CaliperUtils.resolvePath(ccObject.metadataPath); - } else { - throw new Error(`Installing ${contractInfo.id}@${contractInfo.version} with metadata is not supported in Fabric v1.0`); - } - } - - // install to necessary peers of org and process the results - try { - /** @link{ProposalResponseObject} */ - const propRespObject = await admin.installChaincode(request); - CaliperUtils.assertDefined(propRespObject); - - /** Array of @link{ProposalResponse} objects */ - const proposalResponses = propRespObject[0]; - CaliperUtils.assertDefined(proposalResponses); - - proposalResponses.forEach((propResponse, index) => { - if (propResponse instanceof Error) { - const errMsg = `Install proposal error for ${contractInfo.id}@${contractInfo.version} on ${orgPeerTargets[index]}: ${propResponse.message}`; - errors.push(new Error(errMsg)); - return; - } - - /** @link{ProposalResponse} */ - CaliperUtils.assertProperty(propResponse, 'propResponse', 'response'); - - /** @link{ResponseObject} */ - const response = propResponse.response; - CaliperUtils.assertProperty(response, 'response', 'status'); - - if (response.status !== 200) { - const errMsg = `Unsuccessful install status for ${contractInfo.id}@${contractInfo.version} on ${orgPeerTargets[index]}: ${propResponse.response.message}`; - errors.push(new Error(errMsg)); - } - }); - } catch (err) { - throw new Error(`Couldn't install ${contractInfo.id}@${contractInfo.version} on peers ${orgPeerTargets.toString()}: ${err.message}`); - } - - // there were some install errors, proceed to the other orgs to gather more information - if (errors.length > 0) { - continue; - } - - logger.info(`${contractInfo.id}@${contractInfo.version} successfully installed on ${org}'s peers: ${orgPeerTargets.toString()}`); - } - - if (errors.length > 0) { - let errorMsg = `Could not install ${contractInfo.id}@${contractInfo.version} on some peers of ${channel}:`; - for (const err of errors) { - errorMsg += `\n\t- ${err.message}`; - } - - logger.error(errorMsg); - throw new Error(`Could not install ${contractInfo.id}@${contractInfo.version} on some peers of ${channel}`); - } - } - } - } - - /** - * Instantiates the contracts on their channels. - * @return {boolean} True, if at least one contract was instantiated. Otherwise, false. - * @private - * @async - */ - async _instantiateContracts() { - const channels = this.networkUtil.getChannels(); - let contractInstantiated = false; - - // contracts needs to be installed channel by channel - for (const channel of channels) { - const contractInfos = this.networkUtil.getContractsOfChannel(channel); - - for (const contractInfo of contractInfos) { - logger.info(`Instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}. This might take some time...`); - - const ccObject = this.networkUtil.getNetworkObject().channels[channel].contracts.find( - cc => cc.id === contractInfo.id && cc.version === contractInfo.version); - - const targetPeers = Array.from(this.networkUtil.getTargetPeersOfContractOfChannel(contractInfo, channel)); - if (targetPeers.length < 1) { - logger.info(`No target peers are defined for ${contractInfo.id}@${contractInfo.version} in ${channel}, skipping it`); - continue; - } - - // select a target peer for the contract to see if it's instantiated - // these are the same as the install targets, so if one of the peers has already instantiated the contract, - // then the other targets also had done the same - const org = this.networkUtil.getOrganizationOfPeer(targetPeers[0]); - const admin = this.adminProfiles.get(org); - - /** @link{ChaincodeQueryResponse} */ - let queryResponse; - try { - queryResponse = await admin.getChannel(channel, true).queryInstantiatedChaincodes(targetPeers[0], true); - } catch (err) { - throw new Error(`Couldn't query whether ${contractInfo.id}@${contractInfo.version} is instantiated on ${targetPeers[0]}: ${err.message}`); - } - - CaliperUtils.assertDefined(queryResponse); - CaliperUtils.assertProperty(queryResponse, 'queryResponse', 'chaincodes'); - - if (queryResponse.chaincodes.some( - cc => cc.name === contractInfo.id && cc.version === contractInfo.version)) { - logger.info(`${contractInfo.id}@${contractInfo.version} is already instantiated in ${channel}`); - continue; - } - - contractInstantiated = true; - - const txId = admin.newTransactionID(true); - /** @link{ContractInstantiateUpgradeRequest} */ - const request = { - targets: targetPeers, - chaincodeId: ccObject.id, - chaincodeVersion: ccObject.version, - chaincodeType: ccObject.language, - args: ccObject.init || [], - fcn: ccObject.function || 'init', - 'endorsement-policy': ccObject['endorsement-policy'] || - this.networkUtil.getDefaultEndorsementPolicy(channel, { id: ccObject.id, version: ccObject.version }), - transientMap: this.networkUtil.getTransientMapOfContractOfChannel(contractInfo, channel), - txId: txId - }; - - // check contract language - // other contracts types are not supported in every version - if (ccObject.language !== 'golang') { - if (ccObject.language === 'node' && this.networkUtil.isInCompatibilityMode()) { - throw new Error(`${contractInfo.id}@${contractInfo.version} in ${channel}: Node.js contracts are supported starting from Fabric v1.1`); - } - - if (ccObject.language === 'java' && this.version.lessThan('1.3.0')) { - throw new Error(`${contractInfo.id}@${contractInfo.version} in ${channel}: Java contracts are supported starting from Fabric v1.3`); - } - - if (!['golang', 'node', 'java'].includes(ccObject.language)) { - throw new Error(`${contractInfo.id}@${contractInfo.version} in ${channel}: unknown contract type ${ccObject.language}`); - } - } - - // check private collection configuration - if (CaliperUtils.checkProperty(ccObject, 'collections-config')) { - if (this.version.lessThan('1.2.0')) { - throw new Error(`${contractInfo.id}@${contractInfo.version} in ${channel}: private collections are supported from Fabric v1.2`); - } - - request['collections-config'] = ccObject['collections-config']; - } - - /** @link{ProposalResponseObject} */ - let response; - try { - response = await admin.getChannel(channel, true).sendInstantiateProposal(request, this.configContractInstantiateTimeout); - } catch (err) { - throw new Error(`Couldn't endorse ${contractInfo.id}@${contractInfo.version} in ${channel} on peers [${targetPeers.toString()}]: ${err.message}`); - } - - CaliperUtils.assertDefined(response); - - /** @link{Array} */ - const proposalResponses = response[0]; - /** @link{Proposal} */ - const proposal = response[1]; - CaliperUtils.assertDefined(proposalResponses); - CaliperUtils.assertDefined(proposal); - - // check each response - proposalResponses.forEach((propResp, index) => { - CaliperUtils.assertDefined(propResp); - // an Error is returned for a rejected proposal - if (propResp instanceof Error) { - throw new Error(`Invalid endorsement for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${targetPeers[index]}: ${propResp.message}`); - } else if (propResp.response.status !== 200) { - throw new Error(`Invalid endorsement for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${targetPeers[index]}: status code ${propResp.response.status}`); - } - }); - - // connect to every event source of every org in the channel - const eventSources = this._assembleTargetEventSources(channel, targetPeers); - const eventPromises = []; - - try { - // NOTE: everything is resolved, errors are signaled through an Error object - // this makes error handling and reporting easier - eventSources.forEach((es) => { - const promise = new Promise((resolve) => { - const timeoutHandle = setTimeout(() => { - // unregister manually - es.eventHub.unregisterTxEvent(txId.getTransactionID(), false); - resolve(new Error(`Commit timeout for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${es.peer}`)); - }, this.configContractInstantiateEventTimeout); - - es.eventHub.registerTxEvent(txId.getTransactionID(), (tx, code) => { - clearTimeout(timeoutHandle); - if (code !== 'VALID') { - resolve(new Error(`Invalid commit code for ${contractInfo.id}@${contractInfo.version} in ${channel} from ${es.peer}: ${code}`)); - } else { - resolve(code); - } - }, /* Error handler */ (err) => { - clearTimeout(timeoutHandle); - resolve(new Error(`Event hub error from ${es.peer} during instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${err.message}`)); - }); - - es.eventHub.connect(); - }); - - eventPromises.push(promise); - }); - - /** @link{TransactionRequest} */ - const ordererRequest = { - txId: txId, - proposalResponses: proposalResponses, - proposal: proposal - }; - - /** @link{BroadcastResponse} */ - let broadcastResponse; - try { - broadcastResponse = await admin.getChannel(channel, true).sendTransaction(ordererRequest); - } catch (err) { - throw new Error(`Orderer error for instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${err.message}`); - } - - CaliperUtils.assertDefined(broadcastResponse); - CaliperUtils.assertProperty(broadcastResponse, 'broadcastResponse', 'status'); - - if (broadcastResponse.status !== 'SUCCESS') { - throw new Error(`Orderer error for instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}: ${broadcastResponse.status}`); - } - - // since every event promise is resolved, this shouldn't throw an error - const eventResults = await Promise.all(eventPromises); - - // if we received an error, propagate it - if (eventResults.some(er => er instanceof Error)) { - let errMsg = `The following errors occured while instantiating ${contractInfo.id}@${contractInfo.version} in ${channel}:`; - let err; // keep the last error - for (const eventResult of eventResults) { - if (eventResult instanceof Error) { - err = eventResult; - errMsg += `\n\t- ${eventResult.message}`; - } - } - - logger.error(errMsg); - throw err; - } - - logger.info(`Successfully instantiated ${contractInfo.id}@${contractInfo.version} in ${channel}`); - } finally { - eventSources.forEach(es => { - if (es.eventHub.isconnected()) { - es.eventHub.disconnect(); - } - }); - } - } - } - - return contractInstantiated; - } - - /** - * Joins the peers to the specified channels is necessary. - * @return {boolean} True, if at least one peer joined a channel. Otherwise, false. - * @private - * @async - */ - async _joinChannels() { - const channels = this.networkUtil.getChannels(); - let channelJoined = false; - const errors = []; - - for (const channelName of channels) { - let genesisBlock = null; - const orgs = this.networkUtil.getOrganizationsOfChannel(channelName); - - for (const org of orgs) { - const admin = this.adminProfiles.get(org); - const channelObject = admin.getChannel(channelName, true); - - const peers = this.networkUtil.getPeersOfOrganizationAndChannel(org, channelName); - const peersToJoin = []; - - for (const peer of peers) { - try { - /** {@link ChannelQueryResponse} */ - const resp = await admin.queryChannels(peer, true); - if (resp.channels.some(ch => ch.channel_id === channelName)) { - logger.info(`${peer} has already joined ${channelName}`); - continue; - } - - peersToJoin.push(peer); - } catch (err) { - errors.push(new Error(`Couldn't query ${channelName} information from ${peer}: ${err.message}`)); - } - } - - if (errors.length > 0) { - let errMsg = `The following errors occurred while querying ${channelName} information from ${org}'s peers:`; - for (const err of errors) { - errMsg += `\n\t- ${err.message}`; - } - - logger.error(errMsg); - throw new Error(`Couldn't query ${channelName} information from ${org}'s peers`); - } - - // all target peers of the org have already joined the channel - if (peersToJoin.length < 1) { - continue; - } - - channelJoined = true; - - // only retrieve the genesis block once, and "cache" it - if (genesisBlock === null) { - try { - const genesisTxId = admin.newTransactionID(true); - /** @link{OrdererRequest} */ - const genesisRequest = { - txId: genesisTxId - }; - genesisBlock = await channelObject.getGenesisBlock(genesisRequest); - } catch (err) { - throw new Error(`Couldn't retrieve the genesis block for ${channelName}: ${err.message}`); - } - } - - const joinTxId = admin.newTransactionID(true); - const joinRequest = { - block: genesisBlock, - txId: joinTxId, - targets: peersToJoin - }; - - try { - /**{@link ProposalResponse} array*/ - const joinRespArray = await channelObject.joinChannel(joinRequest); - CaliperUtils.assertDefined(joinRespArray); - - // Some errors are returned as Error instances, some as error messages - joinRespArray.forEach((propResponse, index) => { - if (propResponse instanceof Error) { - errors.push(new Error(`${peersToJoin[index]} could not join ${channelName}: ${propResponse.message}`)); - } else if (propResponse.response.status !== 200) { - errors.push(new Error(`${peersToJoin[index]} could not join ${channelName}: ${propResponse.response.message}`)); - } - }); - } catch (err) { - throw new Error(`Couldn't join peers ${peersToJoin.toString()} to ${channelName}: ${err.message}`); - } - - if (errors.length > 0) { - let errMsg = `The following errors occurred while ${org}'s peers tried to join ${channelName}:`; - for (const err of errors) { - errMsg += `\n\t- ${err.message}`; - } - - logger.error(errMsg); - throw new Error(`${org}'s peers couldn't join ${channelName}`); - } - - logger.info(`${org}'s peers successfully joined ${channelName}: ${peersToJoin}`); - } - } - - return channelJoined; - } - - /** - * Prepares caches (pre-calculated values) used during transaction invokes. - * @private - */ - _prepareCaches() { - // assemble random target peer cache for each channel's each contract - for (const channel of this.networkUtil.getChannels()) { - this.randomTargetPeerCache.set(channel, new Map()); - - for (const contract of this.networkUtil.getContractsOfChannel(channel)) { - const idAndVersion = `${contract.id}@${contract.version}`; - this.randomTargetPeerCache.get(channel).set(idAndVersion, new Map()); - - const targetOrgs = new Set(); - const targetPeers = this.networkUtil.getTargetPeersOfContractOfChannel(contract, channel); - - // get target orgs - for (const peer of targetPeers) { - targetOrgs.add(this.networkUtil.getOrganizationOfPeer(peer)); - } - - // set target peers in each org - for (const org of targetOrgs) { - const peersOfOrg = this.networkUtil.getPeersOfOrganizationAndChannel(org, channel); - - // the peers of the org that target the given contract of the given channel - // one of these peers needs to be a target for every org - // NOTE: this assumes an n-of-n endorsement policy, which is a safe default - this.randomTargetPeerCache.get(channel).get(idAndVersion).set(org, [...peersOfOrg].filter(p => targetPeers.has(p))); - } - } - } - - // assemble random target orderer cache for each channel - for (const channel of this.networkUtil.getChannels()) { - this.randomTargetOrdererCache.set(channel, Array.from(this.networkUtil.getOrderersOfChannel(channel))); - } - } - - /** - * Partially assembles a Client object containing general network information. - * @param {string} org The name of the organization the client belongs to. Mandatory, if the client name is omitted. - * @param {string} clientName The name of the client to base the profile on. - * If omitted, then the first client of the organization will be used. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @return {Promise} The partially assembled Client object. - * @private - * @async - */ - async _prepareClientProfile(org, clientName, profileName) { - let client = clientName; - if (!client) { - CaliperUtils.assertDefined(org); - // base it on the first client connection profile of the org - const clients = this.networkUtil.getClientsOfOrganization(org); - - // NOTE: this assumes at least one client per org, which is reasonable, the clients will interact with the network - if (clients.size < 1) { - throw new Error(`At least one client specification for ${org} is needed to initialize the ${profileName || 'profile'}`); - } - - client = Array.from(clients)[0]; - } - - // load the general network data from a clone of the network object - // NOTE: if we provide a common object instead, the Client class will use it directly, - // and it will be overwritten when loading the next client - const profile = FabricClient.loadFromConfig(this.networkUtil.getNewNetworkObject()); - profile.loadFromConfig({ - version: '1.0', - client: this.networkUtil.getClientObject(client) - }); - - try { - await profile.initCredentialStores(); - } catch (err) { - throw new Error(`Couldn't initialize the credential stores for ${org}'s ${profileName || 'profile'}: ${err.message}`); - } - - return profile; - } - - /** - * Sets the mutual TLS for the admin of the given organization. - * @param {string} org The name of the organization. - * @private - */ - _setTlsAdminCertAndKey(org) { - const profile = this.adminProfiles.get(org); - const crypto = this.networkUtil.getAdminCryptoContentOfOrganization(org); - profile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); - } - - /** - * Tries to set the given identity as the current user context for the given profile. Enrolls it if needed and can. - * @param {Client} profile The Client object whose user context must be set. - * @param {string} userName The name of the user. - * @param {string} password The password for the user. - * @param {string} profileName Optional name of the profile that will appear in error messages. - * @private - * @async - */ - async _setUserContextByEnrollment(profile, userName, password, profileName) { - try { - // automatically tries to enroll the given identity with the CA (must be registered) - await profile.setUserContext({ - username: userName, - password: password - }, false); - } catch (err) { - throw new Error(`Couldn't enroll ${profileName || 'the user'} or set it as user context: ${err.message}`); - } - } - - /** - * Queries the specified contract according to the provided settings. - * - * @param {ContractQuerySettings} querySettings The settings associated with the query. - * @param {number} timeout The timeout for the call in milliseconds. - * @return {Promise} The result and stats of the transaction query. - */ - async _submitSingleQuery(querySettings, timeout) { - const startTime = Date.now(); - this.txIndex++; - - // retrieve the necessary client/admin profile - let invoker; - let admin = false; - - if (querySettings.invokerIdentity.startsWith('#')) { - invoker = this.adminProfiles.get(querySettings.invokerIdentity.substring(1)); - admin = true; - } else { - invoker = this.clientProfiles.get(querySettings.invokerIdentity); - } - - // this hints at an error originating from the outside, so it should terminate - if (!invoker) { - throw Error(`Invoker ${querySettings.invokerIdentity} not found!`); - } - - const txIdObject = invoker.newTransactionID(admin); - const txId = txIdObject.getTransactionID(); - - const invokeStatus = new TxStatus(txId); - invokeStatus.Set('request_type', 'query'); - invokeStatus.SetVerification(true); // querying is a one-step process unlike a normal transaction, so the result is always verified - - //////////////////////////////// - // SEND TRANSACTION PROPOSALS // - //////////////////////////////// - - const targetPeers = querySettings.targetPeers || - this._assembleRandomTargetPeers(querySettings.channel, querySettings.contractId, querySettings.contractVersion); - - /** @link{ChaincodeInvokeRequest} */ - const proposalRequest = { - chaincodeId: querySettings.contractId, - fcn: querySettings.contractFunction, - args: querySettings.contractArguments || [], - transientMap: querySettings.transientMap, - targets: targetPeers - }; - - // the exception should propagate up for an invalid channel name, indicating a user callback module error - const channel = invoker.getChannel(querySettings.channel, true); - - /** Array of {Buffer|Error} */ - let results = null; - - // NOTE: everything happens inside a try-catch - // no exception should escape, query failures have to be handled gracefully - try { - // NOTE: wrap it in a Promise to enforce user-provided timeout - const resultPromise = new Promise(async (resolve, reject) => { - const timeoutHandle = setTimeout(() => { - reject(new Error('TIMEOUT')); - }, this._getRemainingTimeout(startTime, timeout)); - - invokeStatus.Set('time_create', Date.now()); - try { - const result = await channel.queryByChaincode(proposalRequest, admin); - clearTimeout(timeoutHandle); - resolve(result); - } catch(err) { - clearTimeout(timeoutHandle); - reject(err); - } - }); - - results = await resultPromise; - - /////////////////////// - // CHECK THE RESULTS // - /////////////////////// - - let errMsg; - - // filter for errors inside, so we have accurate indices for the corresponding peers - results.forEach((value, index) => { - const targetName = targetPeers[index]; - if (value instanceof Error) { - invokeStatus.Set(`endorsement_result_error_${targetName}`, value.message); - errMsg = `\n\t- Endorsement error from ${targetName}: ${value.message}`; - } else { - // NOTE: the last result will be kept - invokeStatus.SetResult(value); - invokeStatus.Set(`endorsement_result_${targetName}`, value); - } - }); - - if (errMsg) { - invokeStatus.SetStatusFail(); - logger.error(`Query error for ${querySettings.contractId}@${querySettings.contractVersion} in ${querySettings.channel}:${errMsg}`); - } else { - invokeStatus.SetStatusSuccess(); - } - } catch (err) { - invokeStatus.SetStatusFail(); - invokeStatus.Set('unexpected_error', err.message); - logger.error(`Unexpected query error for ${querySettings.contractId}@${querySettings.contractVersion} in ${querySettings.channel}: ${err.stack ? err.stack : err}`); - } - - return invokeStatus; - } - - /** - * Invokes the specified contract according to the provided settings. - * - * @param {ContractInvokeSettings} invokeSettings The settings associated with the transaction submission. - * @param {number} timeout The timeout for the whole transaction life-cycle in milliseconds. - * @return {Promise} The result and stats of the transaction invocation. - */ - async _submitSingleTransaction(invokeSettings, timeout) { - // note start time to adjust the timeout parameter later - const startTime = Date.now(); - this.txIndex++; // increase the counter - - // NOTE: since this function is a hot path, there aren't any assertions for the sake of efficiency - - // retrieve the necessary client/admin profile - let invoker; - let admin = false; - - if (invokeSettings.invokerIdentity.startsWith('#')) { - invoker = this.adminProfiles.get(invokeSettings.invokerIdentity.substring(1)); - admin = true; - } else { - invoker = this.clientProfiles.get(invokeSettings.invokerIdentity); - } - - // this hints at an error originating from the outside, so it should terminate - if (!invoker) { - throw Error(`Invoker ${invokeSettings.invokerIdentity} not found!`); - } - - //////////////////////////////// - // PREPARE SOME BASIC OBJECTS // - //////////////////////////////// - - const txIdObject = invoker.newTransactionID(admin); - const txId = txIdObject.getTransactionID(); - - // timestamps are recorded for every phase regardless of success/failure - const invokeStatus = new TxStatus(txId); - invokeStatus.Set('request_type', 'transaction'); - - const errors = []; // errors are collected during response validations - - //////////////////////////////// - // SEND TRANSACTION PROPOSALS // - //////////////////////////////// - - const channel = invoker.getChannel(invokeSettings.channel, true); - let targetPeers; - - if (invokeSettings.targetPeers) { - targetPeers = invokeSettings.targetPeers; - } else if (invokeSettings.targetOrganizations) { - if (Array.isArray(invokeSettings.targetOrganizations) && invokeSettings.targetOrganizations.length > 0) { - // discovery is never enabled for low level fabric client, so just target all peers in the org - targetPeers = this._getEndorsingPeersForOrgs(channel, invokeSettings.targetOrganizations); - } else { - logger.warn(`${invokeSettings.targetOrganizations} is not a populated array, no orgs targetted`); - } - } - if (!targetPeers) { - targetPeers = this._assembleRandomTargetPeers(invokeSettings.channel, invokeSettings.contractId, invokeSettings.contractVersion); - } else if (targetPeers.length === 0) { - logger.warn('No peers found to be able to target. If targetting organizations, check the organization exists'); - } - - /** @link{ChaincodeInvokeRequest} */ - const proposalRequest = { - chaincodeId: invokeSettings.contractId, - fcn: invokeSettings.contractFunction, - args: invokeSettings.contractArguments || [], - txId: txIdObject, - transientMap: invokeSettings.transientMap, - targets: targetPeers - }; - - - /** @link{ProposalResponseObject} */ - let proposalResponseObject = null; - - // NOTE: everything happens inside a try-catch - // no exception should escape, transaction failures have to be handled gracefully - try { - try { - // account for the elapsed time up to this point - invokeStatus.Set('time_create', Date.now()); - proposalResponseObject = await channel.sendTransactionProposal(proposalRequest, - this._getRemainingTimeout(startTime, timeout)); - - invokeStatus.Set('time_endorse', Date.now()); - } catch (err) { - invokeStatus.Set('time_endorse', Date.now()); - invokeStatus.Set('proposal_error', err.message); - - // error occurred, early life-cycle termination, definitely failed - invokeStatus.SetVerification(true); - - errors.push(err); - throw errors; // handle every logging in one place at the end - } - - ////////////////////////////////// - // CHECKING ENDORSEMENT RESULTS // - ////////////////////////////////// - - /** @link{Array} */ - const proposalResponses = proposalResponseObject[0]; - /** @link{Proposal} */ - const proposal = proposalResponseObject[1]; - - // NOTES: filter inside, so we have accurate indices corresponding to the original target peers - proposalResponses.forEach((value, index) => { - const targetName = targetPeers[index]; - - // Errors from peers/contract are returned as an Error object - if (value instanceof Error) { - invokeStatus.Set(`proposal_response_error_${targetName}`, value.message); - - // explicit rejection, early life-cycle termination, definitely failed - invokeStatus.SetVerification(true); - errors.push(new Error(`Proposal response error by ${targetName}: ${value.message}`)); - return; - } - - /** @link{ProposalResponse} */ - const proposalResponse = value; - - // save a contract results/response - // NOTE: the last one will be kept as result - invokeStatus.SetResult(proposalResponse.response.payload); - invokeStatus.Set(`endorsement_result_${targetName}`, proposalResponse.response.payload); - - // verify the endorsement signature and identity if configured - if (this.configVerifyProposalResponse) { - if (!channel.verifyProposalResponse(proposalResponse)) { - invokeStatus.Set(`endorsement_verify_error_${targetName}`, 'INVALID'); - - // explicit rejection, early life-cycle termination, definitely failed - invokeStatus.SetVerification(true); - errors.push(new Error(`Couldn't verify endorsement signature or identity of ${targetName}`)); - return; - } - } - - /** @link{ResponseObject} */ - const responseObject = proposalResponse.response; - - if (responseObject.status !== 200) { - invokeStatus.Set(`endorsement_result_error_${targetName}`, `${responseObject.status} ${responseObject.message}`); - - // explicit rejection, early life-cycle termination, definitely failed - invokeStatus.SetVerification(true); - errors.push(new Error(`Endorsement denied by ${targetName}: ${responseObject.message}`)); - } - }); - - // if there were errors, stop further processing, jump to the end - if (errors.length > 0) { - throw errors; - } - - if (this.configVerifyReadWriteSets) { - // check all the read/write sets to see if they're the same - if (!channel.compareProposalResponseResults(proposalResponses)) { - invokeStatus.Set('read_write_set_error', 'MISMATCH'); - - // r/w set mismatch, early life-cycle termination, definitely failed - invokeStatus.SetVerification(true); - errors.push(new Error('Read/Write set mismatch between endorsements')); - throw errors; - } - } - - ///////////////////////////////// - // REGISTERING EVENT LISTENERS // - ///////////////////////////////// - - const eventPromises = []; // to wait for every event response - - // NOTE: in compatibility mode, the same EventHub can be used for multiple channels - // if the peer is part of multiple channels - this.channelEventSourcesCache.get(invokeSettings.channel).forEach((eventSource) => { - eventPromises.push(this._createEventRegistrationPromise(eventSource, - txId, invokeStatus, startTime, timeout)); - }); - - /////////////////////////////////////////// - // SUBMITTING TRANSACTION TO THE ORDERER // - /////////////////////////////////////////// - - const targetOrderer = invokeSettings.orderer || this._getRandomTargetOrderer(invokeSettings.channel); - let orderer; - - if (typeof(targetOrderer) === 'string' || targetOrderer instanceof String) { - // Using an orderer name - orderer = channel.getOrderer(targetOrderer); - } else { - // Have been passed an orderer as an object within invokeSettings.orderer - throw new Error('Orderer object passed within invokeSettings: must reference target orderer by name'); - } - - /** @link{TransactionRequest} */ - const transactionRequest = { - proposalResponses: proposalResponses, - proposal: proposal, - orderer - }; - - /** @link{BroadcastResponse} */ - let broadcastResponse; - try { - // wrap it in a Promise to add explicit timeout to the call - const responsePromise = new Promise(async (resolve, reject) => { - const timeoutHandle = setTimeout(() => { - reject(new Error('TIMEOUT')); - }, this._getRemainingTimeout(startTime, timeout)); - - try { - const result = await channel.sendTransaction(transactionRequest); - clearTimeout(timeoutHandle); - resolve(result); - } catch(err) { - clearTimeout(timeoutHandle); - reject(err); - } - }); - - broadcastResponse = await responsePromise; - } catch (err) { - // either an explicit deny from the orderer - // or a timeout occurred (eating up all the allocated time for the TX) - invokeStatus.Set(`broadcast_error_${targetOrderer}`, err.message); - invokeStatus.SetVerification(true); - - errors.push(new Error(`Broadcast error from ${targetOrderer}: ${err.message}`)); - throw errors; - } - - invokeStatus.Set('time_orderer_ack', Date.now()); - - if (broadcastResponse.status !== 'SUCCESS') { - invokeStatus.Set(`broadcast_response_error_${targetOrderer}`, broadcastResponse.status); - - // the submission was explicitly rejected, so the Tx will definitely not be ordered - invokeStatus.SetVerification(true); - errors.push(new Error(`${targetOrderer} response error with status ${broadcastResponse.status}`)); - throw errors; - } - - ////////////////////////////// - // PROCESSING EVENT RESULTS // - ////////////////////////////// - - // this shouldn't throw, otherwise the error handling is not robust - const eventResults = await Promise.all(eventPromises); - - // NOTE: this is the latency@threshold support described by the PSWG in their first paper - const failedNotifications = eventResults.filter(er => !er.successful); - - // NOTE: an error from any peer indicates some problem, don't mask it; - // although one successful transaction should be enough for "eventual" success; - // errors from some peer indicate transient problems, errors from all peers probably indicate validation errors - if (failedNotifications.length > 0) { - invokeStatus.SetStatusFail(); - - let logMsg = `Transaction[${txId.substring(0, 10)}] commit errors:`; - for (const commitErrors of failedNotifications) { - logMsg += `\n\t- ${commitErrors.message}`; - } - - logger.error(logMsg); - } else { - // sort ascending by finish time - eventResults.sort((a, b) => a.time - b.time); - - // transform to (0,length] by *, then to (-1,length-1] by -, then to [0,length-1] by ceil - const thresholdIndex = Math.ceil(eventResults.length * this.configLatencyThreshold - 1); - - // every commit event contained a VALID code - // mark the time corresponding to the set threshold - invokeStatus.SetStatusSuccess(eventResults[thresholdIndex].time); - } - } catch (err) { - invokeStatus.SetStatusFail(); - - // not the expected error array was thrown, an unexpected error occurred, log it with stack if available - if (!Array.isArray(err)) { - invokeStatus.Set('unexpected_error', err.message); - logger.error(`Transaction[${txId.substring(0, 10)}] unexpected error: ${err.stack ? err.stack : err}`); - } else if (err.length > 0) { - let logMsg = `Transaction[${txId.substring(0, 10)}] life-cycle errors:`; - for (const execError of err) { - logMsg += `\n\t- ${execError.message}`; - } - - logger.error(logMsg); - } - } - - return invokeStatus; - } - - /** - * Get the named contract for a named user - * @param {string} invokerIdentity the user identity for interacting with the contract - * @param {string} contractName the name of the contract to return - * @returns {FabricNetworkAPI.Contract} A contract that may be used to submit or evaluate transactions - * @async - */ - async _getUserContract(invokerIdentity, contractName) { - - // Determine the invoking user for this transaction - let userName; - if (invokerIdentity.startsWith('#')) { - userName = invokerIdentity.substring(1); - } else { - userName = invokerIdentity; - } - - const contractSet = this.userContracts.get(userName); - - // If no contract set found, there is a user configuration/test specification error, so it should terminate - if (!contractSet) { - throw Error(`No contracts for Invoker ${userName} found!`); - } - - // Retrieve the named Network Contract for the invoking user from the Map - const contract = contractSet.get(contractName); - - // If no contract found, there is a user configuration/test specification error, so it should terminate - if (!contract) { - throw Error(`No contract named ${contractName} found!`); - } - - return contract; - } - - /** - * Initialize the channels and event hubs - * @async - */ - async _initializeChannelsAndEventHubs() { - for (const channel of this.networkUtil.getChannels()) { - // initialize the channels by getting the config from the orderer - await this._initializeChannel(this.adminProfiles, channel, true); - await this._initializeChannel(this.clientProfiles, channel, false); - } - - if (this.networkUtil.isInCompatibilityMode()) { - // NOTE: for old event hubs we have a single connection to every peer set as an event source - const EventHub = require('fabric-client/lib/EventHub.js'); - - for (const peer of this.networkUtil.getAllEventSources()) { - const org = this.networkUtil.getOrganizationOfPeer(peer); - const admin = this.adminProfiles.get(org); - - const eventHub = new EventHub(admin); - eventHub.setPeerAddr(this.networkUtil.getPeerEventUrl(peer), - this.networkUtil.getGrpcOptionsOfPeer(peer)); - - // we can use the same peer for multiple channels in case of peer-level eventing - this.eventSources.push({ - channel: this.networkUtil.getChannelsOfPeer(peer), - peer: peer, - eventHub: eventHub - }); - } - } else { - // NOTE: for channel event hubs we might have multiple connections to a peer, - // so connect to the defined event sources of every org in every channel - for (const channel of this.networkUtil.getChannels()) { - for (const org of this.networkUtil.getOrganizationsOfChannel(channel)) { - //TODO: Why are admins used here ? clients should be used really - const admin = this.adminProfiles.get(org); - - // The API for retrieving channel event hubs changed, from SDK v1.2 it expects the MSP ID of the org - const orgId = this.version.lessThan('1.2.0') ? org : this.networkUtil.getMspIdOfOrganization(org); - - const eventHubs = admin.getChannel(channel, true).getChannelEventHubsForOrg(orgId); - - // the peer (as an event source) is associated with exactly one channel in case of channel-level eventing - for (const eventHub of eventHubs) { - this.eventSources.push({ - channel: [channel], - peer: this.networkUtil.getPeerNameOfEventHub(eventHub), - eventHub: eventHub - }); - } - } - } - } - - this.eventSources.forEach((es) => { - es.eventHub.connect(false); - }); - - // rebuild the event source cache - this.channelEventSourcesCache = new Map(); - - for (const es of this.eventSources) { - const channels = es.channel; - - // an event source can be used for multiple channels in compatibility mode - for (const c of channels) { - // initialize the cache for a channel with an empty array at the first time - if (!this.channelEventSourcesCache.has(c)) { - this.channelEventSourcesCache.set(c, []); - } - - // add the event source to the channels collection - const eventSources = this.channelEventSourcesCache.get(c); - eventSources.push(es); - } - } - } - - /** - * Send a single request to the backing SUT. - * @param {FabricRequestSettings} request The request object. - */ - async _sendSingleRequest(request) { - if (!request.hasOwnProperty('channel')) { - const contractDetails = this.networkUtil.getContractDetails(request.contractId); - if (!contractDetails) { - throw new Error(`Could not find details for contract ID ${request.contractId}`); - } - request.channel = contractDetails.channel; - request.contractId = contractDetails.id; - request.contractVersion = contractDetails.version; - } - - if (!request.invokerIdentity) { - request.invokerIdentity = this.defaultInvoker; - } - - const timeout = (request.timeout || this.configDefaultTimeout) * 1000; - if (request.readOnly) { - return this._submitSingleQuery(request, timeout); - } else { - return this._submitSingleTransaction(request, timeout); - } - } -} - -module.exports = LegacyV1Fabric; diff --git a/packages/caliper-fabric/lib/connector-versions/v2/fabric-gateway.js b/packages/caliper-fabric/lib/connector-versions/v2/fabric-gateway.js deleted file mode 100644 index 529b9f1ed..000000000 --- a/packages/caliper-fabric/lib/connector-versions/v2/fabric-gateway.js +++ /dev/null @@ -1,645 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, Gateway, Wallets } = require('fabric-network'); -const { ConnectorBase, CaliperUtils, TxStatus, Version, ConfigUtil } = require('@hyperledger/caliper-core'); - -const FabricNetwork = require('../../fabricNetwork.js'); -const RegistrarHelper = require('./registrarHelper'); - -const logger = CaliperUtils.getLogger('connectors/v2/fabric-gateway'); - -const EventStrategies = { - msp_all : DefaultEventHandlerStrategies.MSPID_SCOPE_ALLFORTX, - msp_any : DefaultEventHandlerStrategies.MSPID_SCOPE_ANYFORTX, - network_all : DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX, - network_any : DefaultEventHandlerStrategies.NETWORK_SCOPE_ANYFORTX, -}; - -const QueryStrategies = { - msp_single : DefaultQueryHandlerStrategies.MSPID_SCOPE_SINGLE, - msp_round_robin : DefaultQueryHandlerStrategies.MSPID_SCOPE_ROUND_ROBIN, -}; - -////////////////////// -// TYPE DEFINITIONS // -////////////////////// - -/** - * @typedef {Object} ContractInvokeSettings - * - * @property {string} contractId Required. The name/ID of the contract whose function - * should be invoked. - * @property {string} contractVersion Required. The version of the contract whose function - * should be invoked. - * @property {string} contractFunction Required. The name of the function that should be - * invoked in the contract. - * @property {string[]} [contractArguments] Optional. The list of {string} arguments that should - * be passed to the contract. - * @property {Map} [transientMap] Optional. The transient map that should be - * passed to the contract. - * @property {string} invokerIdentity Required. The name of the client who should invoke the - * contract. If an admin is needed, use the organization name prefixed with a # symbol. - * @property {string} channel Required. The name of the channel whose contract should be invoked. - * @property {string[]} [targetPeers] Optional. An array of endorsing - * peer names as the targets of the invoke. When this - * parameter is omitted the target list will include the endorsing peers assigned - * to the target contract, or if it is also omitted, to the channel. - * @property {string[]} [targetOrganizations] Optional. An array of endorsing - * organizations as the targets of the invoke. If both targetPeers and targetOrganizations - * are specified then targetPeers will take precedence - * @property {string} [orderer] Optional. The name of the orderer to whom the request should - * be submitted. If omitted, then the first orderer node of the channel will be used. - */ - -/** - * @typedef {Object} ContractQuerySettings - * - * @property {string} contractId Required. The name/ID of the contract whose function - * should be invoked. - * @property {string} contractVersion Required. The version of the contract whose function - * should be invoked. - * @property {string} contractFunction Required. The name of the function that should be - * invoked in the contract. - * @property {string[]} [contractArguments] Optional. The list of {string} arguments that should - * be passed to the contract. - * @property {Map} [transientMap] Optional. The transient map that should be - * passed to the contract. - * @property {string} invokerIdentity Required. The name of the client who should invoke the - * contract. If an admin is needed, use the organization name prefixed with a # symbol. - * @property {string} channel Required. The name of the channel whose contract should be invoked. - * @property {boolean} [countAsLoad] Optional. Indicates whether to count this query as workload. - */ - -///////////////////////////// -// END OF TYPE DEFINITIONS // -///////////////////////////// - -/** - * Legacy (old network config format) Connector for V2 Node SDK using Gateway API - */ -class LegacyV2FabricGateway extends ConnectorBase { - /** - * Initializes the Fabric adapter. - * @param {object} networkObject The parsed network configuration. - * @param {number} workerIndex the worker index - * @param {string} bcType The target SUT type - */ - constructor(networkObject, workerIndex, bcType) { - super(workerIndex, bcType); - this.version = new Version(require('fabric-network/package').version); - - // clone the object to prevent modification by other objects - this.network = CaliperUtils.parseYamlString(CaliperUtils.stringifyYaml(networkObject)); - - this.txIndex = -1; - this.networkUtil = new FabricNetwork(this.network); - this.defaultInvoker = Array.from(this.networkUtil.getClients())[0]; - - this.orgWallets = new Map(); - this.userContracts = new Map(); - this.userGateways = new Map(); - this.peerCache = new Map(); - this.context = undefined; - - // Timeouts - this.configSmallestTimeout = 1000; - this.configDefaultTimeout = ConfigUtil.get(ConfigUtil.keys.Fabric.Timeout.InvokeOrQuery, 60); - this.configCountQueryAsLoad = ConfigUtil.get(ConfigUtil.keys.Fabric.CountQueryAsLoad, true); - - // Gateway connector - this.configLocalHost = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.LocalHost, true); - this.configDiscovery = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.Discovery, false); - this.eventStrategy = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.EventStrategy, 'msp_all'); - this.queryStrategy = ConfigUtil.get(ConfigUtil.keys.Fabric.Gateway.QueryStrategy, 'msp_single'); - } - - //////////////////////////////// - // INTERNAL UTILITY FUNCTIONS // - //////////////////////////////// - - /** - * Initialize the connector - */ - async _initConnector() { - logger.debug('Entering _initConnector'); - const tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' - : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); - logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}`); - - await this._prepareOrgWallets(); - await this._initializeAdmins(); - // Initialize registrars *after* initialization of admins so that admins are not created - this.registrarHelper = await RegistrarHelper.newWithNetwork(this.networkUtil); - await this._initializeUsers(); - logger.debug('Exiting _initConnector'); - } - - /** - * Initializes the admins of the organizations. - * - * @private - * @async - */ - async _initializeAdmins() { - logger.info('Initializing administrators'); - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const adminName = `admin.${org}`; - - // Check if the caliper config file has this identity supplied - if (!this.networkUtil.getClients().has(adminName)) { - logger.info(`No ${adminName} found in caliper configuration file - unable to perform admin options`); - continue; - } - - // Since admin exists, conditionally use it - logger.info(`Administrator ${adminName} found in caliper configuration file - checking for ability to use the identity`); - const usesOrgWallets = this.networkUtil.usesOrganizationWallets(); - if (usesOrgWallets) { - // If a file wallet is provided, it is expected that *all* required identities are provided - // Admin is a super-user identity, and is consequently optional - const orgWallet = this.orgWallets.get(org); - const hasAdmin = await orgWallet.get(adminName); - if (!hasAdmin) { - logger.info(`No ${adminName} found in wallet - unable to perform admin options using client specified in caliper configuration file`); - } - } else { - // Build up the admin identity based on caliper client items and add to the in-memory wallet - const cryptoContent = this.networkUtil.getAdminCryptoContentOfOrganization(org); - if (!cryptoContent) { - logger.info(`No ${adminName} cryptoContent found in caliper configuration file - unable to perform admin options`); - continue; - } else { - await this._addToOrgWallet(org, cryptoContent.signedCertPEM, cryptoContent.privateKeyPEM, adminName); - } - } - logger.info(`${org}'s admin's materials are successfully loaded`); - } - logger.info('Completed initializing administrators'); - } - - /** - * Registers and enrolls the specified users if necessary. - * - * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. - * @private - * @async - */ - async _initializeUsers() { - logger.info('Initializing users'); - - // Ensure clients passed by the config are able to be used - // - They must be present in a wallet - // - Use passed material if present - // - Register and enroll each client with its organization's CA as a fall back option - for (const clientName of this.networkUtil.getClients()) { - const orgName = this.networkUtil.getOrganizationOfClient(clientName); - - const orgWallet = this.orgWallets.get(orgName); - const hasClient = await orgWallet.get(clientName); - if (hasClient) { - logger.info(`Client ${clientName} present in wallet: skipping client enrollment`); - continue; - } else { - // Extract required information from the supplied caliper config - const cryptoContent = this.networkUtil.getClientCryptoContent(clientName); - if (cryptoContent) { - logger.info(`Client ${clientName} being initialized using provided crypto content`); - await this._addToOrgWallet(orgName, cryptoContent.signedCertPEM.toString('utf8'), cryptoContent.privateKeyPEM.toString('utf8'), clientName); - } else { - logger.info(`No crypto content provided for client ${clientName}; will attempt to register and enrol`); - try { - // Check if there is a valid registrar to use for the org - if (this.registrarHelper.registrarExistsForOrg(orgName)) { - // Do we have an enrollment secret? - const enrollmentSecret = this.networkUtil.getClientEnrollmentSecret(clientName); - if (enrollmentSecret) { - // enrolled, so register with enrollment secret - const enrollment = await this.registrarHelper.enrollUserForOrg(orgName, clientName, enrollmentSecret); - await this._addToOrgWallet(orgName, enrollment.certificate, enrollment.key.toBytes(), clientName); - } else { - // Register and enrol - const secret = await this.registrarHelper.registerUserForOrg(orgName, clientName); - const enrollment = await this.registrarHelper.enrollUserForOrg(orgName, clientName, secret); - await this._addToOrgWallet(orgName, enrollment.certificate, enrollment.key.toBytes(), clientName); - } - } else { - logger.warn(`Required registrar for organization ${orgName} does not exist; unable to enroll client with identity ${clientName}.`); - } - } catch (error) { - logger.warn(`Failed to enrol client with identity ${clientName}. This client will be unavailable for use, due to error ${error.toString()}`); - continue; - } - } - } - } - logger.info('Completed initializing users'); - } - - /** - * Add a user to the wallet under a provided name - * @param {string} org, the organization name - * @param {string} certificate the user certificate - * @param {string} key the private key matching the certificate - * @param {string} identityName the name to store the User as within the wallet - * @async - */ - async _addToOrgWallet(org, certificate, key, identityName) { - logger.info(`Adding identity for name ${identityName} to wallet for organization ${org}`); - const identity = { - credentials: { - certificate: certificate, - privateKey: key, - }, - mspId: this.networkUtil.getMspIdOfOrganization(org), - type: 'X.509', - }; - - const orgWallet = this.orgWallets.get(org); - await orgWallet.put(identityName, identity); - logger.info(`Identity ${identityName} created and imported to wallet`); - } - - /** - * Extract and persist Contracts from Gateway Networks for identities listed within the wallet - * @async - */ - async _initializeContracts() { - logger.debug('Entering _initializeContracts'); - for (const walletOrg of this.orgWallets.keys()) { - logger.info(`Retrieving and persisting contract map for organization ${walletOrg}`); - const orgWallet = this.orgWallets.get(walletOrg); - - // Prepare client contracts based on wallet identities only - const walletIdentities = await orgWallet.list(); - for (const identity of walletIdentities) { - logger.info(`Retrieving and persisting contract map for identity ${identity}`); - // Retrieve - const contractMap = await this._retrieveContractMapForIdentity(identity, orgWallet); - // Persist - this.userContracts.set(identity, contractMap); - } - } - logger.debug('Exiting _initializeContracts'); - } - - /** - * Retrieve all Contracts using a passed identity and organization wallet - * @param {string} identity, the unique client identity name - * @param {FileSystemWallet | InMemoryWallet} wallet, the wallet that holds the passed identity name - * @returns {Map} A map of all Contracts retrieved from the client Gateway - * @async - */ - async _retrieveContractMapForIdentity(identity, wallet) { - logger.debug('Entering _retrieveContractsForIdentity'); - // Retrieve the gateway for the passed identity. The gateway object is persisted for easier cleanup. - // - userName must match that created for wallet userId in init phases - const gateway = await this._retrieveUserGateway(identity, wallet); - this.userGateways.set(identity, gateway); - - // Work on all channels to build a contract map - logger.info(`Generating contract map for user ${identity}`); - const contractMap = new Map(); - const channels = this.networkUtil.getChannels(); - for (const channel of channels) { - // retrieve the channel network - const network = await gateway.getNetwork(channel); - // Work on all contracts/smart contracts in the channel - const contracts = this.networkUtil.getContractsOfChannel(channel); - for (const contract of contracts) { - const networkContract = await network.getContract(contract.id); - contractMap.set(`${channel}_${contract.id}`, networkContract); - } - } - - logger.debug('Exiting _retrieveContractsForIdentity'); - return contractMap; - } - - /** - * Retrieve a Gateway object for the passed userId - * @param {string} identity string identity - * @param {FileSystemWallet | InMemoryWallet} wallet, the wallet that holds the passed identity name - * @returns {FabricNet.Gateway} a gateway object for the passed user identity - * @async - */ - async _retrieveUserGateway(identity, wallet) { - logger.debug(`Entering _retrieveUserGateway for identity name ${identity}`); - // Build options for the connection - const opts = { - identity, - wallet, - discovery: { - asLocalhost: this.configLocalHost, - enabled: this.configDiscovery - }, - eventHandlerOptions: { - commitTimeout: this.configDefaultTimeout, - strategy: EventStrategies[this.eventStrategy] - }, - queryHandlerOptions: { - timeout: this.configDefaultTimeout, - strategy: QueryStrategies[this.queryStrategy] - } - }; - - // Optional on mutual auth - if (this.networkUtil.isMutualTlsEnabled()) { - opts.clientTlsIdentity = identity; - } - - // Retrieve gateway using ccp and options - const gateway = new Gateway(); - - try { - logger.info(`Connecting user with identity ${identity} to a Network Gateway`); - await gateway.connect(this.networkUtil.getNetworkObject(), opts); - logger.info(`Successfully connected user with identity ${identity} to a Network Gateway`); - } catch (err) { - logger.error(`Connecting user with identity ${identity} to a Network Gateway failed with error: ${err}`); - } - - // return the gateway object - logger.debug('Exiting _retrieveUserGateway'); - return gateway; - } - - /** - * Initialize channel objects for use in peer targeting. Requires user gateways to have been - * formed in advance. - */ - async _initializePeerCache() { - logger.debug('Entering _initializePeerCache'); - for (const userName of this.userGateways.keys()) { - const gateway = this.userGateways.get(userName); - // Loop over known channel names - const channelNames = this.networkUtil.getChannels(); - for (const channelName of channelNames) { - const network = await gateway.getNetwork(channelName); - const channel = network.getChannel(); - - // Add all peers - for (const peerObject of channel.client.getEndorsers()) { - this.peerCache.set(peerObject.name, peerObject); - } - } - } - logger.debug('Exiting _initializePeerCache'); - } - - /** - * Conditionally initializes the organization wallet map depending on network configuration - * @private - */ - async _prepareOrgWallets() { - logger.debug('Entering _prepareOrgWallets'); - if (this.networkUtil.usesOrganizationWallets()) { - logger.info('Using defined organization file system wallets'); - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const fileWalletPath = this.networkUtil.getWalletPathForOrganization(org); - if (fileWalletPath) { - const wallet = await Wallets.newFileSystemWallet(fileWalletPath); - this.orgWallets.set(org, wallet); - } else { - logger.warn(`No defined organization wallet for org ${org}`); - } - } - } else { - logger.info('Creating new InMemoryWallets for organizations'); - const orgs = this.networkUtil.getOrganizations(); - for (const org of orgs) { - const wallet = await Wallets.newInMemoryWallet(); - this.orgWallets.set(org, wallet); - } - } - logger.debug('Exiting _prepareOrgWallets'); - } - - /** - * Perform a transaction using a Gateway contract - * @param {ContractInvokeSettings|ContractQuerySettings} invokeSettings The settings associated with the transaction submission. - * @param {boolean} isSubmit boolean flag to indicate if the transaction is a submit or evaluate - * @return {Promise} The result and stats of the transaction invocation. - * @async - */ - async _performGatewayTransaction(invokeSettings, isSubmit) { - // Retrieve the existing contract for the invokerIdentity - const smartContract = await this._getUserContract(invokeSettings.invokerIdentity, invokeSettings.channel, invokeSettings.contractId); - - // Create a transaction - const transaction = smartContract.createTransaction(invokeSettings.contractFunction); - - // Build the Caliper TxStatus, this is a reduced item when compared to the low level API capabilities - // - TxID is not available until after transaction submit/evaluate and must be set at that point - const invokeStatus = new TxStatus(); - - // Add transient data if present - // - passed as key value pairing such as {"hello":"world"} - if (invokeSettings.transientMap) { - const transientData = {}; - const keys = Array.from(Object.keys(invokeSettings.transientMap)); - keys.forEach((key) => { - transientData[key] = Buffer.from(invokeSettings.transientMap[key]); - }); - transaction.setTransient(transientData); - } - - // Set endorsing peers if passed as a string array - if (invokeSettings.targetPeers) { - // Retrieved cached peer objects - const targetPeerObjects = []; - for (const name of invokeSettings.targetPeers) { - const peer = this.peerCache.get(name); - if (peer) { - targetPeerObjects.push(peer); - } - } - // Set the peer objects in the transaction - if (targetPeerObjects.length > 0) { - transaction.setEndorsingPeers(targetPeerObjects); - } - } else if (invokeSettings.targetOrganizations) { - if (Array.isArray(invokeSettings.targetOrganizations) && invokeSettings.targetOrganizations.length > 0) { - transaction.setEndorsingOrganizations(...invokeSettings.targetOrganizations); - } else { - logger.warn(`${invokeSettings.targetOrganizations} is not a populated array, no orgs targetted`); - } - } - - try { - let result; - if (isSubmit) { - invokeStatus.Set('request_type', 'transaction'); - invokeStatus.Set('time_create', Date.now()); - result = await transaction.submit(...invokeSettings.contractArguments); - } else { - if (invokeSettings.targetPeers || invokeSettings.targetOrganizations) { - logger.warn('targetPeers or targetOrganizations options are not valid for query requests'); - } - invokeStatus.Set('request_type', 'query'); - invokeStatus.Set('time_create', Date.now()); - result = await transaction.evaluate(...invokeSettings.contractArguments); - } - invokeStatus.result = result; - invokeStatus.verified = true; - invokeStatus.SetStatusSuccess(); - invokeStatus.SetID(transaction.getTransactionId()); - return invokeStatus; - } catch (err) { - logger.error(`Failed to perform ${isSubmit ? 'submit' : 'query' } transaction [${invokeSettings.contractFunction}] using arguments [${invokeSettings.contractArguments}], with error: ${err.stack ? err.stack : err}`); - invokeStatus.SetStatusFail(); - invokeStatus.result = []; - invokeStatus.SetID(transaction.getTransactionId()); - return invokeStatus; - } - } - - /** - * Get the named contract for a named user - * @param {string} invokerIdentity the user identity for interacting with the contract - * @param {string} channelName the channel name the contract exists on - * @param {string} contractId the name of the contract to return - * @returns {FabricNetworkAPI.Contract} A contract that may be used to submit or evaluate transactions - * @async - */ - async _getUserContract(invokerIdentity, channelName, contractId) { - logger.debug('Entering _getUserContract'); - // Determine the invoking user for this transaction - let userName; - if (invokerIdentity.startsWith('#')) { - userName = invokerIdentity.substring(1); - } else { - userName = invokerIdentity; - } - - const contractSet = this.userContracts.get(userName); - - // If no contract set found, there is a user configuration/test specification error, so it should terminate - if (!contractSet) { - throw Error(`No contracts for Invoker ${userName} found!`); - } - - // Retrieve the named Network Contract for the invoking user from the Map - const contract = contractSet.get(`${channelName}_${contractId}`); - - // If no contract found, there is a user configuration/test specification error, so it should terminate - if (!contract) { - throw Error(`Unable to find specified contract ${contractId} on channel ${channelName}!`); - } - - logger.debug('Exiting _getUserContract'); - return contract; - } - - ////////////////////////// - // PUBLIC API FUNCTIONS // - ////////////////////////// - - /** - * Prepares the adapter by either: - * - building a gateway object linked to a wallet ID - * - loading user data and connection to the event hubs. - * - * @param {Number} roundIndex The zero-based round index of the test. - * @param {Array} args Unused. - * @return {Promise<{networkInfo : FabricNetwork, eventSources: EventSource[]}>} Returns the network utility object. - * @async - */ - async getContext(roundIndex, args) { - // Reset counter for new test round - this.txIndex = -1; - - // Build Gateway Network Contracts for possible users and return the network object - // - within submit/evaluate, a contract will be used for a nominated user - await this._initializeContracts(); - - // - use gateways to build a peer cache - await this._initializePeerCache(); - - // We are done - return the networkUtil object - this.context = { - networkInfo: this.networkUtil, - clientIdx: this.workerIndex - }; - - return this.context; - } - - /** - * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and contracts. - * @param {boolean} workerInit unused - * @async - */ - async init() { - const tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' - : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); - logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}`); - - logger.warn(`Administrative actions are not possible with Fabric SDK version: ${this.version.toString()}`); - await this._initConnector(); - } - - /** - * Installs and initializes the specified contracts. - * @async - */ - async installSmartContract() { - logger.warn(`Install smart contract not available with Fabric SDK version: ${this.version.toString()}`); - } - - /** - * Send a single request to the backing SUT. - * @param {FabricRequestSettings} request The request object. - */ - async _sendSingleRequest(request) { - if (!request.hasOwnProperty('channel')) { - const contractDetails = this.networkUtil.getContractDetails(request.contractId); - if (!contractDetails) { - throw new Error(`Could not find details for contract ID ${request.contractId}`); - } - request.channel = contractDetails.channel; - request.contractId = contractDetails.id; - request.contractVersion = contractDetails.version; - } - - if (!request.invokerIdentity) { - request.invokerIdentity = this.defaultInvoker; - } - - return this._performGatewayTransaction(request, request.readOnly === undefined || !request.readOnly); - } - - /** - * Releases the resources of the adapter. - * - * @async - */ - async releaseContext() { - // Disconnect from all persisted user gateways - for (const userName of this.userGateways.keys()) { - const gateway = this.userGateways.get(userName); - logger.info(`disconnecting gateway for user ${userName}`); - gateway.disconnect(); - } - - // Clear peer cache - this.peerCache.clear(); - this.context = undefined; - }} - -module.exports = LegacyV2FabricGateway; diff --git a/packages/caliper-fabric/lib/connector-versions/v2/registrarHelper.js b/packages/caliper-fabric/lib/connector-versions/v2/registrarHelper.js deleted file mode 100644 index d262e3340..000000000 --- a/packages/caliper-fabric/lib/connector-versions/v2/registrarHelper.js +++ /dev/null @@ -1,236 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; -const { Wallets } = require('fabric-network'); -const { CaliperUtils } = require('@hyperledger/caliper-core'); -const logger = CaliperUtils.getLogger('identityHelper'); -const FabricCAServices = require('fabric-ca-client'); - -/** - * Helper for registering and enrolling with a Certificate Authority - */ -class RegistrarHelper { - - /** - * Retrieve an initialized RegistrarHelper - * @param {FabricNetwork} networkUtil the network - */ - static async newWithNetwork(networkUtil) { - logger.debug('Entering newWithNetwork'); - const identityHelper = new RegistrarHelper(networkUtil); - await identityHelper.initialize(); - logger.debug('Exiting newWithNetwork'); - return identityHelper; - } - - /** - * Constructor - * @param {FabricNetwork} networkUtil the network - */ - constructor(networkUtil) { - this.networkUtil = networkUtil; - this.registrarInfo = new Map(); - } - - /** - * Initialize the helper - */ - async initialize() { - logger.debug('Entering initialize'); - // Use a wallet for convenience - this.wallet = await Wallets.newInMemoryWallet(); - - // Loop over all known orgs to configure accessible CAs - for (const orgName of this.networkUtil.getOrganizations()) { - logger.debug(`Operating on organization ${orgName}`); - const caName = this.networkUtil.getCertificateAuthorityOfOrganization(orgName); - const caObject = this.networkUtil.getCertificateAuthority(caName); - if (caObject) { - logger.debug(`Retrieved Certificate Authority for organization ${orgName}`); - const tlsOptions = { - trustedRoots: [], - verify: caObject.verify || false - }; - const orgCA = new FabricCAServices(caObject.url, tlsOptions, caName); - - const registrarInfo = this.networkUtil.getRegistrarOfOrganization(orgName); - if (registrarInfo) { - logger.debug(`Retrieved Registrar information for organization ${orgName}`); - const registrarName = this.getRegistrarNameForOrg(orgName); - const registrarEnrollment = await this.enrollUser(orgCA, registrarInfo.enrollId, registrarInfo.enrollSecret); - const registrarIdentity = { - credentials: { - certificate: registrarEnrollment.certificate, - privateKey: registrarEnrollment.key.toBytes(), - }, - mspId: this.networkUtil.getMspIdOfOrganization(orgName), - type: 'X.509', - }; - await this.wallet.put(registrarName, registrarIdentity); - this.registrarInfo.set(orgName, {orgCA, registrarName}); - } else { - logger.warn(`No registrar information for org ${orgName}; unable to enrol users`); - } - } else { - logger.warn(`No CA provided for org ${orgName}; unable to enrol users`); - continue; - } - } - logger.debug('Exiting initialize'); - } - - /** - * Retrieve the registrar name for the passed org - * @param {string} orgName the org name - * @returns {string} the registrar name - */ - getRegistrarNameForOrg(orgName) { - return `registrar.${orgName}`; - } - - /** - * Check if a registrar exists for a passed org name - * @param {string} orgName the organization name - * @returns {boolean} boolean true if registrar exists; otherwise false - */ - registrarExistsForOrg(orgName) { - return this.registrarInfo.has(orgName); - } - - /** - * Register a user and return an enrollment secret - * @param {string} orgName the organization to register under - * @param {string} userID The user identity name to be registered - */ - async registerUserForOrg(orgName, userID) { - logger.debug(`Entering registerUserForOrg for organization ${orgName} userID ${userID}`); - const registrarInfo = this.registrarInfo.get(orgName); - const affiliation = this.networkUtil.getAffiliationOfUser(userID); - const userSecret = await this.registerUser(registrarInfo, userID, { affiliation }); - logger.debug('Exiting registerUserForOrg'); - return userSecret; - } - - /** - * Register a userID through a CA using an admin identity - * @param {object} registrarInfo {orgCA, registrarName} Registrar information used to perform the registration action - * @param {string} userID The user identity name to be registered - * @param {object} options options to be used during registration - */ - async registerUser(registrarInfo, userID, options = {}) { - logger.debug(`Entering registerUser for userID ${userID}`); - const identity = await this.wallet.get(registrarInfo.registrarName); - const provider = this.wallet.getProviderRegistry().getProvider(identity.type); - const user = await provider.getUserContext(identity, registrarInfo.registrarName); - - const userSecret = `${userID}_secret`; - const registerRequest = { - enrollmentID: userID, - enrollmentSecret: userSecret, - affiliation: options.affiliation || 'org1', // or eg. org1.department1 - attrs: [], - maxEnrollments: options.maxEnrollments || -1, // infinite enrollment by default - role: options.role || 'client' - }; - - if (options.issuer) { - // Everyone caliper creates can register clients. - registerRequest.attrs.push({ - name: 'hf.Registrar.Roles', - value: 'client' - }); - - // Everyone caliper creates can register clients that can register clients. - registerRequest.attrs.push({ - name: 'hf.Registrar.Attributes', - value: 'hf.Registrar.Roles, hf.Registrar.Attributes' - }); - } - - let idAttributes = options.attributes; - if (typeof idAttributes === 'string') { - try { - idAttributes = JSON.parse(idAttributes); - } catch (error) { - throw new Error('attributes provided are not valid JSON. ' + error); - } - } - - for (const attribute in idAttributes) { - registerRequest.attrs.push({ - name: attribute, - value: idAttributes[attribute] - }); - } - - try { - await registrarInfo.orgCA.register(registerRequest, user); - } catch (error) { - // Might fail if previously registered, in which case pass back the known secret - if (error.toString().includes(`Identity '${userID}' is already registered`)) { - logger.warn(`Identity ${userID} is already registered`); - return userSecret; - } else { - throw error; - } - } - - logger.debug('Exiting registerUser'); - return userSecret; - } - - /** - * Enroll a user for the organization using the enrollment secret - * @param {string} orgName the organization to enrol under - * @param {string} clientName the client name to enrol - * @param {string} enrollmentSecret the enrollment secret - * @return {Promise<{key: ECDSA_KEY, certificate: string}>} The resulting private key and certificate. - */ - async enrollUserForOrg(orgName, clientName, enrollmentSecret) { - logger.debug(`Entering enrollUserForOrg for organization ${orgName} and identity name ${clientName}`); - const caDetails = this.registrarInfo.get(orgName); - const enrollment = await this.enrollUser(caDetails.orgCA, clientName, enrollmentSecret); - logger.debug('Exiting enrollUserForOrg'); - return enrollment; - } - - /** - * Enrolls the given user through its corresponding CA. - * @param {CertificateAuthority} ca The certificate authority object whose user must be enrolled. - * @param {string} id The enrollment ID. - * @param {string} secret The enrollment secret. - * @return {Promise<{key: ECDSA_KEY, certificate: string}>} The resulting private key and certificate. - * @private - * @async - */ - async enrollUser(ca, id, secret) { - logger.debug(`Entering enrollUser with enrollment ID ${id}`); - // this call will throw an error if the CA configuration is not found - // this error should propagate up - try { - const enrollment = await ca.enroll({ - enrollmentID: id, - enrollmentSecret: secret - }); - logger.debug('Exiting enrollUser'); - return enrollment; - } catch (err) { - throw new Error(`Couldn't enroll 'user' ${id}: ${err.message}`); - } - } - -} - -module.exports = RegistrarHelper; diff --git a/packages/caliper-fabric/lib/fabricNetwork.js b/packages/caliper-fabric/lib/fabricNetwork.js deleted file mode 100644 index d3162d2f6..000000000 --- a/packages/caliper-fabric/lib/fabricNetwork.js +++ /dev/null @@ -1,951 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const fs = require('fs'); -const CaliperUtils = require('@hyperledger/caliper-core').CaliperUtils; - -/** - * Utility class for accessing information in a Common Connection Profile configuration - * (and the Caliper specific extensions) without relying on its structure. - * - * @property {object} network The loaded network configuration object. - * @property {object} clientConfigs The map of client names to their client configuration objects. - * @property {boolean} compatibilityMode Indicates whether the configuration describes a v1.0 Fabric network. - * @property {boolean} tls Indicates whether TLS communication is configured for the network. - * @property {boolean} mutualTls Indicates whether mutual TLS communication is configured for the network. - * @property {Map} The mapping of contract IDs to contract details. - */ -class FabricNetwork { - /** - * Loads and verifies the Common Connection Profile settings. - * - * @param {string|object} networkConfig The relative or absolute file path, or the object itself of the Common Connection Profile settings. - */ - constructor(networkConfig) { - CaliperUtils.assertDefined(networkConfig, '[FabricNetwork.constructor] Parameter \'networkConfig\' if undefined or null'); - - this.network = undefined; - if (typeof networkConfig === 'string') { - // resolve path will by default use the known workspace root - const configPath = CaliperUtils.resolvePath(networkConfig); - this.network = CaliperUtils.parseYaml(configPath); - } else if (typeof networkConfig === 'object' && networkConfig !== null) { - // clone the object to prevent modification by other objects - this.network = CaliperUtils.parseYamlString(CaliperUtils.stringifyYaml(networkConfig)); - } else { - throw new Error('[FabricNetwork.constructor] Parameter "networkConfig" is neither a file path nor an object'); - } - - this.clientConfigs = {}; - this.compatibilityMode = false; // if event URLs are detected for the peers, we're using Fabric 1.0 - this.tls = false; - this.mutualTls = false; - this.contractMapping = new Map(); - this._processConfiguration(); - } - - - /** - * Internal utility function for retrieving key information from the network configuration. - * - * @private - */ - _processConfiguration() { - this.mutualTls = !!this.network['mutual-tls']; - - if (this.network.clients) { - const clients = this.getClients(); - for (const client of clients) { - this.clientConfigs[client] = this.network.clients[client]; - - const cObj = this.network.clients[client].client; - // normalize paths - if (cObj.credentialStore) { - cObj.credentialStore.path = CaliperUtils.resolvePath(cObj.credentialStore.path); - cObj.credentialStore.cryptoStore.path = CaliperUtils.resolvePath(cObj.credentialStore.cryptoStore.path); - } - - if (cObj.clientPrivateKey && cObj.clientPrivateKey.path) { - cObj.clientPrivateKey.path = CaliperUtils.resolvePath(cObj.clientPrivateKey.path); - } - - if (cObj.clientSignedCert && cObj.clientSignedCert.path) { - cObj.clientSignedCert.path = CaliperUtils.resolvePath(cObj.clientSignedCert.path); - } - } - } - - if (this.network.channels) { - const channels = this.getChannels(); - for (const channel of channels) { - const cObj = this.network.channels[channel]; - - for (const cc of cObj.contracts) { - if (!cc.contractID) { - cc.contractID = cc.id; - } - - this.contractMapping.set(cc.contractID, {channel: channel, id: cc.id, version: cc.version}); - if (cc.language && cc.language !== 'golang' && cc.path) { - cc.path = CaliperUtils.resolvePath(cc.path); - } - } - } - } - - if (this.network.organizations) { - const orgs = this.getOrganizations(); - for (const org of orgs) { - const oObj = this.network.organizations[org]; - - if (oObj.adminPrivateKey && oObj.adminPrivateKey.path) { - oObj.adminPrivateKey.path = CaliperUtils.resolvePath(oObj.adminPrivateKey.path); - } - - if (oObj.signedCert && oObj.signedCert.path) { - oObj.signedCert.path = CaliperUtils.resolvePath(oObj.signedCert.path); - } - } - } - - if (this.network.orderers) { - const orderers = this.getOrderers(); - for (const orderer of orderers) { - const oObj = this.network.orderers[orderer]; - - this.tls |= oObj.url.startsWith('grpcs://'); - - if (oObj.tlsCACerts && oObj.tlsCACerts.path) { - oObj.tlsCACerts.path = CaliperUtils.resolvePath(oObj.tlsCACerts.path); - } - } - } - - if (this.network.peers) { - const peers = this.getPeers(); - for (const peer of peers) { - const pObj = this.network.peers[peer]; - - this.tls |= pObj.url.startsWith('grpcs://'); - - if (pObj.tlsCACerts && pObj.tlsCACerts.path) { - pObj.tlsCACerts.path = CaliperUtils.resolvePath(pObj.tlsCACerts.path); - } - - if (pObj.eventUrl) { - this.compatibilityMode = true; - } - } - } - - if (this.network.certificateAuthorities) { - const cas = this.getCertificateAuthorities(); - for (const ca of cas) { - const caObj = this.network.certificateAuthorities[ca]; - - this.tls |= caObj.url.startsWith('https://'); - - if (caObj.tlsCACerts && caObj.tlsCACerts.path) { - caObj.tlsCACerts.path = CaliperUtils.resolvePath(caObj.tlsCACerts.path); - } - } - } - - if (this.mutualTls && this.compatibilityMode) { - throw new Error('Mutual TLS is not supported for Fabric v1.0'); - } - } - - /** - * Gets the admin crypto materials for the given organization. - * @param {string} org The name of the organization. - * @returns {{privateKeyPEM: Buffer, signedCertPEM: Buffer}} The object containing the signing key and cert in PEM format. - */ - getAdminCryptoContentOfOrganization(org) { - const orgObject = this.network.organizations[org]; - - // if either is missing, the result is undefined - if (!CaliperUtils.checkAllProperties(orgObject, 'adminPrivateKey', 'signedCert')) { - return undefined; - } - - const privateKey = orgObject.adminPrivateKey; - const signedCert = orgObject.signedCert; - - let privateKeyPEM; - let signedCertPEM; - - if (CaliperUtils.checkProperty(privateKey, 'path')) { - privateKeyPEM = fs.readFileSync(privateKey.path); - } else { - privateKeyPEM = privateKey.pem; - } - - if (CaliperUtils.checkProperty(signedCert, 'path')) { - signedCertPEM = fs.readFileSync(signedCert.path); - } else { - signedCertPEM = signedCert.pem; - } - - // if either is missing, the result is undefined - if (!privateKeyPEM || !signedCertPEM) { - return undefined; - } - - return { - privateKeyPEM: privateKeyPEM, - signedCertPEM: signedCertPEM - }; - } - - /** - * Gets the affiliation of the given client. - * @param {string} client The client name. - * @returns {string|undefined} The affiliation or 'undefined' if omitted from the configuration. - */ - getAffiliationOfUser(client) { - if (CaliperUtils.checkProperty(this.clientConfigs[client].client, 'affiliation')) { - return this.clientConfigs[client].client.affiliation; - } - - return undefined; - } - - /** - * Gets the set of event sources (peer names) in the network. - * @return {Set} The set of peer names functioning as an event source. - */ - getAllEventSources() { - const result = new Set(); - for (const channel of this.getChannels()) { - for (const peer of this.getPeersOfChannel(channel)) { - const peerObject = this.network.channels[channel].peers[peer]; - // defaults to true, or explicitly set - if (!CaliperUtils.checkProperty(peerObject, 'eventSource') || peerObject.eventSource) { - result.add(peer); - } - } - } - - if (result.size === 0) { - throw new Error('Could not find any event source'); - } - - return result; - } - - /** - * Gets the registration attributes of the given client. - * @param {string} client The client name. - * @returns {{name: string, value: string, ecert: boolean}[]} The attributes or empty array if omitted from the configuration. - */ - getAttributesOfUser(client) { - if (CaliperUtils.checkProperty(this.clientConfigs[client].client, 'attributes')) { - return this.clientConfigs[client].client.attributes; - } - - return []; - } - - /** - * Gets the certificate authority names defined in the network configuration. - * - * @returns {Set} The set of CA names. - */ - getCertificateAuthorities() { - const result = new Set(); - const cas = this.network.certificateAuthorities; - for (const key in cas) { - if (!cas.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the first CA name for the given organization. - * @param {string} org The organization name. - * @returns {string} The CA name. - */ - getCertificateAuthorityOfOrganization(org) { - if (!CaliperUtils.checkProperty(this.network.organizations[org], 'certificateAuthorities') || - this.network.organizations[org].certificateAuthorities.size < 1) { - return undefined; - } - - // TODO: only one CA per org is supported - return this.network.organizations[org].certificateAuthorities[0]; - } - - /** - * Gets the CA object corresponding to the passed name. - * @param {string} caName The CA name. - * @returns {object} The CA object. - */ - getCertificateAuthority(caName) { - if (!CaliperUtils.checkProperty(this.network, 'certificateAuthorities') || - !CaliperUtils.checkProperty(this.network.certificateAuthorities, caName) ) { - return undefined; - } - - return this.network.certificateAuthorities[caName]; - } - - /** - * Gets the contract names and versions belonging to the given channel. - * @param {string} channel The channel name. - * @returns {Set<{id: string, version: string}>} The set of contract names. - */ - getContractsOfChannel(channel) { - return new Set(this.network.channels[channel].contracts.map(cc => { - return { - id: cc.id, - version: cc.version - }; - })); - } - - /** - * Gets the channel names defined in the network configuration. - * @returns {Set} The set of channel names. - */ - getChannels() { - const result = new Set(); - const channels = this.network.channels; - - for (const key in channels) { - if (!channels.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the array of channels that the peer belongs to. - * @param {string} peer The name of the peer. - * @return {string[]} The array of channel names the peer belongs to. - */ - getChannelsOfPeer(peer) { - const result = [...this.getChannels()].filter(c => this.getPeersOfChannel(c).has(peer)); - - if (result.length === 0) { - throw new Error(`${peer} does not belong to any channel`); - } - - return result; - } - - /** - * Gets the crypto materials for the given user. - * @param {string} client The name of the user. - * @returns {{privateKeyPEM: Buffer, signedCertPEM: Buffer}} The object containing the signing key and cert. - */ - getClientCryptoContent(client) { - const clientObject = this.network.clients[client].client; - - if (!CaliperUtils.checkAllProperties(clientObject, 'clientPrivateKey', 'clientSignedCert')) { - return undefined; - } - - const privateKey = clientObject.clientPrivateKey; - const signedCert = clientObject.clientSignedCert; - let privateKeyPEM; - let signedCertPEM; - - if (CaliperUtils.checkProperty(privateKey, 'path')) { - privateKeyPEM = fs.readFileSync(privateKey.path); - } else { - privateKeyPEM = privateKey.pem; - } - - if (CaliperUtils.checkProperty(signedCert, 'path')) { - signedCertPEM = fs.readFileSync(signedCert.path); - } else { - signedCertPEM = signedCert.pem; - } - - return { - privateKeyPEM: privateKeyPEM, - signedCertPEM: signedCertPEM - }; - } - - /** - * Gets the enrollment secret of the given client. - * @param {string} client The client name. - * @returns {string} The enrollment secret. - */ - getClientEnrollmentSecret(client) { - if (CaliperUtils.checkProperty(this.network.clients[client].client, 'enrollmentSecret')) { - return this.network.clients[client].client.enrollmentSecret; - } - - return undefined; - } - - /** - * Checks if organization wallets are specified - * @returns {boolean} boolean value for existence of organization wallets - */ - usesOrganizationWallets() { - return CaliperUtils.checkProperty(this.network, 'organizationWallets') && (Object.getOwnPropertyNames(this.network.organizationWallets).length !== 0); - } - - /** - * Gets the path to the wallet being used by the given organization. - * @param {string} org The organization name. - * @returns {string} The resolved wallet path. - */ - getWalletPathForOrganization(org) { - if (this.usesOrganizationWallets()) { - if (CaliperUtils.checkProperty(this.network.organizationWallets, org)) { - const walletObject = this.network.organizationWallets[org]; - return CaliperUtils.resolvePath(walletObject.path); - } - } - - return undefined; - } - - /** - * Gets the path to the wallet being used by the given client. - * @param {string} client The client name. - * @returns {string} The resolved wallet path. - */ - getWalletPathForClient(client) { - const org = this.getOrganizationOfClient(client); - return this.getWalletPathForOrganization(org); - } - - /** - * Gets the raw configuration object for the given client. - * - * Use it only when you need access to the client objects itself (which is rare)!! - * @param {string} client The client name. - * @returns {{version: string, client: object}} The client object. - */ - getClientObject(client) { - return this.network.clients[client].client; - } - - /** - * Gets the clients names defined in the network configuration. - * @returns {Set} The set of client names. - */ - getClients() { - const result = new Set(); - const clients = this.network.clients; - - for (const key in clients) { - if (!clients.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the client names belonging to the given organization. - * @param {string} org The organization name. - * @returns {Set} The set of client names. - */ - getClientsOfOrganization(org) { - const clients = this.getClients(); - const result = new Set(); - - for (const client of clients) { - if (this.network.clients[client].client.organization === org) { - result.add(client); - } - } - - return result; - } - - /** - * Gets the details (channel, id and version) for the given contract. - * @param {string} contractID The unique ID of the contract. - * @return {{channel: string, id: string, version: string}} The details of the contract. - */ - getContractDetails(contractID) { - return this.contractMapping.get(contractID); - } - - /** - * Constructs an N-of-N endorsement policy for the given contract of the given channel. - * @param {string} channel The name of the channel. - * @param {{id: string, version: string}} contractInfo The contract name and version. - * @return {object} The assembled endorsement policy. - * @private - */ - getDefaultEndorsementPolicy(channel, contractInfo) { - const targetPeers = this.getTargetPeersOfContractOfChannel(contractInfo, channel); - const targetOrgs = new Set(); - - for (const peer of targetPeers) { - targetOrgs.add(this.getOrganizationOfPeer(peer)); - } - - const orgArray = Array.from(targetOrgs).sort(); - - const policy = { - identities: [], - policy: {} - }; - - policy.policy[`${orgArray.length}-of`] = []; - - for (let i = 0; i < orgArray.length; ++i) { - policy.identities[i] = { - role: { - name: 'member', - mspId: this.getMspIdOfOrganization(orgArray[i]) - } - }; - - policy.policy[`${orgArray.length}-of`][i] = { - 'signed-by': i - }; - } - - return policy; - } - - /** - * Gets the GRPC options of the peer extended with the CA certificate PEM of the peer if present. - * @param {string} peer The name of the peer. - * @return {object} An object containing the GRPC options of the peer. - */ - getGrpcOptionsOfPeer(peer) { - const peerObj = this.network.peers[peer]; - const grpcObj = peerObj.grpcOptions; - - if (CaliperUtils.checkProperty(peerObj, 'tlsCACerts')) { - grpcObj.pem = this.getTlsCaCertificateOfPeer(peer); - } - - return grpcObj; - } - - /** - * Gets the MSP ID of the given organization. - * @param {string} org The organization name. - * @returns {string} The MSP ID. - */ - getMspIdOfOrganization(org) { - return this.network.organizations[org].mspid; - } - - /** - * Gets the raw Common Connection Profile object describing the network. - * - * Use it only when you need access to the network-related objects itself (which is rare)!! - * @returns {object} The Common Connection Profile object (with the Caliper extensions). - */ - getNetworkObject() { - return this.network; - } - - /** - * Gets a new network configuration object instance based on the loaded one. - * @returns {object} The network configuration object. - */ - getNewNetworkObject() { - return CaliperUtils.parseYamlString(CaliperUtils.stringifyYaml(this.network)); - } - - /** - * Gets the orderer names defined in the network configuration. - * @returns {Set} The set of orderer names. - */ - getOrderers() { - const result = new Set(); - const orderers = this.network.orderers; - - for (const key in orderers) { - if (!orderers.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the orderer names belonging to the given channel. - * @param {string} channel The name of the channel. - * @returns {Set} The set of orderer names. - */ - getOrderersOfChannel(channel) { - return new Set(this.network.channels[channel].orderers); - } - - /** - * Get the orderer object from the network definition - * @param {string} ordererName the orderer name to return - * @returns {object} orderer object - */ - getOrdererObject(ordererName) { - return this.network.orderers[ordererName]; - } - - /** - * Gets the organization that the given CA belongs to. - * @param {string} ca The name of the CA. - * @return {string} The name of the organization. - */ - getOrganizationOfCertificateAuthority(ca) { - const orgs = this.getOrganizations(); - for (const org of orgs) { - if (this.network.organizations[org].certificateAuthorities.includes(ca)) { - return org; - } - } - - return undefined; - } - - /** - * Gets the organization name that the given client belongs to. - * @param {string} client The client name. - * @returns {string} The organization name. - */ - getOrganizationOfClient(client) { - return this.clientConfigs[client].client.organization; - } - - /** - * Gets the origanization name in which the given peer belongs to. - * @param {string} peer The peer name. - * @returns {string} The organization name. - */ - getOrganizationOfPeer(peer) { - const orgs = this.getOrganizations(); - for (const org of orgs) { - const peers = this.getPeersOfOrganization(org); - if (peers.has(peer)) { - return org; - } - } - - throw new Error('Peer ' + peer + ' not found in any organization'); - } - - /** - * Gets the organization names defined in the network configuration. - * @returns {Set} The set of organization names. - */ - getOrganizations() { - const result = new Set(); - const orgs = this.network.organizations; - - for (const key in orgs) { - if (!orgs.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the organization names belonging to the given channel. - * @param {string} channel The name of the channel. - * @returns {Set} The set of organization names. - */ - getOrganizationsOfChannel(channel) { - const peers = this.getPeersOfChannel(channel); - const result = new Set(); - - for (const peer of peers) { - result.add(this.getOrganizationOfPeer(peer)); - } - - return result; - } - - /** - * Gets the event connection URL of the given peer. - * @param {string} peer The name of the peer. - * @return {string} The event URL of the peer. - */ - getPeerEventUrl(peer) { - return this.network.peers[peer].eventUrl; - } - - /** - * Gets the name of the peer corresponding to the given address. - * @param {string} address The address of the peer. - * @return {string} The name of the peer. - */ - getPeerNameForAddress(address) { - const peers = this.network.peers; - for (const peer in peers) { - if (!peers.hasOwnProperty(peer)) { - continue; - } - - // remove protocol from address in the config - const url = peers[peer].url.replace(/(^\w+:|^)\/\//, ''); - if (url === address) { - return peer.toString(); - } - } - - return undefined; - } - - /** - * Gets the peer name corresponding to the given event hub. - * @param {EventHub|ChannelEventHub} eventHub The event hub instance. - * @return {string} The name of the peer. - */ - getPeerNameOfEventHub(eventHub) { - const peerAddress = eventHub.getPeerAddr(); - return this.getPeerNameForAddress(peerAddress); - } - - /** - * Gets the peer names defined in the network configuration. - * - * @returns {Set} The set of peer names. - */ - getPeers() { - const result = new Set(); - const peers = this.network.peers; - - for (const peerKey in peers) { - if (!peers.hasOwnProperty(peerKey)) { - continue; - } - - result.add(peerKey.toString()); - } - - return result; - } - - /** - * Gets the peer names belonging to the given channel. - * @param {string} channel The name of the channel. - * @returns {Set} The set of peer names. - */ - getPeersOfChannel(channel) { - const peers = this.network.channels[channel].peers; - const result = new Set(); - - for (const key in peers) { - if (!peers.hasOwnProperty(key)) { - continue; - } - - result.add(key.toString()); - } - - return result; - } - - /** - * Gets the peer names belonging to the given organization. - * @param {string} org The name of the organization. - * @returns {Set} The set of peer names. - */ - getPeersOfOrganization(org) { - return new Set(this.network.organizations[org].peers); - } - - /** - * Gets the peer names belonging to the given organization AND channel. - * @param {string} org The name of the organization. - * @param {string} channel The name of the channel. - * @returns {Set} The set of peer names. - */ - getPeersOfOrganizationAndChannel(org, channel) { - const peersInOrg = this.getPeersOfOrganization(org); - const peersInChannel = this.getPeersOfChannel(channel); - - // return the intersection of the two sets - return new Set([...peersInOrg].filter(p => peersInChannel.has(p))); - } - - /** - * Gets the registrar belonging to the first CA of the given organization. - * @param {string} org The organization name. - * @returns {{enrollId: string, enrollSecret: string}} The enrollment ID and secret of the registrar. - */ - getRegistrarOfOrganization(org) { - const ca = this.getCertificateAuthorityOfOrganization(org); - - if (!ca || !CaliperUtils.checkProperty(this.network.certificateAuthorities[ca], 'registrar')) { - return undefined; - } - - // TODO: only one registrar per CA is supported - return this.network.certificateAuthorities[ca].registrar[0]; - } - - /** - * Gets the peer names on which the given contract of the given channel should be installed and instantiated. - * @param {{id: string, version: string}} contractInfo The contract name and version. - * @param {string} channel The channel name. - * @returns {Set} The set of peer names. - */ - getTargetPeersOfContractOfChannel(contractInfo, channel) { - const cc = this.network.channels[channel].contracts.find( - cc => cc.id === contractInfo.id && cc.version === contractInfo.version); - - CaliperUtils.assertDefined(cc, `Could not find the following contract in the configuration: ${contractInfo.id}@${contractInfo.version}`); - // targets are explicitly defined - if (CaliperUtils.checkProperty(cc, 'targetPeers')) { - return new Set(cc.targetPeers); - } - - // we need to gather the target peers from the channel's peer section - // based on their provided functionality (endorsing and cc query) - const results = new Set(); - const peers = this.network.channels[channel].peers; - for (const key in peers) { - if (!peers.hasOwnProperty(key)) { - continue; - } - - const peer = peers[key]; - // if only the peer name is present in the config, then it is a target based on the default values - if (!CaliperUtils.checkDefined(peer)) { - results.add(key.toString()); - } - - // the default value of 'endorsingPeer' is true, or it's explicitly set to true - if (!CaliperUtils.checkProperty(peer, 'endorsingPeer') || - (CaliperUtils.checkProperty(peer, 'endorsingPeer') && peer.endorsingPeer)) { - results.add(key.toString()); - continue; - } - - // the default value of 'chaincodeQuery' is true, or it's explicitly set to true - if (!CaliperUtils.checkProperty(peer, 'chaincodeQuery') || - (CaliperUtils.checkProperty(peer, 'chaincodeQuery') && peer.chaincodeQuery)) { - results.add(key.toString()); - } - } - - return results; - } - - /** - * Gets the TLS CA certificate of the given peer. - * @param {string} peer The name of the peer. - * @return {string} The PEM encoded CA certificate. - */ - getTlsCaCertificateOfPeer(peer) { - const peerObject = this.network.peers[peer]; - - if (!CaliperUtils.checkProperty(peerObject, 'tlsCACerts')) { - return undefined; - } - - const tlsCACert = peerObject.tlsCACerts; - let tlsPEM; - - if (CaliperUtils.checkProperty(tlsCACert, 'path')) { - tlsPEM = fs.readFileSync(tlsCACert.path).toString(); - } else { - tlsPEM = tlsCACert.pem; - } - - return tlsPEM; - } - - /** - * Gets the transient map for the given contract for the given channel. - * @param {{id: string, version: string}} contract The contract name and version. - * @param {string} channel The channel name. - * - * @return {Map} The map of attribute names to byte arrays. - */ - getTransientMapOfContractOfChannel(contract, channel) { - const map = {}; - const cc = this.network.channels[channel].contracts.find( - cc => cc.id === contract.id && cc.version === contract.version); - - if (!CaliperUtils.checkProperty(cc, 'initTransientMap')) { - return map; - } - - for (const key in cc.initTransientMap) { - if (!cc.initTransientMap.hasOwnProperty(key)) { - continue; - } - - const value = cc.initTransientMap[key]; - map[key.toString()] = Buffer.from(value.toString()); - } - - return map; - } - - - /** - * Return the name of the first client in the named organisation - * @param {string} org the organisation name - * @returns {string} the client name - */ - getFirstClientInOrg(org) { - return this.getClientsOfOrganization(org)[0]; - } - - /** - * Indicates whether the network is a Fabric v1.0 network or not. - * @return {boolean} True, if the network contains legacy event service URLs. Otherwise false. - */ - isInCompatibilityMode() { - return this.compatibilityMode; - } - - /** - * Indicates whether mutual TLS is configured for the adapter. - * @return {boolean} True, if mutual TLS is configured. Otherwise, false. - */ - isMutualTlsEnabled() { - return this.mutualTls; - } - - /** - * Indicates whether server-side TLS is configured for the adapter. - * @return {boolean} True, if server-side TLS is configured. Otherwise, false. - */ - isTlsEnabled() { - return this.tls; - } - -} - -module.exports = FabricNetwork; diff --git a/packages/caliper-fabric/test/FabricConnectorFactory.js b/packages/caliper-fabric/test/FabricConnectorFactory.js index 6588f4b32..cb0fa3d64 100644 --- a/packages/caliper-fabric/test/FabricConnectorFactory.js +++ b/packages/caliper-fabric/test/FabricConnectorFactory.js @@ -23,11 +23,10 @@ const mockery = require('mockery'); const path = require('path'); const { Constants } = require('./connector-versions/v1/ClientStubs'); -const { Wallets } = require('./connector-versions/v2/V2GatewayStubs'); const { ConnectorFactory } = require('../lib/FabricConnectorFactory'); const { ConfigUtil } = require('@hyperledger/caliper-core'); -const legacyConfig = './sample-configs/LegacyNetworkConfig.yaml'; +const unsupportedConfig = './sample-configs/LegacyNetworkConfig.yaml'; const v2Config = './sample-configs/NoIdentitiesNetworkConfig.yaml'; const unknownVersionConfig = './sample-configs/UnknownVersionConfig.yaml'; @@ -80,18 +79,22 @@ const loadFromConfig = () => { return { getCertificateAuthority };}; -mockery.enable({ - warnOnReplace: false, - warnOnUnregistered: false -}); describe('A Fabric Connector Factory', () => { - after(() => { + beforeEach(() => { + mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false, + useCleanCache: true + }); + }); + + afterEach(() => { mockery.deregisterAll(); mockery.disable(); }); - it('should create a legacy V1 Gateway connector if a 1.4 fabric library is bound and usegateway was specified', async () => { + it('should throw an error if a version 1.0 network configuration is used', async () => { mockery.registerMock('fabric-network', { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, @@ -103,31 +106,13 @@ describe('A Fabric Connector Factory', () => { loadFromConfig }); mockery.registerMock('fabric-client/package', {version: '1.4.11'}); - ConfigUtil.set(ConfigUtil.keys.NetworkConfig, path.resolve(__dirname, legacyConfig)); - ConfigUtil.set(ConfigUtil.keys.Fabric.Gateway.Enabled, true); - const connector = await ConnectorFactory(1); - connector.constructor.name.should.equal('LegacyV1FabricGateway'); - mockery.deregisterAll(); - }); - - it('should create a legacy V2 Gateway connector if a 2.x fabric library is bound and usegateway was specified', async () => { - mockery.registerMock('./registrarHelper', { - newWithNetwork: () => '' - }); - mockery.registerMock('fabric-network', { - DefaultEventHandlerStrategies, - DefaultQueryHandlerStrategies, - Wallets - }); - mockery.registerMock('fabric-network/package', {version: '2.2.1'}); - ConfigUtil.set(ConfigUtil.keys.NetworkConfig, path.resolve(__dirname, legacyConfig)); + ConfigUtil.set(ConfigUtil.keys.NetworkConfig, path.resolve(__dirname, unsupportedConfig)); ConfigUtil.set(ConfigUtil.keys.Fabric.Gateway.Enabled, true); - const connector = await ConnectorFactory(1); - connector.constructor.name.should.equal('LegacyV2FabricGateway'); + await ConnectorFactory(1).should.be.rejectedWith(/Network configuration version 1.0 is not supported anymore, please use version 2/); mockery.deregisterAll(); }); - it('should create a new V1 Gateway connector when a 1.4 fabric library is bound, usegateway was specified and a v2 network config', async () => { + it('should create a V1 Gateway connector when a 1.4 fabric library is bound, usegateway was specified', async () => { mockery.registerMock('fabric-network', { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, @@ -144,7 +129,7 @@ describe('A Fabric Connector Factory', () => { mockery.deregisterAll(); }); - it('should create a new V2 Gateway connector when a 2.x fabric library is bound, usegateway was specified and a v2 network config', async () => { + it('should create a V2 Gateway connector when a 2.x fabric library is bound, usegateway was specified', async () => { mockery.registerMock('fabric-network', { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, @@ -158,27 +143,7 @@ describe('A Fabric Connector Factory', () => { mockery.deregisterAll(); }); - it('should create a legacy V1 Fabric connector if a 1.4 fabric library is bound and usegateway was not specified', async () => { - mockery.registerMock('fabric-network', { - DefaultEventHandlerStrategies, - DefaultQueryHandlerStrategies, - InMemoryWallet, - X509WalletMixin - }); - mockery.registerMock('fabric-network/package', {version: '1.4.11'}); - mockery.registerMock('fabric-client', { - loadFromConfig - }); - mockery.registerMock('fabric-client/lib/Constants', Constants); - mockery.registerMock('fabric-client/package', {version: '1.4.11'}); - ConfigUtil.set(ConfigUtil.keys.NetworkConfig, path.resolve(__dirname, legacyConfig)); - ConfigUtil.set(ConfigUtil.keys.Fabric.Gateway.Enabled, false); - const connector = await ConnectorFactory(1); - connector.constructor.name.should.equal('LegacyV1Fabric'); - mockery.deregisterAll(); - }); - - it('should create a new V1 Fabric connector if a 1.4 fabric library is bound and usegateway was not specified', async () => { + it('should create a V1 Fabric connector if a 1.4 fabric library is bound and usegateway was not specified', async () => { mockery.registerMock('fabric-network', { DefaultEventHandlerStrategies, DefaultQueryHandlerStrategies, @@ -198,7 +163,6 @@ describe('A Fabric Connector Factory', () => { mockery.deregisterAll(); }); - it('should throw an error if no fabric library is bound', async () => { // Can't test this with mockery because really need require to fail trying to // find `fabric-network` or `fabric-client` @@ -207,15 +171,13 @@ describe('A Fabric Connector Factory', () => { it('should throw an error if fabric library bound is not V1 or V2', async () => { mockery.registerMock('fabric-network', {}); mockery.registerMock('fabric-network/package', {version: '3.0.0'}); - mockery.registerMock('fabric-client', {}); - mockery.registerMock('fabric-client/package', {version: '3.0.0'}); ConfigUtil.set(ConfigUtil.keys.NetworkConfig, path.resolve(__dirname, v2Config)); ConfigUtil.set(ConfigUtil.keys.Fabric.Gateway.Enabled, true); await ConnectorFactory(1).should.be.rejectedWith(/Installed SDK version 3.0.0 did not match any compatible Fabric connectors/); mockery.deregisterAll(); }); - it('should throw an error version if not for legacy or the new connector', async () => { + it('should throw a generic error if network configuration version is not 1.0 or 2.0', async () => { mockery.registerMock('fabric-network', {}); mockery.registerMock('fabric-network/package', {version: '1.4.11'}); mockery.registerMock('fabric-client', {}); diff --git a/packages/caliper-fabric/test/configValidator.js b/packages/caliper-fabric/test/configValidator.js deleted file mode 100644 index 08f21925c..000000000 --- a/packages/caliper-fabric/test/configValidator.js +++ /dev/null @@ -1,3788 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const ConfigValidator = require('../lib/configValidator.js'); - -const chai = require('chai'); -chai.should(); - -const arrow = '\u21B3'; - -/** - * Returns the property name prefixed with an arrow symbol. - * @param {string} propertyName The text to prefix. - * @return {string} The prefixed text. - */ -function prop(propertyName) { - return `${arrow} ${propertyName}`; -} - -describe('Class: ConfigValidator', () => { - // General remarks: - // - variables "outside" the "it" functions are always reset at the appropriate hierarchy - // - variables "outside" the "it" functions are always initialized to assist code completion - // - every test category/hierarchy starts with a test for accepting the valid configuration - // - the tests are grouped by according to the config property hierarchy - - // these vars have the same structure for every test, thus can be global - let flowOptions = { - performStart: true, - performInit: true, - performInstall: true, - performTest: true, - performEnd: true - }; - - let discovery = false; - let gateway = false; - let tls = undefined; - - // reset the global vars before every test - beforeEach(() => { - flowOptions = { - performStart: true, - performInit: true, - performInstall: true, - performTest: true, - performEnd: true - }; - - discovery = false; - gateway = false; - tls = undefined; - }); - - describe('Function: validateNetwork', () => { - let config = { - name: 'Fabric', - version: '1.0', - 'mutual-tls': false, - caliper: { - blockchain: 'fabric' - }, - clients: { - 'client0.org1.example.com': { - client: { - organization: 'Org1', - credentialStore: { - path: 'path', - cryptoStore: { - path: 'path' - } - }, - - clientPrivateKey: { - path: 'path' - }, - clientSignedCert: { - path: 'path' - } - } - }, - 'client0.org2.example.com': { - client: { - organization: 'Org2', - credentialStore: { - path: 'path', - cryptoStore: { - path: 'path' - } - }, - - clientPrivateKey: { - path: 'path' - }, - clientSignedCert: { - path: 'path' - } - } - } - }, - channels: { - channel1: { - created: false, - configBinary: 'path', - orderers: ['orderer.example.com'], - peers: { - 'peer0.org1.example.com': {}, - 'peer0.org2.example.com': {} - }, - contracts: [ { id: 'drm', version: 'v0' } ] - }, - channel2: { - created: false, - configBinary: 'path', - orderers: ['orderer.example.com'], - peers: { - 'peer0.org1.example.com': {}, - 'peer0.org2.example.com': {} - }, - contracts: [ { id: 'drm', contractID: 'drm2', version: 'v0' } ] - } - }, - organizations: { - Org1: { - mspid: 'Org1MSP', - peers: [ - 'peer0.org1.example.com' - ], - certificateAuthorities: [ - 'ca.org1.example.com' - ] - }, - Org2: { - mspid: 'Org2MSP', - peers: [ - 'peer0.org2.example.com' - ], - certificateAuthorities: [ - 'ca.org2.example.com' - ] - } - }, - orderers: { - 'orderer.example.com': { - url: 'grpcs://localhost:7051', - tlsCACerts: { - path: 'my/path/tocert' - } - } - }, - peers: { - 'peer0.org1.example.com': { - url: 'grpcs://localhost:7051', - tlsCACerts: { - path: 'my/path/tocert' - } - }, - 'peer0.org2.example.com': { - url: 'grpcs://localhost:7051', - tlsCACerts: { - path: 'my/path/tocert' - } - } - }, - certificateAuthorities: { - 'ca.org1.example.com': { - url: 'https://localhost:7054', - tlsCACerts: { - path: 'my/path/tocert' - }, - registrar: [ - { enrollId: 'admin1', enrollSecret: 'secret1' }, - { enrollId: 'admin2', enrollSecret: 'secret2' } - ] - }, - 'ca.org2.example.com': { - url: 'https://localhost:7054', - tlsCACerts: { - path: 'my/path/tocert' - }, - registrar: [ - { enrollId: 'admin1', enrollSecret: 'secret1' }, - { enrollId: 'admin2', enrollSecret: 'secret2' } - ] - } - } - }; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateNetwork(config, flowOptions, discovery, gateway); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - describe('Flow consistency', () => { - it('should throw when using discovery without the gateway mode', () => { - const err = 'Use of discovery is only supported through a gateway transaction'; - discovery = true; - gateway = false; - call.should.throw(err); - }); - - it('should throw when using discovery with init phase', () => { - const err = 'Use of service discovery is only valid with a `caliper-flow-only-test` flag'; - discovery = true; - gateway = true; - flowOptions.performStart = flowOptions.performInstall = flowOptions.performTest = flowOptions.performEnd = false; - call.should.throw(err); - }); - - it('should throw when using discovery with install phase', () => { - const err = 'Use of service discovery is only valid with a `caliper-flow-only-test` flag'; - discovery = true; - gateway = true; - flowOptions.performStart = flowOptions.performInit = flowOptions.performTest = flowOptions.performEnd = false; - call.should.throw(err); - }); - - it('should not throw when omitting top sections in script-only flow', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.certificateAuthorities; - delete config.clients; - delete config.peers; - delete config.orderers; - delete config.organizations; - delete config.channels; - call.should.not.throw(); - }); - - it('should detect incorrect peer TLS based on orderer TLS', () => { - const err = 'Invalid "peer0.org1.example.com" peer configuration: child "url" fails because ["url" with value "grpc://localhost:7051" fails to match the required pattern: /^grpcs:\\/\\//]'; - delete config.certificateAuthorities; - delete config.organizations.Org1.certificateAuthorities; - delete config.organizations.Org2.certificateAuthorities; - delete config.peers['peer0.org1.example.com'].tlsCACerts; - config.peers['peer0.org1.example.com'].url = 'grpc://localhost:7051'; - call.should.throw(err); - }); - - it('should detect incorrect peer TLS based on other peer when CAs and orderers are missing', () => { - const err = 'Invalid "peer0.org2.example.com" peer configuration: child "url" fails because ["url" with value "grpc://localhost:7051" fails to match the required pattern: /^grpcs:\\/\\//]'; - flowOptions.performStart = flowOptions.performInit = flowOptions.performInstall = flowOptions.performEnd = false; - discovery = true; - gateway = true; - delete config.certificateAuthorities; - delete config.organizations.Org1.certificateAuthorities; - delete config.organizations.Org2.certificateAuthorities; - delete config.orderers; - delete config.channels.channel1.orderers; - delete config.channels.channel2.orderers; - delete config.peers['peer0.org2.example.com'].tlsCACerts; - config.peers['peer0.org2.example.com'].url = 'grpc://localhost:7051'; - call.should.throw(err); - }); - - }); - - describe('Peer references', () => { - it('should throw when a non-existing peer is referenced in an organization', () => { - const err = 'Invalid "Org1" organization configuration: child "peers" fails because ["peers" at position 1 fails because ["1" must be one of [peer0.org1.example.com, peer0.org2.example.com]]]'; - config.organizations.Org1.peers.push('peer5.org1.example.com'); - call.should.throw(err); - }); - - it('should throw when a non-existing peer is referenced in a channel', () => { - const err = 'Invalid "channel1" channel configuration: child "peers" fails because ["peer5.org1.example.com" is not allowed]'; - config.channels.channel1.peers['peer5.org1.example.com'] = {}; - call.should.throw(err); - }); - - it('should throw when a non-existing peer is referenced in a contract', () => { - const err = 'Invalid "channel1" channel configuration: child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" at position 0 fails because ["0" must be one of [peer0.org1.example.com, peer0.org2.example.com]]]]]'; - config.channels.channel1.contracts[0].targetPeers = ['peer5.org1.example.com']; - call.should.throw(err); - }); - }); - - describe('Orderer references', () => { - it('should throw when a non-existing orderer is referenced in a channel', () => { - const err = 'Invalid "channel1" channel configuration: child "orderers" fails because ["orderers" at position 1 fails because ["1" must be one of [orderer.example.com]]]'; - config.channels.channel1.orderers.push('orderer5.example.com'); - call.should.throw(err); - }); - }); - - describe('CA references', () => { - it('should throw when a non-existing CA is referenced in an organization', () => { - const err = 'Invalid "Org1" organization configuration: child "certificateAuthorities" fails because ["certificateAuthorities" at position 1 fails because ["1" must be one of [ca.org1.example.com, ca.org2.example.com]]]'; - config.organizations.Org1.certificateAuthorities.push('ca5.org1.example.com'); - call.should.throw(err); - }); - }); - - describe('MSP ID references', () => { - it('should throw when a non-existing MSP ID is referenced in a channel definition ', () => { - const err = 'Invalid "channel1" channel configuration: child "definition" fails because [child "msps" fails because ["msps" at position 1 fails because ["1" must be one of [Org1MSP, Org2MSP]]]]'; - delete config.channels.channel1.configBinary; - config.channels.channel1.definition = { - capabilities : [], - consortium : 'SampleConsortium', - msps : [ 'Org1MSP', 'Org5MSP' ], - version : 0 - }; - call.should.throw(err); - }); - - it('should throw when a non-existing MSP ID is referenced in a contract endorsement policy', () => { - const err = 'Invalid "channel1" channel configuration: child "contracts" fails because ["contracts" at position 1 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be one of [Org1MSP, Org2MSP]]]]]]]]'; - config.channels.channel1.contracts.push({ - id: 'marbles', - contractID: 'ContractMarbles', - version: 'v0', - language: 'golang', - path: 'path', - 'endorsement-policy': { - identities: [ - { role: { name: 'member', mspId: 'Org1MSP' }}, - { role: { name: 'member', mspId: 'Org2MSP' }} - ], - policy: { - '2-of': [ - { 'signed-by': 1}, - { '1-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }]} - ] - } - } - }); - - config.channels.channel1.contracts[1]['endorsement-policy'].identities[0].role.mspId = 'Org5MSP'; - call.should.throw(err); - }); - - it('should throw when a non-existing MSP ID is referenced in a contract collections config policy', () => { - const err = 'Invalid "channel1" channel configuration: child "contracts" fails because ["contracts" at position 1 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be one of [Org1MSP, Org2MSP]]]]]]]]]]'; - config.channels.channel1.contracts.push({ - id: 'marbles', - contractID: 'ContractMarbles', - version: 'v0', - language: 'golang', - path: 'path', - 'collections-config': [{ - name: 'name', - policy: { - identities: [ - {role: {name: 'member', mspId: 'Org1MSP'}}, - {role: {name: 'member', mspId: 'Org2MSP'}} - ], - policy: { - '2-of': [ - {'signed-by': 1}, - {'1-of': [{'signed-by': 0}, {'signed-by': 1}]} - ] - } - }, - requiredPeerCount: 1, - maxPeerCount: 2, - blockToLive: 0 - }] - }); - - config.channels.channel1.contracts[1]['collections-config'][0].policy.identities[0].role.mspId = 'Org5MSP'; - call.should.throw(err); - }); - }); - - describe('TLS consistency', () => { - it('should throw for inconsistent TLS protocol in CAs', () => { - const err = 'Invalid "ca.org2.example.com" CA configuration: child "url" fails because ["url" with value "https://localhost:7054" fails to match the required pattern: /^http:\\/\\//]'; - delete config.certificateAuthorities['ca.org1.example.com'].tlsCACerts; - config.certificateAuthorities['ca.org1.example.com'].url = 'http://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for inconsistent TLS protocol in peers', () => { - const err = 'Invalid "peer0.org1.example.com" peer configuration: child "url" fails because ["url" with value "grpc://localhost:7051" fails to match the required pattern: /^grpcs:\\/\\//]'; - delete config.peers['peer0.org1.example.com'].tlsCACerts; - config.peers['peer0.org1.example.com'].url = 'grpc://localhost:7051'; - call.should.throw(err); - }); - - it('should throw for inconsistent TLS protocol in peer eventing', () => { - const err = 'Invalid "peer0.org1.example.com" peer configuration: child "eventUrl" fails because ["eventUrl" with value "grpc://localhost:7051" fails to match the required pattern: /^grpcs:\\/\\//]'; - config.peers['peer0.org1.example.com'].eventUrl = 'grpc://localhost:7051'; - call.should.throw(err); - }); - - it('should throw for inconsistent TLS protocol in orderers', () => { - const err = 'Invalid "orderer.example.com" orderer configuration: child "url" fails because ["url" with value "grpc://localhost:7051" fails to match the required pattern: /^grpcs:\\/\\//]'; - delete config.orderers['orderer.example.com'].tlsCACerts; - config.orderers['orderer.example.com'].url = 'grpc://localhost:7051'; - call.should.throw(err); - }); - }); - - describe('Peer eventing consistency', () => { - it('should throw for inconsistent event URL usage among peers', () => { - const err = 'Invalid "peer0.org2.example.com" peer configuration: child "eventUrl" fails because ["eventUrl" is required]'; - config.peers['peer0.org1.example.com'].eventUrl = 'grpcs://localhost:7053'; - call.should.throw(err); - }); - }); - - describe('Informative errors', () => { - it('should throw an informative error about invalid client configuration', () => { - const err = 'Invalid "client0.org1.example.com" client configuration: "affiliation" is not allowed'; - config.clients['client0.org1.example.com'].affiliation = 'aff1'; - call.should.throw(err); - }); - }); - }); - - describe('Function: _validateTopLevel', () => { - // good practice for auto complete and easy backup - let config = { - name: 'Fabric', - version: '1.0', - 'mutual-tls': false, - caliper: { - blockchain: 'fabric', - command: { start: 'start command', end: 'end command' } - }, - info: { info1: 'some info' }, - clients: { client1: {} }, - channels: { channel1: {} }, - organizations: { org1: {} }, - orderers: { orderer1: {} }, - peers: { peer1: {} }, - certificateAuthorities: { ca1: {} } - }; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator._validateTopLevel(config, flowOptions, discovery, tls); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - describe(prop('name'), () => { - it('should throw for missing required property', () => { - const err = 'child "name" fails because ["name" is required]'; - delete config.name; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "name" fails because ["name" is not allowed to be empty, "name" length must be at least 1 characters long]'; - config.name = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "name" fails because ["name" must be a string]'; - config.name = true; - call.should.throw(err); - }); - }); - - describe(prop('version'), () => { - it('should throw for missing required property', () => { - const err = 'child "version" fails because ["version" is required]'; - delete config.version; - call.should.throw(err); - }); - - it('should throw for an invalid string value', () => { - const err = 'child "version" fails because ["version" must be one of [1.0]]'; - config.version = '2.0'; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "version" fails because ["version" must be a string]'; - config.version = true; - call.should.throw(err); - }); - }); - - describe(prop('mutual-tls'), () => { - it('should not throw for missing optional property', () => { - delete config['mutual-tls']; - call.should.not.throw(); - }); - - it('should not throw for any valid value when TLS is not known', () => { - config['mutual-tls'] = false; - call.should.not.throw(); - config['mutual-tls'] = true; - call.should.not.throw(); - }); - - it('should throw for a non-boolean value', () => { - const err = 'child "mutual-tls" fails because ["mutual-tls" must be a boolean]'; - config['mutual-tls'] = 'yes'; - call.should.throw(err); - }); - - it('should not throw when set to "true" with server TLS', () => { - tls = true; - config['mutual-tls'] = true; - call.should.not.throw(); - }); - - it('should throw when set to "true" without server TLS', () => { - const err = 'child "mutual-tls" fails because ["mutual-tls" must be one of [false]]'; - tls = false; - config['mutual-tls'] = true; - call.should.throw(err); - }); - }); - - describe(prop('organizationWallets'), () => { - it('should not throw for missing optional property', () => { - call.should.not.throw(); - }); - - it('should not throw for a correctly defined object', () => { - config.organizationWallets = { - org0: { - path : 'myWalletPath' - }, - org1: { - path : 'myOtherWalletPath' - } - }; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "organizationWallets" fails because ["organizationWallets" must be an object]'; - config.organizationWallets = 'yes'; - call.should.throw(err); - }); - - }); - - describe(prop('caliper'), () => { - const err = 'child "caliper" fails because ["caliper" is required]'; - it('should throw for missing required property', () => { - delete config.caliper; - call.should.throw(err); - }); - - it('should throw for non-object value', () => { - const err = 'child "caliper" fails because ["caliper" must be an object]'; - config.caliper = ''; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "caliper" fails because ["unknown" is not allowed]'; - config.caliper.unknown = ''; - call.should.throw(err); - }); - - describe(prop('blockchain'), () => { - it('should throw for missing required property', () => { - const err = 'child "caliper" fails because [child "blockchain" fails because ["blockchain" is required]]'; - delete config.caliper.blockchain; - call.should.throw(err); - }); - - it('should throw for an invalid string value', () => { - const err = 'child "caliper" fails because [child "blockchain" fails because ["blockchain" must be one of [fabric]]]'; - config.caliper.blockchain = 'ethereum'; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "caliper" fails because [child "blockchain" fails because ["blockchain" must be a string]]'; - config.caliper.blockchain = true; - call.should.throw(err); - }); - }); - - describe(prop('command'), () => { - it('should throw for a non-object value', () => { - const err = 'child "caliper" fails because [child "command" fails because ["command" must be an object]]'; - config.caliper.command = ''; - call.should.throw(err); - }); - - it('should not throw for missing optional property', () => { - delete config.caliper.command; - call.should.not.throw(); - }); - - it('should throw for an empty object value', () => { - const err = 'child "caliper" fails because [child "command" fails because ["value" must contain at least one of [start, end]]]'; - delete config.caliper.command.start; - delete config.caliper.command.end; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "caliper" fails because [child "command" fails because ["unknown" is not allowed]]'; - config.caliper.command.unknown = ''; - call.should.throw(err); - }); - - it('should not throw for only missing the optional "start" child property', () => { - delete config.caliper.command.start; - call.should.not.throw(); - }); - - it('should not throw for only missing the optional "end" child property', () => { - delete config.caliper.command.end; - call.should.not.throw(); - }); - - describe(prop('start'), () => { - it('should throw for an empty string value', () => { - const err = 'child "caliper" fails because [child "command" fails because [child "start" fails because ["start" is not allowed to be empty, "start" length must be at least 1 characters long]]]'; - config.caliper.command.start = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "caliper" fails because [child "command" fails because [child "start" fails because ["start" must be a string]]]'; - config.caliper.command.start = true; - call.should.throw(err); - }); - }); - - describe(prop('end'), () => { - it('should throw for an empty string property', () => { - const err = 'child "caliper" fails because [child "command" fails because [child "end" fails because ["end" is not allowed to be empty, "end" length must be at least 1 characters long]]]'; - config.caliper.command.end = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "caliper" fails because [child "command" fails because [child "end" fails because ["end" must be a string]]]'; - config.caliper.command.end = true; - call.should.throw(err); - }); - }); - }); - }); - - describe(prop('info'), () => { - it('should not throw for missing optional property', () => { - delete config.info; - call.should.not.throw(); - }); - - it('should not throw for empty value', () => { - config.info = {}; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "info" fails because ["info" must be an object]'; - config.info = 'yes'; - call.should.throw(err); - }); - }); - - describe(prop('clients'), () => { - it('should throw for missing required property', () => { - const err = 'child "clients" fails because ["clients" is required]'; - delete config.clients; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "clients" fails because ["clients" must be an object]'; - config.clients = 'yes'; - call.should.throw(err); - }); - - it('should not throw for missing property when only scripts are executed', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.clients; - call.should.not.throw(); - }); - }); - - describe(prop('channels'), () => { - it('should throw for missing required property', () => { - const err = 'child "channels" fails because ["channels" is required]'; - delete config.channels; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "channels" fails because ["channels" must be an object]'; - config.channels = 'yes'; - call.should.throw(err); - }); - - it('should not throw for missing property when only scripts are executed', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.channels; - call.should.not.throw(); - }); - }); - - describe(prop('organizations'), () => { - it('should throw for missing required property', () => { - const err = 'child "organizations" fails because ["organizations" is required]'; - delete config.organizations; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "organizations" fails because ["organizations" must be an object]'; - config.organizations = 'yes'; - call.should.throw(err); - }); - - it('should not throw for missing property when only scripts are executed', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.organizations; - call.should.not.throw(); - }); - }); - - describe(prop('orderers'), () => { - it('should throw for missing required property', () => { - const err = 'child "orderers" fails because ["orderers" is required]'; - delete config.orderers; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "orderers" fails because ["orderers" must be an object]'; - config.orderers = 'yes'; - call.should.throw(err); - }); - - it('should not throw for missing property when only scripts are executed', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.orderers; - call.should.not.throw(); - }); - - it('should not throw for missing property in discovery mode', () => { - discovery = true; - delete config.orderers; - call.should.not.throw(); - }); - }); - - describe(prop('peers'), () => { - it('should throw for missing required property', () => { - const err = 'child "peers" fails because ["peers" is required]'; - delete config.peers; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "peers" fails because ["peers" must be an object]'; - config.peers = 'yes'; - call.should.throw(err); - }); - - it('should not throw for missing property when only scripts are executed', () => { - flowOptions.performInit = flowOptions.performInstall = flowOptions.performTest = false; - delete config.peers; - call.should.not.throw(); - }); - }); - - describe(prop('certificateAuthorities'), () => { - it('should not throw for missing optional property', () => { - delete config.certificateAuthorities; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" must be an object]'; - config.certificateAuthorities = 'yes'; - call.should.throw(err); - }); - }); - }); - - describe('Function: validateCertificateAuthority', () => { - let config = { - url: 'https://localhost:7054', - httpOptions: { - verify: false - }, - tlsCACerts: { - path: 'my/path/tocert' - }, - registrar: [ - { enrollId: 'admin1', enrollSecret: 'secret1' }, - { enrollId: 'admin2', enrollSecret: 'secret2' } - ] - }; - const configString = JSON.stringify(config); - - let configNoRegistrar = { - url: 'https://localhost:7054', - httpOptions: { - verify: false - }, - tlsCACerts: { - path: 'my/path/tocert' - } - }; - const configStringNoRegistrar = JSON.stringify(configNoRegistrar); - - // reset the local config before every test - beforeEach(() => { - config = JSON.parse(configString); - configNoRegistrar = JSON.parse(configStringNoRegistrar); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateCertificateAuthority(config, tls, 'required'); - } - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function callNoRegistrar() { - ConfigValidator.validateCertificateAuthority(configNoRegistrar, tls, 'optional'); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should not throw for a valid value', () => { - callNoRegistrar.should.not.throw(); - }); - - it('should throw for an unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - describe(prop('url'), () => { - it('should throw for missing required property', () => { - const err = 'child "url" fails because ["url" is required]'; - delete config.url; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "url" fails because ["url" is not allowed to be empty, "url" must be a valid uri, "url" with value "" fails to match the required pattern: /^(https|http):\\/\\//]'; - delete config.tlsCACerts; - config.url = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "url" fails because ["url" must be a string]'; - delete config.tlsCACerts; - config.url = true; - call.should.throw(err); - }); - - it('should throw for a wrong protocol value', () => { - const err = 'child "url" fails because ["url" with value "grpc://localhost:7054" fails to match the required pattern: /^(https|http):\\/\\//]'; - delete config.tlsCACerts; - config.url = 'grpc://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a non-URI value', () => { - const err = 'child "url" fails because ["url" must be a valid uri, "url" with value "invalid" fails to match the required pattern: /^(https|http):\\/\\//]'; - delete config.tlsCACerts; - config.url = 'invalid'; - call.should.throw(err); - }); - - it('should not throw for any valid protocol value when TLS is not known', () => { - config.url = 'https://localhost:7054'; - call.should.not.throw(); - - delete config.tlsCACerts; - config.url = 'http://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for a non-TLS protocol when TLS is set', () => { - const err = 'child "url" fails because ["url" with value "http://localhost:7054" fails to match the required pattern: /^https:\\/\\//]'; - tls = true; - delete config.tlsCACerts; - config.url = 'http://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a TLS protocol when TLS is not set', () => { - const err = 'child "url" fails because ["url" with value "https://localhost:7054" fails to match the required pattern: /^http:\\/\\//]'; - tls = false; - config.url = 'https://localhost:7054'; - call.should.throw(err); - }); - }); - - describe(prop('httpOptions'), () => { - it('should not throw for missing optional property', () => { - delete config.httpOptions; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "httpOptions" fails because ["httpOptions" must be an object]'; - config.httpOptions = 'yes'; - call.should.throw(err); - }); - }); - - describe(prop('tlsCACerts'), () => { - it('should throw for a non-object value', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" must be an object]'; - config.tlsCACerts = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "tlsCACerts" fails because ["value" must contain at least one of [pem, path]]'; - config.tlsCACerts = {}; - call.should.throw(err); - }); - - it('should throw for missing required property when using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is required]'; - delete config.tlsCACerts; - call.should.throw(err); - }); - - it('should not throw for missing property when not using TLS', () => { - delete config.tlsCACerts; - config.url = 'http://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for forbidden property when not using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is not allowed]'; - config.url = 'http://localhost:7054'; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "tlsCACerts" fails because ["value" contains a conflict between exclusive peers [pem, path]]'; - config.tlsCACerts.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "tlsCACerts" fails because ["unknown" is not allowed]'; - config.tlsCACerts.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]'; - config.tlsCACerts.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" must be a string]]'; - config.tlsCACerts.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.tlsCACerts.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.tlsCACerts.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" must be a string]]'; - config.tlsCACerts.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]'; - config.tlsCACerts.pem = ''; - call.should.throw(err); - }); - }); - }); - - describe(prop('registrar'), () => { - it('should throw for missing required property', () => { - const err = 'child "registrar" fails because ["registrar" is required]'; - delete config.registrar; - call.should.throw(err); - }); - - it('should throw for a non-array value', () => { - const err = 'child "registrar" fails because ["registrar" must be an array]'; - config.registrar = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty array value', () => { - const err = 'child "registrar" fails because ["registrar" must contain at least 1 items]'; - config.registrar = []; - call.should.throw(err); - }); - - it('should throw for an undefined array item', () => { - const err = 'child "registrar" fails because ["registrar" must not be a sparse array]'; - config.registrar[2] = undefined; - call.should.throw(err); - }); - - describe(prop('[item].enrollId'), () => { - it('should throw for an item with a non-string value', () => { - const err = 'child "registrar" fails because ["registrar" at position 0 fails because [child "enrollId" fails because ["enrollId" must be a string]]]'; - config.registrar[0].enrollId = true; - call.should.throw(err); - }); - - it('should throw for an item with an empty string value', () => { - const err = 'child "registrar" fails because ["registrar" at position 0 fails because [child "enrollId" fails because ["enrollId" is not allowed to be empty, "enrollId" length must be at least 1 characters long]]]'; - config.registrar[0].enrollId = ''; - call.should.throw(err); - }); - - it('should throw for an item with a duplicate value', () => { - const err = 'child "registrar" fails because ["registrar" position 1 contains a duplicate value]'; - config.registrar[1].enrollId = 'admin1'; - call.should.throw(err); - }); - }); - - describe(prop('[item].enrollSecret'), () => { - it('should throw for an item with a non-string value', () => { - const err = 'child "registrar" fails because ["registrar" at position 0 fails because [child "enrollSecret" fails because ["enrollSecret" must be a string]]]'; - config.registrar[0].enrollSecret = true; - call.should.throw(err); - }); - - it('should throw for an item with an empty string value', () => { - const err = 'child "registrar" fails because ["registrar" at position 0 fails because [child "enrollSecret" fails because ["enrollSecret" is not allowed to be empty, "enrollSecret" length must be at least 1 characters long]]]'; - config.registrar[0].enrollSecret = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe('Function: validatePeer', () => { - let config = { - url: 'grpcs://localhost:7051', - eventUrl: 'grpcs://localhost:7053', - grpcOptions: { - 'ssl-target-name-override': 'peer0.org1.example.com', - }, - tlsCACerts: { - path: 'my/path/tocert' - } - }; - - let eventUrl = true; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - eventUrl = true; - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validatePeer(config, tls, eventUrl); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for an unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - describe(prop('url'), () => { - it('should throw for missing required property', () => { - const err = 'child "url" fails because ["url" is required]'; - delete config.url; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "url" fails because ["url" must be a string]. child "eventUrl" fails because ["eventUrl" with value "grpcs://localhost:7053" fails to match the required pattern: /^grpc:\\/\\//]'; - delete config.tlsCACerts; - config.url = true; - call.should.throw(err); - }); - - it('should throw for a wrong protocol value', () => { - const err = 'child "url" fails because ["url" with value "https://localhost:7054" fails to match the required pattern: /^(grpcs|grpc):\\/\\//]'; - delete config.tlsCACerts; - delete config.eventUrl; - config.url = 'https://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a non-URI value', () => { - const err = 'child "url" fails because ["url" must be a valid uri, "url" with value "invalid" fails to match the required pattern: /^(grpcs|grpc):\\/\\//]'; - delete config.tlsCACerts; - delete config.eventUrl; - config.url = 'invalid'; - call.should.throw(err); - }); - - it('should not throw for any valid protocol value when TLS is not known', () => { - config.url = 'grpcs://localhost:7054'; - call.should.not.throw(); - - delete config.tlsCACerts; - config.eventUrl = 'grpc://localhost:7054'; - config.url = 'grpc://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for a non-TLS protocol when TLS is set', () => { - const err = 'child "url" fails because ["url" with value "grpc://localhost:7054" fails to match the required pattern: /^grpcs:\\/\\//]'; - tls = true; - delete config.tlsCACerts; - config.eventUrl = 'grpc://localhost:7054'; - config.url = 'grpc://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a TLS protocol when TLS is not set', () => { - const err = 'child "url" fails because ["url" with value "grpcs://localhost:7054" fails to match the required pattern: /^grpc:\\/\\//]'; - tls = false; - config.url = 'grpcs://localhost:7054'; - call.should.throw(err); - }); - }); - - describe(prop('eventUrl'), () => { - it('should not throw for missing optional property', () => { - eventUrl = false; - delete config.eventUrl; - call.should.not.throw(); - }); - - it('should throw for missing property when other peers also set it', () => { - const err = 'child "eventUrl" fails because ["eventUrl" is required]'; - delete config.eventUrl; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "eventUrl" fails because ["eventUrl" must be a string]'; - config.eventUrl = true; - call.should.throw(err); - }); - - it('should throw for a non-URI value', () => { - const err = 'child "eventUrl" fails because ["eventUrl" must be a valid uri, "eventUrl" with value "invalid" fails to match the required pattern: /^grpcs:\\/\\//]'; - config.eventUrl = 'invalid'; - call.should.throw(err); - }); - - it('should throw for a mismatching protocol value', () => { - const err = 'child "eventUrl" fails because ["eventUrl" with value "grpc://localhost:7054" fails to match the required pattern: /^grpcs:\\/\\//]'; - config.eventUrl = 'grpc://localhost:7054'; - call.should.throw(err); - }); - }); - - describe(prop('grpcOptions'), () => { - it('should not throw for missing optional property', () => { - delete config.grpcOptions; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "grpcOptions" fails because ["grpcOptions" must be an object]'; - config.grpcOptions = 'yes'; - call.should.throw(err); - }); - }); - - describe(prop('tlsCACerts'), () => { - it('should throw for a non-object value', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" must be an object]'; - config.tlsCACerts = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "tlsCACerts" fails because ["value" must contain at least one of [pem, path]]'; - config.tlsCACerts = {}; - call.should.throw(err); - }); - - it('should throw for missing required property when using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is required]'; - delete config.tlsCACerts; - call.should.throw(err); - }); - - it('should not throw for missing property when not using TLS', () => { - delete config.tlsCACerts; - config.url = 'grpc://localhost:7054'; - config.eventUrl = 'grpc://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for forbidden property when not using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is not allowed]'; - config.url = 'grpc://localhost:7054'; - config.eventUrl = 'grpc://localhost:7054'; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "tlsCACerts" fails because ["value" contains a conflict between exclusive peers [pem, path]]'; - config.tlsCACerts.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "tlsCACerts" fails because ["unknown" is not allowed]'; - config.tlsCACerts.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]'; - config.tlsCACerts.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" must be a string]]'; - config.tlsCACerts.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.tlsCACerts.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.tlsCACerts.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" must be a string]]'; - config.tlsCACerts.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]'; - config.tlsCACerts.pem = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe('Function: validateOrderer', () => { - let config = { - url: 'grpcs://localhost:7051', - grpcOptions: { - 'ssl-target-name-override': 'orderer.example.com', - }, - tlsCACerts: { - path: 'my/path/tocert' - } - }; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateOrderer(config, tls); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for an unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - describe(prop('url'), () => { - it('should throw for missing required property', () => { - const err = 'child "url" fails because ["url" is required]'; - delete config.url; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "url" fails because ["url" must be a string]'; - delete config.tlsCACerts; - config.url = true; - call.should.throw(err); - }); - - it('should throw for a wrong protocol value', () => { - const err = 'child "url" fails because ["url" with value "https://localhost:7054" fails to match the required pattern: /^(grpcs|grpc):\\/\\//]'; - delete config.tlsCACerts; - config.url = 'https://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a non-URI value', () => { - const err = 'child "url" fails because ["url" must be a valid uri, "url" with value "invalid" fails to match the required pattern: /^(grpcs|grpc):\\/\\//]'; - delete config.tlsCACerts; - config.url = 'invalid'; - call.should.throw(err); - }); - - it('should not throw for any valid protocol value when TLS is not known', () => { - config.url = 'grpcs://localhost:7054'; - call.should.not.throw(); - - delete config.tlsCACerts; - config.url = 'grpc://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for a non-TLS protocol when TLS is set', () => { - const err = 'child "url" fails because ["url" with value "grpc://localhost:7054" fails to match the required pattern: /^grpcs:\\/\\//]'; - tls = true; - delete config.tlsCACerts; - config.url = 'grpc://localhost:7054'; - call.should.throw(err); - }); - - it('should throw for a TLS protocol when TLS is not set', () => { - const err = 'child "url" fails because ["url" with value "grpcs://localhost:7054" fails to match the required pattern: /^grpc:\\/\\//]'; - tls = false; - config.url = 'grpcs://localhost:7054'; - call.should.throw(err); - }); - }); - - describe(prop('grpcOptions'), () => { - it('should not throw for missing optional property', () => { - delete config.grpcOptions; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "grpcOptions" fails because ["grpcOptions" must be an object]'; - config.grpcOptions = 'yes'; - call.should.throw(err); - }); - }); - - describe(prop('tlsCACerts'), () => { - it('should throw for a non-object value', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" must be an object]'; - config.tlsCACerts = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "tlsCACerts" fails because ["value" must contain at least one of [pem, path]]'; - config.tlsCACerts = {}; - call.should.throw(err); - }); - - it('should throw for missing required property when using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is required]'; - delete config.tlsCACerts; - call.should.throw(err); - }); - - it('should not throw for missing property when not using TLS', () => { - delete config.tlsCACerts; - config.url = 'grpc://localhost:7054'; - call.should.not.throw(); - }); - - it('should throw for forbidden property when not using TLS', () => { - const err = 'child "tlsCACerts" fails because ["tlsCACerts" is not allowed]'; - config.url = 'grpc://localhost:7054'; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "tlsCACerts" fails because ["value" contains a conflict between exclusive peers [pem, path]]'; - config.tlsCACerts.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "tlsCACerts" fails because ["unknown" is not allowed]'; - config.tlsCACerts.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]'; - config.tlsCACerts.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "path" fails because ["path" must be a string]]'; - config.tlsCACerts.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.tlsCACerts.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.tlsCACerts.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" must be a string]]'; - config.tlsCACerts.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "tlsCACerts" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]'; - config.tlsCACerts.pem = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe('Function: validateOrganization', () => { - let config = { - mspid: 'Org1MSP', - peers: [ - 'peer0.org1.example.com', - 'peer1.org1.example.com' - ], - certificateAuthorities: [ - 'ca0.org1.example.com', - 'ca1.org1.example.com' - ], - adminPrivateKey: { - path: 'path' - }, - signedCert: { - path: 'path' - } - }; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateOrganization(config, - ['peer0.org1.example.com', 'peer1.org1.example.com'], - ['ca0.org1.example.com', 'ca1.org1.example.com']); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for an unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - describe(prop('mspid'), () => { - it('should throw for missing required property', () => { - const err = 'child "mspid" fails because ["mspid" is required]'; - delete config.mspid; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "mspid" fails because ["mspid" must be a string]'; - config.mspid = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "mspid" fails because ["mspid" is not allowed to be empty, "mspid" length must be at least 1 characters long]'; - config.mspid = ''; - call.should.throw(err); - }); - }); - - describe(prop('peers'), () => { - it('should not throw for missing optional property', () => { - delete config.peers; - call.should.not.throw(); - }); - - it('should throw for a non-array value', () => { - const err = 'child "peers" fails because ["peers" must be an array]'; - config.peers = true; - call.should.throw(err); - }); - - it('should throw for an empty array value', () => { - const err = 'child "peers" fails because ["peers" must contain at least 1 items]'; - config.peers = []; - call.should.throw(err); - }); - - it('should throw for an undefined element', () => { - const err = 'child "peers" fails because ["peers" must not be a sparse array]'; - config.peers.push(undefined); - call.should.throw(err); - }); - - it('should throw for a duplicate reference', () => { - const err = 'child "peers" fails because ["peers" position 2 contains a duplicate value]'; - config.peers.push('peer0.org1.example.com'); - call.should.throw(err); - }); - - it('should throw for a non-existing reference', () => { - const err = 'child "peers" fails because ["peers" at position 2 fails because ["2" must be one of [peer0.org1.example.com, peer1.org1.example.com]]]'; - config.peers.push('peer3.org1.example.com'); - call.should.throw(err); - }); - }); - - describe(prop('certificateAuthorities'), () => { - it('should not throw for missing optional property', () => { - delete config.certificateAuthorities; - call.should.not.throw(); - }); - - it('should throw for a non-array value', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" must be an array]'; - config.certificateAuthorities = true; - call.should.throw(err); - }); - - it('should throw for an empty array value', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" must contain at least 1 items]'; - config.certificateAuthorities = []; - call.should.throw(err); - }); - - it('should throw for an undefined element', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" must not be a sparse array]'; - config.certificateAuthorities.push(undefined); - call.should.throw(err); - }); - - it('should throw for a duplicate reference', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" position 2 contains a duplicate value]'; - config.certificateAuthorities.push('ca0.org1.example.com'); - call.should.throw(err); - }); - - it('should throw for a non-existing reference', () => { - const err = 'child "certificateAuthorities" fails because ["certificateAuthorities" at position 2 fails because ["2" must be one of [ca0.org1.example.com, ca1.org1.example.com]]]'; - config.certificateAuthorities.push('ca5.org1.example.com'); - call.should.throw(err); - }); - }); - - describe(prop('adminPrivateKey'), () => { - it('should not throw for missing property when sibling "signedCert" not set either', () => { - delete config.adminPrivateKey; - delete config.signedCert; - call.should.not.throw(); - }); - - it('should throw for not setting together with sibling "signedCert" property', () => { - const err = '"value" contains [adminPrivateKey] without its required peers [signedCert]'; - delete config.signedCert; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "adminPrivateKey" fails because ["adminPrivateKey" must be an object]'; - config.adminPrivateKey = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "adminPrivateKey" fails because ["value" must contain at least one of [pem, path]]'; - config.adminPrivateKey = {}; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "adminPrivateKey" fails because ["value" contains a conflict between exclusive peers [pem, path]]'; - config.adminPrivateKey.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "adminPrivateKey" fails because ["unknown" is not allowed]'; - config.adminPrivateKey.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "adminPrivateKey" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]'; - config.adminPrivateKey.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "adminPrivateKey" fails because [child "path" fails because ["path" must be a string]]'; - config.adminPrivateKey.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.adminPrivateKey.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.adminPrivateKey.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "adminPrivateKey" fails because [child "pem" fails because ["pem" must be a string]]'; - config.adminPrivateKey.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "adminPrivateKey" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]'; - config.adminPrivateKey.pem = ''; - call.should.throw(err); - }); - }); - }); - - describe(prop('signedCert'), () => { - it('should throw for not setting together with sibling "adminPrivateKey" property', () => { - const err = '"value" contains [signedCert] without its required peers [adminPrivateKey]'; - delete config.adminPrivateKey; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "signedCert" fails because ["signedCert" must be an object]'; - config.signedCert = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "signedCert" fails because ["value" must contain at least one of [pem, path]]'; - config.signedCert = {}; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "signedCert" fails because ["value" contains a conflict between exclusive peers [pem, path]]'; - config.signedCert.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "signedCert" fails because ["unknown" is not allowed]'; - config.signedCert.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "signedCert" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]'; - config.signedCert.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "signedCert" fails because [child "path" fails because ["path" must be a string]]'; - config.signedCert.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.signedCert.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.signedCert.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "signedCert" fails because [child "pem" fails because ["pem" must be a string]]'; - config.signedCert.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "signedCert" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]'; - config.signedCert.pem = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe('Function: validateOrganizationWallets', () => { - let config = { - org0: { - path : 'myWalletPath' - }, - org1: { - path : 'myOtherWalletPath' - } - }; - - const validOrgs = ['org0', 'org1']; - const configString = JSON.stringify(config); - - - // reset the config before every test - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateOrganizationWallets(config, validOrgs); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for an invalid org name', () => { - const err = '"value" must be one of [org0, org1]'; - config = { - org0: { - path : 'myWalletPath' - }, - orgNotHere: { - path : 'myOtherWalletPath' - } - }; - call.should.throw(err); - }); - - it('should throw for a missing path', () => { - const err = 'child "path" fails because ["path" is required]'; - config = { - org0: {}, - org1: { - path : 'myOtherWalletPath' - } - }; - call.should.throw(err); - }); - - it('should throw for a non-string value for the path', () => { - const err = 'child "path" fails because ["path" must be a string]'; - config = { - org0: { - path : 1 - }, - org1: { - path : 'myOtherWalletPath' - } - }; - call.should.throw(err); - }); - - it('should throw for invalid additional items within the object', () => { - const err = 'child "path" fails because ["path" must be a string]. "aNumber" is not allowed'; - config = { - org0: { - path : 1, - aNumber : 1 - }, - org1: { - path : 'myOtherWalletPath' - } - }; - call.should.throw(err); - }); - - }); - - describe('Function: validateClient', () => { - let config = { - client: { - organization: 'Org1', - credentialStore: { - path: 'path', - cryptoStore: { - path: 'path' - } - }, - - clientPrivateKey: { - path: 'path' - }, - clientSignedCert: { - path: 'path' - }, - connection: { - timeout: { - peer: { - endorser: 120, - eventHub: 60, - eventReg: 3 - }, - orderer: 30 - }, - } - // other properties are added during the tests - } - }; - - const configString = JSON.stringify(config); - let hasOrgWallet = false; - - // reset the config before every test - beforeEach(() => { - hasOrgWallet = false; - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateClient(config, - ['Org1', 'Org2'], hasOrgWallet); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - describe(prop('client'), () => { - it('should throw for missing property', () => { - const err = 'child "client" fails because ["client" is required]'; - delete config.client; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because ["client" must be an object]'; - config.client = true; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = 'invalid'; - call.should.throw(err); - }); - - it('should throw if "clientSignedCert" is set without "clientPrivateKey"', () => { - const err = 'child "client" fails because ["value" contains [clientSignedCert] without its required peers [clientPrivateKey]]'; - delete config.client.clientPrivateKey; - call.should.throw(err); - }); - - it('should throw if "affiliation" is set together with client materials', () => { - const err = 'child "client" fails because ["value" contains a conflict between exclusive peers [affiliation, enrollmentSecret, clientSignedCert]]'; - config.client.affiliation = 'aff'; - call.should.throw(err); - }); - - it('should throw if "enrollmentSecret" is set together with client materials', () => { - const err = 'child "client" fails because ["value" contains a conflict between exclusive peers [affiliation, enrollmentSecret, clientSignedCert]]'; - config.client.enrollmentSecret = 'secret'; - call.should.throw(err); - }); - - it('should throw if "enrollmentSecret" is set together with "affiliation"', () => { - const err = 'child "client" fails because ["value" contains a conflict between exclusive peers [affiliation, enrollmentSecret, clientSignedCert]]'; - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - config.client.affiliation = 'aff'; - config.client.enrollmentSecret = 'secret'; - call.should.throw(err); - }); - - it('should throw if no credential options are set when not using a wallet', () => { - const err = 'child "client" fails because ["value" must contain at least one of [affiliation, enrollmentSecret, clientSignedCert]]'; - delete config.client.affiliation; - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - hasOrgWallet = false; - call.should.throw(err); - }); - - it('should not throw if no credential options are set when using a wallet', () => { - delete config.client.affiliation; - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - delete config.client.credentialStore; - hasOrgWallet = true; - call.should.not.throw(); - }); - - describe(prop('organization'), () => { - it('should throw for missing required property', () => { - const err = 'child "client" fails because [child "organization" fails because ["organization" is required]]'; - delete config.client.organization; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "organization" fails because ["organization" must be a string]]'; - config.client.organization = true; - call.should.throw(err); - }); - - it('should throw for a non-existing reference', () => { - const err = 'child "client" fails because [child "organization" fails because ["organization" must be one of [Org1, Org2]]]'; - config.client.organization = 'Org5'; - call.should.throw(err); - }); - }); - - describe(prop('credentialStore'), () => { - it('should throw for missing required property', () => { - const err = 'child "client" fails because [child "credentialStore" fails because ["credentialStore" is required]]'; - delete config.client.credentialStore; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because ["credentialStore" must be an object]]'; - config.client.credentialStore = true; - call.should.throw(err); - }); - - it('should throw for property when using a wallet', () => { - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - const err = 'child "client" fails because [child "credentialStore" fails because ["credentialStore" is not allowed]]'; - hasOrgWallet = true; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "client" fails because [child "credentialStore" fails because ["unknown" is not allowed]]'; - config.client.credentialStore.unknown = 'invalid'; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for missing required property', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "path" fails because ["path" is required]]]'; - delete config.client.credentialStore.path; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "path" fails because ["path" must be a string]]]'; - config.client.credentialStore.path = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]]'; - config.client.credentialStore.path = ''; - call.should.throw(err); - }); - }); - - describe(prop('cryptoStore'), () => { - it('should throw for missing required property', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "cryptoStore" fails because ["cryptoStore" is required]]]'; - delete config.client.credentialStore.cryptoStore; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "cryptoStore" fails because ["cryptoStore" must be an object]]]'; - config.client.credentialStore.cryptoStore = true; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for missing required property', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "cryptoStore" fails because [child "path" fails because ["path" is required]]]]'; - delete config.client.credentialStore.cryptoStore.path; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "cryptoStore" fails because [child "path" fails because ["path" must be a string]]]]'; - config.client.credentialStore.cryptoStore.path = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "credentialStore" fails because [child "cryptoStore" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]]]'; - config.client.credentialStore.cryptoStore.path = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe(prop('clientPrivateKey'), () => { - it('should throw for property when using a wallet', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because ["clientPrivateKey" is not allowed]]'; - hasOrgWallet = true; - delete config.client.credentialStore; - delete config.client.clientSignedCert; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because ["clientPrivateKey" must be an object]]'; - config.client.clientPrivateKey = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because ["value" must contain at least one of [pem, path]]]'; - config.client.clientPrivateKey = {}; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because ["value" contains a conflict between exclusive peers [pem, path]]]'; - config.client.clientPrivateKey.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because ["unknown" is not allowed]]'; - config.client.clientPrivateKey.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]]'; - config.client.clientPrivateKey.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because [child "path" fails because ["path" must be a string]]]'; - config.client.clientPrivateKey.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.client.clientPrivateKey.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.client.clientPrivateKey.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because [child "pem" fails because ["pem" must be a string]]]'; - config.client.clientPrivateKey.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "clientPrivateKey" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]]'; - config.client.clientPrivateKey.pem = ''; - call.should.throw(err); - }); - }); - }); - - describe(prop('clientSignedCert'), () => { - it('should throw for property when using a wallet', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because ["clientSignedCert" is not allowed]]'; - hasOrgWallet = true; - delete config.client.credentialStore; - delete config.client.clientPrivateKey; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because ["clientSignedCert" must be an object]]'; - config.client.clientSignedCert = 'yes'; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because ["value" must contain at least one of [pem, path]]]'; - config.client.clientSignedCert = {}; - call.should.throw(err); - }); - - it('should throw when setting both "path" and "pem" child properties', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because ["value" contains a conflict between exclusive peers [pem, path]]]'; - config.client.clientSignedCert.pem = 'asdf'; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because ["unknown" is not allowed]]'; - config.client.clientSignedCert.unknown = ''; - call.should.throw(err); - }); - - describe(prop('path'), () => { - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]]'; - config.client.clientSignedCert.path = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because [child "path" fails because ["path" must be a string]]]'; - config.client.clientSignedCert.path = true; - call.should.throw(err); - }); - }); - - describe(prop('pem'), () => { - beforeEach(() => { - delete config.client.clientSignedCert.path; - }); - - it('should not throw when setting property instead of sibling "path" property', () => { - config.client.clientSignedCert.pem = 'asdf'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because [child "pem" fails because ["pem" must be a string]]]'; - config.client.clientSignedCert.pem = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "clientSignedCert" fails because [child "pem" fails because ["pem" is not allowed to be empty, "pem" length must be at least 1 characters long]]]'; - config.client.clientSignedCert.pem = ''; - call.should.throw(err); - }); - }); - }); - - describe(prop('connection'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "connection" fails because ["connection" must be an object]]'; - config.client.connection = true; - call.should.throw(err); - }); - - it('should throw for an unknown child property', () => { - const err = 'child "client" fails because [child "connection" fails because ["unknown" is not allowed]]'; - config.client.connection.unknown = ''; - call.should.throw(err); - }); - - describe(prop('timeout'), () => { - it('should throw for missing property', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because ["timeout" is required]]]'; - delete config.client.connection.timeout; - call.should.throw(err); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because ["timeout" must be an object]]]'; - config.client.connection.timeout = true; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because ["value" must contain at least one of [peer, orderer]]]]'; - config.client.connection.timeout = {}; - call.should.throw(err); - }); - - it('should throw for an unknown child property', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because ["unknown" is not allowed]]]'; - config.client.connection.timeout.unknown = ''; - call.should.throw(err); - }); - - describe(prop('peer'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection.timeout.peer; - call.should.not.throw(); - }); - - it('should throw for a non-object value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because ["peer" must be an object]]]]'; - config.client.connection.timeout.peer = true; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because ["value" must contain at least one of [endorser, eventHub, eventReg]]]]]'; - config.client.connection.timeout.peer = {}; - call.should.throw(err); - }); - - it('should throw for an unknown child property', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because ["unknown" is not allowed]]]]'; - config.client.connection.timeout.peer.unknown = ''; - call.should.throw(err); - }); - - describe(prop('endorser'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection.timeout.peer.endorser; - call.should.not.throw(); - }); - - it('should throw for a non-number value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "endorser" fails because ["endorser" must be a number]]]]]'; - config.client.connection.timeout.peer.endorser = true; - call.should.throw(err); - }); - - it('should throw for a negative value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "endorser" fails because ["endorser" must be a positive number]]]]]'; - config.client.connection.timeout.peer.endorser = -10; - call.should.throw(err); - }); - }); - - describe(prop('eventHub'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection.timeout.peer.eventHub; - call.should.not.throw(); - }); - - it('should throw for a non-number value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "eventHub" fails because ["eventHub" must be a number]]]]]'; - config.client.connection.timeout.peer.eventHub = true; - call.should.throw(err); - }); - - it('should throw for a negative value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "eventHub" fails because ["eventHub" must be a positive number]]]]]'; - config.client.connection.timeout.peer.eventHub = -10; - call.should.throw(err); - }); - }); - - describe(prop('eventReg'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection.timeout.peer.eventReg; - call.should.not.throw(); - }); - - it('should throw for a non-number value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "eventReg" fails because ["eventReg" must be a number]]]]]'; - config.client.connection.timeout.peer.eventReg = true; - call.should.throw(err); - }); - - it('should throw for a negative value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "peer" fails because [child "eventReg" fails because ["eventReg" must be a positive number]]]]]'; - config.client.connection.timeout.peer.eventReg = -10; - call.should.throw(err); - }); - }); - }); - - describe(prop('orderer'), () => { - it('should not throw for missing optional property', () => { - delete config.client.connection.timeout.orderer; - call.should.not.throw(); - }); - - it('should throw for a non-number value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "orderer" fails because ["orderer" must be a number]]]]'; - config.client.connection.timeout.orderer = true; - call.should.throw(err); - }); - - it('should throw for a negative value', () => { - const err = 'child "client" fails because [child "connection" fails because [child "timeout" fails because [child "orderer" fails because ["orderer" must be a positive number]]]]'; - config.client.connection.timeout.orderer = -10; - call.should.throw(err); - }); - }); - }); - }); - - describe(prop('affiliation'), () => { - beforeEach(() => { - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - }); - - it('should throw for property when using a wallet', () => { - const err = 'child "client" fails because [child "affiliation" fails because ["affiliation" is not allowed]]'; - hasOrgWallet = true; - delete config.client.credentialStore; - config.client.affiliation = 'aff'; - call.should.throw(err); - }); - - it('should not throw for setting it without client materials', () => { - config.client.affiliation = 'aff'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "affiliation" fails because ["affiliation" must be a string]]'; - config.client.affiliation = true; - call.should.throw(err); - }); - - it('should throw for an empty string', () => { - const err = 'child "client" fails because [child "affiliation" fails because ["affiliation" is not allowed to be empty, "affiliation" length must be at least 1 characters long]]'; - config.client.affiliation = ''; - call.should.throw(err); - }); - }); - - describe(prop('attributes'), () => { - beforeEach(() => { - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - config.client.affiliation = 'aff'; - }); - - it('should throw for property when using a wallet', () => { - const err = 'child "client" fails because [child "affiliation" fails because ["affiliation" is not allowed]]'; - hasOrgWallet = true; - delete config.client.credentialStore; - config.client.affiliation = 'aff'; - call.should.throw(err); - }); - - it('should not throw for setting it without client materials but with affiliation', () => { - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: true}, {name: 'attr2', value: 'val2'}]; - call.should.not.throw(); - }); - - it('should throw if set without affiliation', () => { - const err = 'child "client" fails because ["attributes" missing required peer "affiliation", "value" must contain at least one of [affiliation, enrollmentSecret, clientSignedCert]]'; - delete config.client.affiliation; - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: true}, {name: 'attr2', value: 'val2'}]; - call.should.throw(err); - }); - - it('should throw for a non-array value', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" must be an array]]'; - config.client.attributes = true; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" must contain at least 1 items]]'; - config.client.attributes = []; - call.should.throw(err); - }); - - it('should throw for an undefined value', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" must not be a sparse array]]'; - config.client.attributes = [undefined]; - call.should.throw(err); - }); - - it('should throw for an unknown child property of an item', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" at position 1 fails because ["unknown" is not allowed]]]'; - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: true}, {name: 'attr2', value: 'val2', unknown: ''}]; - call.should.throw(err); - }); - - describe(prop('[item].name'), () => { - it('should throw for duplicate names', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" position 1 contains a duplicate value]]'; - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: true}, {name: 'attr1', value: 'val2'}]; - call.should.throw(err); - }); - - it('should throw for name with empty string', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" at position 1 fails because [child "name" fails because ["name" is not allowed to be empty, "name" length must be at least 1 characters long]]]]'; - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: true}, {name: '', value: 'val2'}]; - call.should.throw(err); - }); - - it('should throw for missing name', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" at position 0 fails because [child "name" fails because ["name" is required]]]]'; - config.client.attributes = [ {value: 'val1', ecert: true}, {name: 'attr1', value: 'val2'}]; - call.should.throw(err); - }); - }); - - describe(prop('[item].value'), () => { - it('should throw for missing value', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" at position 0 fails because [child "value" fails because ["value" is required]]]]'; - config.client.attributes = [ {name: 'attr1', ecert: true}, {name: 'attr2', value: 'val2'}]; - call.should.throw(err); - }); - }); - - describe(prop('[item].ecert'), () => { - it('should throw for non-boolean value', () => { - const err = 'child "client" fails because [child "attributes" fails because ["attributes" at position 0 fails because [child "ecert" fails because ["ecert" must be a boolean]]]]'; - config.client.attributes = [ {name: 'attr1', value: 'val1', ecert: 'ecert'}, {name: 'attr2', value: 'val2'}]; - call.should.throw(err); - }); - }); - }); - - describe(prop('enrollmentSecret'), () => { - beforeEach(() => { - delete config.client.clientPrivateKey; - delete config.client.clientSignedCert; - }); - - it('should throw for property when using a wallet', () => { - const err = 'child "client" fails because [child "enrollmentSecret" fails because ["enrollmentSecret" is not allowed]]'; - hasOrgWallet = true; - delete config.client.credentialStore; - config.client.enrollmentSecret = 'secret'; - call.should.throw(err); - }); - - it('should not throw for if set without client materials', () => { - config.client.enrollmentSecret = 'secret'; - call.should.not.throw(); - }); - - it('should throw for a non-string value', () => { - const err = 'child "client" fails because [child "enrollmentSecret" fails because ["enrollmentSecret" must be a string]]'; - config.client.enrollmentSecret = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "client" fails because [child "enrollmentSecret" fails because ["enrollmentSecret" is not allowed to be empty, "enrollmentSecret" length must be at least 1 characters long]]'; - config.client.enrollmentSecret = ''; - call.should.throw(err); - }); - }); - }); - }); - - describe('Function: validateChannel', () => { - let config = { - created: false, - configBinary: 'path', - orderers: ['orderer.example.com'], - peers: { - 'peer0.org1.example.com': { - eventSource: true, - endorsingPeer: true, - chaincodeQuery: true, - ledgerQuery: true - }, - 'peer0.org2.example.com': { - eventSource: true, - endorsingPeer: true, - chaincodeQuery: true, - ledgerQuery: true - } - }, - contracts: [ - { - id: 'marbles', - contractID: 'ContractMarbles', - version: 'v0', - language: 'golang', - path: 'path', - metadataPath: 'path', - targetPeers: ['peer0.org1.example.com', 'peer0.org2.example.com'], - 'endorsement-policy': { - identities: [ - { role: { name: 'member', mspId: 'Org1MSP' }}, - { role: { name: 'member', mspId: 'Org2MSP' }} - ], - policy: { - '2-of': [ - { 'signed-by': 1}, - { '1-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }]} - ] - } - }, - init: [], - function: 'init', - initTransientMap: { - key: 'value' - }, - 'collections-config': [ - { - name: 'name', - policy: { - identities: [ - {role: {name: 'member', mspId: 'Org1MSP'}}, - {role: {name: 'member', mspId: 'Org2MSP'}} - ], - policy: { - '2-of': [ - {'signed-by': 1}, - {'1-of': [{'signed-by': 0}, {'signed-by': 1}]} - ] - } - }, - requiredPeerCount: 1, - maxPeerCount: 2, - blockToLive: 0 - }, - { - name: 'name2', - policy: { - identities: [ - {role: {name: 'member', mspId: 'Org1MSP'}}, - {role: {name: 'member', mspId: 'Org2MSP'}} - ], - policy: { - '2-of': [ - {'signed-by': 1}, - {'1-of': [{'signed-by': 0}, {'signed-by': 1}]} - ] - } - }, - requiredPeerCount: 1, - maxPeerCount: 2, - blockToLive: 0 - } - ] - }, - { - id: 'drm', - version: 'v0' - } - ] - - // additional properties added by the tests - }; - const configString = JSON.stringify(config); - - beforeEach(() => { - config = JSON.parse(configString); - }); - - /** - * Wraps the actual call, so "should" can call this function without parameters - */ - function call() { - ConfigValidator.validateChannel(config, - ['orderer.example.com'], - ['peer0.org1.example.com', 'peer0.org2.example.com'], - ['Org1MSP', 'Org2MSP'], - ['Contract1'], - flowOptions, - discovery); - } - - it('should not throw for a valid value', () => { - call.should.not.throw(); - }); - - it('should throw for unknown child property', () => { - const err = '"unknown" is not allowed'; - config.unknown = ''; - call.should.throw(err); - }); - - it('should throw when both "configBinary" and "definition" is set for created channel', () => { - const err = '"value" contains a conflict between optional exclusive peers [configBinary, definition]'; - config.created = true; - config.definition = { - capabilities : [], - consortium : 'SampleConsortium', - msps : [ 'Org1MSP', 'Org2MSP' ], - version : 0 - }; - call.should.throw(err); - }); - - describe(prop('created'), () => { - it('should not throw for missing optional property', () => { - delete config.created; - call.should.not.throw(); - }); - - it('should throw for a non-boolean value', () => { - const err = 'child "created" fails because ["created" must be a boolean]'; - config.created = 'yes'; - call.should.throw(err); - }); - }); - - describe(prop('configBinary'), () => { - it('should throw for an empty string value', () => { - const err = 'child "configBinary" fails because ["configBinary" is not allowed to be empty, "configBinary" length must be at least 1 characters long]'; - config.configBinary = ''; - call.should.throw(err); - }); - - it('should throw for a non-string value', () => { - const err = 'child "configBinary" fails because ["configBinary" must be a string]'; - config.configBinary = true; - call.should.throw(err); - }); - - it('should not throw for missing property when channel is created', () => { - config.created = true; - delete config.configBinary; - call.should.not.throw(); - }); - - it('should not throw for missing property when definition is set', () => { - delete config.configBinary; - config.definition = { - capabilities : [], - consortium : 'SampleConsortium', - msps : [ 'Org1MSP', 'Org2MSP' ], - version : 0 - }; - call.should.not.throw(); - }); - - it('should throw for property when definition is set', () => { - const err = 'child "configBinary" fails because ["configBinary" is not allowed]'; - config.definition = { - capabilities : [], - consortium : 'SampleConsortium', - msps : [ 'Org1MSP', 'Org2MSP' ], - version : 0 - }; - call.should.throw(err); - }); - }); - - describe(prop('definition'), () => { - beforeEach(() => { - delete config.configBinary; - config.definition = { - capabilities : [], - consortium : 'SampleConsortium', - msps : [ 'Org1MSP', 'Org2MSP' ], - version : 0 - }; - }); - - it('should not throw for setting it instead of "configBinary"', () => { - call.should.not.throw(); - }); - - it('should not throw for missing property when channel is created', () => { - config.created = true; - delete config.definition; - call.should.not.throw(); - }); - - it('should throw for non-object value', () => { - const err = 'child "definition" fails because ["definition" must be an object]'; - config.definition = true; - call.should.throw(err); - }); - - it('should throw for empty object', () => { - const err = 'child "definition" fails because [child "capabilities" fails because ["capabilities" is required], child "consortium" fails because ["consortium" is required], child "msps" fails because ["msps" is required], child "version" fails because ["version" is required]]'; - config.definition = {}; - call.should.throw(err); - }); - - describe(prop('capabilities'), () => { - it('should throw for missing required property', () => { - const err = 'child "definition" fails because [child "capabilities" fails because ["capabilities" is required]]'; - delete config.definition.capabilities; - call.should.throw(err); - }); - - it('should throw for non-array value', () => { - const err = 'child "definition" fails because [child "capabilities" fails because ["capabilities" must be an array]]'; - config.definition.capabilities = true; - call.should.throw(err); - }); - - it('should throw for undefined elements', () => { - const err = 'child "definition" fails because [child "capabilities" fails because ["capabilities" must not be a sparse array]]'; - config.definition.capabilities = [ undefined ]; - call.should.throw(err); - }); - }); - - describe(prop('consortium'), () => { - it('should throw for missing required property', () => { - const err = 'child "definition" fails because [child "consortium" fails because ["consortium" is required]]'; - delete config.definition.consortium; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "definition" fails because [child "consortium" fails because ["consortium" must be a string]]'; - config.definition.consortium = true; - call.should.throw(err); - }); - - it('should throw for an empty string value', () => { - const err = 'child "definition" fails because [child "consortium" fails because ["consortium" is not allowed to be empty, "consortium" length must be at least 1 characters long]]'; - config.definition.consortium = ''; - call.should.throw(err); - }); - }); - - describe(prop('msps'), () => { - it('should throw for missing required property', () => { - const err = 'child "definition" fails because [child "msps" fails because ["msps" is required]]'; - delete config.definition.msps; - call.should.throw(err); - }); - - it('should throw for non-array value', () => { - const err = 'child "definition" fails because [child "msps" fails because ["msps" must be an array]]'; - config.definition.msps = true; - call.should.throw(err); - }); - - it('should throw for an undefined item', () => { - const err = 'child "definition" fails because [child "msps" fails because ["msps" must not be a sparse array]]'; - config.definition.msps.push(undefined); - call.should.throw(err); - }); - - it('should throw for an invalid item', () => { - const err = 'child "definition" fails because [child "msps" fails because ["msps" at position 2 fails because ["2" must be one of [Org1MSP, Org2MSP]]]]'; - config.definition.msps.push('Org5MSP'); - call.should.throw(err); - }); - - it('should throw for duplicated items', () => { - const err = 'child "definition" fails because [child "msps" fails because ["msps" position 2 contains a duplicate value]]'; - config.definition.msps.push('Org1MSP'); - call.should.throw(err); - }); - }); - - describe(prop('version'), () => { - it('should throw for missing required property', () => { - const err = 'child "definition" fails because [child "version" fails because ["version" is required]]'; - delete config.definition.version; - call.should.throw(err); - }); - - it('should throw for non-integer value', () => { - const err = 'child "definition" fails because [child "version" fails because ["version" must be an integer]]'; - config.definition.version = 3.14; - call.should.throw(err); - }); - - it('should throw for a negative value', () => { - const err = 'child "definition" fails because [child "version" fails because ["version" must be larger than or equal to 0]]'; - config.definition.version = -10; - call.should.throw(err); - }); - }); - }); - - describe(prop('orderers'), () => { - it('should throw for missing required property', () => { - const err = 'child "orderers" fails because ["orderers" is required]'; - delete config.orderers; - call.should.throw(err); - }); - - it('should not throw for missing optional property in discovery mode', () => { - discovery = true; - delete config.orderers; - call.should.not.throw(); - }); - - it('should throw for non-array value', () => { - const err = 'child "orderers" fails because ["orderers" must be an array]'; - config.orderers = true; - call.should.throw(err); - }); - - it('should throw for an undefined item', () => { - const err = 'child "orderers" fails because ["orderers" must not be a sparse array]'; - config.orderers.push(undefined); - call.should.throw(err); - }); - - it('should throw for an invalid item', () => { - const err = 'child "orderers" fails because ["orderers" at position 1 fails because ["1" must be one of [orderer.example.com]]]'; - config.orderers.push('orderer2.example.com'); - call.should.throw(err); - }); - - it('should throw for duplicated items', () => { - const err = 'child "orderers" fails because ["orderers" position 1 contains a duplicate value]'; - config.orderers.push('orderer.example.com'); - call.should.throw(err); - }); - }); - - describe(prop('peers'), () => { - it('should throw for missing required property', () => { - const err = 'child "peers" fails because ["peers" is required]'; - delete config.peers; - call.should.throw(err); - }); - - it('should throw for non-object value', () => { - const err = 'child "peers" fails because ["peers" must be an object]'; - config.peers = true; - call.should.throw(err); - }); - - it('should throw for invalid child property, even if its structure would be valid', () => { - const err = 'child "peers" fails because ["unknown" is not allowed]'; - config.peers.unknown = {}; - call.should.throw(err); - }); - - describe(prop('[child].endorsingPeer'), () => { - it('should not throw for missing optional property', () => { - delete config.peers['peer0.org1.example.com'].endorsingPeer; - call.should.not.throw(); - }); - - it('should throw for non-bool value', () => { - const err = 'child "peers" fails because [child "peer0.org1.example.com" fails because [child "endorsingPeer" fails because ["endorsingPeer" must be a boolean]]]'; - config.peers['peer0.org1.example.com'].endorsingPeer = ''; - call.should.throw(err); - }); - }); - - describe(prop('[child].chaincodeQuery'), () => { - it('should not throw for missing optional property', () => { - delete config.peers['peer0.org1.example.com'].chaincodeQuery; - call.should.not.throw(); - }); - - it('should throw for non-bool value', () => { - const err = 'child "peers" fails because [child "peer0.org1.example.com" fails because [child "chaincodeQuery" fails because ["chaincodeQuery" must be a boolean]]]'; - config.peers['peer0.org1.example.com'].chaincodeQuery = ''; - call.should.throw(err); - }); - }); - - describe(prop('[child].ledgerQuery'), () => { - it('should not throw for missing optional property', () => { - delete config.peers['peer0.org1.example.com'].ledgerQuery; - call.should.not.throw(); - }); - - it('should throw for non-bool value', () => { - const err = 'child "peers" fails because [child "peer0.org1.example.com" fails because [child "ledgerQuery" fails because ["ledgerQuery" must be a boolean]]]'; - config.peers['peer0.org1.example.com'].ledgerQuery = ''; - call.should.throw(err); - }); - }); - - describe(prop('[child].eventSource'), () => { - it('should not throw for missing optional property', () => { - delete config.peers['peer0.org1.example.com'].eventSource; - call.should.not.throw(); - }); - - it('should throw for non-bool value', () => { - const err = 'child "peers" fails because [child "peer0.org1.example.com" fails because [child "eventSource" fails because ["eventSource" must be a boolean]]]'; - config.peers['peer0.org1.example.com'].eventSource = ''; - call.should.throw(err); - }); - }); - }); - - describe(prop('contracts'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" is required]'; - delete config.contracts; - call.should.throw(err); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" must be an array]'; - config.contracts = true; - call.should.throw(err); - }); - - it('should throw for an undefined item', () => { - const err = 'child "contracts" fails because ["contracts" must not be a sparse array]'; - config.contracts.push(undefined); - call.should.throw(err); - }); - - it('should throw "metadataPath" is set without "path"', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because ["metadataPath" missing required peer "path"]]'; - delete config.contracts[0].path; - call.should.throw(err); - }); - - it('should throw "path" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["path" missing required peer "language"]]'; - // on second contract - config.contracts[1].path = 'path'; - call.should.throw(err); - }); - - it('should throw "init" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["init" missing required peer "language"]]'; - // on second contract - config.contracts[1].init = []; - call.should.throw(err); - }); - - it('should throw "function" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["function" missing required peer "language"]]'; - // on second contract - config.contracts[1].function = 'init'; - call.should.throw(err); - }); - - it('should throw "initTransientMap" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["initTransientMap" missing required peer "language"]]'; - // on second contract - config.contracts[1].initTransientMap = {}; - call.should.throw(err); - }); - - it('should throw "collections-config" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["collections-config" missing required peer "language"]]'; - // on second contract - config.contracts[1]['collections-config'] = [{ - name: 'name', - policy: { - identities: [ - {role: {name: 'member', mspId: 'Org1MSP'}}, - {role: {name: 'member', mspId: 'Org2MSP'}} - ], - policy: { - '2-of': [ - {'signed-by': 1}, - {'1-of': [{'signed-by': 0}, {'signed-by': 1}]} - ] - } - }, - requiredPeerCount: 1, - maxPeerCount: 2, - blockToLive: 0 - }]; - call.should.throw(err); - }); - - it('should throw "endorsement-policy" is set without "language"', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because ["endorsement-policy" missing required peer "language"]]'; - // on second contract - config.contracts[1]['endorsement-policy'] = { - identities: [ - { role: { name: 'member', mspId: 'Org1MSP' }}, - { role: { name: 'member', mspId: 'Org2MSP' }} - ], - policy: { - '2-of': [ - { 'signed-by': 1}, - { '1-of': [{ 'signed-by': 0 }, { 'signed-by': 1 }]} - ] - } - }; - call.should.throw(err); - }); - - describe(prop('[item].id'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "id" fails because ["id" is required]]]'; - delete config.contracts[0].id; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "id" fails because ["id" must be a string]]]'; - config.contracts[0].id = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "id" fails because ["id" is not allowed to be empty, "id" length must be at least 1 characters long]]]'; - config.contracts[0].id = ''; - call.should.throw(err); - }); - }); - - describe(prop('[item].version'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "version" fails because ["version" is required]]]'; - delete config.contracts[0].version; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "version" fails because ["version" must be a string]]]'; - config.contracts[0].version = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "version" fails because ["version" is not allowed to be empty, "version" length must be at least 1 characters long]]]'; - config.contracts[0].version = ''; - call.should.throw(err); - }); - }); - - describe(prop('[item].contractID'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].contractID; - call.should.not.throw(); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "contractID" fails because ["contractID" must be a string]]]'; - config.contracts[0].contractID = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "contractID" fails because ["contractID" is not allowed to be empty, "contractID" length must be at least 1 characters long]]]'; - config.contracts[0].contractID = ''; - call.should.throw(err); - }); - - it('should throw for duplicate items based on "contractID" vs "contractID"', () => { - const err = 'child "contracts" fails because ["contracts" position 1 contains a duplicate value]'; - config.contracts[1].contractID = 'ContractMarbles'; - call.should.throw(err); - }); - - it('should throw for duplicate items based on "contractID" vs "id"', () => { - const err = 'child "contracts" fails because ["contracts" position 1 contains a duplicate value]'; - config.contracts[1].id = 'ContractMarbles'; - call.should.throw(err); - }); - - it('should throw for duplicate items based on "id" vs "contractID"', () => { - const err = 'child "contracts" fails because ["contracts" position 1 contains a duplicate value]'; - delete config.contracts[0].contractID; - config.contracts[1].contractID = 'marbles'; - call.should.throw(err); - }); - - it('should throw for duplicate items based on "id" vs "id"', () => { - const err = 'child "contracts" fails because ["contracts" position 1 contains a duplicate value]'; - delete config.contracts[0].contractID; - config.contracts[1].id = 'marbles'; - call.should.throw(err); - }); - }); - - // using the second contract - describe(prop('[item].language'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[1].language; - call.should.not.throw(); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because [child "language" fails because ["language" must be a string]]]'; - config.contracts[1].language = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because [child "language" fails because ["language" is not allowed to be empty, "language" must be one of [golang, node, java]]]]'; - config.contracts[1].language = ''; - call.should.throw(err); - }); - - it('should throw for invalid string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 1 fails because [child "language" fails because ["language" must be one of [golang, node, java]]]]'; - config.contracts[1].language = 'noooode'; - call.should.throw(err); - }); - }); - - describe(prop('[item].path'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].metadataPath; - delete config.contracts[0].path; - call.should.not.throw(); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "path" fails because ["path" must be a string]]]'; - config.contracts[0].path = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "path" fails because ["path" is not allowed to be empty, "path" length must be at least 1 characters long]]]'; - config.contracts[0].path = ''; - call.should.throw(err); - }); - }); - - describe(prop('[item].metadataPath'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].metadataPath; - call.should.not.throw(); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "metadataPath" fails because ["metadataPath" must be a string]]]'; - config.contracts[0].metadataPath = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "metadataPath" fails because ["metadataPath" is not allowed to be empty, "metadataPath" length must be at least 1 characters long]]]'; - config.contracts[0].metadataPath = ''; - call.should.throw(err); - }); - }); - - describe(prop('[item].init'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].init; - call.should.not.throw(); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "init" fails because ["init" must be an array]]]'; - config.contracts[0].init = true; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "init" fails because ["init" must not be a sparse array]]]'; - config.contracts[0].init.push(undefined); - call.should.throw(err); - }); - - it('should throw for non-string item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "init" fails because ["init" at position 0 fails because ["0" must be a string]]]]'; - config.contracts[0].init.push(true); - call.should.throw(err); - }); - }); - - describe(prop('[item].function'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].function; - call.should.not.throw(); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "function" fails because ["function" must be a string]]]'; - config.contracts[0].function = true; - call.should.throw(err); - }); - }); - - describe(prop('[item].initTransientMap'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].initTransientMap; - call.should.not.throw(); - }); - - it('should throw for non-object value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "initTransientMap" fails because ["initTransientMap" must be an object]]]'; - config.contracts[0].initTransientMap = true; - call.should.throw(err); - }); - - it('should throw for non-string child property key', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "initTransientMap" fails because [child "false" fails because ["false" must be a string]]]]'; - config.contracts[0].initTransientMap.false = true; - call.should.throw(err); - }); - - it('should throw for non-string child property value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "initTransientMap" fails because [child "key" fails because ["key" must be a string]]]]'; - config.contracts[0].initTransientMap.key = true; - call.should.throw(err); - }); - }); - - describe(prop('[item].collections-config'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0]['collections-config']; - call.should.not.throw(); - }); - - it('should not throw for string form instead of object form', () => { - config.contracts[0]['collections-config'] = 'path'; - call.should.not.throw(); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" must be an array]]]'; - config.contracts[0]['collections-config'] = true; - call.should.throw(err); - }); - - it('should throw for empty array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" must contain at least 1 items]]]'; - config.contracts[0]['collections-config'] = []; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" must not be a sparse array]]]'; - config.contracts[0]['collections-config'].push(undefined); - call.should.throw(err); - }); - - it('should throw for unknown child property of item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because ["unknown" is not allowed]]]]'; - config.contracts[0]['collections-config'][0].unknown = ''; - call.should.throw(err); - }); - - describe(prop('[item].name'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "name" fails because ["name" is required]]]]]'; - delete config.contracts[0]['collections-config'][0].name; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "name" fails because ["name" must be a string]]]]]'; - config.contracts[0]['collections-config'][0].name = true; - call.should.throw(err); - }); - - it('should throw for empty string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "name" fails because ["name" is not allowed to be empty, "name" length must be at least 1 characters long]]]]]'; - config.contracts[0]['collections-config'][0].name = ''; - call.should.throw(err); - }); - - it('should throw for duplicate value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" position 1 contains a duplicate value]]]'; - config.contracts[0]['collections-config'][0].name = 'name2'; - call.should.throw(err); - }); - }); - - describe(prop('[item].requiredPeerCount'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" is required], child "maxPeerCount" fails because ["maxPeerCount" references "requiredPeerCount" which is not a number]]]]]'; - delete config.contracts[0]['collections-config'][0].requiredPeerCount; - call.should.throw(err); - }); - - it('should throw for non-integer value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" must be an integer]]]]]'; - config.contracts[0]['collections-config'][0].requiredPeerCount = 0.14; - call.should.throw(err); - }); - - it('should throw for negative value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" must be larger than or equal to 0]]]]]'; - config.contracts[0]['collections-config'][0].requiredPeerCount = -1; - call.should.throw(err); - }); - - it('should throw for value greater than "maxPeerCount"', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" must be less than or equal to 2], child "maxPeerCount" fails because ["maxPeerCount" must be larger than or equal to 3]]]]]'; - config.contracts[0]['collections-config'][0].requiredPeerCount = 3; - call.should.throw(err); - }); - }); - - describe(prop('[item].maxPeerCount'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" references "maxPeerCount" which is not a number], child "maxPeerCount" fails because ["maxPeerCount" is required]]]]]'; - delete config.contracts[0]['collections-config'][0].maxPeerCount; - call.should.throw(err); - }); - - it('should throw for non-integer value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "maxPeerCount" fails because ["maxPeerCount" must be an integer]]]]]'; - config.contracts[0]['collections-config'][0].maxPeerCount = 3.14; - call.should.throw(err); - }); - - it('should throw for negative value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" must be less than or equal to -1], child "maxPeerCount" fails because ["maxPeerCount" must be larger than or equal to 1]]]]]'; - config.contracts[0]['collections-config'][0].maxPeerCount = -1; - call.should.throw(err); - }); - - it('should throw for value less than "requiredPeerCount"', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "requiredPeerCount" fails because ["requiredPeerCount" must be less than or equal to 0], child "maxPeerCount" fails because ["maxPeerCount" must be larger than or equal to 1]]]]]'; - config.contracts[0]['collections-config'][0].maxPeerCount = 0; - call.should.throw(err); - }); - }); - - describe(prop('[item].blockToLive'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "blockToLive" fails because ["blockToLive" is required]]]]]'; - delete config.contracts[0]['collections-config'][0].blockToLive; - call.should.throw(err); - }); - - it('should throw for non-integer value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "blockToLive" fails because ["blockToLive" must be an integer]]]]]'; - config.contracts[0]['collections-config'][0].blockToLive = 3.14; - call.should.throw(err); - }); - - it('should throw for negative value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "blockToLive" fails because ["blockToLive" must be larger than or equal to 0]]]]]'; - config.contracts[0]['collections-config'][0].blockToLive = -1; - call.should.throw(err); - }); - }); - - describe(prop('[item].policy'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because ["policy" is required]]]]]'; - delete config.contracts[0]['collections-config'][0].policy; - call.should.throw(err); - }); - - it('should throw for non-object value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because ["policy" must be an object]]]]]'; - config.contracts[0]['collections-config'][0].policy = true; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because ["unknown" is not allowed]]]]]'; - config.contracts[0]['collections-config'][0].policy.unknown = ''; - call.should.throw(err); - }); - - describe(prop('identities'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" is required]]]]]]'; - delete config.contracts[0]['collections-config'][0].policy.identities; - call.should.throw(err); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" must be an array]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities = true; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" must not be a sparse array]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities.push(undefined); - call.should.throw(err); - }); - - it('should throw for duplicate items', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" position 2 contains a duplicate value]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities.push({role: {name: 'member', mspId: 'Org1MSP'}}); - call.should.throw(err); - }); - - it('should throw for unknown child property of items', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because ["unknown" is not allowed]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].unknown = ''; - call.should.throw(err); - }); - - describe(prop('[item].role'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because ["role" is required]]]]]]]]'; - delete config.contracts[0]['collections-config'][0].policy.identities[0].role; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because ["unknown" is not allowed]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].role.unknown = ''; - call.should.throw(err); - }); - - describe(prop('name'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" is required]]]]]]]]]'; - delete config.contracts[0]['collections-config'][0].policy.identities[0].role.name; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" must be a string]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].role.name = true; - call.should.throw(err); - }); - - it('should throw for invalid value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" must be one of [member, admin]]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].role.name = 'not-member'; - call.should.throw(err); - }); - }); - - describe(prop('mspId'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" is required]]]]]]]]]'; - delete config.contracts[0]['collections-config'][0].policy.identities[0].role.mspId; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be a string]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].role.mspId = true; - call.should.throw(err); - }); - - it('should throw for invalid value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be one of [Org1MSP, Org2MSP]]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.identities[0].role.mspId = 'Org5MSP'; - call.should.throw(err); - }); - }); - }); - }); - - describe(prop('policy'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because ["policy" is required]]]]]]'; - delete config.contracts[0]['collections-config'][0].policy.policy; - call.should.throw(err); - }); - - it('should throw for non-object value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because ["policy" must be an object]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy = true; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because ["policy" must have 1 children]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy = {}; - call.should.throw(err); - }); - - it('should throw for an invalid child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because ["of2" is not allowed]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy.of2 = {}; - call.should.throw(err); - }); - - describe(prop('X-of'), () => { - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must be an array]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'] = true; - call.should.throw(err); - }); - - it('should throw for empty value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must contain at least 1 items]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'] = []; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must not be a sparse array]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'].push(undefined); - call.should.throw(err); - }); - - it('should throw for item with invalid key', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 2 fails because ["of2" is not allowed]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'].push({ of2: true }); - call.should.throw(err); - }); - - it('should throw for empty item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 2 fails because ["2" must have at least 1 children]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'].push({}); - call.should.throw(err); - }); - - // the recursive X-of items are covered by the above tests - describe(prop('[item].signed-by'), () => { - it('should throw for non-integer value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 0 fails because [child "signed-by" fails because ["signed-by" must be an integer]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'][0]['signed-by'] = 3.14; - call.should.throw(err); - }); - - it('should throw for negative value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "collections-config" fails because ["collections-config" must be a string, "collections-config" at position 0 fails because [child "policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 0 fails because [child "signed-by" fails because ["signed-by" must be larger than or equal to 0]]]]]]]]]'; - config.contracts[0]['collections-config'][0].policy.policy['2-of'][0]['signed-by'] = -10; - call.should.throw(err); - }); - }); - }); - - }); - }); - }); - - describe(prop('[item].endorsement-policy'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0]['endorsement-policy']; - call.should.not.throw(); - }); - - it('should throw for non-object value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because ["endorsement-policy" must be an object]]]'; - config.contracts[0]['endorsement-policy'] = true; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because ["unknown" is not allowed]]]'; - config.contracts[0]['endorsement-policy'].unknown = ''; - call.should.throw(err); - }); - - describe(prop('identities'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" is required]]]]'; - delete config.contracts[0]['endorsement-policy'].identities; - call.should.throw(err); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" must be an array]]]]'; - config.contracts[0]['endorsement-policy'].identities = true; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" must not be a sparse array]]]]'; - config.contracts[0]['endorsement-policy'].identities.push(undefined); - call.should.throw(err); - }); - - it('should throw for duplicate items', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" position 2 contains a duplicate value]]]]'; - config.contracts[0]['endorsement-policy'].identities.push({ - role: { - name: 'member', - mspId: 'Org1MSP' - } - }); - call.should.throw(err); - }); - - it('should throw for unknown child property of items', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because ["unknown" is not allowed]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].unknown = ''; - call.should.throw(err); - }); - - describe(prop('[item].role'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because ["role" is required]]]]]]'; - delete config.contracts[0]['endorsement-policy'].identities[0].role; - call.should.throw(err); - }); - - it('should throw for unknown child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because ["unknown" is not allowed]]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].role.unknown = ''; - call.should.throw(err); - }); - - describe(prop('name'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" is required]]]]]]]'; - delete config.contracts[0]['endorsement-policy'].identities[0].role.name; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" must be a string]]]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].role.name = true; - call.should.throw(err); - }); - - it('should throw for invalid value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "name" fails because ["name" must be one of [member, admin]]]]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].role.name = 'not-member'; - call.should.throw(err); - }); - }); - - describe(prop('mspId'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" is required]]]]]]]'; - delete config.contracts[0]['endorsement-policy'].identities[0].role.mspId; - call.should.throw(err); - }); - - it('should throw for non-string value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be a string]]]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].role.mspId = true; - call.should.throw(err); - }); - - it('should throw for invalid value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "identities" fails because ["identities" at position 0 fails because [child "role" fails because [child "mspId" fails because ["mspId" must be one of [Org1MSP, Org2MSP]]]]]]]]'; - config.contracts[0]['endorsement-policy'].identities[0].role.mspId = 'Org5MSP'; - call.should.throw(err); - }); - }); - }); - }); - - describe(prop('policy'), () => { - it('should throw for missing required property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because ["policy" is required]]]]'; - delete config.contracts[0]['endorsement-policy'].policy; - call.should.throw(err); - }); - - it('should throw for non-object value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because ["policy" must be an object]]]]'; - config.contracts[0]['endorsement-policy'].policy = true; - call.should.throw(err); - }); - - it('should throw for an empty value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because ["policy" must have 1 children]]]]'; - config.contracts[0]['endorsement-policy'].policy = {}; - call.should.throw(err); - }); - - it('should throw for an invalid child property', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because ["of2" is not allowed]]]]'; - config.contracts[0]['endorsement-policy'].policy.of2 = {}; - call.should.throw(err); - }); - - describe(prop('X-of'), () => { - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must be an array]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'] = true; - call.should.throw(err); - }); - - it('should throw for empty value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must contain at least 1 items]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'] = []; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" must not be a sparse array]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'].push(undefined); - call.should.throw(err); - }); - - it('should throw for item with invalid key', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 2 fails because ["of2" is not allowed]]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'].push({of2: true}); - call.should.throw(err); - }); - - it('should throw for empty item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 2 fails because ["2" must have at least 1 children]]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'].push({}); - call.should.throw(err); - }); - - // the recursive X-of items are covered by the above tests - describe(prop('[item].signed-by'), () => { - it('should throw for non-integer value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 0 fails because [child "signed-by" fails because ["signed-by" must be an integer]]]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'][0]['signed-by'] = 3.14; - call.should.throw(err); - }); - - it('should throw for negative value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "endorsement-policy" fails because [child "policy" fails because [child "2-of" fails because ["2-of" at position 0 fails because [child "signed-by" fails because ["signed-by" must be larger than or equal to 0]]]]]]]'; - config.contracts[0]['endorsement-policy'].policy['2-of'][0]['signed-by'] = -10; - call.should.throw(err); - }); - }); - }); - }); - }); - - describe(prop('[item].targetPeers'), () => { - it('should not throw for missing optional property', () => { - delete config.contracts[0].targetPeers; - call.should.not.throw(); - }); - - it('should throw for non-array value', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" must be an array]]]'; - config.contracts[0].targetPeers = true; - call.should.throw(err); - }); - - it('should throw for empty array', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" must contain at least 1 items]]]'; - config.contracts[0].targetPeers = []; - call.should.throw(err); - }); - - it('should throw for undefined item', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" must not be a sparse array]]]'; - config.contracts[0].targetPeers.push(undefined); - call.should.throw(err); - }); - - it('should throw for invalid peer reference', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" at position 2 fails because ["2" must be one of [peer0.org1.example.com, peer0.org2.example.com]]]]]'; - config.contracts[0].targetPeers.push('peer0.org5.example.com'); - call.should.throw(err); - }); - - it('should throw for duplicate peer reference', () => { - const err = 'child "contracts" fails because ["contracts" at position 0 fails because [child "targetPeers" fails because ["targetPeers" position 2 contains a duplicate value]]]'; - config.contracts[0].targetPeers.push('peer0.org1.example.com'); - call.should.throw(err); - }); - }); - }); - }); -}); diff --git a/packages/caliper-fabric/test/fabricNetwork.js b/packages/caliper-fabric/test/fabricNetwork.js deleted file mode 100644 index ec05c62b8..000000000 --- a/packages/caliper-fabric/test/fabricNetwork.js +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -const FabricNetwork = require('../lib/fabricNetwork'); - -const chai = require('chai'); -const should = chai.should(); - -describe('FabricNetwork', () => { - - describe('constructor', () => { - - it('should throw if networkConfig is not provided', () => { - (() => { - new FabricNetwork(); - }).should.throw('[FabricNetwork.constructor] Parameter "networkConfig" is neither a file path nor an object'); - }); - }); - - describe('usesOrganizationWallets', () => { - - it('should return false if the org does not have an organizationWallets stanza', () => { - const config = {}; - const fabricNetwork = new FabricNetwork(config); - fabricNetwork.usesOrganizationWallets().should.be.false; - }); - - it('should return false if the org does not have any organizationWallets specified', () => { - const config = { - organizationWallets: {}, - }; - const fabricNetwork = new FabricNetwork(config); - fabricNetwork.usesOrganizationWallets().should.be.false; - }); - - it('should return true if the org does have a organizationWallets specified', () => { - const config = { - organizationWallets: { - Org0: { - path: 'myPath' - } - }, - }; - const fabricNetwork = new FabricNetwork(config); - fabricNetwork.usesOrganizationWallets().should.be.true; - }); - }); - - describe('getWalletPathForOrganization', () => { - - it('should return undefined if the org does not have a wallet specified', () => { - const config = { - organizationWallets: {}, - }; - const fabricNetwork = new FabricNetwork(config); - should.not.exist(fabricNetwork.getWalletPathForOrganization('myOrg')); - - }); - - it('should return a filepath for a correctly defined and reference wallet', () => { - const config = { - organizationWallets: { - Org0: { - path: 'myWalletPath' - }, - Org1: { - path: 'myWalletOtherPath' - } - } - }; - const fabricNetwork = new FabricNetwork(config); - fabricNetwork.getWalletPathForOrganization('Org0').endsWith('myWalletPath').should.be.true; - }); - }); - - - describe('getWalletPathForClient', () => { - - it('should return undefined if the client does not have a wallet specified for its organization', () => { - const config = { - organizationWallets: {}, - clients: { - myClient: { - client: { - organization: 'Org0' - } - } - } - }; - const fabricNetwork = new FabricNetwork(config); - should.not.exist(fabricNetwork.getWalletPathForClient('myClient')); - - }); - - it('should return a filepath for a correctly defined and reference wallet', () => { - const config = { - organizationWallets: { - Org0: { - path: 'myWalletPath' - }, - Org1: { - path: 'myWalletOtherPath' - } - }, - clients: { - myClient: { - client: { - organization: 'Org0' - } - } - } - }; - const fabricNetwork = new FabricNetwork(config); - fabricNetwork.getWalletPathForClient('myClient').endsWith('myWalletPath').should.be.true; - }); - }); - -}); diff --git a/packages/caliper-tests-integration/fabric_tests/phase1/networkconfig.yaml b/packages/caliper-tests-integration/fabric_tests/phase1/networkconfig.yaml index ec062b1e6..c71827c93 100644 --- a/packages/caliper-tests-integration/fabric_tests/phase1/networkconfig.yaml +++ b/packages/caliper-tests-integration/fabric_tests/phase1/networkconfig.yaml @@ -13,7 +13,7 @@ # name: Fabric -version: "1.0" +version: "2.0.0" caliper: blockchain: fabric diff --git a/packages/caliper-tests-integration/fabric_tests/phase2/networkconfig-legacy.yaml b/packages/caliper-tests-integration/fabric_tests/phase2/networkconfig-legacy.yaml deleted file mode 100644 index b25b3e62a..000000000 --- a/packages/caliper-tests-integration/fabric_tests/phase2/networkconfig-legacy.yaml +++ /dev/null @@ -1,162 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Fabric -version: "1.0" -mutual-tls: true - -caliper: - blockchain: fabric - -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - -channels: - mychannel: - created: false - configBinary: ../config/mychannel.tx - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: mymarbles - version: v0 - language: node - path: src/marbles/node - metadataPath: src/marbles/node/metadata - yourchannel: - created: false - definition: - capabilities: [] - consortium: 'SampleConsortium2' - msps: ['Org1MSP', 'Org2MSP'] - version: 0 - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: yourmarbles - version: v0 - language: golang - path: marbles/go - metadataPath: src/marbles/go/metadata - -organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem - -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw - - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw diff --git a/packages/caliper-tests-integration/fabric_tests/phase3/networkconfig-legacy.yaml b/packages/caliper-tests-integration/fabric_tests/phase3/networkconfig-legacy.yaml deleted file mode 100644 index a86ea09f4..000000000 --- a/packages/caliper-tests-integration/fabric_tests/phase3/networkconfig-legacy.yaml +++ /dev/null @@ -1,158 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Fabric -version: "1.0" -mutual-tls: true - -caliper: - blockchain: fabric - -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - -channels: - mychannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - - contracts: - - id: marbles - contractID: mymarbles - version: v0 - language: node - path: src/marbles/node - metadataPath: src/marbles/node/metadata - yourchannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - - contracts: - - id: marbles - contractID: yourmarbles - version: v0 - language: golang - path: marbles/go - metadataPath: src/marbles/go/metadata - -organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem - -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw - - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw diff --git a/packages/caliper-tests-integration/fabric_tests/phase4/networkconfig-legacy.yaml b/packages/caliper-tests-integration/fabric_tests/phase4/networkconfig-legacy.yaml deleted file mode 100644 index f68882b7a..000000000 --- a/packages/caliper-tests-integration/fabric_tests/phase4/networkconfig-legacy.yaml +++ /dev/null @@ -1,150 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Fabric -version: "1.0" -mutual-tls: true - -caliper: - blockchain: fabric - -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - -channels: - mychannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: mymarbles - version: v0 - yourchannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: yourmarbles - version: v0 - -organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem - -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw - - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw diff --git a/packages/caliper-tests-integration/fabric_tests/phase5/networkconfig-legacy.yaml b/packages/caliper-tests-integration/fabric_tests/phase5/networkconfig-legacy.yaml deleted file mode 100644 index f68882b7a..000000000 --- a/packages/caliper-tests-integration/fabric_tests/phase5/networkconfig-legacy.yaml +++ /dev/null @@ -1,150 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Fabric -version: "1.0" -mutual-tls: true - -caliper: - blockchain: fabric - -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - -channels: - mychannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: mymarbles - version: v0 - yourchannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: yourmarbles - version: v0 - -organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem - -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw - - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw diff --git a/packages/caliper-tests-integration/fabric_tests/phase6/networkconfig-legacy.yaml b/packages/caliper-tests-integration/fabric_tests/phase6/networkconfig-legacy.yaml deleted file mode 100644 index f68882b7a..000000000 --- a/packages/caliper-tests-integration/fabric_tests/phase6/networkconfig-legacy.yaml +++ /dev/null @@ -1,150 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -name: Fabric -version: "1.0" -mutual-tls: true - -caliper: - blockchain: fabric - -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - -channels: - mychannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: mymarbles - version: v0 - yourchannel: - created: true - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: yourmarbles - version: v0 - -organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem - -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw - - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw diff --git a/packages/caliper-tests-integration/fabric_tests/phase7/networkconfig.yaml b/packages/caliper-tests-integration/fabric_tests/phase7/networkconfig.yaml index bc9bb5823..cc7ab138e 100644 --- a/packages/caliper-tests-integration/fabric_tests/phase7/networkconfig.yaml +++ b/packages/caliper-tests-integration/fabric_tests/phase7/networkconfig.yaml @@ -13,7 +13,7 @@ # name: Fabric -version: "1.0" +version: "2.0.0" caliper: blockchain: fabric diff --git a/packages/caliper-tests-integration/fabric_tests/run.sh b/packages/caliper-tests-integration/fabric_tests/run.sh index e71763c96..a6fe3af37 100755 --- a/packages/caliper-tests-integration/fabric_tests/run.sh +++ b/packages/caliper-tests-integration/fabric_tests/run.sh @@ -30,7 +30,7 @@ cd ${DIR} # bind during CI tests, using the package dir as CWD # Note: do not use env variables for binding settings, as subsequent launch calls will pick them up and bind again if [[ "${BIND_IN_PACKAGE_DIR}" = "true" ]]; then - ${CALL_METHOD} bind --caliper-bind-sut fabric:1.4 --caliper-bind-cwd ./../../caliper-fabric/ --caliper-bind-args="--save-dev" + ${CALL_METHOD} bind --caliper-bind-sut fabric:1.4 --caliper-bind-cwd ./../../caliper-fabric/ fi # change default settings (add config paths too) @@ -91,15 +91,6 @@ if [[ ${rc} != 0 ]]; then exit ${rc}; fi -echo "Run Legacy connector. NOTE: Marble creation will fail with errors as they have already been created." -${CALL_METHOD} launch manager --caliper-workspace phase4 --caliper-networkconfig networkconfig-legacy.yaml --caliper-flow-only-test -rc=$? -if [[ ${rc} != 0 ]]; then - echo "Failed CI step 6"; - dispose; - exit ${rc}; -fi - # PHASE 5: testing through the gateway API (v1 SDK) ${CALL_METHOD} launch manager --caliper-workspace phase5 --caliper-flow-only-test --caliper-fabric-gateway-enabled rc=$? @@ -109,24 +100,15 @@ if [[ ${rc} != 0 ]]; then exit ${rc}; fi -echo "Run Legacy connector" -${CALL_METHOD} launch manager --caliper-workspace phase5 --caliper-networkconfig networkconfig-legacy.yaml --caliper-flow-only-test --caliper-fabric-gateway-enabled -rc=$? -if [[ ${rc} != 0 ]]; then - echo "Failed CI step 6"; - dispose; - exit ${rc}; -fi - # UNBIND SDK, using the package dir as CWD # Note: do not use env variables for unbinding settings, as subsequent launch calls will pick them up and bind again if [[ "${BIND_IN_PACKAGE_DIR}" = "true" ]]; then - ${CALL_METHOD} unbind --caliper-bind-sut fabric:1.4 --caliper-bind-cwd ./../../caliper-fabric/ --caliper-bind-args="--save-dev" --caliper-projectconfig ./caliper.yaml + ${CALL_METHOD} unbind --caliper-bind-sut fabric:1.4 --caliper-bind-cwd ./../../caliper-fabric/ --caliper-projectconfig ./caliper.yaml fi -# BIND with 2.2.2 SDK, using the package dir as CWD +# BIND with 2.2 SDK, using the package dir as CWD # Note: do not use env variables for unbinding settings, as subsequent launch calls will pick them up and bind again if [[ "${BIND_IN_PACKAGE_DIR}" = "true" ]]; then - ${CALL_METHOD} bind --caliper-bind-sut fabric:2.1 --caliper-bind-cwd ./../../caliper-fabric/ --caliper-bind-args="--save-dev" + ${CALL_METHOD} bind --caliper-bind-sut fabric:2.2 --caliper-bind-cwd ./../../caliper-fabric/ fi # PHASE 6: testing through the gateway API (v2 SDK) @@ -138,16 +120,6 @@ if [[ ${rc} != 0 ]]; then exit ${rc}; fi -echo "Run Legacy connector, NOTE: Marble creation will fail with errors as they have already been created." -${CALL_METHOD} launch manager --caliper-workspace phase6 --caliper-networkconfig networkconfig-legacy.yaml --caliper-flow-only-test --caliper-fabric-gateway-enabled -rc=$? -if [[ ${rc} != 0 ]]; then - echo "Failed CI step 7"; - dispose; - exit ${rc}; -fi - - # PHASE 7: just disposing of the network ${CALL_METHOD} launch manager --caliper-workspace phase7 --caliper-flow-only-end --caliper-fabric-gateway-enabled rc=$? diff --git a/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org1.yaml b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org1.yaml new file mode 100644 index 000000000..32851f2a8 --- /dev/null +++ b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org1.yaml @@ -0,0 +1,79 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: test-network-org1 +version: 1.0.0 +client: + organization: Org1 + connection: + timeout: + peer: + endorser: '300' + +organizations: + Org1: + mspid: Org1MSP + peers: + - peer0.org1.example.com + +orderers: + orderer0.example.com: + url: grpcs://localhost:7050 + grpcOptions: + ssl-target-name-override: orderer0.example.com + tlsCACerts: + path: ./fabric/config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem + orderer1.example.com: + url: grpcs://localhost:8050 + grpcOptions: + ssl-target-name-override: orderer1.example.com + tlsCACerts: + path: ./fabric/config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem + +peers: + peer0.org1.example.com: + url: grpcs://localhost:7051 + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + grpc.keepalive_time_ms: 600000 + tlsCACerts: + path: ./fabric/config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem + + peer0.org2.example.com: + url: grpcs://localhost:8051 + grpcOptions: + ssl-target-name-override: peer0.org2.example.com + grpc.keepalive_time_ms: 600000 + tlsCACerts: + path: ./fabric/config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem + +channels: + mychannel: + orderers: + - orderer0.example.com + - orderer1.example.com + peers: + peer0.org1.example.com: + eventSource: true + peer0.org2.example.com: + eventSource: true + yourchannel: + orderers: + - orderer0.example.com + - orderer1.example.com + peers: + peer0.org1.example.com: + eventSource: true + peer0.org2.example.com: + eventSource: true diff --git a/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org2.yaml b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org2.yaml new file mode 100644 index 000000000..92d92fbd8 --- /dev/null +++ b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/ccp-org2.yaml @@ -0,0 +1,79 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: test-network-org2 +version: 1.0.0 +client: + organization: Org2 + connection: + timeout: + peer: + endorser: '300' + +organizations: + Org2: + mspid: Org2MSP + peers: + - peer0.org2.example.com + +orderers: + orderer0.example.com: + url: grpcs://localhost:7050 + grpcOptions: + ssl-target-name-override: orderer0.example.com + tlsCACerts: + path: ./fabric/config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem + orderer1.example.com: + url: grpcs://localhost:8050 + grpcOptions: + ssl-target-name-override: orderer1.example.com + tlsCACerts: + path: ./fabric/config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem + +peers: + peer0.org1.example.com: + url: grpcs://localhost:7051 + grpcOptions: + ssl-target-name-override: peer0.org1.example.com + grpc.keepalive_time_ms: 600000 + tlsCACerts: + path: ./fabric/config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem + + peer0.org2.example.com: + url: grpcs://localhost:8051 + grpcOptions: + ssl-target-name-override: peer0.org2.example.com + grpc.keepalive_time_ms: 600000 + tlsCACerts: + path: ./fabric/config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem + +channels: + mychannel: + orderers: + - orderer0.example.com + - orderer1.example.com + peers: + peer0.org1.example.com: + eventSource: true + peer0.org2.example.com: + eventSource: true + yourchannel: + orderers: + - orderer0.example.com + - orderer1.example.com + peers: + peer0.org1.example.com: + eventSource: true + peer0.org2.example.com: + eventSource: true diff --git a/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/networkconfig.yaml b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/networkconfig.yaml index da5749f46..5df6ed798 100644 --- a/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/networkconfig.yaml +++ b/packages/caliper-tests-integration/generator_tests/fabric/myWorkspace/networkconfig.yaml @@ -13,131 +13,53 @@ # name: Fabric -version: "1.0" -mutual-tls: true +version: "2.0.0" caliper: blockchain: fabric + sutOptions : + mutualTls: true command: start: docker-compose -p caliper up -d;rm -rf /tmp/hfc-*;sleep 5s end: docker-compose -p caliper down;(test -z \"$(docker ps -aq)\") || docker rm $(docker ps -aq);(test -z \"$(docker images dev* -q)\") || docker rmi $(docker images dev* -q);rm -rf /tmp/hfc-* -clients: - client0.org1.example.com: - client: - organization: Org1 - credentialStore: - path: /tmp/hfc-kvs/org1 - cryptoStore: - path: /tmp/hfc-cvs/org1 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem - - client0.org2.example.com: - client: - organization: Org2 - credentialStore: - path: /tmp/hfc-kvs/org2 - cryptoStore: - path: /tmp/hfc-cvs/org2 - clientPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/keystore/key.pem - clientSignedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/User1@org2.example.com/msp/signcerts/User1@org2.example.com-cert.pem - channels: - mychannel: - created: false - configBinary: ../config/mychannel.tx - orderers: - - orderer0.example.com - - orderer1.example.com - peers: - peer0.org1.example.com: - eventSource: true - peer0.org2.example.com: - eventSource: true - contracts: - - id: marbles - contractID: mymarbles - version: v0 - language: node - path: ../src/marbles/node - metadataPath: ../src/marbles/node/metadata + - channelName: mychannel + create: + prebuiltTransaction: ../config/mychannel.tx + contracts: + - id: marbles + contractID: mymarbles + install: + version: v0 + language: node + path: ../src/marbles/node + metadataPath: ../src/marbles/node/metadata organizations: - Org1: - mspid: Org1MSP - peers: - - peer0.org1.example.com - certificateAuthorities: - - ca.org1.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem - - Org2: - mspid: Org2MSP - peers: - - peer0.org2.example.com - certificateAuthorities: - - ca.org2.example.com - adminPrivateKey: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem - signedCert: - path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem - -orderers: - orderer0.example.com: - url: grpcs://localhost:7050 - grpcOptions: - ssl-target-name-override: orderer0.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - orderer1.example.com: - url: grpcs://localhost:8050 - grpcOptions: - ssl-target-name-override: orderer1.example.com - tlsCACerts: - path: ../config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/msp/tlscacerts/tlsca.example.com-cert.pem - -peers: - peer0.org1.example.com: - url: grpcs://localhost:7051 - grpcOptions: - ssl-target-name-override: peer0.org1.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem - - peer0.org2.example.com: - url: grpcs://localhost:8051 - grpcOptions: - ssl-target-name-override: peer0.org2.example.com - grpc.keepalive_time_ms: 600000 - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/msp/tlscacerts/tlsca.org2.example.com-cert.pem + - mspid: Org1MSP + identities: + certificates: + - name: 'admin.org1.example.com' + admin: true + clientPrivateKey: + path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/key.pem + clientSignedCert: + path: ../config/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem + connectionProfile: + path: './ccp-org1.yaml' + discover: false -certificateAuthorities: - ca.org1.example.com: - url: https://localhost:7054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw + - mspid: Org2MSP + identities: + certificates: + - name: 'admin.org2.example.com' + admin: true + clientPrivateKey: + path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/key.pem + clientSignedCert: + path: ../config/crypto-config/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/signcerts/Admin@org2.example.com-cert.pem + connectionProfile: + path: './ccp-org2.yaml' + discover: false - ca.org2.example.com: - url: https://localhost:8054 - httpOptions: - verify: false - tlsCACerts: - path: ../config/crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem - registrar: - - enrollId: admin - enrollSecret: adminpw