From d890a3dfcf3f495ce449b7529fc38b1a7065ee04 Mon Sep 17 00:00:00 2001 From: Attila Klenik Date: Wed, 26 Feb 2020 12:05:18 -0100 Subject: [PATCH] Rework CLI and worker spawning to enhance the distributed worker feature Signed-off-by: Attila Klenik --- .travis/checks-and-unit-tests.sh | 8 +- packages/caliper-burrow/index.js | 3 +- ...rrowWorkerFactory.js => adapterFactory.js} | 22 +- packages/caliper-burrow/lib/burrow.js | 33 +- packages/caliper-burrow/lib/burrowWorker.js | 51 -- packages/caliper-cli/caliper.js | 18 +- packages/caliper-cli/index.js | 2 +- .../lib/benchmark/lib/runBenchmark.js | 96 --- .../lib/benchmark/runBenchmarkCommand.js | 45 -- packages/caliper-cli/lib/bindCommand.js | 16 +- .../launchMasterCommand.js} | 21 +- .../lib/launch/launchWorkerCommand.js | 47 ++ .../lib/launch/lib/launchMaster.js | 81 +++ .../lib/launch/lib/launchWorker.js | 89 +++ .../{workerCommand.js => launchCommand.js} | 12 +- .../caliper-cli/lib/{bind => lib}/bind.js | 66 +- .../caliper-cli/lib/{bind => lib}/config.yaml | 8 +- packages/caliper-cli/lib/utility/utils.js | 38 - packages/caliper-cli/lib/worker/lib/launch.js | 58 -- packages/caliper-core/.eslintrc.yml | 2 + .../caliper-core/lib/common/config/Config.js | 1 - .../lib/common/config/default.yaml | 4 +- .../lib/common/core/blockchain-interface.js | 10 +- .../common/messaging/messenger-interface.js | 8 + .../lib/common/messaging/messenger.js | 30 +- .../lib/common/messaging/mqtt-master.js | 18 + .../lib/common/messaging/mqtt-worker.js | 18 + .../lib/common/messaging/process-master.js | 7 + .../lib/common/messaging/process-worker.js | 7 + .../lib/common/utils/caliper-utils.js | 143 +++- .../caliper-core/lib/master/caliper-engine.js | 21 +- .../orchestrators/round-orchestrator.js | 7 +- .../orchestrators/worker-orchestrator.js | 23 +- .../lib/worker/client/message-handler.js | 9 +- packages/caliper-ethereum/index.js | 3 +- .../lib/adapterFactory.js} | 22 +- packages/caliper-ethereum/lib/ethereum.js | 21 +- .../caliper-ethereum/lib/ethereumWorker.js | 51 -- .../lib/ethereumWorkerFactory.js | 35 - packages/caliper-fabric/index.js | 3 +- ...bricWorkerFactory.js => adapterFactory.js} | 25 +- .../adaptor-versions/v1/fabric-gateway-v1.js | 46 +- .../lib/adaptor-versions/v1/fabric-v1.js | 59 +- .../adaptor-versions/v2/fabric-gateway-v2.js | 51 +- .../lib/adaptor-versions/v2/fabric-v2.js | 59 +- packages/caliper-fabric/lib/fabric.js | 38 +- packages/caliper-fabric/lib/fabricWorker.js | 54 -- packages/caliper-fabric/package.json | 7 +- packages/caliper-fisco-bcos/index.js | 3 +- .../caliper-fisco-bcos/lib/adapterFactory.js | 29 + packages/caliper-fisco-bcos/lib/fiscoBcos.js | 22 +- .../caliper-fisco-bcos/lib/fiscoBcosWorker.js | 51 -- .../lib/fiscoBcosWorkerFactory.js | 35 - packages/caliper-iroha/index.js | 3 +- .../lib/adapterFactory.js} | 21 +- packages/caliper-iroha/lib/iroha.js | 20 +- packages/caliper-iroha/lib/irohaWorker.js | 51 -- .../caliper-publish/artifacts/docker-build.sh | 2 +- packages/caliper-publish/caliper.Dockerfile | 33 +- packages/caliper-publish/lib/dockerCommand.js | 8 +- packages/caliper-publish/lib/impl/docker.js | 29 +- packages/caliper-sawtooth/index.js | 3 +- .../caliper-sawtooth/lib/adapterFactory.js | 29 + packages/caliper-sawtooth/lib/sawtooth.js | 18 +- .../caliper-sawtooth/lib/sawtoothWorker.js | 51 -- .../lib/sawtoothWorkerFactory.js | 35 - .../besu_tests/run.sh | 10 +- .../ethereum_tests/run.sh | 8 +- .../.gitignore | 4 + .../benchconfig.yaml | 33 + .../caliper.yaml | 54 ++ .../fabric_docker_distributed_tests/clean.sh | 22 + .../config/.gitignore | 5 + .../config/configtx.yaml | 92 +++ .../config/crypto-config.yaml | 36 + .../config/generate.sh | 20 + .../docker-compose.yaml | 308 ++++++++ .../fabric_docker_distributed_tests/init.js | 47 ++ .../mosquitto/mosquitto.conf | 1 + .../networkconfig.yaml | 164 +++++ .../package.json | 15 + .../fabric_docker_distributed_tests/query.js | 40 ++ .../run-with-build.sh | 43 ++ .../run-without-build.sh | 32 + .../src/marbles/go/marbles.go | 665 ++++++++++++++++++ .../statedb/couchdb/indexes/indexOwner.json | 1 + .../src/marbles/node/marbles.js | 509 ++++++++++++++ .../statedb/couchdb/indexes/indexOwner.json | 1 + .../src/marbles/node/package.json | 17 + .../fabric_docker_local_tests/.gitignore | 4 + .../benchconfig.yaml | 33 + .../fabric_docker_local_tests/caliper.yaml | 53 ++ .../fabric_docker_local_tests/clean.sh | 22 + .../config/.gitignore | 5 + .../config/configtx.yaml | 92 +++ .../config/crypto-config.yaml | 36 + .../config/generate.sh | 20 + .../docker-compose.yaml | 274 ++++++++ .../fabric_docker_local_tests/init.js | 47 ++ .../networkconfig.yaml | 164 +++++ .../fabric_docker_local_tests/package.json | 15 + .../fabric_docker_local_tests/query.js | 40 ++ .../run-with-build.sh | 43 ++ .../run-without-build.sh | 32 + .../src/marbles/go/marbles.go | 665 ++++++++++++++++++ .../statedb/couchdb/indexes/indexOwner.json | 1 + .../src/marbles/node/marbles.js | 509 ++++++++++++++ .../statedb/couchdb/indexes/indexOwner.json | 1 + .../src/marbles/node/package.json | 17 + .../fabric_tests/run.sh | 16 +- .../fisco-bcos_tests/run.sh | 8 +- .../generator_tests/fabric/run.sh | 8 +- .../caliper-tests-integration/package.json | 13 + .../sawtooth_tests/run.sh | 8 +- scripts/check-package-names.sh | 2 +- 115 files changed, 5124 insertions(+), 1070 deletions(-) rename packages/caliper-burrow/lib/{burrowWorkerFactory.js => adapterFactory.js} (56%) delete mode 100644 packages/caliper-burrow/lib/burrowWorker.js delete mode 100644 packages/caliper-cli/lib/benchmark/lib/runBenchmark.js delete mode 100644 packages/caliper-cli/lib/benchmark/runBenchmarkCommand.js rename packages/caliper-cli/lib/{worker/workerLaunchCommand.js => launch/launchMasterCommand.js} (58%) create mode 100644 packages/caliper-cli/lib/launch/launchWorkerCommand.js create mode 100644 packages/caliper-cli/lib/launch/lib/launchMaster.js create mode 100644 packages/caliper-cli/lib/launch/lib/launchWorker.js rename packages/caliper-cli/lib/{workerCommand.js => launchCommand.js} (62%) rename packages/caliper-cli/lib/{bind => lib}/bind.js (68%) rename packages/caliper-cli/lib/{bind => lib}/config.yaml (91%) delete mode 100644 packages/caliper-cli/lib/utility/utils.js delete mode 100644 packages/caliper-cli/lib/worker/lib/launch.js rename packages/{caliper-iroha/lib/irohaWorkerFactory.js => caliper-ethereum/lib/adapterFactory.js} (55%) delete mode 100644 packages/caliper-ethereum/lib/ethereumWorker.js delete mode 100644 packages/caliper-ethereum/lib/ethereumWorkerFactory.js rename packages/caliper-fabric/lib/{fabricWorkerFactory.js => adapterFactory.js} (50%) delete mode 100644 packages/caliper-fabric/lib/fabricWorker.js create mode 100644 packages/caliper-fisco-bcos/lib/adapterFactory.js delete mode 100644 packages/caliper-fisco-bcos/lib/fiscoBcosWorker.js delete mode 100644 packages/caliper-fisco-bcos/lib/fiscoBcosWorkerFactory.js rename packages/{caliper-cli/lib/benchmark.js => caliper-iroha/lib/adapterFactory.js} (55%) delete mode 100644 packages/caliper-iroha/lib/irohaWorker.js create mode 100644 packages/caliper-sawtooth/lib/adapterFactory.js delete mode 100644 packages/caliper-sawtooth/lib/sawtoothWorker.js delete mode 100644 packages/caliper-sawtooth/lib/sawtoothWorkerFactory.js create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/benchconfig.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/caliper.yaml create mode 100755 packages/caliper-tests-integration/fabric_docker_distributed_tests/clean.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/config/.gitignore create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/config/configtx.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/config/crypto-config.yaml create mode 100755 packages/caliper-tests-integration/fabric_docker_distributed_tests/config/generate.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/docker-compose.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/init.js create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/mosquitto/mosquitto.conf create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/networkconfig.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/package.json create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/query.js create mode 100755 packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh create mode 100755 packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/benchconfig.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/caliper.yaml create mode 100755 packages/caliper-tests-integration/fabric_docker_local_tests/clean.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/config/.gitignore create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/config/configtx.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/config/crypto-config.yaml create mode 100755 packages/caliper-tests-integration/fabric_docker_local_tests/config/generate.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/docker-compose.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/init.js create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/networkconfig.yaml create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/package.json create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/query.js create mode 100755 packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh create mode 100755 packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json diff --git a/.travis/checks-and-unit-tests.sh b/.travis/checks-and-unit-tests.sh index 7d0ba86a5..228edca7c 100755 --- a/.travis/checks-and-unit-tests.sh +++ b/.travis/checks-and-unit-tests.sh @@ -18,13 +18,7 @@ set -e set -o pipefail # check reference Caliper package names -# publishNpmPackages.js contains the package dir names as caliper-*, those are fine -if grep -rnE --exclude-dir="caliper-publish" "['\"]caliper-(cli|core|burrow|composer|ethereum|fabric|fisco-bcos|iroha|sawtooth)['\"]" . ; then - echo "^^^ Found incorrect Caliper package names. Use the @hyperledger/ prefix for Caliper packages, e.g., @hyperledger/caliper-core" - exit 1 -fi - -echo "Caliper package names are correct." +./scripts/check-package-names.sh # Bootstrap the project again npm i && npm run repoclean -- --yes && npm run bootstrap diff --git a/packages/caliper-burrow/index.js b/packages/caliper-burrow/index.js index 95bf06e47..30c6aa78d 100644 --- a/packages/caliper-burrow/index.js +++ b/packages/caliper-burrow/index.js @@ -14,5 +14,4 @@ 'use strict'; -module.exports.AdminClient = require('./lib/burrow'); -module.exports.WorkerFactory = require('./lib/burrowWorkerFactory'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-burrow/lib/burrowWorkerFactory.js b/packages/caliper-burrow/lib/adapterFactory.js similarity index 56% rename from packages/caliper-burrow/lib/burrowWorkerFactory.js rename to packages/caliper-burrow/lib/adapterFactory.js index 840549ac9..af42be2f9 100644 --- a/packages/caliper-burrow/lib/burrowWorkerFactory.js +++ b/packages/caliper-burrow/lib/adapterFactory.js @@ -14,22 +14,16 @@ 'use strict'; -const childProcess = require('child_process'); -const path = require('path'); +const BurrowAdapter = require('./burrow'); /** - * Class used to spawn burrow workers + * Constructs a Burrow adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async */ -class BurrowWorkerFactory { - - /** - * Spawn the worker - * @returns {Object} the child process - */ - async spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './burrowWorker.js'), process.argv.slice(2), { env: process.env}); - return child; - } +async function adapterFactory(workerIndex) { + return new BurrowAdapter(workerIndex); } -module.exports = BurrowWorkerFactory; +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-burrow/lib/burrow.js b/packages/caliper-burrow/lib/burrow.js index a1be6365f..5662e74fa 100644 --- a/packages/caliper-burrow/lib/burrow.js +++ b/packages/caliper-burrow/lib/burrow.js @@ -16,16 +16,15 @@ const fs = require('fs'); const monax = require('@monax/burrow'); -const { BlockchainInterface, CaliperUtils, TxStatus } = require('@hyperledger/caliper-core'); +const { BlockchainInterface, CaliperUtils, ConfigUtil, TxStatus } = require('@hyperledger/caliper-core'); const logger = CaliperUtils.getLogger('burrow.js'); /** Read the connection details from the config file. @param {object} config Adapter config. - @param {string} workspace_root The absolute path to the root location for the configuration files. @return {object} url, account Connection settings. */ -function burrowConnect(config, workspace_root) { +function burrowConnect(config) { let host = config.burrow.network.validator.host; if (host === null) { throw new Error('host url not set'); @@ -38,7 +37,7 @@ function burrowConnect(config, workspace_root) { let account; try { - account = fs.readFileSync(CaliperUtils.resolvePath(config.burrow.network.validator.address, workspace_root)).toString(); + account = fs.readFileSync(CaliperUtils.resolvePath(config.burrow.network.validator.address)).toString(); } catch (err) { account = config.burrow.network.validator.address.toString(); } @@ -60,14 +59,14 @@ class Burrow extends BlockchainInterface { /** * Create a new instance of the {Burrow} class. - * @param {string} config_path The path of the Burrow network configuration file. - * @param {string} workspace_root The absolute path to the root location for the application configuration files. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. Currently unused. */ - constructor(config_path, workspace_root) { - super(config_path); + constructor(workerIndex) { + super(); + let configPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); + this.config = require(configPath); this.statusInterval = null; this.bcType = 'burrow'; - this.workspaceRoot = workspace_root; } /** @@ -80,10 +79,10 @@ class Burrow extends BlockchainInterface { /** * Initialize the {Burrow} object. - * @return {time} sleep + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. */ - init() { - return CaliperUtils.sleep(2000); + async init(workerInit) { + return await CaliperUtils.sleep(2000); } /** @@ -91,14 +90,13 @@ class Burrow extends BlockchainInterface { * @return {object} Promise execution for namereg. */ async installSmartContract() { - let config = require(this.configPath); - let connection = burrowConnect(config, this.workspaceRoot); + let connection = burrowConnect(this.config); let options = { objectReturn: true }; let burrow = monax.createInstance(connection.url, connection.account, options); let data, abi, bytecode, contract; try { - data = JSON.parse(fs.readFileSync(CaliperUtils.resolvePath(config.contract.path, this.workspaceRoot)).toString()); + data = JSON.parse(fs.readFileSync(CaliperUtils.resolvePath(this.config.contract.path)).toString()); abi = data.Abi; bytecode = data.Evm.Bytecode.Object; @@ -130,12 +128,11 @@ class Burrow extends BlockchainInterface { * @async */ async getContext(name, args) { - let config = require(this.configPath); - let context = config.burrow.context; + let context = this.config.burrow.context; if (typeof context === 'undefined') { - let connection = burrowConnect(config, this.workspaceRoot); + let connection = burrowConnect(this.config); let options = { objectReturn: true }; let burrow = monax.createInstance(connection.url, connection.account, options); diff --git a/packages/caliper-burrow/lib/burrowWorker.js b/packages/caliper-burrow/lib/burrowWorker.js deleted file mode 100644 index 21632be25..000000000 --- a/packages/caliper-burrow/lib/burrowWorker.js +++ /dev/null @@ -1,51 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const BurrowClient = require('./burrow'); - -/** - * Handles the init message. Constructs the Burrow adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - return new BurrowClient(context.networkConfigPath, context.workspacePath, context.workerId); -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'burrow'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-cli/caliper.js b/packages/caliper-cli/caliper.js index ad0a9b529..ef05d22e4 100755 --- a/packages/caliper-cli/caliper.js +++ b/packages/caliper-cli/caliper.js @@ -23,20 +23,26 @@ const version = 'v' + require('./package.json').version; let results = yargs .commandDir('./lib') + .completion() + .recommendCommands() .help() - .example('caliper bind\ncaliper benchmark run\n caliper worker launch') - .demand(1) + .demandCommand(1, 1, 'Please specify a command to continue') + .example('caliper bind\ncaliper launch master\ncaliper launch worker') .wrap(null) .epilogue('For more information on Hyperledger Caliper: https://hyperledger.github.io/caliper/') - .alias('v', 'version') + .alias('version', 'v') + .alias('help', 'h') .version(version) - .describe('v', 'show version information') + .describe('version', 'Show version information') + .describe('help', 'Show usage information') .strict(false) .argv; results.thePromise.then( () => { - process.exit(0); + // DO NOT EXIT THE PROCESS HERE + // The event loops of the workers are still running at this point + // The default exit code is 0 anyway }).catch((error) => { - Logger.error(error); + Logger.error(`Error during command execution: ${error}`); process.exit(1); }); diff --git a/packages/caliper-cli/index.js b/packages/caliper-cli/index.js index 299e63bdc..807cb3a36 100644 --- a/packages/caliper-cli/index.js +++ b/packages/caliper-cli/index.js @@ -14,4 +14,4 @@ 'use strict'; -module.exports.version = require('./package.json'); +module.exports.version = require('./package.json').version; diff --git a/packages/caliper-cli/lib/benchmark/lib/runBenchmark.js b/packages/caliper-cli/lib/benchmark/lib/runBenchmark.js deleted file mode 100644 index 58ae694ff..000000000 --- a/packages/caliper-cli/lib/benchmark/lib/runBenchmark.js +++ /dev/null @@ -1,96 +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 {CaliperEngine, CaliperUtils, ConfigUtil} = require('@hyperledger/caliper-core'); -const logger = CaliperUtils.getLogger('CLI'); -const path = require('path'); -const fs = require('fs'); -/** - * Caliper benchmark Run command - * @private - */ -class RunBenchmark { - - /** - * Command process for run benchmark command - * @param {string} argv argument list from caliper benchmark command - */ - static async handler(argv) { - let workspacePath = ConfigUtil.get(ConfigUtil.keys.Workspace); - let benchmarkConfigPath = ConfigUtil.get(ConfigUtil.keys.BenchConfig); - let networkConfigPath = ConfigUtil.get(ConfigUtil.keys.NetworkConfig); - - // Workspace is expected to be the root location of working folders - workspacePath = path.resolve(workspacePath); - benchmarkConfigPath = CaliperUtils.resolvePath(benchmarkConfigPath, workspacePath); - networkConfigPath = CaliperUtils.resolvePath(networkConfigPath, workspacePath); - - if(!benchmarkConfigPath || !fs.existsSync(benchmarkConfigPath)) { - let msg = `Benchmark configuration file "${benchmarkConfigPath || 'UNSET'}" does not exist`; - logger.error(msg); - throw new Error(msg); - } - - if(!networkConfigPath || !fs.existsSync(networkConfigPath)) { - let msg = `Network configuration file "${networkConfigPath || 'UNSET'}" does not exist`; - logger.error(msg); - throw new Error(msg); - } - - let benchmarkConfig = CaliperUtils.parseYaml(benchmarkConfigPath); - let networkConfig = CaliperUtils.parseYaml(networkConfigPath); - - let blockchainType = ''; - if (networkConfig.caliper && networkConfig.caliper.blockchain) { - blockchainType = networkConfig.caliper.blockchain; - } else { - let msg = `Network configuration file "${networkConfigPath}" is missing its "caliper.blockchain" attribute`; - logger.error(msg); - throw new Error(msg); - } - - let knownError = false; - - try { - logger.info(`Set workspace path: ${workspacePath}`); - logger.info(`Set benchmark configuration path: ${benchmarkConfigPath}`); - logger.info(`Set network configuration path: ${networkConfigPath}`); - logger.info(`Detected SUT type: ${blockchainType}`); - - const {AdminClient, WorkerFactory} = require(`@hyperledger/caliper-${blockchainType}`); - const blockchainAdapter = new AdminClient(networkConfigPath, workspacePath); - - const engine = new CaliperEngine(benchmarkConfig, networkConfig, blockchainAdapter, WorkerFactory); - const response = await engine.run(); - - if (response === 0) { - logger.info('Benchmark successfully finished'); - } else { - knownError = true; - let msg = `Benchmark failed with error code ${response}`; - logger.error(msg); - throw new Error(msg); - } - } catch (err) { - if (!knownError) { - logger.error(`Unexpected error during benchmark execution: ${err.stack || err}`); - } - throw err; - } - } -} - -module.exports = RunBenchmark; diff --git a/packages/caliper-cli/lib/benchmark/runBenchmarkCommand.js b/packages/caliper-cli/lib/benchmark/runBenchmarkCommand.js deleted file mode 100644 index 40e9efa3d..000000000 --- a/packages/caliper-cli/lib/benchmark/runBenchmarkCommand.js +++ /dev/null @@ -1,45 +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 RunBenchmark = require ('./lib/runBenchmark'); -const Utils = require('../utility/utils'); - -// enforces singletons -const checkFn = (argv, options) => { - const uniqueArgs = ['caliper-benchconfig','caliper-networkconfig','caliper-workspace']; - return Utils.checkSingleton(argv, uniqueArgs); -}; -module.exports._checkFn = checkFn; -module.exports.command = 'run [options]'; -module.exports.describe = 'Run a Caliper benchmark'; -module.exports.builder = function (yargs){ - - yargs.options({ - 'caliper-benchconfig' : {describe: 'Path to the benchmark workload file that describes the test client(s), test rounds and monitor.', type: 'string' }, - 'caliper-networkconfig' : {describe:'Path to the blockchain configuration file that contains information required to interact with the SUT', type: 'string'}, - 'caliper-workspace' : {describe:'Workspace directory that contains all configuration information', type: 'string'} - }); - yargs.usage('caliper benchmark run --caliper-workspace ~/myCaliperProject --caliper-benchconfig my-app-test-config.yaml --caliper-networkconfig my-sut-config.yaml'); - - // enforce singletons - yargs.check(checkFn); - - return yargs; -}; - -module.exports.handler = (argv) => { - return argv.thePromise = RunBenchmark.handler(argv); -}; diff --git a/packages/caliper-cli/lib/bindCommand.js b/packages/caliper-cli/lib/bindCommand.js index 4e9a2cdee..f44b3d70b 100644 --- a/packages/caliper-cli/lib/bindCommand.js +++ b/packages/caliper-cli/lib/bindCommand.js @@ -14,13 +14,14 @@ 'use strict'; -const Bind = require('./bind/bind.js'); -const Utils = require('./utility/utils'); +const { CaliperUtils } = require('@hyperledger/caliper-core'); + +const Bind = require('./lib/bind.js'); // enforces singletons const checkFn = (argv) => { - const uniqueArgs = ['caliper-bind-sut','caliper-bind-sdk', 'caliper-bind-args', 'caliper-bind-cwd', 'caliper-bind-file']; - return Utils.checkSingleton(argv, uniqueArgs); + const uniqueArgs = ['caliper-bind-sut', 'caliper-bind-args', 'caliper-bind-cwd', 'caliper-bind-file']; + return CaliperUtils.checkSingleton(argv, uniqueArgs); }; module.exports._checkFn = checkFn; @@ -29,15 +30,14 @@ module.exports.describe = 'Bind Caliper to a specific SUT and its SDK version'; module.exports.builder = function (yargs){ yargs.options({ - 'caliper-bind-sut' : {describe: 'The name of the platform to bind to', type: 'string' }, - 'caliper-bind-sdk' : {describe: 'Version of the platform SDK to bind to', type: 'string'}, + 'caliper-bind-sut' : {describe: 'The name and version of the platform and its SDK to bind to', type: 'string' }, 'caliper-bind-cwd' : {describe: 'The working directory for performing the SDK install', type: 'string' }, 'caliper-bind-args' : {describe: 'Additional arguments to pass to "npm install". Use the "=" notation when setting this parameter', type: 'string' }, 'caliper-bind-file' : {describe: 'Yaml file to override default (supported) package versions when binding an SDK', type: 'string' } }); - yargs.usage('Usage:\n caliper bind --caliper-bind-sut fabric --caliper-bind-sdk 1.4.1 --caliper-bind-cwd ./ --caliper-bind-args="-g"'); + yargs.usage('Usage:\n caliper bind --caliper-bind-sut fabric:1.4.1 --caliper-bind-cwd ./ --caliper-bind-args="-g"'); // enforce the option after these options - yargs.requiresArg(['caliper-bind-sut','caliper-bind-sdk','caliper-bind-args', 'caliper-bind-cwd']); + yargs.requiresArg(['caliper-bind-sut', 'caliper-bind-args', 'caliper-bind-cwd']); // enforce singletons yargs.check(checkFn); diff --git a/packages/caliper-cli/lib/worker/workerLaunchCommand.js b/packages/caliper-cli/lib/launch/launchMasterCommand.js similarity index 58% rename from packages/caliper-cli/lib/worker/workerLaunchCommand.js rename to packages/caliper-cli/lib/launch/launchMasterCommand.js index f5f22022f..ebeee1063 100644 --- a/packages/caliper-cli/lib/worker/workerLaunchCommand.js +++ b/packages/caliper-cli/lib/launch/launchMasterCommand.js @@ -14,28 +14,27 @@ 'use strict'; -const Launch = require('./lib/launch.js'); -const Utils = require('../utility/utils'); +const LaunchMaster = require('./lib/launchMaster'); +const { CaliperUtils } = require('@hyperledger/caliper-core'); // enforces singletons const checkFn = (argv) => { - const uniqueArgs = ['caliper-bind-sut','caliper-bind-sdk', 'caliper-bind-args', 'caliper-bind-cwd', 'caliper-bind-file']; - return Utils.checkSingleton(argv, uniqueArgs); + const uniqueArgs = ['caliper-bind-sut', 'caliper-bind-args', 'caliper-bind-cwd', 'caliper-bind-file']; + return CaliperUtils.checkSingleton(argv, uniqueArgs); }; module.exports._checkFn = checkFn; -module.exports.command = 'launch [options]'; -module.exports.describe = 'Launch a Caliper worker for a target SUT with a bound SDK version'; -module.exports.builder = function (yargs){ +module.exports.command = 'master [options]'; +module.exports.describe = 'Launch a Caliper master process to coordinate the benchmark run'; +module.exports.builder = yargs => { yargs.options({ - 'caliper-bind-sut' : {describe: 'The name of the platform to bind to', type: 'string' }, - 'caliper-bind-sdk' : {describe: 'Version of the platform SDK to bind to', type: 'string'}, + 'caliper-bind-sut' : {describe: 'The name and version of the platform to bind to', type: 'string' }, 'caliper-bind-cwd' : {describe: 'The working directory for performing the SDK install', type: 'string' }, 'caliper-bind-args' : {describe: 'Additional arguments to pass to "npm install". Use the "=" notation when setting this parameter', type: 'string' }, 'caliper-bind-file' : {describe: 'Yaml file to override default (supported) package versions when binding an SDK', type: 'string' } }); - yargs.usage('Usage:\n caliper worker launch --caliper-bind-sut fabric --caliper-bind-sdk 1.4.1 --caliper-bind-cwd ./ --caliper-bind-args="-g"'); + yargs.usage('Usage:\n caliper launch master --caliper-bind-sut fabric:1.4.1 [other options]'); // enforce singletons yargs.check(checkFn); @@ -44,5 +43,5 @@ module.exports.builder = function (yargs){ }; module.exports.handler = (argv) => { - return argv.thePromise = Launch.handler(argv); + return argv.thePromise = LaunchMaster.handler(argv).then(() => process.exit(0)); }; diff --git a/packages/caliper-cli/lib/launch/launchWorkerCommand.js b/packages/caliper-cli/lib/launch/launchWorkerCommand.js new file mode 100644 index 000000000..0af48b40c --- /dev/null +++ b/packages/caliper-cli/lib/launch/launchWorkerCommand.js @@ -0,0 +1,47 @@ +/* +* 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 LaunchWorker = require('./lib/launchWorker'); +const { CaliperUtils } = require('@hyperledger/caliper-core'); + +// enforces singletons +const checkFn = (argv) => { + const uniqueArgs = ['caliper-bind-sut', 'caliper-bind-args', 'caliper-bind-cwd', 'caliper-bind-file']; + return CaliperUtils.checkSingleton(argv, uniqueArgs); +}; + +module.exports._checkFn = checkFn; +module.exports.command = 'worker [options]'; +module.exports.describe = 'Launch a Caliper worker process to generate the benchmark workload'; +module.exports.builder = yargs => { + + yargs.options({ + 'caliper-bind-sut' : {describe: 'The name and version of the platform to bind to', type: 'string' }, + 'caliper-bind-cwd' : {describe: 'The working directory for performing the SDK install', type: 'string' }, + 'caliper-bind-args' : {describe: 'Additional arguments to pass to "npm install". Use the "=" notation when setting this parameter', type: 'string' }, + 'caliper-bind-file' : {describe: 'Yaml file to override default (supported) package versions when binding an SDK', type: 'string' } + }); + yargs.usage('Usage:\n caliper launch worker --caliper-bind-sut fabric:1.4.1 [other options]'); + + // enforce singletons + yargs.check(checkFn); + + return yargs; +}; + +module.exports.handler = (argv) => { + return argv.thePromise = LaunchWorker.handler(argv); +}; diff --git a/packages/caliper-cli/lib/launch/lib/launchMaster.js b/packages/caliper-cli/lib/launch/lib/launchMaster.js new file mode 100644 index 000000000..2ba494d5f --- /dev/null +++ b/packages/caliper-cli/lib/launch/lib/launchMaster.js @@ -0,0 +1,81 @@ +/* +* 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 path = require('path'); +const { CaliperEngine, CaliperUtils, ConfigUtil } = require('@hyperledger/caliper-core'); +const Bind = require('./../../lib/bind'); +const logger = CaliperUtils.getLogger('cli-launch-master'); + +/** + * Caliper command for launching a master process. + */ +class LaunchMaster { + + /** + * Command processing for the Launch Master command. + * @param {object} argv Argument list from the caliper Launch Master command. Unused, relying on ConfigUtil instead. + */ + static async handler(argv) { + CaliperUtils.assertConfigurationFilePaths(); + + // Workspace is expected to be the root location of working folders + let workspacePath = path.resolve(ConfigUtil.get(ConfigUtil.keys.Workspace)); + let benchmarkConfigPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.BenchConfig)); + let networkConfigPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); + + let benchmarkConfig = CaliperUtils.parseYaml(benchmarkConfigPath); + let networkConfig = CaliperUtils.parseYaml(networkConfigPath); + + let sutType = networkConfig.caliper.blockchain; + let bindingSpec = ConfigUtil.get(ConfigUtil.keys.Bind.Sut); + + if (bindingSpec) { + logger.info(`Binding specification is present, performing binding for "${bindingSpec}"`); + await Bind.handler(argv, sutType); + } + + let knownError = false; + + try { + logger.info(`Set workspace path: ${workspacePath}`); + logger.info(`Set benchmark configuration path: ${benchmarkConfigPath}`); + logger.info(`Set network configuration path: ${networkConfigPath}`); + logger.info(`Set SUT type: ${bindingSpec || sutType}`); + + let adapterFactory = CaliperUtils.loadModuleFunction(CaliperUtils.getBuiltinAdapterPackageNames(), + sutType, 'AdapterFactory', require); + + const engine = new CaliperEngine(benchmarkConfig, networkConfig, adapterFactory); + const response = await engine.run(); + + if (response === 0) { + logger.info('Benchmark successfully finished'); + } else { + knownError = true; + let msg = `Benchmark failed with error code ${response}`; + logger.error(msg); + throw new Error(msg); + } + } catch (err) { + if (!knownError) { + logger.error(`Unexpected error during benchmark execution: ${err}`); + } + throw err; + } + } +} + +module.exports = LaunchMaster; diff --git a/packages/caliper-cli/lib/launch/lib/launchWorker.js b/packages/caliper-cli/lib/launch/lib/launchWorker.js new file mode 100644 index 000000000..96ce86f01 --- /dev/null +++ b/packages/caliper-cli/lib/launch/lib/launchWorker.js @@ -0,0 +1,89 @@ +/* +* 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, ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); +const Bind = require('./../../lib/bind'); +const logger = CaliperUtils.getLogger('cli-launch-worker'); + +/** + * Caliper command for launching a worker process. + */ +class LaunchWorker { + + /** + * Command processing for the Launch Worker command. + * @param {object} argv Argument list from the caliper Launch Master command. Unused, relying on ConfigUtil instead. + */ + static async handler(argv) { + CaliperUtils.assertConfigurationFilePaths(); + + // Workspace is expected to be the root location of working folders + let workspacePath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.Workspace)); + let benchmarkConfigPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.BenchConfig)); + let networkConfigPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); + + let sutType = CaliperUtils.parseYaml(networkConfigPath).caliper.blockchain; + let bindingSpec = ConfigUtil.get(ConfigUtil.keys.Bind.Sut); + + if (bindingSpec) { + if (CaliperUtils.isForkedProcess()) { + logger.info('Worker is a locally forked process, skipping binding step already performed by the master process'); + } + else { + logger.info(`Binding specification is present, performing binding for "${bindingSpec}"`); + await Bind.handler(argv, sutType); + } + } + + logger.info(`Set workspace path: ${workspacePath}`); + logger.info(`Set benchmark configuration path: ${benchmarkConfigPath}`); + logger.info(`Set network configuration path: ${networkConfigPath}`); + logger.info(`Set SUT type: ${bindingSpec || sutType}`); + + let messagingMethod = ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method); + let isRemoteProcess = ConfigUtil.get(ConfigUtil.keys.Worker.Remote); + + if (messagingMethod === 'process' && !CaliperUtils.isForkedProcess()) { + const msg = 'Process-based communication is configured for the worker, but it does not have a parent process'; + logger.error(msg); + throw new Error(msg); + } + + if (messagingMethod === 'process' && isRemoteProcess) { + const msg = 'Process-based communication is configured for the worker, but the worker is configured as a remote process'; + logger.error(msg); + throw new Error(msg); + } + + let adapterFactory = CaliperUtils.loadModuleFunction(CaliperUtils.getBuiltinAdapterPackageNames(), + sutType, 'AdapterFactory', require); + + // Create the message client using the specified type + const type = `${messagingMethod}-worker`; + const messenger = new Messenger({type, sut: sutType}); + await messenger.initialize(); + + // Create a handler context for this worker + const handlerContext = new MessageHandler({ + init: adapterFactory + }, messenger); + + // Pass to the messenger to configure + await messenger.configure(handlerContext); + } +} + +module.exports = LaunchWorker; diff --git a/packages/caliper-cli/lib/workerCommand.js b/packages/caliper-cli/lib/launchCommand.js similarity index 62% rename from packages/caliper-cli/lib/workerCommand.js rename to packages/caliper-cli/lib/launchCommand.js index d6fe5322d..224d9c398 100644 --- a/packages/caliper-cli/lib/workerCommand.js +++ b/packages/caliper-cli/lib/launchCommand.js @@ -14,10 +14,10 @@ 'use strict'; -module.exports.command = 'worker '; -module.exports.describe = 'Caliper worker command'; -module.exports.builder = function (yargs){ - - return yargs.demandCommand(1, 'Incorrect command. Please see the list of commands above, or enter "caliper worker --help".') - .commandDir('./worker'); +module.exports.command = 'launch '; +module.exports.describe = 'Launch a Caliper process either in a master or worker role.'; +module.exports.builder = yargs => { + return yargs + .demandCommand(1, 'Incorrect command. Please see the list of commands above, or enter "caliper launch --help".') + .commandDir('./launch'); }; diff --git a/packages/caliper-cli/lib/bind/bind.js b/packages/caliper-cli/lib/lib/bind.js similarity index 68% rename from packages/caliper-cli/lib/bind/bind.js rename to packages/caliper-cli/lib/lib/bind.js index 3ed9ef048..132a8d9d8 100644 --- a/packages/caliper-cli/lib/bind/bind.js +++ b/packages/caliper-cli/lib/lib/bind.js @@ -28,23 +28,44 @@ const logger = CaliperUtils.getLogger('bind'); class Bind { /** - * Command process for the bind command. - * @param {string} argv Argument list from the caliper bind command. Unused, relying on ConfigUtil instead. - */ - static async handler(argv) { - let sut = ConfigUtil.get(ConfigUtil.keys.Bind.Sut, undefined); - let sdk = ConfigUtil.get(ConfigUtil.keys.Bind.Sdk, undefined); - let cwd = ConfigUtil.get(ConfigUtil.keys.Bind.Cwd, undefined); - let userArgs = ConfigUtil.get(ConfigUtil.keys.Bind.Args, undefined); - let file = ConfigUtil.get(ConfigUtil.keys.Bind.File, undefined); - - let bindOptions; + * Command process for the bind command. + * @param {object} argv Argument list from the caliper bind command. Unused, relying on ConfigUtil instead. + * @param {string} networkConfigSut Optional SUT type read from the network configuration for cross-checking. + */ + static async handler(argv, networkConfigSut = undefined) { const defaultBindOpts = CaliperUtils.parseYaml(path.join(__dirname, './config.yaml')); + let sutSpec = ConfigUtil.get(ConfigUtil.keys.Bind.Sut); + if (!sutSpec) { + let msg = `SUT binding is not specified. Available SUTs: ${Object.keys(defaultBindOpts.sut).join(' | ')}`; + logger.error(msg); + throw new Error(msg); + } + + let sutSpecParts = sutSpec.split(':'); + if (sutSpecParts.length < 1 || sutSpecParts.length > 2) { + let msg = `SUT binding specification is expected in the : format, not as "${sutSpec}"`; + logger.error(msg); + throw new Error(msg); + } + + let sut = sutSpecParts[0]; + let sdk = sutSpecParts[1]; + if (!sdk) { + logger.warn('SUT SDK version is not specified, defaulting to "latest"'); + sdk = 'latest'; + } + + let cwd = ConfigUtil.get(ConfigUtil.keys.Bind.Cwd); + let userArgs = ConfigUtil.get(ConfigUtil.keys.Bind.Args); + let file = ConfigUtil.get(ConfigUtil.keys.Bind.File); + + let bindOptions; if (file) { // User has passed a configuration file to bind + file = CaliperUtils.resolvePath(file); if (!fs.existsSync(file)) { - let msg = `Passed bind file ${file} does not exist`; + let msg = `Passed custom bind configuration file "${file}" does not exist`; logger.error(msg); throw new Error(msg); } else { @@ -54,28 +75,25 @@ class Bind { bindOptions = defaultBindOpts; } + // TODO: schema validation for the loaded binding configuration + // until then, the error messages won't provide the available settings let sutList = Object.keys(bindOptions.sut); - if (!sut) { - let msg = `SUT name is not specified. Available SUTs: ${Object.keys(defaultBindOpts.sut).join(' | ')}`; - logger.error(msg); - throw new Error(msg); - } - if (!sutList.includes(sut)) { - let msg = `Unknown SUT name "${sut}". Available SUTs: ${Object.keys(defaultBindOpts.sut).join(' | ')}`; + let msg = `Unknown SUT type "${sut}" for binding`; logger.error(msg); throw new Error(msg); } let sutSdkList = Object.keys(bindOptions.sut[sut]); - if (!sdk) { - let msg = `"${sut}" SDK version is not specified. Available versions: ${Object.keys(defaultBindOpts.sut[sut]).join(' | ')}`; + if (!sutSdkList.includes(sdk)) { + let msg = `Unknown "${sut}" SDK version "${sdk}"`; logger.error(msg); throw new Error(msg); } - if (!sutSdkList.includes(sdk)) { - let msg = `Unknown "${sut}" SDK version "${sdk}". Available versions: ${Object.keys(defaultBindOpts.sut[sut]).join(' | ')}`; + // if a SUT type is provided by the launch commands, cross-check it with the explicit binding setting + if (networkConfigSut && sut !== networkConfigSut) { + let msg = `SUT type in the network configuration (${networkConfigSut}) does not match SUT type in the binding specification (${sut})`; logger.error(msg); throw new Error(msg); } @@ -148,7 +166,7 @@ class Bind { logger.info(`Binding working directory: ${cwd}`); logger.info(`Calling npm with: ${argArray.join(' ')}`); - await CaliperUtils.invokeCommand('npm', argArray, settings.env, path.resolve(cwd)); + await CaliperUtils.invokeCommand('npm', argArray, settings.env, cwd); } catch (e) { logger.error(e.message); throw e; diff --git a/packages/caliper-cli/lib/bind/config.yaml b/packages/caliper-cli/lib/lib/config.yaml similarity index 91% rename from packages/caliper-cli/lib/bind/config.yaml rename to packages/caliper-cli/lib/lib/config.yaml index f87978fea..2719cdc73 100644 --- a/packages/caliper-cli/lib/bind/config.yaml +++ b/packages/caliper-cli/lib/lib/config.yaml @@ -54,8 +54,12 @@ sut: packages: ['grpc@1.14.2', 'fabric-ca-client@1.4.2', 'fabric-client@1.4.3', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.2'] 1.4.4: packages: ['grpc@1.14.2', 'fabric-ca-client@1.4.4', 'fabric-client@1.4.4', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.4'] - 1.4.5: &fabric-latest - packages: ['fabric-ca-client@1.4.5', 'fabric-client@1.4.5', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.5'] + 1.4.5: + packages: ['fabric-client@1.4.5', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.5'] + 1.4.6: + packages: ['fabric-client@1.4.6', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.6'] + 1.4.7: &fabric-latest + packages: ['fabric-client@1.4.7', 'fabric-protos@2.0.0-snapshot.1', 'fabric-network@1.4.7'] latest: *fabric-latest sawtooth: diff --git a/packages/caliper-cli/lib/utility/utils.js b/packages/caliper-cli/lib/utility/utils.js deleted file mode 100644 index adeda34c7..000000000 --- a/packages/caliper-cli/lib/utility/utils.js +++ /dev/null @@ -1,38 +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'; - -/** - * utility functions for CLI - */ -class Utils { - - /** - * Utility function to check for singleton values - * @param {string[]} passedArgs arguments passed by user - * @param {string[]} uniqueArgs arguments that must be unique - * @returns {boolean} boolean true if passes check - */ - static checkSingleton(passedArgs, uniqueArgs) { - uniqueArgs.forEach((e) => { - if (Array.isArray(passedArgs[e])) { - throw new Error(`Option [${e}] can only be specified once`); - } - }); - return true; - } -} - -module.exports = Utils; diff --git a/packages/caliper-cli/lib/worker/lib/launch.js b/packages/caliper-cli/lib/worker/lib/launch.js deleted file mode 100644 index e94748a83..000000000 --- a/packages/caliper-cli/lib/worker/lib/launch.js +++ /dev/null @@ -1,58 +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, ConfigUtil } = require('@hyperledger/caliper-core'); -const Bind = require('../../bind/bind'); -const logger = CaliperUtils.getLogger('launch'); - -/** - * Caliper worker Launch command - * @private - */ -class Launch { - - /** - * Command process for the Launch command. - * @param {string} argv Argument list from the caliper Launch command. Unused, relying on ConfigUtil instead. - */ - static async handler(argv) { - let sut = ConfigUtil.get(ConfigUtil.keys.Bind.Sut, undefined); - let sdk = ConfigUtil.get(ConfigUtil.keys.Bind.Sdk, undefined); - - // only valid command if distributed workers - if (!ConfigUtil.get(ConfigUtil.keys.Worker.Remote, false)) { - const msg = 'Configuration definition indicates that worker is not remote: worker launch is invalid in this mode'; - throw new Error(msg); - } - - // not valid for process communications - if (ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method, 'process').localeCompare('process') === 0) { - const msg = 'Configuration definition indicates that worker is based on process communications: worker launch is invalid in this mode'; - throw new Error(msg); - } - - // bind first - logger.info(`Binding worker to SDK ${sdk}`); - await Bind.handler(argv); - - // Launch target worker - logger.info(`Launching worker for sut ${sut}`); - const { WorkerFactory} = require(`@hyperledger/caliper-${sut}`); - WorkerFactory.spawnWorker(); - } -} - -module.exports = Launch; diff --git a/packages/caliper-core/.eslintrc.yml b/packages/caliper-core/.eslintrc.yml index 6616d4923..2348abc54 100644 --- a/packages/caliper-core/.eslintrc.yml +++ b/packages/caliper-core/.eslintrc.yml @@ -4,6 +4,8 @@ env: mocha: true extends: 'eslint:recommended' parserOptions: + ecmaFeatures: + experimentalObjectRestSpread: true ecmaVersion: 8 sourceType: - script diff --git a/packages/caliper-core/lib/common/config/Config.js b/packages/caliper-core/lib/common/config/Config.js index a57f4bd8a..2a4011c98 100644 --- a/packages/caliper-core/lib/common/config/Config.js +++ b/packages/caliper-core/lib/common/config/Config.js @@ -23,7 +23,6 @@ nconf.formats.yaml = require('nconf-yaml'); const keys = { Bind: { Sut: 'caliper-bind-sut', - Sdk: 'caliper-bind-sdk', Args: 'caliper-bind-args', Cwd: 'caliper-bind-cwd', File: 'caliper-bind-file' diff --git a/packages/caliper-core/lib/common/config/default.yaml b/packages/caliper-core/lib/common/config/default.yaml index 0ab7f3b92..b4531f221 100644 --- a/packages/caliper-core/lib/common/config/default.yaml +++ b/packages/caliper-core/lib/common/config/default.yaml @@ -15,10 +15,8 @@ caliper: # Settings related to the binding command bind: - # The name of the platform to bind to + # The binding specification of the SUT in the : format sut: - # The SDK version of the platform to bind to - sdk: # The CWD to use for the binding (i.e., npm install) command cwd: # The additional args to pass to the binding (i.e., npm install) command diff --git a/packages/caliper-core/lib/common/core/blockchain-interface.js b/packages/caliper-core/lib/common/core/blockchain-interface.js index 23c39c53f..7fe1d0366 100644 --- a/packages/caliper-core/lib/common/core/blockchain-interface.js +++ b/packages/caliper-core/lib/common/core/blockchain-interface.js @@ -18,13 +18,6 @@ * Interface of blockchain adapters */ class BlockchainInterface { - /** - * Constructor - * @param {String} configPath path of the blockchain configuration file - */ - constructor(configPath) { - this.configPath = configPath; - } /** * Retrieve the blockchain type the implementation relates to @@ -35,8 +28,9 @@ class BlockchainInterface { /** * Initialise test environment + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. */ - async init() { + async init(workerInit) { throw new Error('init is not implemented for this blockchain system'); } diff --git a/packages/caliper-core/lib/common/messaging/messenger-interface.js b/packages/caliper-core/lib/common/messaging/messenger-interface.js index 6d86e3852..e5aaff064 100644 --- a/packages/caliper-core/lib/common/messaging/messenger-interface.js +++ b/packages/caliper-core/lib/common/messaging/messenger-interface.js @@ -61,6 +61,14 @@ class MessengerInterface { this._throwNotImplementedError('getUUID'); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + // require an explicit noop dispose implementation from child classes + this._throwNotImplementedError('dispose'); + } + /** * Logs and throws a "not implemented" error for the given function. * @param {string} functionName The name of the function. diff --git a/packages/caliper-core/lib/common/messaging/messenger.js b/packages/caliper-core/lib/common/messaging/messenger.js index d4cc8b1df..bf0d1617b 100644 --- a/packages/caliper-core/lib/common/messaging/messenger.js +++ b/packages/caliper-core/lib/common/messaging/messenger.js @@ -13,14 +13,16 @@ */ 'use strict'; -const CaliperUtils = require('..//utils/caliper-utils'); +const path = require('path'); + +const CaliperUtils = require('./../utils/caliper-utils'); const Logger = CaliperUtils.getLogger('messenger.js'); const builtInMessengers = new Map([ - ['mqtt-master', './mqtt-master.js'], - ['mqtt-worker', './mqtt-worker.js'], - ['process-master', './process-master.js'], - ['process-worker', './process-worker.js'] + ['mqtt-master', path.join(__dirname, './mqtt-master.js')], + ['mqtt-worker', path.join(__dirname, './mqtt-worker.js')], + ['process-master', path.join(__dirname, './process-master.js')], + ['process-worker', path.join(__dirname, './process-worker.js')] ]); const Messenger = class { @@ -34,15 +36,8 @@ const Messenger = class { Logger.info(`Creating messenger of type "${configuration.type}" ${configuration.sut ? ` for SUT ${configuration.sut}` : ''}`); - // resolve the type to a module path - let modulePath = builtInMessengers.has(configuration.type) - ? builtInMessengers.get(configuration.type) : CaliperUtils.resolvePath(configuration.type); // TODO: what if it's an external module name? - - let factoryFunction = require(modulePath).createMessenger; - if (!factoryFunction) { - throw new Error(`${configuration.type} does not export the mandatory factory function 'createMessenger'`); - } - + // resolve the type to a module's factory function + let factoryFunction = CaliperUtils.loadModuleFunction(builtInMessengers, configuration.type, 'createMessenger'); this.messenger = factoryFunction(configuration); } @@ -71,6 +66,13 @@ const Messenger = class { return this.messenger.getUUID(); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + await this.messenger.dispose(); + } + /** * Method used to publish message to worker clients * @param {string[]} to string array of workers that the update is intended for diff --git a/packages/caliper-core/lib/common/messaging/mqtt-master.js b/packages/caliper-core/lib/common/messaging/mqtt-master.js index bef963a31..77f883971 100644 --- a/packages/caliper-core/lib/common/messaging/mqtt-master.js +++ b/packages/caliper-core/lib/common/messaging/mqtt-master.js @@ -111,6 +111,24 @@ class MqttMasterMessenger extends MessengerInterface { Logger.debug(`Published message: ${msg}`); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + const messengerDisconnectedPromise = new Promise((resolve, reject) => { + this.messengerDisconnectedPromise = { + resolve: resolve, + reject: reject + }; + }); + + this.mqttClient.end(false, undefined, () => { + this.messengerDisconnectedPromise.resolve(); + }); + + await messengerDisconnectedPromise; + } + } /** diff --git a/packages/caliper-core/lib/common/messaging/mqtt-worker.js b/packages/caliper-core/lib/common/messaging/mqtt-worker.js index d248d72cf..75a02d165 100644 --- a/packages/caliper-core/lib/common/messaging/mqtt-worker.js +++ b/packages/caliper-core/lib/common/messaging/mqtt-worker.js @@ -120,6 +120,24 @@ class MqttWorkerMessenger extends MessengerInterface { }); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + const messengerDisconnectedPromise = new Promise((resolve, reject) => { + this.messengerDisconnectedPromise = { + resolve: resolve, + reject: reject + }; + }); + + this.mqttClient.end(true, undefined, () => { + this.messengerDisconnectedPromise.resolve(); + }); + + await messengerDisconnectedPromise; + } + } /** diff --git a/packages/caliper-core/lib/common/messaging/process-master.js b/packages/caliper-core/lib/common/messaging/process-master.js index cdce5b1cd..63037162a 100644 --- a/packages/caliper-core/lib/common/messaging/process-master.js +++ b/packages/caliper-core/lib/common/messaging/process-master.js @@ -61,6 +61,13 @@ class ProcessMasterMessenger extends MessengerInterface { return process.pid.toString(); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + // NOOP + } + /** * Send a message using the messenger * @param {object} message the message to send diff --git a/packages/caliper-core/lib/common/messaging/process-worker.js b/packages/caliper-core/lib/common/messaging/process-worker.js index de10dc1bb..18ba42217 100644 --- a/packages/caliper-core/lib/common/messaging/process-worker.js +++ b/packages/caliper-core/lib/common/messaging/process-worker.js @@ -58,6 +58,13 @@ class ProcessWorkerMessenger extends MessengerInterface { Logger.debug(`${this.configuration.sut} worker sent message: ${msg}`); } + /** + * Clean up any resources associated with the messenger. + */ + async dispose() { + // NOOP + } + /** * Configure the Messenger for use * @param {MessageHandler} handlerContext a configured message handler diff --git a/packages/caliper-core/lib/common/utils/caliper-utils.js b/packages/caliper-core/lib/common/utils/caliper-utils.js index 1f27606f8..45b8997fa 100644 --- a/packages/caliper-core/lib/common/utils/caliper-utils.js +++ b/packages/caliper-core/lib/common/utils/caliper-utils.js @@ -27,6 +27,141 @@ const Config = require('../config/config-util'); */ class CaliperUtils { + /** + * Indicates whether the process is a forked/child process, i.e., it has a parent process. + * @return {boolean} True, if the process has a parent process. Otherwise, false. + */ + static isForkedProcess() { + return (process.send !== undefined) && (typeof process.send === 'function'); + } + + /** + * Assert that the core configuration file paths are set and exist. + */ + static assertConfigurationFilePaths() { + // Workspace is expected to be the root location of working folders + let workspacePath = Config.get(Config.keys.Workspace); // default is "./" + workspacePath = path.resolve(workspacePath); + + // check benchmark config path + let benchmarkConfigPath = Config.get(Config.keys.BenchConfig); + if(!benchmarkConfigPath) { + let msg = 'Benchmark configuration file path is not set'; + throw new Error(msg); + } + + benchmarkConfigPath = CaliperUtils.resolvePath(benchmarkConfigPath); + if(!fs.existsSync(benchmarkConfigPath)) { + let msg = `Benchmark configuration file "${benchmarkConfigPath}" does not exist`; + throw new Error(msg); + } + + // check network config path + let networkConfigPath = Config.get(Config.keys.NetworkConfig); + if(!networkConfigPath) { + let msg = 'Network configuration file path is not set'; + throw new Error(msg); + } + + networkConfigPath = CaliperUtils.resolvePath(networkConfigPath, workspacePath); + if(!fs.existsSync(networkConfigPath)) { + let msg = `Network configuration file "${networkConfigPath}" does not exist`; + throw new Error(msg); + } + + let networkConfig = CaliperUtils.parseYaml(networkConfigPath); + if (!networkConfig.caliper || !networkConfig.caliper.blockchain || (typeof networkConfig.caliper.blockchain !== 'string')) { + let msg = `Network configuration file "${networkConfigPath}" is missing its "caliper.blockchain" string attribute`; + throw new Error(msg); + } + } + + /** + * Get the mapping of simple builtin adapter names to fully qualified package names. + * @return {Map} The mapping from simple names to package names. + */ + static getBuiltinAdapterPackageNames() { + return new Map([ + ['burrow', '@hyperledger/caliper-burrow'], + ['ethereum', '@hyperledger/caliper-ethereum'], + ['fabric', '@hyperledger/caliper-fabric'], + ['fisco-bcos', '@hyperledger/caliper-fisco-bcos'], + ['iroha', '@hyperledger/caliper-iroha'], + ['sawtooth', '@hyperledger/caliper-sawtooth'] + ]); + } + + /** + * Loads the module at the given path. + * @param {string} modulePath The path to the module or its name. + * @param {Function} requireFunction The "require" function (with appropriate scoping) to use to load the module. + * @return {object} The loaded module. + */ + static loadModule(modulePath, requireFunction = require) { + try { + return requireFunction(modulePath); + } catch (err) { + throw new Error(`Module "${modulePath}" could not be loaded: ${err}\nSearched paths: ${module.paths}`); + } + } + + /** + * Loads the given function from the given module. + * @param {object} module The module exporting the function. + * @param {string} functionName The name of the function. + * @param {string} moduleName The name of the module. + * @return {function} The loaded function. + */ + static loadFunction(module, functionName, moduleName) { + const func = module[functionName]; + if (!func || typeof func !== 'function') { + throw new Error(`Function "${functionName}" could not be loaded for module "${moduleName}"`); + } + + return func; + } + + /** + * Loads the given function from the module at the given path. + * @param {Map} builtInModules The mapping of built-in module names to their path. + * @param {string} moduleName The name of the module. + * @param {string} functionName The name of the function. + * @param {Function} requireFunction The "require" function (with appropriate scoping) to use to load the module. + * @return {Function} The loaded function. + */ + static loadModuleFunction(builtInModules, moduleName, functionName, requireFunction = require) { + let modulePath; + + // get correct module path + if (builtInModules.has(moduleName)) { + modulePath = builtInModules.get(moduleName); + } else if (moduleName.startsWith('./') || moduleName.startsWith('/')) { + // treat it as an external module, but resolve the path, so it's absolute + modulePath = CaliperUtils.resolvePath(moduleName); + } else { + // treat it as a package dependency (user must install it beforehand) + modulePath = moduleName; + } + + let module = CaliperUtils.loadModule(modulePath, requireFunction); + return CaliperUtils.loadFunction(module, functionName, moduleName); + } + + /** + * Utility function to check for singleton values + * @param {string[]} passedArgs arguments passed by user + * @param {string[]} uniqueArgs arguments that must be unique + * @returns {boolean} boolean true if passes check + */ + static checkSingleton(passedArgs, uniqueArgs) { + uniqueArgs.forEach((e) => { + if (Array.isArray(passedArgs[e])) { + throw new Error(`Option [${e}] can only be specified once`); + } + }); + return true; + } + /** * Perform a sleep * @param {*} ms the time to sleep, in ms @@ -285,16 +420,16 @@ class CaliperUtils { * Invokes a given command in a spawned child process and returns its output. * @param {string} cmd The command to be run. * @param {string[]} args The array of arguments to pass to the command. - * @param {Map} env The key-value pairs of environment variables to set. + * @param {object} env The key-value pairs of environment variables to set. * @param {string} cwd The current working directory to set. * @returns {Promise} A Promise that is resolved with the command output or rejected with an Error. */ - static getCommandOutput(cmd, args, env, cwd) { + static getCommandOutput(cmd, args, env = {}, cwd = './') { return new Promise((resolve, reject) => { let output = ''; let proc = spawn(cmd, args, { - cwd: cwd || './', - env: env || process.env + cwd: cwd, + env: { ...env, ...process.env } }); proc.stdout.on('data', (data) => { diff --git a/packages/caliper-core/lib/master/caliper-engine.js b/packages/caliper-core/lib/master/caliper-engine.js index 5e8d18b79..68607904c 100644 --- a/packages/caliper-core/lib/master/caliper-engine.js +++ b/packages/caliper-core/lib/master/caliper-engine.js @@ -30,17 +30,15 @@ class CaliperEngine { * Initializes the CaliperEngine instance. * @param {object} benchmarkConfig The benchmark configuration object. * @param {object} networkConfig The network configuration object. - * @param {AdminClient} blockchainAdapter The blockchain adapter instance. - * @param {ClientFactory} workerFactory The worker process factory. + * @param {function} adapterFactory The factory function for creating an adapter instance. */ - constructor(benchmarkConfig, networkConfig, blockchainAdapter, workerFactory) { + constructor(benchmarkConfig, networkConfig, adapterFactory) { this.benchmarkConfig = benchmarkConfig; this.networkConfig = networkConfig; this.workspace = ConfigUtils.get(ConfigUtils.keys.Workspace); this.returnCode = -1; - this.blockchainAdapter = blockchainAdapter; - this.workerFactory = workerFactory; + this.adapterFactory = adapterFactory; } /** @@ -68,7 +66,7 @@ class CaliperEngine { try { await CaliperUtils.execAsync(`cd ${this.workspace}; ${command}`); } catch (err) { - let msg = `An error occurred while executing the ${commandName} command: ${err.message}`; + let msg = `An error occurred while executing the ${commandName} command: ${err}`; logger.error(msg); this.returnCode = errorStatusStart + 3; throw new Error(msg); @@ -93,7 +91,8 @@ class CaliperEngine { BenchValidator.validateObject(this.benchmarkConfig); logger.info('Starting benchmark flow'); - const blockchainWrapper = new Blockchain(this.blockchainAdapter); + let adapter = await this.adapterFactory(-1); + const blockchainWrapper = new Blockchain(adapter); try { @@ -112,7 +111,7 @@ class CaliperEngine { try { await blockchainWrapper.init(); } catch (err) { - let msg = `Error while performing "init" step: ${err.message}`; + let msg = `Error while performing "init" step: ${err}`; logger.error(msg); this.returnCode = 4; throw new Error(msg); @@ -130,7 +129,7 @@ class CaliperEngine { try { await blockchainWrapper.installSmartContract(); } catch (err) { - let msg = `Error while performing "install" step: ${err.message}`; + let msg = `Error while performing "install" step: ${err}`; logger.error(msg); this.returnCode = 5; throw new Error(msg); @@ -148,14 +147,14 @@ class CaliperEngine { let numberOfWorkers = numberSet ? this.benchmarkConfig.test.workers.number : 1; let workerArguments = await blockchainWrapper.prepareWorkerArguments(numberOfWorkers); - const roundOrchestrator = new RoundOrchestrator(this.benchmarkConfig, this.networkConfig, this.workerFactory, workerArguments); + const roundOrchestrator = new RoundOrchestrator(this.benchmarkConfig, this.networkConfig, workerArguments); await roundOrchestrator.run(); } } catch (err) { // this means that we haven't handled/logged this failure yet if (this.returnCode < 0) { // log full stack - let msg = `Error while performing "test" step: ${err.stack || err}`; + let msg = `Error while performing "test" step: ${err}`; logger.error(msg); this.returnCode = 6; } diff --git a/packages/caliper-core/lib/master/orchestrators/round-orchestrator.js b/packages/caliper-core/lib/master/orchestrators/round-orchestrator.js index 7a14653b7..0aa6ad263 100644 --- a/packages/caliper-core/lib/master/orchestrators/round-orchestrator.js +++ b/packages/caliper-core/lib/master/orchestrators/round-orchestrator.js @@ -31,14 +31,13 @@ class RoundOrchestrator { * Initialize the RoundOrchestrator instance. * @param {object} benchmarkConfig The benchmark configuration object. * @param {object} networkConfig The network configuration object. - * @param {object} workerFactory The factory for worker processes. * @param {object[]} workerArguments List of adaptor specific arguments to pass for each worker processes. */ - constructor(benchmarkConfig, networkConfig, workerFactory, workerArguments) { + constructor(benchmarkConfig, networkConfig, workerArguments) { this.networkConfig = networkConfig; this.benchmarkConfig = benchmarkConfig; - this.workerOrchestrator = new WorkerOrchestrator(this.benchmarkConfig, workerFactory, workerArguments); + this.workerOrchestrator = new WorkerOrchestrator(this.benchmarkConfig, workerArguments); this.monitorOrchestrator = new MonitorOrchestrator(this.benchmarkConfig); this.report = new Report(this.monitorOrchestrator, this.benchmarkConfig, this.networkConfig); this.testObserver = new TestObserver(this.benchmarkConfig); @@ -232,7 +231,7 @@ class RoundOrchestrator { } try { - this.workerOrchestrator.stop(); + await this.workerOrchestrator.stop(); } catch (err) { logger.error(`Error while stopping workers: ${err.stack || err}`); } diff --git a/packages/caliper-core/lib/master/orchestrators/worker-orchestrator.js b/packages/caliper-core/lib/master/orchestrators/worker-orchestrator.js index 088223da1..1cf65713d 100644 --- a/packages/caliper-core/lib/master/orchestrators/worker-orchestrator.js +++ b/packages/caliper-core/lib/master/orchestrators/worker-orchestrator.js @@ -15,6 +15,8 @@ 'use strict'; +const childProcess = require('child_process'); + const CaliperUtils = require('../../common/utils/caliper-utils'); const ConfigUtils = require('../../common/config/config-util'); const logger = CaliperUtils.getLogger('worker-orchestrator'); @@ -28,6 +30,7 @@ const TYPES = { INITIALIZE: 'initialize', PREPARE: 'prepare', TEST: 'test', + EXIT: 'exit', }; // Add in worker message typings @@ -46,12 +49,10 @@ class WorkerOrchestrator { /** * Constructor * @param {object} benchmarkConfig The benchmark configuration object. - * @param {WorkerFactory} workerFactory The factory for the worker processes. * @param {object[]} workerArguments List of adaptor specific arguments to pass for each worker processes. */ - constructor(benchmarkConfig, workerFactory, workerArguments) { + constructor(benchmarkConfig, workerArguments) { this.config = benchmarkConfig.test.workers; - this.workerFactory = workerFactory; this.workerArguments = workerArguments; this.workers = {}; @@ -487,7 +488,10 @@ class WorkerOrchestrator { /** * Stop all test workers (child processes) */ - stop() { + async stop() { + this.messenger.send(['all'], TYPES.EXIT, {}); + await this.messenger.dispose(); + for (let workerObject of this.workerObjects) { workerObject.kill(); } @@ -534,7 +538,16 @@ class WorkerOrchestrator { logger.info(`Launching worker ${index} of ${this.number}`); // Spawn the worker. The index is assigned upon connection - let worker = this.workerFactory.spawnWorker(); + let cliPath = process.argv[1]; + let workerCommands = ['launch', 'worker']; + let remainingArgs = process.argv.slice(4); + + let nodeArgs = workerCommands.concat(remainingArgs); + + let worker = childProcess.fork(cliPath, nodeArgs, { + env: process.env, + cwd: process.cwd() + }); // Collect the launched process so it can be killed later this.workerObjects.push(worker); diff --git a/packages/caliper-core/lib/worker/client/message-handler.js b/packages/caliper-core/lib/worker/client/message-handler.js index ceff91c35..44af9d876 100644 --- a/packages/caliper-core/lib/worker/client/message-handler.js +++ b/packages/caliper-core/lib/worker/client/message-handler.js @@ -196,7 +196,7 @@ class MessageHandler { case 'initialize': { try { await context.beforeInitHandler(context, message); - context.adapter = await context.initHandler(context, message); + context.adapter = await context.initHandler(context.workerId); await context.afterInitHandler(context, message, undefined); } catch (error) { await context.afterInitHandler(context, message, error); @@ -222,6 +222,13 @@ class MessageHandler { break; } + case 'exit': { + logger.info('Handling "exit" message'); + await context.messenger.dispose(); + logger.info(`Handled "exit" message for worker ${context.workerId}`); + process.exit(0); + break; + } default: { let msg = `Unknown message type "${message.type}"`; logger.error(msg, message); diff --git a/packages/caliper-ethereum/index.js b/packages/caliper-ethereum/index.js index 88353a8cd..30c6aa78d 100644 --- a/packages/caliper-ethereum/index.js +++ b/packages/caliper-ethereum/index.js @@ -14,5 +14,4 @@ 'use strict'; -module.exports.AdminClient = require('./lib/ethereum'); -module.exports.WorkerFactory = require('./lib/ethereumWorkerFactory'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-iroha/lib/irohaWorkerFactory.js b/packages/caliper-ethereum/lib/adapterFactory.js similarity index 55% rename from packages/caliper-iroha/lib/irohaWorkerFactory.js rename to packages/caliper-ethereum/lib/adapterFactory.js index ba222ce45..400e118dc 100644 --- a/packages/caliper-iroha/lib/irohaWorkerFactory.js +++ b/packages/caliper-ethereum/lib/adapterFactory.js @@ -14,22 +14,16 @@ 'use strict'; -const childProcess = require('child_process'); -const path = require('path'); +const EthereumAdapter = require('./ethereum'); /** - * Class used to spawn iroha workers + * Constructs an Ethereum adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async */ -class IrohaWorkerFactory { - - /** - * Spawn the worker and perform required init - * @returns {Object} the child process - */ - spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './irohaWorker.js'), process.argv.slice(2), { env: process.env}); - return child; - } +async function adapterFactory(workerIndex) { + return new EthereumAdapter(workerIndex); } -module.exports = IrohaWorkerFactory; +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-ethereum/lib/ethereum.js b/packages/caliper-ethereum/lib/ethereum.js index a49c8fa1e..420b3715e 100644 --- a/packages/caliper-ethereum/lib/ethereum.js +++ b/packages/caliper-ethereum/lib/ethereum.js @@ -16,7 +16,7 @@ const EthereumHDKey = require('ethereumjs-wallet/hdkey'); const Web3 = require('web3'); -const {BlockchainInterface, CaliperUtils, TxStatus} = require('@hyperledger/caliper-core'); +const {BlockchainInterface, CaliperUtils, ConfigUtil, TxStatus} = require('@hyperledger/caliper-core'); const logger = CaliperUtils.getLogger('ethereum.js'); /** @@ -34,18 +34,16 @@ class Ethereum extends BlockchainInterface { /** * Create a new instance of the {Ethereum} class. - * @param {string} config_path The path of the network configuration file. - * @param {string} workspace_root The absolute path to the root location for the application configuration files. - * @param {number} clientIndex The client index + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. */ - constructor(config_path, workspace_root, clientIndex) { - super(config_path); + constructor(workerIndex) { + super(); + let configPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); this.bcType = 'ethereum'; - this.workspaceRoot = workspace_root; - this.ethereumConfig = require(config_path).ethereum; + this.ethereumConfig = require(configPath).ethereum; this.web3 = new Web3(this.ethereumConfig.url); this.web3.transactionConfirmationBlocks = this.ethereumConfig.transactionConfirmationBlocks; - this.clientIndex = clientIndex; + this.clientIndex = workerIndex; } /** @@ -58,9 +56,10 @@ class Ethereum extends BlockchainInterface { /** * Initialize the {Ethereum} object. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @return {object} Promise True if the account got unlocked successful otherwise false. */ - init() { + init(workerInit) { if (this.ethereumConfig.contractDeployerAddressPrivateKey) { this.web3.eth.accounts.wallet.add(this.ethereumConfig.contractDeployerAddressPrivateKey); } else if (this.ethereumConfig.contractDeployerAddressPassword) { @@ -77,7 +76,7 @@ class Ethereum extends BlockchainInterface { let self = this; logger.info('Creating contracts...'); for (const key of Object.keys(this.ethereumConfig.contracts)) { - let contractData = require(CaliperUtils.resolvePath(this.ethereumConfig.contracts[key].path, this.workspaceRoot)); // TODO remove path property + let contractData = require(CaliperUtils.resolvePath(this.ethereumConfig.contracts[key].path)); // TODO remove path property let contractGas = this.ethereumConfig.contracts[key].gas; let estimateGas = this.ethereumConfig.contracts[key].estimateGas; this.ethereumConfig.contracts[key].abi = contractData.abi; diff --git a/packages/caliper-ethereum/lib/ethereumWorker.js b/packages/caliper-ethereum/lib/ethereumWorker.js deleted file mode 100644 index 145b70c49..000000000 --- a/packages/caliper-ethereum/lib/ethereumWorker.js +++ /dev/null @@ -1,51 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const EthereumClient = require('./ethereum'); - -/** - * Handles the init message. Constructs the Ethereum adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - return new EthereumClient(context.networkConfigPath, context.workspacePath, context.workerId); -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'ethereum'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-ethereum/lib/ethereumWorkerFactory.js b/packages/caliper-ethereum/lib/ethereumWorkerFactory.js deleted file mode 100644 index 0ee9d203a..000000000 --- a/packages/caliper-ethereum/lib/ethereumWorkerFactory.js +++ /dev/null @@ -1,35 +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 childProcess = require('child_process'); -const path = require('path'); - -/** - * Class used to spawn ethereum workers - */ -class EthereumWorkerFactory { - - /** - * Spawn the worker and perform required init - * @returns {Object} the child process - */ - static spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './ethereumWorker.js'), process.argv.slice(2), { env: process.env}); - return child; - } -} - -module.exports = EthereumWorkerFactory; diff --git a/packages/caliper-fabric/index.js b/packages/caliper-fabric/index.js index 9936616ba..30c6aa78d 100644 --- a/packages/caliper-fabric/index.js +++ b/packages/caliper-fabric/index.js @@ -14,5 +14,4 @@ 'use strict'; -module.exports.AdminClient = require('./lib/fabric'); -module.exports.WorkerFactory = require('./lib/fabricWorkerFactory'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-fabric/lib/fabricWorkerFactory.js b/packages/caliper-fabric/lib/adapterFactory.js similarity index 50% rename from packages/caliper-fabric/lib/fabricWorkerFactory.js rename to packages/caliper-fabric/lib/adapterFactory.js index b259d12b7..b1076cb8c 100644 --- a/packages/caliper-fabric/lib/fabricWorkerFactory.js +++ b/packages/caliper-fabric/lib/adapterFactory.js @@ -14,22 +14,23 @@ 'use strict'; -const childProcess = require('child_process'); -const path = require('path'); +const FabricAdapter = require('./fabric'); /** - * Class used to spawn fabric workers + * Constructs a Fabric adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async */ -class FabricWorkerFactory { +async function adapterFactory(workerIndex) { + let adapter = new FabricAdapter(workerIndex); - /** - * Spawn the worker and perform required init - * @returns {Object} the child process - */ - static spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './fabricWorker.js'), process.argv.slice(2), { env: process.env}); - return child; + // the master process explicitly calls "init" + if (workerIndex > -1) { + await adapter.init(true); } + + return adapter; } -module.exports = FabricWorkerFactory; +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-gateway-v1.js b/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-gateway-v1.js index 21227abd1..af3656734 100644 --- a/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-gateway-v1.js +++ b/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-gateway-v1.js @@ -558,11 +558,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the admins of the organizations. * - * @param {boolean} initPhase Indicates whether to log admin init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeAdmins(initPhase) { + async _initializeAdmins(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { let adminName = `admin.${org}`; @@ -579,7 +579,7 @@ class Fabric extends BlockchainInterface { this._setTlsAdminCertAndKey(org); } - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -633,11 +633,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the registrars of the organizations. * - * @param {boolean} initPhase Indicates whether to log registrar init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeRegistrars(initPhase) { + async _initializeRegistrars(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { @@ -648,7 +648,7 @@ class Fabric extends BlockchainInterface { } let registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); if (!registrarInfo) { - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s registrar information not provided.`); } continue; @@ -660,7 +660,7 @@ class Fabric extends BlockchainInterface { let registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); if (registrar) { - if (initPhase) { + 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); @@ -672,7 +672,7 @@ class Fabric extends BlockchainInterface { registrarInfo.enrollSecret, `${org}'s registrar`); this.registrarProfiles.set(org, registrarProfile); - if (initPhase) { + if (!workerInit) { logger.info(`${org}'s registrar enrolled successfully`); } } @@ -681,11 +681,11 @@ class Fabric extends BlockchainInterface { /** * Registers and enrolls the specified users if necessary. * - * @param {boolean} initPhase Indicates whether to log user init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeUsers(initPhase) { + async _initializeUsers(workerInit) { let clients = this.networkUtil.getClients(); // register and enroll each client with its organization's CA @@ -704,7 +704,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); } - if (initPhase) { + if (!workerInit) { logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -737,7 +737,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client}'s materials are successfully loaded`); } @@ -767,7 +767,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -801,14 +801,14 @@ class Fabric extends BlockchainInterface { await affService.getOne(userAffiliation, registrar); affiliationExists = true; } catch (err) { - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation doesn't exists`); } } if (!affiliationExists) { await affService.create({name: userAffiliation, force: true}, registrar); - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation added`); } } @@ -827,7 +827,7 @@ class Fabric extends BlockchainInterface { throw new Error(`Couldn't register ${client}: ${err.message}`); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully registered`); } @@ -843,7 +843,7 @@ class Fabric extends BlockchainInterface { //this._setTlsClientCertAndKey(client); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -1645,21 +1645,21 @@ class Fabric extends BlockchainInterface { /** * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and chaincodes. - * @param {boolean} clientOnly boolean value to only configure the client or not + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @async */ - async init(clientOnly = false) { + async init(workerInit = false) { let tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); let compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); + await this._initializeRegistrars(workerInit); + await this._initializeAdmins(workerInit); + await this._initializeUsers(workerInit); this.initPhaseCompleted = true; - if (!clientOnly) { + if (!workerInit) { if (await this._createChannels()) { logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); await CaliperUtils.sleep(this.configSleepAfterCreateChannel); diff --git a/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-v1.js b/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-v1.js index 90ad2a2b5..622f18b0c 100644 --- a/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-v1.js +++ b/packages/caliper-fabric/lib/adaptor-versions/v1/fabric-v1.js @@ -664,11 +664,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the admins of the organizations. * - * @param {boolean} initPhase Indicates whether to log admin init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeAdmins(initPhase) { + async _initializeAdmins(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { let adminName = `admin.${org}`; @@ -684,7 +684,7 @@ class Fabric extends BlockchainInterface { this._setTlsAdminCertAndKey(org); } - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -730,18 +730,18 @@ class Fabric extends BlockchainInterface { /** * Initializes the registrars of the organizations. * - * @param {boolean} initPhase Indicates whether to log registrar init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeRegistrars(initPhase) { + async _initializeRegistrars(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { // providing registrar information is optional and only needed for user registration and enrollment let registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); if (!registrarInfo) { - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s registrar information not provided.`); } continue; @@ -753,7 +753,7 @@ class Fabric extends BlockchainInterface { let registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); if (registrar) { - if (initPhase) { + 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); @@ -765,7 +765,7 @@ class Fabric extends BlockchainInterface { registrarInfo.enrollSecret, `${org}'s registrar`); this.registrarProfiles.set(org, registrarProfile); - if (initPhase) { + if (!workerInit) { logger.info(`${org}'s registrar enrolled successfully`); } } @@ -774,11 +774,11 @@ class Fabric extends BlockchainInterface { /** * Registers and enrolls the specified users if necessary. * - * @param {boolean} initPhase Indicates whether to log user init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeUsers(initPhase) { + async _initializeUsers(workerInit) { let clients = this.networkUtil.getClients(); // register and enroll each client with its organization's CA @@ -797,7 +797,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); } - if (initPhase) { + if (!workerInit) { logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -815,7 +815,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client}'s materials are successfully loaded`); } @@ -841,7 +841,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -872,14 +872,14 @@ class Fabric extends BlockchainInterface { await affService.getOne(userAffiliation, registrar); affiliationExists = true; } catch (err) { - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation doesn't exists`); } } if (!affiliationExists) { await affService.create({name: userAffiliation, force: true}, registrar); - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation added`); } } @@ -898,7 +898,7 @@ class Fabric extends BlockchainInterface { throw new Error(`Couldn't register ${client}: ${err.message}`); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully registered`); } @@ -914,7 +914,7 @@ class Fabric extends BlockchainInterface { //this._setTlsClientCertAndKey(client); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } } @@ -2037,27 +2037,30 @@ class Fabric extends BlockchainInterface { /** * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and chaincodes. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @async */ - async init() { + async init(workerInit = false) { let tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); let compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); + await this._initializeRegistrars(workerInit); + await this._initializeAdmins(workerInit); + await this._initializeUsers(workerInit); this.initPhaseCompleted = true; - if (await this._createChannels()) { - logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterCreateChannel); - } + 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); + if (await this._joinChannels()) { + logger.info(`Sleeping ${this.configSleepAfterJoinChannel / 1000.0}s...`); + await CaliperUtils.sleep(this.configSleepAfterJoinChannel); + } } } diff --git a/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-gateway-v2.js b/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-gateway-v2.js index 9977dc002..37e60ebda 100644 --- a/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-gateway-v2.js +++ b/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-gateway-v2.js @@ -202,17 +202,18 @@ class Fabric extends BlockchainInterface { /** * Initialize the adaptor + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. */ - async _initAdaptor() { + async _initAdaptor(workerInit) { let tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); let compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); await this._prepareWallet(); - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); + await this._initializeRegistrars(workerInit); + await this._initializeAdmins(workerInit); + await this._initializeUsers(workerInit); this.initPhaseCompleted = true; } @@ -573,11 +574,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the admins of the organizations. * - * @param {boolean} initPhase Indicates whether to log admin init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeAdmins(initPhase) { + async _initializeAdmins(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { let adminName = `admin.${org}`; @@ -594,7 +595,7 @@ class Fabric extends BlockchainInterface { this._setTlsAdminCertAndKey(org); } - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -648,11 +649,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the registrars of the organizations. * - * @param {boolean} initPhase Indicates whether to log registrar init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeRegistrars(initPhase) { + async _initializeRegistrars(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { @@ -663,7 +664,7 @@ class Fabric extends BlockchainInterface { } let registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); if (!registrarInfo) { - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s registrar information not provided.`); } continue; @@ -675,7 +676,7 @@ class Fabric extends BlockchainInterface { let registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); if (registrar) { - if (initPhase) { + 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); @@ -687,7 +688,7 @@ class Fabric extends BlockchainInterface { registrarInfo.enrollSecret, `${org}'s registrar`); this.registrarProfiles.set(org, registrarProfile); - if (initPhase) { + if (!workerInit) { logger.info(`${org}'s registrar enrolled successfully`); } } @@ -696,11 +697,11 @@ class Fabric extends BlockchainInterface { /** * Registers and enrolls the specified users if necessary. * - * @param {boolean} initPhase Indicates whether to log user init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeUsers(initPhase) { + async _initializeUsers(workerInit) { let clients = this.networkUtil.getClients(); // register and enroll each client with its organization's CA @@ -719,7 +720,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); } - if (initPhase) { + if (!workerInit) { logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -752,7 +753,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client}'s materials are successfully loaded`); } @@ -782,7 +783,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -816,14 +817,14 @@ class Fabric extends BlockchainInterface { await affService.getOne(userAffiliation, registrar); affiliationExists = true; } catch (err) { - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation doesn't exists`); } } if (!affiliationExists) { await affService.create({name: userAffiliation, force: true}, registrar); - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation added`); } } @@ -842,7 +843,7 @@ class Fabric extends BlockchainInterface { throw new Error(`Couldn't register ${client}: ${err.message}`); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully registered`); } @@ -858,7 +859,7 @@ class Fabric extends BlockchainInterface { //this._setTlsClientCertAndKey(client); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -1687,18 +1688,18 @@ class Fabric extends BlockchainInterface { /** * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and chaincodes. - * @param {boolean} clientOnly boolean value to only configure the client or not + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @async */ - async init(clientOnly = false) { + async init(workerInit = false) { let tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); let compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); - await this._initAdaptor(); + await this._initAdaptor(workerInit); - if (!clientOnly) { + if (!workerInit) { if (await this._createChannels()) { logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); await CaliperUtils.sleep(this.configSleepAfterCreateChannel); diff --git a/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-v2.js b/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-v2.js index fff172a98..b4f35dae2 100644 --- a/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-v2.js +++ b/packages/caliper-fabric/lib/adaptor-versions/v2/fabric-v2.js @@ -664,11 +664,11 @@ class Fabric extends BlockchainInterface { /** * Initializes the admins of the organizations. * - * @param {boolean} initPhase Indicates whether to log admin init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeAdmins(initPhase) { + async _initializeAdmins(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { let adminName = `admin.${org}`; @@ -684,7 +684,7 @@ class Fabric extends BlockchainInterface { this._setTlsAdminCertAndKey(org); } - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s admin's materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -731,17 +731,17 @@ class Fabric extends BlockchainInterface { /** * Initializes the registrars of the organizations. * - * @param {boolean} initPhase Indicates whether to log registrar init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeRegistrars(initPhase) { + async _initializeRegistrars(workerInit) { let orgs = this.networkUtil.getOrganizations(); for (let org of orgs) { let registrarInfo = this.networkUtil.getRegistrarOfOrganization(org); if (!registrarInfo) { - if (initPhase) { + if (!workerInit) { logger.warn(`${org}'s registrar information not provided.`); } continue; @@ -753,7 +753,7 @@ class Fabric extends BlockchainInterface { let registrar = await this._getUserContext(registrarProfile, registrarInfo.enrollId, `${org}'s registrar`); if (registrar) { - if (initPhase) { + 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); @@ -765,7 +765,7 @@ class Fabric extends BlockchainInterface { registrarInfo.enrollSecret, `${org}'s registrar`); this.registrarProfiles.set(org, registrarProfile); - if (initPhase) { + if (!workerInit) { logger.info(`${org}'s registrar enrolled successfully`); } } @@ -774,11 +774,11 @@ class Fabric extends BlockchainInterface { /** * Registers and enrolls the specified users if necessary. * - * @param {boolean} initPhase Indicates whether to log user init progress. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @private * @async */ - async _initializeUsers(initPhase) { + async _initializeUsers(workerInit) { let clients = this.networkUtil.getClients(); // register and enroll each client with its organization's CA @@ -797,7 +797,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(user.getIdentity()._certificate, user.getSigningIdentity()._signer._key.toBytes()); } - if (initPhase) { + if (!workerInit) { logger.warn(`${client}'s materials found locally in file system key-value stores. Make sure it is the right one!`); } @@ -815,7 +815,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(crypto.signedCertPEM.toString(), crypto.privateKeyPEM.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client}'s materials are successfully loaded`); } @@ -841,7 +841,7 @@ class Fabric extends BlockchainInterface { clientProfile.setTlsClientCertAndKey(Buffer.from(enrollment.certificate).toString(), enrollment.key.toString()); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } @@ -872,14 +872,14 @@ class Fabric extends BlockchainInterface { await affService.getOne(userAffiliation, registrar); affiliationExists = true; } catch (err) { - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation doesn't exists`); } } if (!affiliationExists) { await affService.create({name: userAffiliation, force: true}, registrar); - if (initPhase) { + if (!workerInit) { logger.info(`${userAffiliation} affiliation added`); } } @@ -898,7 +898,7 @@ class Fabric extends BlockchainInterface { throw new Error(`Couldn't register ${client}: ${err.message}`); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully registered`); } @@ -914,7 +914,7 @@ class Fabric extends BlockchainInterface { //this._setTlsClientCertAndKey(client); } - if (initPhase) { + if (!workerInit) { logger.info(`${client} successfully enrolled`); } } @@ -2037,27 +2037,30 @@ class Fabric extends BlockchainInterface { /** * Initializes the Fabric adapter: sets up clients, admins, registrars, channels and chaincodes. + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @async */ - async init() { + async init(workerInit = false) { let tlsInfo = this.networkUtil.isMutualTlsEnabled() ? 'mutual' : (this.networkUtil.isTlsEnabled() ? 'server' : 'none'); let compMode = this.networkUtil.isInCompatibilityMode() ? '; Fabric v1.0 compatibility mode' : ''; logger.info(`Fabric SDK version: ${this.version.toString()}; TLS: ${tlsInfo}${compMode}`); - await this._initializeRegistrars(true); - await this._initializeAdmins(true); - await this._initializeUsers(true); + await this._initializeRegistrars(workerInit); + await this._initializeAdmins(workerInit); + await this._initializeUsers(workerInit); this.initPhaseCompleted = true; - if (await this._createChannels()) { - logger.info(`Sleeping ${this.configSleepAfterCreateChannel / 1000.0}s...`); - await CaliperUtils.sleep(this.configSleepAfterCreateChannel); - } + 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); + if (await this._joinChannels()) { + logger.info(`Sleeping ${this.configSleepAfterJoinChannel / 1000.0}s...`); + await CaliperUtils.sleep(this.configSleepAfterJoinChannel); + } } } diff --git a/packages/caliper-fabric/lib/fabric.js b/packages/caliper-fabric/lib/fabric.js index 93999b4a9..1e0d69c4c 100644 --- a/packages/caliper-fabric/lib/fabric.js +++ b/packages/caliper-fabric/lib/fabric.js @@ -18,17 +18,16 @@ const { BlockchainInterface, CaliperUtils, ConfigUtil } = require('@hyperledger/ const Logger = CaliperUtils.getLogger('adapters/fabric'); const semver = require('semver'); +const path = require('path'); const Fabric = class extends BlockchainInterface { /** * Initializes the Fabric adapter. - * @param {string|object} networkConfig The relative or absolute file path, or the object itself of the Common Connection Profile settings. - * @param {string} workspace_root The absolute path to the root location for the application configuration files. - * @param {number} clientIndex the client index + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. */ - constructor(networkConfig, workspace_root, clientIndex) { - super(networkConfig); + constructor(workerIndex) { + super(); // Switch adaptors on the fabric-ca-client, which is a common package across all fabric-sdk-node releases const packageVersion = require('fabric-ca-client/package').version; const version = semver.coerce(packageVersion); @@ -54,8 +53,11 @@ const Fabric = class extends BlockchainInterface { throw new Error(`Installed SDK version ${version} did not match any compatible Fabric adaptors`); } + let networkConfig = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); + let workspaceRoot = path.resolve(ConfigUtil.get(ConfigUtil.keys.Workspace)); + let Fabric = require(modulePath); - this.fabric = new Fabric(networkConfig, workspace_root, clientIndex); + this.fabric = new Fabric(networkConfig, workspaceRoot, workerIndex); } /** @@ -82,11 +84,11 @@ const Fabric = class extends BlockchainInterface { /** * Initializes the Fabric adapter and configures the SUT: sets up clients, admins, registrars, channels and chaincodes. - * @param {boolean} clientOnly boolean value to only configure the client or not + * @param {boolean} workerInit Indicates whether the initialization happens in the worker process. * @async */ - async init(clientOnly) { - await this.fabric.init(clientOnly); + async init(workerInit) { + await this.fabric.init(workerInit); } /** @@ -138,34 +140,34 @@ const Fabric = class extends BlockchainInterface { /** * Initializes the registrars of the organizations. * - * @param {boolean} initPhase Indicates whether to log registrar init progress. + * @param {boolean} masterInit Indicates whether the initialization happens in the master process. * @private * @async */ - async _initializeRegistrars(initPhase) { - await this.fabric._initializeRegistrars(initPhase); + async _initializeRegistrars(masterInit) { + await this.fabric._initializeRegistrars(masterInit); } /** * Initializes the admins of the organizations. * - * @param {boolean} initPhase Indicates whether to log admin init progress. + * @param {boolean} masterInit Indicates whether the initialization happens in the master process. * @private * @async */ - async _initializeAdmins(initPhase) { - await this.fabric._initializeAdmins(initPhase); + async _initializeAdmins(masterInit) { + await this.fabric._initializeAdmins(masterInit); } /** * Registers and enrolls the specified users if necessary. * - * @param {boolean} initPhase Indicates whether to log user init progress. + * @param {boolean} masterInit Indicates whether the initialization happens in the master process. * @private * @async */ - async _initializeUsers(initPhase) { - await this.fabric._initializeUsers(initPhase); + async _initializeUsers(masterInit) { + await this.fabric._initializeUsers(masterInit); } }; diff --git a/packages/caliper-fabric/lib/fabricWorker.js b/packages/caliper-fabric/lib/fabricWorker.js deleted file mode 100644 index d76705feb..000000000 --- a/packages/caliper-fabric/lib/fabricWorker.js +++ /dev/null @@ -1,54 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const FabricClient = require('./fabric'); - -/** - * Handles the init message. Constructs and initializes the Fabric adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - const worker = new FabricClient(context.networkConfigPath, context.workspacePath, context.workerId); - await worker.init(true); - - return worker; -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'fabric'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-fabric/package.json b/packages/caliper-fabric/package.json index cfcc1d53c..75c70fd1c 100644 --- a/packages/caliper-fabric/package.json +++ b/packages/caliper-fabric/package.json @@ -27,9 +27,10 @@ "semver":"7.1.1" }, "devDependencies": { - "fabric-ca-client": "^1.4.0", - "fabric-client": "^1.4.0", - "fabric-network": "^1.4.0", + "grpc": "1.14.2", + "fabric-ca-client": "^1.4.7", + "fabric-client": "^1.4.7", + "fabric-network": "^1.4.7", "fabric-protos": "2.0.0-snapshot.1", "chai": "^3.5.0", "eslint": "^5.16.0", diff --git a/packages/caliper-fisco-bcos/index.js b/packages/caliper-fisco-bcos/index.js index 89c0d7076..30c6aa78d 100644 --- a/packages/caliper-fisco-bcos/index.js +++ b/packages/caliper-fisco-bcos/index.js @@ -14,5 +14,4 @@ 'use strict'; -module.exports.AdminClient = require('./lib/fiscoBcos'); -module.exports.WorkerFactory = require('./lib/fiscoBcosWorkerFactory'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-fisco-bcos/lib/adapterFactory.js b/packages/caliper-fisco-bcos/lib/adapterFactory.js new file mode 100644 index 000000000..632935293 --- /dev/null +++ b/packages/caliper-fisco-bcos/lib/adapterFactory.js @@ -0,0 +1,29 @@ +/* +* 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 FiscoBcosAdapter = require('./fiscoBcos'); + +/** + * Constructs a FISCO-BCOS adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async + */ +async function adapterFactory(workerIndex) { + return new FiscoBcosAdapter(workerIndex); +} + +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-fisco-bcos/lib/fiscoBcos.js b/packages/caliper-fisco-bcos/lib/fiscoBcos.js index b7a5b44f3..e61d59a61 100644 --- a/packages/caliper-fisco-bcos/lib/fiscoBcos.js +++ b/packages/caliper-fisco-bcos/lib/fiscoBcos.js @@ -14,9 +14,12 @@ 'use strict'; +const path = require('path'); + const { BlockchainInterface, - CaliperUtils + CaliperUtils, + ConfigUtil } = require('@hyperledger/caliper-core'); const installSmartContractImpl = require('./installSmartContract'); const invokeSmartContractImpl = require('./invokeSmartContract'); @@ -30,22 +33,21 @@ const commLogger = CaliperUtils.getLogger('fiscoBcos.js'); class FiscoBcos extends BlockchainInterface { /** * Create a new instance of the {FISCO BCOS} class. - * @param {string} config_path The absolute path of the FISCO BCOS network configuration file. - * @param {string} workspace_root The absolute path to the root location for the application configuration files. - * @param {number} clientIdx The client index + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. */ - constructor(config_path, workspace_root, clientIdx) { - super(config_path); + constructor(workerIndex) { + super(); this.bcType = 'fisco-bcos'; - this.workspaceRoot = workspace_root; - this.fiscoBcosSettings = CaliperUtils.parseYaml(this.configPath)['fisco-bcos']; + this.workspaceRoot = path.resolve(ConfigUtil.get(ConfigUtil.keys.Workspace)); + let networkConfig = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); + this.fiscoBcosSettings = CaliperUtils.parseYaml(networkConfig)['fisco-bcos']; if (this.fiscoBcosSettings.network && this.fiscoBcosSettings.network.authentication) { for (let k in this.fiscoBcosSettings.network.authentication) { - this.fiscoBcosSettings.network.authentication[k] = CaliperUtils.resolvePath(this.fiscoBcosSettings.network.authentication[k], workspace_root); + this.fiscoBcosSettings.network.authentication[k] = CaliperUtils.resolvePath(this.fiscoBcosSettings.network.authentication[k]); } } - this.clientIdx = clientIdx; + this.clientIdx = workerIndex; } /** diff --git a/packages/caliper-fisco-bcos/lib/fiscoBcosWorker.js b/packages/caliper-fisco-bcos/lib/fiscoBcosWorker.js deleted file mode 100644 index 4292f78d2..000000000 --- a/packages/caliper-fisco-bcos/lib/fiscoBcosWorker.js +++ /dev/null @@ -1,51 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const FiscoBcosClient = require('./fiscoBcos'); - -/** - * Handles the init message. Constructs the FISCO-BCOS adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - return new FiscoBcosClient(context.networkConfigPath, context.workspacePath, context.workerId); -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'fiscoBCOS'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-fisco-bcos/lib/fiscoBcosWorkerFactory.js b/packages/caliper-fisco-bcos/lib/fiscoBcosWorkerFactory.js deleted file mode 100644 index fa72b0b30..000000000 --- a/packages/caliper-fisco-bcos/lib/fiscoBcosWorkerFactory.js +++ /dev/null @@ -1,35 +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 childProcess = require('child_process'); -const path = require('path'); - -/** - * Class used to spawn FISCO BCOS workers - */ -class FiscoBcosWorkerFactory { - - /** - * Spawn the worker and perform required init - * @returns {Object} the child process - */ - spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './fiscoBcosWorker.js'), process.argv.slice(2), { env: process.env }); - return child; - } -} - -module.exports = FiscoBcosWorkerFactory; diff --git a/packages/caliper-iroha/index.js b/packages/caliper-iroha/index.js index 0cde25a22..30c6aa78d 100644 --- a/packages/caliper-iroha/index.js +++ b/packages/caliper-iroha/index.js @@ -14,5 +14,4 @@ 'use strict'; -module.exports.AdminClient = require('./lib/iroha'); -module.exports.WorkerFactory = require('./lib/irohaWorkerFactory'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-cli/lib/benchmark.js b/packages/caliper-iroha/lib/adapterFactory.js similarity index 55% rename from packages/caliper-cli/lib/benchmark.js rename to packages/caliper-iroha/lib/adapterFactory.js index ee840e63b..8cc3cf37b 100644 --- a/packages/caliper-cli/lib/benchmark.js +++ b/packages/caliper-iroha/lib/adapterFactory.js @@ -14,13 +14,16 @@ 'use strict'; -exports.command = 'benchmark '; -exports.desc = 'Caliper benchmark command'; -exports.builder = function (yargs) { - // apply commands in subdirectories - return yargs.demandCommand(1, 'Incorrect command. Please see the list of commands above, or enter "caliper benchmark --help".') - .commandDir('benchmark'); -}; -exports.handler = function (argv) {}; +const IrohaAdapter = require('./iroha'); -module.exports.RunBenchmark = require('./benchmark/lib/runBenchmark'); +/** + * Constructs an Iroha adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async + */ +async function adapterFactory(workerIndex) { + return new IrohaAdapter(workerIndex); +} + +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-iroha/lib/iroha.js b/packages/caliper-iroha/lib/iroha.js index f1850a1f2..b5e576604 100644 --- a/packages/caliper-iroha/lib/iroha.js +++ b/packages/caliper-iroha/lib/iroha.js @@ -24,7 +24,7 @@ const QueryService_v1Client = IrohaService_v1Client.QueryService_v1Client; const generateKeypair = require('iroha-helpers/lib/cryptoHelper.js').default; -const {BlockchainInterface, CaliperUtils, TxStatus} = require('@hyperledger/caliper-core'); +const {BlockchainInterface, CaliperUtils, ConfigUtil, TxStatus} = require('@hyperledger/caliper-core'); const logger = CaliperUtils.getLogger('iroha.js'); const irohaQueries = require('iroha-helpers/lib/queries').default; @@ -195,11 +195,15 @@ function irohaQuery(queryOptions, commands) { * Implements {BlockchainInterface} for a Iroha backend. */ class Iroha extends BlockchainInterface { - constructor(config_path, workspace_root, clientIndex) { - super(config_path); + /** + * Create a new instance of the {Iroha} class. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. Currently unused. + */ + constructor(workerIndex) { + super(); + this.configPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); this.bcType = 'iroha'; - this.workspaceRoot = workspace_root; - this.clientIndex = clientIndex; + this.clientIndex = workerIndex; } /** @@ -243,8 +247,8 @@ class Iroha extends BlockchainInterface { let admin = config.iroha.admin; let domain = admin.domain; let adminAccount = admin.account + '@' + admin.domain; - let privPath = CaliperUtils.resolvePath(admin['key-priv'], this.workspaceRoot); - let pubPath = CaliperUtils.resolvePath(admin['key-pub'], this.workspaceRoot); + let privPath = CaliperUtils.resolvePath(admin['key-priv']); + let pubPath = CaliperUtils.resolvePath(admin['key-pub']); let adminPriv = fs.readFileSync(privPath).toString(); let adminPub = fs.readFileSync(pubPath).toString(); // test @@ -370,7 +374,7 @@ class Iroha extends BlockchainInterface { for(let i = 0 ; i < fc.length ; i++) { let contract = fc[i]; //load the fakeContract. - let facPath = CaliperUtils.resolvePath(contract.factory,this.workspaceRoot); + let facPath = CaliperUtils.resolvePath(contract.factory); let factory = require(facPath); for(let j = 0 ; j < contract.id.length ; j++) { let id = contract.id[j]; diff --git a/packages/caliper-iroha/lib/irohaWorker.js b/packages/caliper-iroha/lib/irohaWorker.js deleted file mode 100644 index 125fe1f78..000000000 --- a/packages/caliper-iroha/lib/irohaWorker.js +++ /dev/null @@ -1,51 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const IrohaClient = require('./iroha'); - -/** - * Handles the init message. Constructs the Iroha adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - return new IrohaClient(context.networkConfigPath, context.workspacePath, context.workerId); -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'iroha'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-publish/artifacts/docker-build.sh b/packages/caliper-publish/artifacts/docker-build.sh index a996fd4b8..e2e06f721 100755 --- a/packages/caliper-publish/artifacts/docker-build.sh +++ b/packages/caliper-publish/artifacts/docker-build.sh @@ -20,4 +20,4 @@ set -o pipefail # Set ARCH ARCH=`uname -m` -docker build --network=host -t "${IMAGE}:${TAG}" -f caliper.Dockerfile --build-arg "caliper_version=${TAG}" --build-arg "npm_registry=${NPM_REGISTRY}" . +docker build --network=host -t "${IMAGE}:${IMAGE_TAG}" -f caliper.Dockerfile --build-arg "caliper_version=${CALIPER_TAG}" --build-arg "npm_registry=${NPM_REGISTRY}" . diff --git a/packages/caliper-publish/caliper.Dockerfile b/packages/caliper-publish/caliper.Dockerfile index f152d695b..19928bc96 100644 --- a/packages/caliper-publish/caliper.Dockerfile +++ b/packages/caliper-publish/caliper.Dockerfile @@ -12,28 +12,29 @@ # limitations under the License. # -FROM node:10.16-alpine AS caliper-base -WORKDIR /hyperledger/caliper +FROM node:10.16-alpine -# Common steps for all versions -# 1. install packages for grpc compilation -# 2. Create the default workspace directory -# 3. Initialize the working directory -RUN apk add --no-cache python2 make g++ git \ - && mkdir -p /hyperledger/caliper/workspace \ - && npm init -y - - -FROM caliper-base # require to set these explicitly to avoid mistakes ARG npm_registry ARG caliper_version -WORKDIR /hyperledger/caliper +# Install packages for dependency compilation +RUN apk add --no-cache python g++ make git + +# execute as the "node" user, created in the base image +USER node:node +WORKDIR /hyperledger/caliper/workspace -# 4. Install the CLI into the working directory -RUN npm install ${npm_registry} --only=prod @hyperledger/caliper-cli@${caliper_version} +# 1 & 2. change the NPM global install directory +# https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally#manually-change-npms-default-directory +# 3. install Caliper globally +RUN mkdir /home/node/.npm-global \ + && npm config set prefix '/home/node/.npm-global' \ + && npm install ${npm_registry} -g --only=prod @hyperledger/caliper-cli@${caliper_version} +ENV PATH /home/node/.npm-global/bin:$PATH ENV CALIPER_WORKSPACE /hyperledger/caliper/workspace -CMD npx caliper bind && npx caliper benchmark run +ENV CALIPER_BIND_ARGS -g +ENTRYPOINT ["caliper"] +CMD ["--version"] diff --git a/packages/caliper-publish/lib/dockerCommand.js b/packages/caliper-publish/lib/dockerCommand.js index a3b9b6526..7c22567a0 100755 --- a/packages/caliper-publish/lib/dockerCommand.js +++ b/packages/caliper-publish/lib/dockerCommand.js @@ -27,6 +27,12 @@ module.exports.builder = yargs => { type: 'string', describe: 'The name for the built image.' }, + tag: { + alias: 't', + demand: false, + type: 'string', + describe: 'Overrides the version-based tag for testing purposes' + }, registry: { alias: 'r', demand: false, @@ -61,5 +67,5 @@ module.exports.builder = yargs => { }; module.exports.handler = argv => { - argv.thePromise = Docker.handler(argv.image, argv.registry, argv.publish, argv.user, argv.retries); + argv.thePromise = Docker.handler(argv.image, argv.registry, argv.publish, argv.user, argv.retries, argv.tag); }; diff --git a/packages/caliper-publish/lib/impl/docker.js b/packages/caliper-publish/lib/impl/docker.js index 2c2a4db6f..ab82352a9 100644 --- a/packages/caliper-publish/lib/impl/docker.js +++ b/packages/caliper-publish/lib/impl/docker.js @@ -45,9 +45,10 @@ class Docker { * @param {boolean} publish Indicates whether to publish the built image. Requires that DOCKER_TOKEN and "user" argument is set. * @param {string} user The user to use for publishing the built Docker image. Required when "publish" is true. * @param {number} retries The number of times to retry the build in case of failures. + * @param {string} tag Override for the version-based tag for testing purposes. * @async */ - static async handler(image, registry, publish, user, retries) { + static async handler(image, registry, publish, user, retries, tag) { const cliPackageJsonPath = path.join(packagesRoot, 'caliper-cli', 'package.json'); const cliPackageVersion = require(cliPackageJsonPath).version; const cliPackageName = `@hyperledger/caliper-cli@${cliPackageVersion}`; @@ -85,9 +86,11 @@ class Docker { } // this object will configure the build script + let imageTag = tag || cliPackageVersion; let envs = { IMAGE: image, - TAG: cliPackageVersion + IMAGE_TAG: imageTag, + CALIPER_TAG: cliPackageVersion }; if (registry !== '') { @@ -97,24 +100,24 @@ class Docker { try { let built = false; for (let i = 0; i < retries; i++) { - log(`Building ${image}@${cliPackageVersion}. Attempt ${i+1}/${retries}:`); + log(`Building ${image}@${imageTag}. Attempt ${i+1}/${retries}:`); try { await utils.invokeCommand(buildScriptPath, [], envs, thisPackageRoot); - log(`Built ${image}@${cliPackageVersion}\n`); + log(`Built ${image}@${imageTag}\n`); built = true; break; } catch (error) { - log(`Failed to build ${image}@${cliPackageVersion} (attempt ${i+1}/${retries})`); + log(`Failed to build ${image}@${imageTag} (attempt ${i+1}/${retries})`); log(error); } } if (!built) { - log(`Aborting, could not build ${image}@${cliPackageVersion}`); + log(`Aborting, could not build ${image}@${imageTag}`); process.exit(1); } } catch (err) { - log(`Aborting, could not build ${image}@${cliPackageVersion}`); + log(`Aborting, could not build ${image}@${imageTag}`); log(err); process.exit(1); } @@ -126,30 +129,30 @@ class Docker { let publishEnvs = { DOCKER_USER: user, IMAGE: image, - TAG: cliPackageVersion + TAG: imageTag }; try { let published = false; for (let i = 0; i < retries; i++) { - log(`Publishing ${image}@${cliPackageVersion}. Attempt ${i+1}/${retries}:`); + log(`Publishing ${image}@${imageTag}. Attempt ${i+1}/${retries}:`); try { await utils.invokeCommand(publishScriptPath, [], publishEnvs, thisPackageRoot); - log(`Published ${image}@${cliPackageVersion}\n`); + log(`Published ${image}@${imageTag}\n`); published = true; break; } catch (error) { - log(`Failed to publish ${image}@${cliPackageVersion} (attempt ${i+1}/${retries})`); + log(`Failed to publish ${image}@${imageTag} (attempt ${i+1}/${retries})`); log(error); } } if (!published) { - log(`Aborting, could not publish ${image}@${cliPackageVersion}`); + log(`Aborting, could not publish ${image}@${imageTag}`); process.exit(1); } } catch (err) { - log(`Aborting, could not publish ${image}@${cliPackageVersion}`); + log(`Aborting, could not publish ${image}@${imageTag}`); log(err); process.exit(1); } diff --git a/packages/caliper-sawtooth/index.js b/packages/caliper-sawtooth/index.js index e996ba21d..431be3a5b 100644 --- a/packages/caliper-sawtooth/index.js +++ b/packages/caliper-sawtooth/index.js @@ -14,6 +14,5 @@ 'use strict'; -module.exports.AdminClient = require('./lib/sawtooth'); -module.exports.WorkerFactory = require('./lib/sawtoothWorkerFactory'); module.exports.BatchBuilder = require('./lib/batch/BatchBuilder'); +module.exports.AdapterFactory = require('./lib/adapterFactory').adapterFactory; diff --git a/packages/caliper-sawtooth/lib/adapterFactory.js b/packages/caliper-sawtooth/lib/adapterFactory.js new file mode 100644 index 000000000..67f9c730f --- /dev/null +++ b/packages/caliper-sawtooth/lib/adapterFactory.js @@ -0,0 +1,29 @@ +/* +* 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 SawtoothAdapter = require('./sawtooth'); + +/** + * Constructs a Sawtooth adapter. + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. + * @return {Promise} The initialized adapter instance. + * @async + */ +async function adapterFactory(workerIndex) { + return new SawtoothAdapter(workerIndex); +} + +module.exports.adapterFactory = adapterFactory; diff --git a/packages/caliper-sawtooth/lib/sawtooth.js b/packages/caliper-sawtooth/lib/sawtooth.js index e9763275e..3e2b16482 100644 --- a/packages/caliper-sawtooth/lib/sawtooth.js +++ b/packages/caliper-sawtooth/lib/sawtooth.js @@ -15,7 +15,9 @@ 'use strict'; -const { BlockchainInterface, CaliperUtils, TxStatus }= require('@hyperledger/caliper-core'); +const path = require('path'); + +const { BlockchainInterface, CaliperUtils, ConfigUtil, TxStatus }= require('@hyperledger/caliper-core'); const logger = CaliperUtils.getLogger('sawtooth.js'); const BatchBuilderFactory = require('./batch/BatchBuilderFactory.js'); @@ -317,16 +319,14 @@ async function submitBatches(block_num, batchBytes, timeout) { class Sawtooth extends BlockchainInterface { /** * Constructor - * @param {String} config_path path of the Sawtooth configuration file - * @param {string} workspace_root The absolute path to the root location for the application configuration files. - * @param {number} clientIndex The client index + * @param {number} workerIndex The zero-based index of the worker who wants to create an adapter instance. -1 for the master process. Currently unused. */ - constructor(config_path, workspace_root, clientIndex) { - super(config_path); - configPath = config_path; + constructor(workerIndex) { + super(); + configPath = CaliperUtils.resolvePath(ConfigUtil.get(ConfigUtil.keys.NetworkConfig)); this.bcType = 'sawtooth'; - this.workspaceRoot = workspace_root; - this.clientIndex = clientIndex; + this.workspaceRoot = path.resolve(ConfigUtil.get(ConfigUtil.keys.Workspace)); + this.clientIndex = workerIndex; } /** diff --git a/packages/caliper-sawtooth/lib/sawtoothWorker.js b/packages/caliper-sawtooth/lib/sawtoothWorker.js deleted file mode 100644 index 821c55225..000000000 --- a/packages/caliper-sawtooth/lib/sawtoothWorker.js +++ /dev/null @@ -1,51 +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 { ConfigUtil, Messenger, MessageHandler } = require('@hyperledger/caliper-core'); -const SawtoothClient = require('./sawtooth'); - -/** - * Handles the init message. Constructs the Sawtooth adapter. - * @param {object} context The context of the message handler object. - * @param {object} message The message object. - * @return {Promise} The initialized adapter instance. - * @async - */ -async function initHandler(context, message) { - return new SawtoothClient(context.networkConfigPath, context.workspacePath, context.workerId); -} - -/** - * Main process - */ -async function main (){ - - // Create the message client using the specified type - const type = `${ConfigUtil.get(ConfigUtil.keys.Worker.Communication.Method)}-worker`; - const messenger = new Messenger({type, sut: 'sawtooth'}); - await messenger.initialize(); - - // Create a handler context for this worker - const handlerContext = new MessageHandler({ - init: initHandler - }, messenger); - - // Pass to the messenger to configure - messenger.configure(handlerContext); - -} - -main(); diff --git a/packages/caliper-sawtooth/lib/sawtoothWorkerFactory.js b/packages/caliper-sawtooth/lib/sawtoothWorkerFactory.js deleted file mode 100644 index b7d7e21a9..000000000 --- a/packages/caliper-sawtooth/lib/sawtoothWorkerFactory.js +++ /dev/null @@ -1,35 +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 childProcess = require('child_process'); -const path = require('path'); - -/** - * Class used to spawn sawtooth workers - */ -class SawtoothWorkerFactory { - - /** - * Spawn the worker and perform required init - * @returns {Object} the child process - */ - spawnWorker() { - const child = childProcess.fork(path.join(__dirname, './sawtoothWorker.js'), process.argv.slice(2), { env: process.env}); - return child; - } -} - -module.exports = SawtoothWorkerFactory; diff --git a/packages/caliper-tests-integration/besu_tests/run.sh b/packages/caliper-tests-integration/besu_tests/run.sh index 8c82255e5..fdc0fe625 100755 --- a/packages/caliper-tests-integration/besu_tests/run.sh +++ b/packages/caliper-tests-integration/besu_tests/run.sh @@ -24,11 +24,11 @@ cd "${DIR}" export CALIPER_PROJECTCONFIG=../caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-workspace phase1 --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-workspace phase1 --caliper-flow-only-end } # PHASE 1: just starting the network -${CALL_METHOD} benchmark run --caliper-workspace phase1 --caliper-flow-only-start +${CALL_METHOD} launch master --caliper-workspace phase1 --caliper-flow-only-start rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 1"; @@ -37,7 +37,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 2: single caliper client, estimate gas on open -${CALL_METHOD} benchmark run --caliper-workspace phase2 --caliper-flow-skip-start --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-workspace phase2 --caliper-flow-skip-start --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 2"; @@ -46,7 +46,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 3: multiple caliper clients, all gas fixed -${CALL_METHOD} benchmark run --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 3"; @@ -55,7 +55,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 5: just disposing of the network -${CALL_METHOD} benchmark run --caliper-workspace phase1 --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-workspace phase1 --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 5"; diff --git a/packages/caliper-tests-integration/ethereum_tests/run.sh b/packages/caliper-tests-integration/ethereum_tests/run.sh index ea563815e..1723dc508 100755 --- a/packages/caliper-tests-integration/ethereum_tests/run.sh +++ b/packages/caliper-tests-integration/ethereum_tests/run.sh @@ -24,11 +24,11 @@ cd "${DIR}" export CALIPER_PROJECTCONFIG=caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-flow-only-end } # PHASE 1: just starting the network -${CALL_METHOD} benchmark run --caliper-flow-only-start +${CALL_METHOD} launch master --caliper-flow-only-start rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 1"; @@ -37,7 +37,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 2: init, install, test -${CALL_METHOD} benchmark run --caliper-flow-skip-start --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-flow-skip-start --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 2"; @@ -46,7 +46,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 5: just disposing of the network -${CALL_METHOD} benchmark run --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 3"; diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore b/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore new file mode 100644 index 000000000..c9216fbdd --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore @@ -0,0 +1,4 @@ +**/*.log +report.html +node_modules/* +package-lock.json diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/benchconfig.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/benchconfig.yaml new file mode 100644 index 000000000..4540b86ef --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/benchconfig.yaml @@ -0,0 +1,33 @@ +# +# 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. +# + +--- +test: + workers: + type: local + number: 2 + rounds: + - label: init + txNumber: 1000 + rateControl: { type: 'fixed-rate', opts: { tps: 20 } } + callback: ./init.js + - label: query + txNumber: 100 + rateControl: { type: 'linear-rate', opts: { startingTps: 5, finishingTps: 10 } } + callback: ./query.js +observer: + interval: 5 + type: local +monitor: + type: ['none'] diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/caliper.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/caliper.yaml new file mode 100644 index 000000000..cdffc5daf --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/caliper.yaml @@ -0,0 +1,54 @@ +# +# 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. +# + +caliper: + benchconfig: ./benchconfig.yaml + networkconfig: ./networkconfig.yaml + bind: + sut: fabric:1.4.1 + report: + path: ./report.html + logging: + template: '%timestamp%%level%%module%%message%%metadata%' + formats: + timestamp: 'YYYY.MM.DD-HH:mm:ss.SSS ZZ' + label: false + json: false + pad: true + align: false + attributeformat: + level: ' %attribute%' + module: ' [%attribute%] ' + metadata: ' (%attribute%)' + colorize: + all: true + colors: + info: green + error: red + warn: yellow + debug: grey + targets: + console: + target: console + enabled: true + options: + level: debug + file: + target: file + enabled: false + worker: + remote: true + communication: + method: mqtt + address: mqtt://mosquitto:1883 diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/clean.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/clean.sh new file mode 100755 index 000000000..0cf43d427 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/clean.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +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) + diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/.gitignore b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/.gitignore new file mode 100644 index 000000000..2bc6f264d --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/.gitignore @@ -0,0 +1,5 @@ +bin/* +config/* +crypto-config/* +genesis.block +mychannel.tx diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/configtx.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/configtx.yaml new file mode 100644 index 000000000..8ee584f0c --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/configtx.yaml @@ -0,0 +1,92 @@ +# +# 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. +# + +--- + +Organizations: +- &OrdererOrg + Name: OrdererMSP + ID: OrdererMSP + MSPDir: crypto-config/ordererOrganizations/example.com/msp + AdminPrincipal: Role.MEMBER + +- &Org1 + Name: Org1MSP + ID: Org1MSP + MSPDir: crypto-config/peerOrganizations/org1.example.com/msp + AdminPrincipal: Role.ADMIN + AnchorPeers: + - Host: peer0.org1.example.com + Port: 7051 + +- &Org2 + Name: Org2MSP + ID: Org2MSP + MSPDir: crypto-config/peerOrganizations/org2.example.com/msp + AdminPrincipal: Role.ADMIN + AnchorPeers: + - Host: peer0.org2.example.com + Port: 7051 + +Orderer: &OrdererDefaults + OrdererType: etcdraft + Addresses: + - orderer0.example.com:7050 + - orderer1.example.com:7050 + + BatchTimeout: 500ms + BatchSize: + MaxMessageCount: 50 + AbsoluteMaxBytes: 1 MB + PreferredMaxBytes: 1 MB + + MaxChannels: 0 + EtcdRaft: + Consenters: + - Host: orderer0.example.com + Port: 7050 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/tls/server.crt + - Host: orderer1.example.com + Port: 7050 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/tls/server.crt + + Organizations: + +Application: &ApplicationDefaults + Organizations: + +Profiles: + OrdererGenesis: + Orderer: + <<: *OrdererDefaults + Organizations: + - *OrdererOrg + Consortiums: + SampleConsortium: + Organizations: + - *Org1 + - *Org2 + SampleConsortium2: + Organizations: + - *Org1 + - *Org2 + ChannelConfig: + Consortium: SampleConsortium + Application: + <<: *ApplicationDefaults + Organizations: + - *Org1 + - *Org2 diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/crypto-config.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/crypto-config.yaml new file mode 100644 index 000000000..d66bf601d --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/crypto-config.yaml @@ -0,0 +1,36 @@ +# +# 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. +# + + +OrdererOrgs: +- Name: Orderer + Domain: example.com + + Template: + Count: 2 + +PeerOrgs: +- Name: Org1 + Domain: org1.example.com + Template: + Count: 1 + Users: + Count: 1 + +- Name: Org2 + Domain: org2.example.com + Template: + Count: 1 + Users: + Count: 1 diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/generate.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/generate.sh new file mode 100755 index 000000000..2a2e3f4cf --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/config/generate.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# if the binaries are not available, download them +if [[ ! -d "bin" ]]; then + curl -sSL http://bit.ly/2ysbOFE | bash -s -- 1.4.1 1.4.1 0.4.15 -ds +fi + +rm -rf ./crypto-config/ +rm -f ./genesis.block +rm -f ./mychannel.tx + +./bin/cryptogen generate --config=./crypto-config.yaml +./bin/configtxgen -profile OrdererGenesis -outputBlock genesis.block -channelID syschannel +./bin/configtxgen -profile ChannelConfig -outputCreateChannelTx mychannel.tx -channelID mychannel + +# Rename the key files we use to be key.pem instead of a uuid +for KEY in $(find crypto-config -type f -name "*_sk"); do + KEY_DIR=$(dirname ${KEY}) + mv ${KEY} ${KEY_DIR}/key.pem +done diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/docker-compose.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/docker-compose.yaml new file mode 100644 index 000000000..27120e48e --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/docker-compose.yaml @@ -0,0 +1,308 @@ +# +# 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. +# + +version: '3' + +volumes: + prometheus_data: {} + +services: + +####### +# CAs # +####### + + ca.org1.example.com: + image: hyperledger/fabric-ca:1.4.1 + environment: + - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server + - FABRIC_CA_SERVER_CA_NAME=ca.org1.example.com + - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem + - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/key.pem + # TLS + - FABRIC_CA_SERVER_TLS_ENABLED=true + - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-tls/tlsca.org1.example.com-cert.pem + - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-tls/key.pem +# ports: +# - "7054:7054" + command: sh -c 'fabric-ca-server start -b admin:adminpw -d' + volumes: + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config + - ./config/crypto-config/peerOrganizations/org1.example.com/tlsca/:/etc/hyperledger/fabric-ca-server-tls + container_name: ca.org1.example.com + + ca.org2.example.com: + image: hyperledger/fabric-ca:1.4.1 + environment: + - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server + - FABRIC_CA_SERVER_CA_NAME=ca.org2.example.com + - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org2.example.com-cert.pem + - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/key.pem + # TLS + - FABRIC_CA_SERVER_TLS_ENABLED=true + - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-tls/tlsca.org2.example.com-cert.pem + - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-tls/key.pem +# ports: +# - "8054:7054" + command: sh -c 'fabric-ca-server start -b admin:adminpw -d' + volumes: + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/fabric-ca-server-config + - ./config/crypto-config/peerOrganizations/org2.example.com/tlsca/:/etc/hyperledger/fabric-ca-server-tls + container_name: ca.org2.example.com + +############ +# ORDERERS # +############ + + orderer0.example.com: + container_name: orderer0.example.com + image: hyperledger/fabric-orderer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 + - ORDERER_GENERAL_GENESISMETHOD=file + - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block + - ORDERER_GENERAL_LOCALMSPID=OrdererMSP + - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp + # TLS + - ORDERER_GENERAL_TLS_ENABLED=true + - ORDERER_GENERAL_TLS_PRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + - ORDERER_GENERAL_TLS_CERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_TLS_ROOTCAS=[/etc/hyperledger/msp/orderer/tls/ca.crt] + # Mutual TLS + - ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED=true + - ORDERER_GENERAL_TLS_CLIENTROOTCAS=[/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem, /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem, /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem] + # Raft TLS + - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + # setting up metrics + - ORDERER_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - ORDERER_OPERATIONS_TLS_ENABLED=false + - ORDERER_METRICS_ENABLE=true + - ORDERER_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: orderer +# ports: +# - 7050:7050 + volumes: + - ./config/:/etc/hyperledger/configtx + - ./config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/:/etc/hyperledger/msp/orderer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - ca.org1.example.com + - ca.org2.example.com + + orderer1.example.com: + container_name: orderer1.example.com + image: hyperledger/fabric-orderer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 + - ORDERER_GENERAL_GENESISMETHOD=file + - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block + - ORDERER_GENERAL_LOCALMSPID=OrdererMSP + - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp + # TLS + - ORDERER_GENERAL_TLS_ENABLED=true + - ORDERER_GENERAL_TLS_PRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + - ORDERER_GENERAL_TLS_CERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_TLS_ROOTCAS=[/etc/hyperledger/msp/orderer/tls/ca.crt] + # Mutual TLS + - ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED=true + - ORDERER_GENERAL_TLS_CLIENTROOTCAS=[/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem, /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem, /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem] + # Raft TLS + - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + # setting up metrics + - ORDERER_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - ORDERER_OPERATIONS_TLS_ENABLED=false + - ORDERER_METRICS_ENABLE=true + - ORDERER_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: orderer +# ports: +# - 8050:7050 + volumes: + - ./config/:/etc/hyperledger/configtx + - ./config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/:/etc/hyperledger/msp/orderer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - ca.org1.example.com + - ca.org2.example.com + +######### +# PEERS # +######### + + peer0.org1.example.com: + container_name: peer0.org1.example.com + image: hyperledger/fabric-peer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - CORE_CHAINCODE_LOGGING_LEVEL=INFO + - CORE_CHAINCODE_LOGGING_SHIM=INFO + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_PEER_ID=peer0.org1.example.com + - CORE_PEER_ENDORSER_ENABLED=true + - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/msp/ + - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 + - CORE_PEER_GOSSIP_USELEADERELECTION=true + - CORE_PEER_GOSSIP_ORGLEADER=false + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051 + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=caliper_default + # CouchDB + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.peer0.org1.example.com:5984 + # TLS + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/msp/peer/tls/server.key + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/msp/peer/tls/server.crt + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/msp/peer/tls/ca.crt + # Mutual TLS + - CORE_PEER_TLS_CLIENTAUTHREQUIRED=true + - CORE_PEER_TLS_CLIENTROOTCAS_FILES=/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem + # setting up metrics + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - CORE_OPERATIONS_TLS_ENABLED=false + - CORE_METRICS_ENABLE=true + - CORE_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: peer node start +# ports: +# - 7051:7051 + volumes: + - /var/run/:/host/var/run/ + - ./config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/:/etc/hyperledger/msp/peer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - orderer0.example.com + - orderer1.example.com + - couchdb.peer0.org1.example.com + + couchdb.peer0.org1.example.com: + container_name: couchdb.peer0.org1.example.com + image: hyperledger/fabric-couchdb:0.4.14 +# ports: +# - 5984:5984 + environment: + DB_URL: http://localhost:5984/member_db + + peer0.org2.example.com: + container_name: peer0.org2.example.com + image: hyperledger/fabric-peer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - CORE_CHAINCODE_LOGGING_LEVEL=INFO + - CORE_CHAINCODE_LOGGING_SHIM=INFO + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_PEER_ID=peer0.org2.example.com + - CORE_PEER_ENDORSER_ENABLED=true + - CORE_PEER_LOCALMSPID=Org2MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/msp/ + - CORE_PEER_ADDRESS=peer0.org2.example.com:7051 + - CORE_PEER_GOSSIP_USELEADERELECTION=true + - CORE_PEER_GOSSIP_ORGLEADER=false + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:7051 + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=caliper_default + # CouchDB + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.peer0.org2.example.com:5984 + # TLS + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/msp/peer/tls/server.key + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/msp/peer/tls/server.crt + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/msp/peer/tls/ca.crt + # Mutual TLS + - CORE_PEER_TLS_CLIENTAUTHREQUIRED=true + - CORE_PEER_TLS_CLIENTROOTCAS_FILES=/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem + # setting up metrics + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - CORE_OPERATIONS_TLS_ENABLED=false + - CORE_METRICS_ENABLE=true + - CORE_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: peer node start +# ports: +# - 8051:7051 + volumes: + - /var/run/:/host/var/run/ + - ./config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/:/etc/hyperledger/msp/peer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - orderer0.example.com + - orderer1.example.com + - couchdb.peer0.org2.example.com + + couchdb.peer0.org2.example.com: + container_name: couchdb.peer0.org2.example.com + image: hyperledger/fabric-couchdb:0.4.14 +# ports: +# - 6984:5984 + environment: + DB_URL: http://localhost:5984/member_db + +#################### +# CALIPER SERVICES # +#################### + + caliper-master: + image: caliper:test + container_name: caliper-master + command: launch master + volumes: + - ./:/hyperledger/caliper/workspace + depends_on: + - mosquitto + + caliper-worker1: + image: caliper:test + container_name: caliper-worker1 + command: launch worker + volumes: + - ./:/hyperledger/caliper/workspace + depends_on: + - mosquitto + + caliper-worker2: + image: caliper:test + container_name: caliper-worker2 + command: launch worker + volumes: + - ./:/hyperledger/caliper/workspace + depends_on: + - mosquitto + +############### +# MQTT BROKER # +############### + + mosquitto: + image: eclipse-mosquitto + hostname: mosquitto + container_name: mosquitto + restart: always + ports: + - "1883:1883" + - "9001:9001" + volumes: + - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/init.js b/packages/caliper-tests-integration/fabric_docker_distributed_tests/init.js new file mode 100644 index 000000000..56139eea1 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/init.js @@ -0,0 +1,47 @@ +/* +* 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 uuidv4 = require('uuid/v4'); + +module.exports.info = 'Creating marbles.'; + +let txIndex = 0; +let colors = ['red', 'blue', 'green', 'black', 'white', 'pink', 'rainbow']; +let owners = ['Alice', 'Bob', 'Claire', 'David']; +let bc, contx; + +module.exports.init = async function(blockchain, context, args) { + bc = blockchain; + contx = context; +}; + +module.exports.run = async function() { + txIndex++; + let marbleName = 'marble_' + txIndex.toString() + '_' + uuidv4(); + let marbleColor = colors[txIndex % colors.length]; + let marbleSize = (((txIndex % 10) + 1) * 10).toString(); // [10, 100] + let marbleOwner = owners[txIndex % owners.length]; + + let args = { + chaincodeFunction: 'initMarble', + chaincodeArguments: [marbleName, marbleColor, marbleSize, marbleOwner], + }; + + let targetCC = txIndex % 2 === 0 ? 'mymarbles' : 'yourmarbles'; + return bc.invokeSmartContract(contx, targetCC, 'v0', args, 5); +}; + +module.exports.end = async function() {}; diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/mosquitto/mosquitto.conf b/packages/caliper-tests-integration/fabric_docker_distributed_tests/mosquitto/mosquitto.conf new file mode 100644 index 000000000..4f9950610 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/mosquitto/mosquitto.conf @@ -0,0 +1 @@ +persistence false diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/networkconfig.yaml b/packages/caliper-tests-integration/fabric_docker_distributed_tests/networkconfig.yaml new file mode 100644 index 000000000..7d2dff03c --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/networkconfig.yaml @@ -0,0 +1,164 @@ +# +# 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 + + chaincodes: + - 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 + + chaincodes: + - 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://orderer0.example.com: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://orderer1.example.com:7050 + 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://peer0.org1.example.com: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://peer0.org2.example.com:7051 + 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://ca.org1.example.com: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://ca.org2.example.com:7054 + 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_docker_distributed_tests/package.json b/packages/caliper-tests-integration/fabric_docker_distributed_tests/package.json new file mode 100644 index 000000000..010b55227 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/package.json @@ -0,0 +1,15 @@ +{ + "name": "fabric_docker_distributed_tests", + "version": "1.0.0", + "description": "Distributed Docker-based Caliper test for Fabric", + "main": "init.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "uuid": "^3.4.0" + } +} diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/query.js b/packages/caliper-tests-integration/fabric_docker_distributed_tests/query.js new file mode 100644 index 000000000..1d084d354 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/query.js @@ -0,0 +1,40 @@ +/* +* 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'; + +module.exports.info = 'Querying marbles.'; + +let txIndex = 0; +let owners = ['Alice', 'Bob', 'Claire', 'David']; +let bc, contx; + +module.exports.init = async function(blockchain, context, args) { + bc = blockchain; + contx = context; +}; + +module.exports.run = async function() { + txIndex++; + let marbleOwner = owners[txIndex % owners.length]; + let args = { + chaincodeFunction: 'queryMarblesByOwner', + chaincodeArguments: [marbleOwner] + }; + + let targetCC = txIndex % 2 === 0 ? 'mymarbles' : 'yourmarbles'; + return bc.querySmartContract(contx, targetCC, '', args, 10); +}; + +module.exports.end = async function() {}; diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh new file mode 100755 index 000000000..678866dcc --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +# Grab the parent (fabric_tests) directory. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "${DIR}" + +# generate the crypto materials +cd ./config +./generate.sh + +# back to this dir +cd ${DIR} + +# Publish the packages locally and build a local test image +cd ./../../caliper-publish/ +./publish.js verdaccio start +sleep 5s +./publish.js npm --registry http://localhost:4873 +./publish.js docker --registry http://localhost:4873 --image caliper --tag test +./publish.js verdaccio stop + +# back to this dir +cd ${DIR} + +npm i +docker-compose -p caliper up -d + diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh new file mode 100755 index 000000000..ce3d289a5 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +# Grab the parent (fabric_tests) directory. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "${DIR}" + +# generate the crypto materials +cd ./config +./generate.sh + +# back to this dir +cd ${DIR} + +npm i +docker-compose -p caliper up -d + diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go new file mode 100644 index 000000000..4e932984b --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go @@ -0,0 +1,665 @@ +/* +* 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. +*/ + +/* +* NOTE: This implementation is a replica of the following: +* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +*/ + +// ====CHAINCODE EXECUTION SAMPLES (CLI) ================== + +// ==== Invoke marbles ==== +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble1","blue","35","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble2","red","50","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble3","blue","70","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarble","marble2","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarblesBasedOnColor","blue","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["delete","marble1"]}' + +// ==== Query marbles ==== +// peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getMarblesByRange","marble1","marble3"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' + +// Rich Query (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// INDEXES TO SUPPORT COUCHDB RICH QUERIES +// +// Indexes in CouchDB are required in order to make JSON queries efficient and are required for +// any JSON query with a sort. As of Hyperledger Fabric 1.1, indexes may be packaged alongside +// chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own +// text file with extension *.json with the index definition formatted in JSON following the +// CouchDB index JSON syntax as documented at: +// http://docs.couchdb.org/en/2.1.1/api/database/find.html#db-index +// +// This marbles02 example chaincode demonstrates a packaged +// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json. +// For deployment of chaincode to production environments, it is recommended +// to define any indexes alongside chaincode so that the chaincode and supporting indexes +// are deployed automatically as a unit, once the chaincode has been installed on a peer and +// instantiated on a channel. See Hyperledger Fabric documentation for more details. +// +// If you have access to the your peer's CouchDB state database in a development environment, +// you may want to iteratively test various indexes in support of your chaincode queries. You +// can use the CouchDB Fauxton interface or a command line curl utility to create and update +// indexes. Then once you finalize an index, include the index definition alongside your +// chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment +// to managed environments. +// +// In the examples below you can find index definitions that support marbles02 +// chaincode queries, along with the syntax that you can use in development environments +// to create the indexes in the CouchDB Fauxton interface or a curl command line utility. +// + +//Example hostname:port configurations to access CouchDB. +// +//To access CouchDB docker container from within another docker container or from vagrant environments: +// http://couchdb:5984/ +// +//Inside couchdb docker container +// http://127.0.0.1:5984/ + +// Index for docType, owner. +// Note that docType and owner fields must be prefixed with the "data" wrapper +// +// Index definition for use with Fauxton interface +// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} +// +// Example curl command line to define index in the CouchDB channel_chaincode database +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// + +// Index for docType, owner, size (descending order). +// Note that docType, owner and size fields must be prefixed with the "data" wrapper +// +// Index definition for use with Fauxton interface +// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} +// +// Example curl command line to define index in the CouchDB channel_chaincode database +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index + +// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' + +// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/hyperledger/fabric/core/chaincode/shim" + pb "github.com/hyperledger/fabric/protos/peer" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +type marble struct { + ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database + Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around + Color string `json:"color"` + Size int `json:"size"` + Owner string `json:"owner"` +} + +// =================================================================================== +// Main +// =================================================================================== +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting Simple chaincode: %s", err) + } +} + +// Init initializes chaincode +// =========================== +func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { + var err error + marbles := []marble{ + marble{ObjectType: "marble", Name: "marble1", Color: "Green", Size: 5, Owner: "Alice"}, + marble{ObjectType: "marble", Name: "marble2", Color: "Yellow", Size: 10, Owner: "Bob"}, + marble{ObjectType: "marble", Name: "marble3", Color: "Blue", Size: 15, Owner: "Tom"}, + } + for i, marble := range marbles { + marbleJSONasBytes, _ := json.Marshal(marble) + stub.PutState(marbles[i].Name, marbleJSONasBytes) + if err != nil { + return shim.Error("Failed to put to world state. %s") + } + } + return shim.Success(nil) +} + +// Invoke - Our entry point for Invocations +// ======================================== +func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + function, args := stub.GetFunctionAndParameters() + fmt.Println("invoke is running " + function) + + // Handle different functions + if function == "initMarble" { //create a new marble + return t.initMarble(stub, args) + } else if function == "transferMarble" { //change owner of a specific marble + return t.transferMarble(stub, args) + } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color + return t.transferMarblesBasedOnColor(stub, args) + } else if function == "delete" { //delete a marble + return t.delete(stub, args) + } else if function == "readMarble" { //read a marble + return t.readMarble(stub, args) + } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query + return t.queryMarblesByOwner(stub, args) + } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query + return t.queryMarbles(stub, args) + } else if function == "getHistoryForMarble" { //get history of values for a marble + return t.getHistoryForMarble(stub, args) + } else if function == "getMarblesByRange" { //get marbles based on range query + return t.getMarblesByRange(stub, args) + } + + fmt.Println("invoke did not find func: " + function) //error + return shim.Error("Received unknown function invocation") +} + +// ============================================================ +// initMarble - create a new marble, store into chaincode state +// ============================================================ +func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var err error + + // 0 1 2 3 + // "asdf", "blue", "35", "bob" + if len(args) != 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") + } + + // ==== Input sanitation ==== + fmt.Println("- start init marble") + if len(args[0]) <= 0 { + return shim.Error("1st argument must be a non-empty string") + } + if len(args[1]) <= 0 { + return shim.Error("2nd argument must be a non-empty string") + } + if len(args[2]) <= 0 { + return shim.Error("3rd argument must be a non-empty string") + } + if len(args[3]) <= 0 { + return shim.Error("4th argument must be a non-empty string") + } + marbleName := args[0] + color := strings.ToLower(args[1]) + owner := strings.ToLower(args[3]) + size, err := strconv.Atoi(args[2]) + if err != nil { + return shim.Error("3rd argument must be a numeric string") + } + + // ==== Check if marble already exists ==== + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble: " + err.Error()) + } else if marbleAsBytes != nil { + fmt.Println("This marble already exists: " + marbleName) + return shim.Error("This marble already exists: " + marbleName) + } + + // ==== Create marble object and marshal to JSON ==== + objectType := "marble" + marble := &marble{objectType, marbleName, color, size, owner} + marbleJSONasBytes, err := json.Marshal(marble) + if err != nil { + return shim.Error(err.Error()) + } + //Alternatively, build the marble json string manually if you don't want to use struct marshalling + //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` + //marbleJSONasBytes := []byte(str) + + // === Save marble to state === + err = stub.PutState(marbleName, marbleJSONasBytes) + if err != nil { + return shim.Error(err.Error()) + } + + // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== + // An 'index' is a normal key/value entry in state. + // The key is a composite key, with the elements that you want to range query on listed first. + // In our case, the composite key is based on indexName~color~name. + // This will enable very efficient state range queries based on composite keys matching indexName~color~* + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) + if err != nil { + return shim.Error(err.Error()) + } + // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. + // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value + value := []byte{0x00} + stub.PutState(colorNameIndexKey, value) + + // ==== Marble saved and indexed. Return success ==== + fmt.Println("- end init marble") + return shim.Success(nil) +} + +// =============================================== +// readMarble - read a marble from chaincode state +// =============================================== +func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var name, jsonResp string + var err error + + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting name of the marble to query") + } + + name = args[0] + valAsbytes, err := stub.GetState(name) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" + return shim.Error(jsonResp) + } + + return shim.Success(valAsbytes) +} + +// ================================================== +// delete - remove a marble key/value pair from state +// ================================================== +func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var jsonResp string + var marbleJSON marble + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + marbleName := args[0] + + // to maintain the color~name index, we need to read the marble first and get its color + valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) + if err != nil { + jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = stub.DelState(marbleName) //remove the marble from chaincode state + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + + // maintain the index + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) + if err != nil { + return shim.Error(err.Error()) + } + + // Delete index entry to state. + err = stub.DelState(colorNameIndexKey) + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + return shim.Success(nil) +} + +// =========================================================== +// transfer a marble by setting a new owner name on the marble +// =========================================================== +func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "name", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + marbleName := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarble ", marbleName, newOwner) + + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble:" + err.Error()) + } else if marbleAsBytes == nil { + return shim.Error("Marble does not exist") + } + + marbleToTransfer := marble{} + err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() + if err != nil { + return shim.Error(err.Error()) + } + marbleToTransfer.Owner = newOwner //change the owner + + marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) + err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble + if err != nil { + return shim.Error(err.Error()) + } + + fmt.Println("- end transferMarble (success)") + return shim.Success(nil) +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + // buffer is a JSON array containing QueryResults + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"Key\":") + buffer.WriteString("\"") + buffer.WriteString(queryResponse.Key) + buffer.WriteString("\"") + + buffer.WriteString(", \"Record\":") + // Record is a JSON object, so we write as-is + buffer.WriteString(string(queryResponse.Value)) + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} + +// ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= +// transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. +// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. +// Committing peers will re-execute range queries to guarantee that result sets are stable +// between endorsement time and commit time. The transaction is invalidated by the +// committing peers if the result set has changed between endorsement time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "color", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + color := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) + + // Query the color~name index by color + // This will execute a key range query on all keys starting with 'color' + coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) + if err != nil { + return shim.Error(err.Error()) + } + defer coloredMarbleResultsIterator.Close() + + // Iterate through result set and for each marble found, transfer to newOwner + var i int + for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { + // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key + responseRange, err := coloredMarbleResultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + + // get the color and name from color~name composite key + objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) + if err != nil { + return shim.Error(err.Error()) + } + returnedColor := compositeKeyParts[0] + returnedMarbleName := compositeKeyParts[1] + fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) + + // Now call the transfer function for the found marble. + // Re-use the same function that is used to transfer individual marbles + response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) + // if the transfer failed break out of loop and return error + if response.Status != shim.OK { + return shim.Error("Transfer failed: " + response.Message) + } + } + + responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) + fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) + return shim.Success([]byte(responsePayload)) +} + +// =======Rich queries ========================================================================= +// Two examples of rich queries are provided below (parameterized query and ad hoc query). +// Rich queries pass a query string to the state database. +// Rich queries are only supported by state database implementations +// that support rich query (e.g. CouchDB). +// The query string is in the syntax of the underlying state database. +// With rich queries there is no guarantee that the result set hasn't changed between +// endorsement time and commit time, aka 'phantom reads'. +// Therefore, rich queries should not be used in update transactions, unless the +// application handles the possibility of result set changes between endorsement and commit time. +// Rich queries can be used for point-in-time queries against a peer. +// ============================================================================================ + +// ===== Example: Parameterized rich query ================================================= +// queryMarblesByOwner queries for marbles based on a passed in owner. +// This is an example of a parameterized query where the query logic is baked into the chaincode, +// and accepting a single query parameter (owner). +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "bob" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + owner := strings.ToLower(args[0]) + + queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ===== Example: Ad hoc rich query ======================================================== +// queryMarbles uses a query string to perform a query for marbles. +// Query string matching state database syntax is passed in and executed as is. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + queryString := args[0] + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryString executes the passed in query string. +// Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, err := stub.GetQueryResult(queryString) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + // buffer is a JSON array containing QueryRecords + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"Key\":") + buffer.WriteString("\"") + buffer.WriteString(queryResponse.Key) + buffer.WriteString("\"") + + buffer.WriteString(", \"Record\":") + // Record is a JSON object, so we write as-is + buffer.WriteString(string(queryResponse.Value)) + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + + return buffer.Bytes(), nil +} + +func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + marbleName := args[0] + + fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) + + resultsIterator, err := stub.GetHistoryForKey(marbleName) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + // buffer is a JSON array containing historic values for the marble + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + response, err := resultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"TxId\":") + buffer.WriteString("\"") + buffer.WriteString(response.TxId) + buffer.WriteString("\"") + + buffer.WriteString(", \"Value\":") + // if it was a delete operation on given key, then we need to set the + //corresponding value null. Else, we will write the response.Value + //as-is (as the Value itself a JSON marble) + if response.IsDelete { + buffer.WriteString("null") + } else { + buffer.WriteString(string(response.Value)) + } + + buffer.WriteString(", \"Timestamp\":") + buffer.WriteString("\"") + buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) + buffer.WriteString("\"") + + buffer.WriteString(", \"IsDelete\":") + buffer.WriteString("\"") + buffer.WriteString(strconv.FormatBool(response.IsDelete)) + buffer.WriteString("\"") + + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json new file mode 100644 index 000000000..305f09044 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json @@ -0,0 +1 @@ +{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js new file mode 100644 index 000000000..f36228a19 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js @@ -0,0 +1,509 @@ +/* +* 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. +*/ + +/* +* NOTE: This implementation is a derivative work of the following: +* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +* The modifications include: bug fixes and refactoring for eslint compliance. +*/ + +/* eslint-disable no-console */ + +'use strict'; +const shim = require('fabric-shim'); +const util = require('util'); + +/** + * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. + * @type {SimpleChaincode} + * @extends {ChaincodeInterface} + */ +let Chaincode = class { + /** + * Called during chaincode instantiate and upgrade. This method can be used + * to initialize asset states. + * @async + * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim + * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub + * encapsulates the APIs between the chaincode implementation and the Fabric peer. + * @return {Promise} Returns a promise of a response indicating the result of the invocation. + */ + async Init(stub) { + const marbles = [ + { + name: 'marble1', + color: 'Green', + size: '5', + owner: 'Alice' + }, + { + name: 'marble2', + color: 'Yellow', + size: '15', + owner: 'Bob' + }, + { + name: 'marble3', + color: 'Blue', + size: '10', + owner: 'Tom' + } + ] + for (let i=0; i < marbles.length; i++) { + marbles[i].docType = 'marble'; + await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); + console.info('Added <--> ', marbles[i]); + } + console.info('=========== Instantiated Marbles Chaincode ==========='); + return shim.success(); + } + + /** + * Called throughout the life time of the chaincode to carry out business + * transaction logic and effect the asset states. + * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, + * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. + * @async + * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim + * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub + * encapsulates the APIs between the chaincode implementation and the Fabric peer. + * @return {Promise} Returns a promise of a response indicating the result of the invocation. + */ + async Invoke(stub) { + console.info('Transaction ID: ' + stub.getTxID()); + console.info(util.format('Args: %j', stub.getArgs())); + + let ret = stub.getFunctionAndParameters(); + console.info(ret); + + let method = this[ret.fcn]; + if (!method) { + console.log('no function of name:' + ret.fcn + ' found'); + throw new Error('Received unknown function ' + ret.fcn + ' invocation'); + } + try { + let payload = await method(stub, ret.params, this); + return shim.success(payload); + } catch (err) { + console.log(err); + return shim.error(err); + } + } + + /** + * Creates a new marble with the given attributes. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. + * Index 2: marble size. Index 3: marble owner. + */ + async initMarble(stub, args) { + if (args.length !== 4) { + throw new Error('Incorrect number of arguments. Expecting 4'); + } + // ==== Input sanitation ==== + console.info('--- start init marble ---'); + if (args[0].length <= 0) { + throw new Error('1st argument must be a non-empty string'); + } + if (args[1].length <= 0) { + throw new Error('2nd argument must be a non-empty string'); + } + if (args[2].length <= 0) { + throw new Error('3rd argument must be a non-empty string'); + } + if (args[3].length <= 0) { + throw new Error('4th argument must be a non-empty string'); + } + let marbleName = args[0]; + let color = args[1].toLowerCase(); + let owner = args[3].toLowerCase(); + let size = parseInt(args[2]); + if (isNaN(size)) { + throw new Error('3rd argument must be a numeric string'); + } + + // ==== Check if marble already exists ==== + let marbleState = await stub.getState(marbleName); + if (marbleState.toString()) { + throw new Error('This marble already exists: ' + marbleName); + } + + // ==== Create marble object and marshal to JSON ==== + let marble = {}; + marble.docType = 'marble'; + marble.name = marbleName; + marble.color = color; + marble.size = size; + marble.owner = owner; + + // === Save marble to state === + await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); + let indexName = 'color~name'; + let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); + console.info(colorNameIndexKey); + // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. + // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value + await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); + // ==== Marble saved and indexed. Return success ==== + console.info('- end init marble'); + } + + /** + * Retrieves the information about a marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + * @return {Promise} The byte representation of the marble. + */ + async readMarble(stub, args) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); + } + + let name = args[0]; + if (!name) { + throw new Error(' marble name must not be empty'); + } + let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state + if (!marbleAsBytes.toString()) { + let jsonResp = {}; + jsonResp.Error = 'Marble does not exist: ' + name; + throw new Error(JSON.stringify(jsonResp)); + } + console.info('======================================='); + console.log(marbleAsBytes.toString()); + console.info('======================================='); + return marbleAsBytes; + } + + /** + * Deletes the given marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + */ + async delete(stub, args) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); + } + let marbleName = args[0]; + if (!marbleName) { + throw new Error('marble name must not be empty'); + } + // to maintain the color~name index, we need to read the marble first and get its color + let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state + let jsonResp = {}; + if (!valAsBytes) { + jsonResp.error = 'marble does not exist: ' + marbleName; + throw new Error(jsonResp); + } + let marbleJSON = {}; + try { + marbleJSON = JSON.parse(valAsBytes.toString()); + } catch (err) { + jsonResp = {}; + jsonResp.error = 'Failed to decode JSON of: ' + marbleName; + throw new Error(jsonResp); + } + + await stub.deleteState(marbleName); //remove the marble from chaincode state + + // delete the index + let indexName = 'color~name'; + let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); + if (!colorNameIndexKey) { + throw new Error(' Failed to create the createCompositeKey'); + } + // Delete index entry to state. + await stub.deleteState(colorNameIndexKey); + } + + // =========================================================== + // transfer a marble by setting a new owner name on the marble + // =========================================================== + /** + * Transfers the given marble to a new owner. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. + */ + async transferMarble(stub, args) { + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting marble name and owner'); + } + + let marbleName = args[0]; + let newOwner = args[1].toLowerCase(); + console.info('- start transferMarble ', marbleName, newOwner); + + let marbleAsBytes = await stub.getState(marbleName); + if (!marbleAsBytes || !marbleAsBytes.toString()) { + throw new Error('marble does not exist'); + } + let marbleToTransfer = {}; + try { + marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal + } catch (err) { + let jsonResp = {}; + jsonResp.error = 'Failed to decode JSON of: ' + marbleName; + throw new Error(jsonResp); + } + console.info(marbleToTransfer); + marbleToTransfer.owner = newOwner; //change the owner + + let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); + await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble + + console.info('- end transferMarble (success)'); + } + + /** + * Performs a range query based on the start and end keys provided. + * + * Read-only function results are not typically submitted to ordering. If the read-only + * results are submitted to ordering, or if the query is used in an update transaction + * and submitted to ordering, then the committing peers will re-execute to guarantee that + * result sets are stable between endorsement time and commit time. The transaction is + * invalidated by the committing peers if the result set has changed between endorsement + * time and commit time. + * Therefore, range queries are a safe option for performing update transactions based on query results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The marbles in the given range. + */ + async getMarblesByRange(stub, args, thisObject) { + + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting 2'); + } + + let startKey = args[0]; + let endKey = args[1]; + + let resultsIterator = await stub.getStateByRange(startKey, endKey); + let results = await thisObject.getAllResults(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + /** + * Transfers marbles of a given color to a certain new owner. + * + * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. + * Committing peers will re-execute range queries to guarantee that result sets are stable + * between endorsement time and commit time. The transaction is invalidated by the + * committing peers if the result set has changed between endorsement time and commit time. + * Therefore, range queries are a safe option for performing update transactions based on query results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. + * @param {Chaincode} thisObject The chaincode object context. + */ + async transferMarblesBasedOnColor(stub, args, thisObject) { + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting color and owner'); + } + + let color = args[0]; + let newOwner = args[1].toLowerCase(); + console.info('- start transferMarblesBasedOnColor ', color, newOwner); + + // Query the color~name index by color + // This will execute a key range query on all keys starting with 'color' + let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); + + let hasNext = true; + // Iterate through result set and for each marble found, transfer to newOwner + while (hasNext) { + let responseRange; + try { + responseRange = await coloredMarbleResultsIterator.next(); + } catch (err) { + hasNext = false; + continue; + } + + if (!responseRange || !responseRange.value || !responseRange.value.key) { + return; + } + console.log(responseRange.value.key); + + // let value = res.value.value.toString('utf8'); + let objectType; + let attributes; + ({ + objectType, + attributes + } = await stub.splitCompositeKey(responseRange.value.key)); + + let returnedColor = attributes[0]; + let returnedMarbleName = attributes[1]; + console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); + + // Now call the transfer function for the found marble. + // Re-use the same function that is used to transfer individual marbles + await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); + } + + let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); + console.info('- end transferMarblesBasedOnColor: ' + responsePayload); + } + + /** + * Queries for marbles based on a passed in owner. + * This is an example of a parameterized query where the query logic is baked into the chaincode, + * and accepting a single query parameter (owner). + * Only available on state databases that support rich query (e.g. CouchDB) + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble owner. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The marbles of the specified owner. + */ + async queryMarblesByOwner(stub, args, thisObject) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting owner name.'); + } + + let owner = args[0].toLowerCase(); + let queryString = {}; + queryString.selector = {}; + queryString.selector.docType = 'marble'; + queryString.selector.owner = owner; + return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); + } + + /** + * Uses a query string to perform a query for marbles. + * Query string matching state database syntax is passed in and executed as is. + * Supports ad hoc queries that can be defined at runtime by the client. + * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. + * Only available on state databases that support rich query (e.g. CouchDB) + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: query string. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The results of the specified query. + */ + async queryMarbles(stub, args, thisObject) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting queryString'); + } + let queryString = args[0]; + if (!queryString) { + throw new Error('queryString must not be empty'); + } + + return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); + } + + /** + * Gets the results of a specified iterator. + * @async + * @param {Object} iterator The iterator to use. + * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. + * @return {Promise} The array of results in JSON format. + */ + async getAllResults(iterator, isHistory) { + let allResults = []; + let hasNext = true; + while (hasNext) { + let res; + try { + res = await iterator.next(); + } catch (err) { + hasNext = false; + continue; + } + + if (res.value && res.value.value.toString()) { + let jsonRes = {}; + console.log(res.value.value.toString('utf8')); + + if (isHistory && isHistory === true) { + jsonRes.TxId = res.value.tx_id; + jsonRes.Timestamp = res.value.timestamp; + jsonRes.IsDelete = res.value.is_delete.toString(); + try { + jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); + } catch (err) { + console.log(err); + jsonRes.Value = res.value.value.toString('utf8'); + } + } else { + jsonRes.Key = res.value.key; + try { + jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); + } catch (err) { + console.log(err); + jsonRes.Record = res.value.value.toString('utf8'); + } + } + allResults.push(jsonRes); + } + if (res.done) { + console.log('end of data'); + await iterator.close(); + console.info(allResults); + return allResults; + } + } + } + + /** + * Executes the provided query string. + * Result set is built and returned as a byte array containing the JSON results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String} queryString The query string to execute. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The results of the specified query. + */ + async getQueryResultForQueryString(stub, queryString, thisObject) { + + console.info('- getQueryResultForQueryString queryString:\n' + queryString); + let resultsIterator = await stub.getQueryResult(queryString); + + let results = await thisObject.getAllResults(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + /** + * Retrieves the history for a marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The history entries of the specified marble. + */ + async getHistoryForMarble(stub, args, thisObject) { + + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting 1'); + } + let marbleName = args[0]; + console.info('- start getHistoryForMarble: %s\n', marbleName); + + let resultsIterator = await stub.getHistoryForKey(marbleName); + let results = await thisObject.getAllResults(resultsIterator, true); + + return Buffer.from(JSON.stringify(results)); + } +}; + +shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json new file mode 100644 index 000000000..305f09044 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json @@ -0,0 +1 @@ +{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json new file mode 100644 index 000000000..724fea100 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json @@ -0,0 +1,17 @@ +{ + "name": "marbles", + "version": "1.0.0", + "description": "marbles chaincode implemented in node.js", + "engines": { + "node": ">=8.4.0", + "npm": ">=5.3.0" + }, + "scripts": { + "start": "node marbles.js" + }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "~1.4.0" + } +} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore b/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore new file mode 100644 index 000000000..c9216fbdd --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore @@ -0,0 +1,4 @@ +**/*.log +report.html +node_modules/* +package-lock.json diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/benchconfig.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/benchconfig.yaml new file mode 100644 index 000000000..4540b86ef --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/benchconfig.yaml @@ -0,0 +1,33 @@ +# +# 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. +# + +--- +test: + workers: + type: local + number: 2 + rounds: + - label: init + txNumber: 1000 + rateControl: { type: 'fixed-rate', opts: { tps: 20 } } + callback: ./init.js + - label: query + txNumber: 100 + rateControl: { type: 'linear-rate', opts: { startingTps: 5, finishingTps: 10 } } + callback: ./query.js +observer: + interval: 5 + type: local +monitor: + type: ['none'] diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/caliper.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/caliper.yaml new file mode 100644 index 000000000..f31c62e20 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/caliper.yaml @@ -0,0 +1,53 @@ +# +# 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. +# + +caliper: + benchconfig: ./benchconfig.yaml + networkconfig: ./networkconfig.yaml + bind: + sut: fabric:1.4.1 + report: + path: ./report.html + logging: + template: '%timestamp%%level%%module%%message%%metadata%' + formats: + timestamp: 'YYYY.MM.DD-HH:mm:ss.SSS ZZ' + label: false + json: false + pad: true + align: false + attributeformat: + level: ' %attribute%' + module: ' [%attribute%] ' + metadata: ' (%attribute%)' + colorize: + all: true + colors: + info: green + error: red + warn: yellow + debug: grey + targets: + console: + target: console + enabled: true + options: + level: debug + file: + target: file + enabled: false + worker: + remote: false + communication: + method: process diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/clean.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/clean.sh new file mode 100755 index 000000000..0cf43d427 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/clean.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +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) + diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/config/.gitignore b/packages/caliper-tests-integration/fabric_docker_local_tests/config/.gitignore new file mode 100644 index 000000000..2bc6f264d --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/config/.gitignore @@ -0,0 +1,5 @@ +bin/* +config/* +crypto-config/* +genesis.block +mychannel.tx diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/config/configtx.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/config/configtx.yaml new file mode 100644 index 000000000..8ee584f0c --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/config/configtx.yaml @@ -0,0 +1,92 @@ +# +# 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. +# + +--- + +Organizations: +- &OrdererOrg + Name: OrdererMSP + ID: OrdererMSP + MSPDir: crypto-config/ordererOrganizations/example.com/msp + AdminPrincipal: Role.MEMBER + +- &Org1 + Name: Org1MSP + ID: Org1MSP + MSPDir: crypto-config/peerOrganizations/org1.example.com/msp + AdminPrincipal: Role.ADMIN + AnchorPeers: + - Host: peer0.org1.example.com + Port: 7051 + +- &Org2 + Name: Org2MSP + ID: Org2MSP + MSPDir: crypto-config/peerOrganizations/org2.example.com/msp + AdminPrincipal: Role.ADMIN + AnchorPeers: + - Host: peer0.org2.example.com + Port: 7051 + +Orderer: &OrdererDefaults + OrdererType: etcdraft + Addresses: + - orderer0.example.com:7050 + - orderer1.example.com:7050 + + BatchTimeout: 500ms + BatchSize: + MaxMessageCount: 50 + AbsoluteMaxBytes: 1 MB + PreferredMaxBytes: 1 MB + + MaxChannels: 0 + EtcdRaft: + Consenters: + - Host: orderer0.example.com + Port: 7050 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/tls/server.crt + - Host: orderer1.example.com + Port: 7050 + ClientTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/tls/server.crt + ServerTLSCert: crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/tls/server.crt + + Organizations: + +Application: &ApplicationDefaults + Organizations: + +Profiles: + OrdererGenesis: + Orderer: + <<: *OrdererDefaults + Organizations: + - *OrdererOrg + Consortiums: + SampleConsortium: + Organizations: + - *Org1 + - *Org2 + SampleConsortium2: + Organizations: + - *Org1 + - *Org2 + ChannelConfig: + Consortium: SampleConsortium + Application: + <<: *ApplicationDefaults + Organizations: + - *Org1 + - *Org2 diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/config/crypto-config.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/config/crypto-config.yaml new file mode 100644 index 000000000..d66bf601d --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/config/crypto-config.yaml @@ -0,0 +1,36 @@ +# +# 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. +# + + +OrdererOrgs: +- Name: Orderer + Domain: example.com + + Template: + Count: 2 + +PeerOrgs: +- Name: Org1 + Domain: org1.example.com + Template: + Count: 1 + Users: + Count: 1 + +- Name: Org2 + Domain: org2.example.com + Template: + Count: 1 + Users: + Count: 1 diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/config/generate.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/config/generate.sh new file mode 100755 index 000000000..2a2e3f4cf --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/config/generate.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# if the binaries are not available, download them +if [[ ! -d "bin" ]]; then + curl -sSL http://bit.ly/2ysbOFE | bash -s -- 1.4.1 1.4.1 0.4.15 -ds +fi + +rm -rf ./crypto-config/ +rm -f ./genesis.block +rm -f ./mychannel.tx + +./bin/cryptogen generate --config=./crypto-config.yaml +./bin/configtxgen -profile OrdererGenesis -outputBlock genesis.block -channelID syschannel +./bin/configtxgen -profile ChannelConfig -outputCreateChannelTx mychannel.tx -channelID mychannel + +# Rename the key files we use to be key.pem instead of a uuid +for KEY in $(find crypto-config -type f -name "*_sk"); do + KEY_DIR=$(dirname ${KEY}) + mv ${KEY} ${KEY_DIR}/key.pem +done diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/docker-compose.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/docker-compose.yaml new file mode 100644 index 000000000..a079bc09b --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/docker-compose.yaml @@ -0,0 +1,274 @@ +# +# 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. +# + +version: '3' + +services: + +####### +# CAs # +####### + + ca.org1.example.com: + image: hyperledger/fabric-ca:1.4.1 + environment: + - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server + - FABRIC_CA_SERVER_CA_NAME=ca.org1.example.com + - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org1.example.com-cert.pem + - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/key.pem + # TLS + - FABRIC_CA_SERVER_TLS_ENABLED=true + - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-tls/tlsca.org1.example.com-cert.pem + - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-tls/key.pem +# ports: +# - "7054:7054" + command: sh -c 'fabric-ca-server start -b admin:adminpw -d' + volumes: + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/fabric-ca-server-config + - ./config/crypto-config/peerOrganizations/org1.example.com/tlsca/:/etc/hyperledger/fabric-ca-server-tls + container_name: ca.org1.example.com + + ca.org2.example.com: + image: hyperledger/fabric-ca:1.4.1 + environment: + - FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server + - FABRIC_CA_SERVER_CA_NAME=ca.org2.example.com + - FABRIC_CA_SERVER_CA_CERTFILE=/etc/hyperledger/fabric-ca-server-config/ca.org2.example.com-cert.pem + - FABRIC_CA_SERVER_CA_KEYFILE=/etc/hyperledger/fabric-ca-server-config/key.pem + # TLS + - FABRIC_CA_SERVER_TLS_ENABLED=true + - FABRIC_CA_SERVER_TLS_CERTFILE=/etc/hyperledger/fabric-ca-server-tls/tlsca.org2.example.com-cert.pem + - FABRIC_CA_SERVER_TLS_KEYFILE=/etc/hyperledger/fabric-ca-server-tls/key.pem +# ports: +# - "8054:7054" + command: sh -c 'fabric-ca-server start -b admin:adminpw -d' + volumes: + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/fabric-ca-server-config + - ./config/crypto-config/peerOrganizations/org2.example.com/tlsca/:/etc/hyperledger/fabric-ca-server-tls + container_name: ca.org2.example.com + +############ +# ORDERERS # +############ + + orderer0.example.com: + container_name: orderer0.example.com + image: hyperledger/fabric-orderer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 + - ORDERER_GENERAL_GENESISMETHOD=file + - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block + - ORDERER_GENERAL_LOCALMSPID=OrdererMSP + - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp + # TLS + - ORDERER_GENERAL_TLS_ENABLED=true + - ORDERER_GENERAL_TLS_PRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + - ORDERER_GENERAL_TLS_CERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_TLS_ROOTCAS=[/etc/hyperledger/msp/orderer/tls/ca.crt] + # Mutual TLS + - ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED=true + - ORDERER_GENERAL_TLS_CLIENTROOTCAS=[/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem, /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem, /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem] + # Raft TLS + - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + # setting up metrics + - ORDERER_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - ORDERER_OPERATIONS_TLS_ENABLED=false + - ORDERER_METRICS_ENABLE=true + - ORDERER_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: orderer +# ports: +# - 7050:7050 + volumes: + - ./config/:/etc/hyperledger/configtx + - ./config/crypto-config/ordererOrganizations/example.com/orderers/orderer0.example.com/:/etc/hyperledger/msp/orderer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - ca.org1.example.com + - ca.org2.example.com + + orderer1.example.com: + container_name: orderer1.example.com + image: hyperledger/fabric-orderer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 + - ORDERER_GENERAL_GENESISMETHOD=file + - ORDERER_GENERAL_GENESISFILE=/etc/hyperledger/configtx/genesis.block + - ORDERER_GENERAL_LOCALMSPID=OrdererMSP + - ORDERER_GENERAL_LOCALMSPDIR=/etc/hyperledger/msp/orderer/msp + # TLS + - ORDERER_GENERAL_TLS_ENABLED=true + - ORDERER_GENERAL_TLS_PRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + - ORDERER_GENERAL_TLS_CERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_TLS_ROOTCAS=[/etc/hyperledger/msp/orderer/tls/ca.crt] + # Mutual TLS + - ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED=true + - ORDERER_GENERAL_TLS_CLIENTROOTCAS=[/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem, /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem, /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem] + # Raft TLS + - ORDERER_GENERAL_CLUSTER_CLIENTCERTIFICATE=/etc/hyperledger/msp/orderer/tls/server.crt + - ORDERER_GENERAL_CLUSTER_CLIENTPRIVATEKEY=/etc/hyperledger/msp/orderer/tls/server.key + # setting up metrics + - ORDERER_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - ORDERER_OPERATIONS_TLS_ENABLED=false + - ORDERER_METRICS_ENABLE=true + - ORDERER_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: orderer +# ports: +# - 8050:7050 + volumes: + - ./config/:/etc/hyperledger/configtx + - ./config/crypto-config/ordererOrganizations/example.com/orderers/orderer1.example.com/:/etc/hyperledger/msp/orderer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - ca.org1.example.com + - ca.org2.example.com + +######### +# PEERS # +######### + + peer0.org1.example.com: + container_name: peer0.org1.example.com + image: hyperledger/fabric-peer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - CORE_CHAINCODE_LOGGING_LEVEL=INFO + - CORE_CHAINCODE_LOGGING_SHIM=INFO + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_PEER_ID=peer0.org1.example.com + - CORE_PEER_ENDORSER_ENABLED=true + - CORE_PEER_LOCALMSPID=Org1MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/msp/ + - CORE_PEER_ADDRESS=peer0.org1.example.com:7051 + - CORE_PEER_GOSSIP_USELEADERELECTION=true + - CORE_PEER_GOSSIP_ORGLEADER=false + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.example.com:7051 + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=caliper_default + # CouchDB + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.peer0.org1.example.com:5984 + # TLS + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/msp/peer/tls/server.key + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/msp/peer/tls/server.crt + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/msp/peer/tls/ca.crt + # Mutual TLS + - CORE_PEER_TLS_CLIENTAUTHREQUIRED=true + - CORE_PEER_TLS_CLIENTROOTCAS_FILES=/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem + # setting up metrics + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - CORE_OPERATIONS_TLS_ENABLED=false + - CORE_METRICS_ENABLE=true + - CORE_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: peer node start +# ports: +# - 7051:7051 + volumes: + - /var/run/:/host/var/run/ + - ./config/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/:/etc/hyperledger/msp/peer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - orderer0.example.com + - orderer1.example.com + - couchdb.peer0.org1.example.com + + couchdb.peer0.org1.example.com: + container_name: couchdb.peer0.org1.example.com + image: hyperledger/fabric-couchdb:0.4.14 +# ports: +# - 5984:5984 + environment: + DB_URL: http://localhost:5984/member_db + + peer0.org2.example.com: + container_name: peer0.org2.example.com + image: hyperledger/fabric-peer:1.4.1 + environment: + - FABRIC_LOGGING_SPEC=info + - CORE_CHAINCODE_LOGGING_LEVEL=INFO + - CORE_CHAINCODE_LOGGING_SHIM=INFO + - CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock + - CORE_PEER_ID=peer0.org2.example.com + - CORE_PEER_ENDORSER_ENABLED=true + - CORE_PEER_LOCALMSPID=Org2MSP + - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/peer/msp/ + - CORE_PEER_ADDRESS=peer0.org2.example.com:7051 + - CORE_PEER_GOSSIP_USELEADERELECTION=true + - CORE_PEER_GOSSIP_ORGLEADER=false + - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org2.example.com:7051 + - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=caliper_default + # CouchDB + - CORE_LEDGER_STATE_STATEDATABASE=CouchDB + - CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb.peer0.org2.example.com:5984 + # TLS + - CORE_PEER_TLS_ENABLED=true + - CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/msp/peer/tls/server.key + - CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/msp/peer/tls/server.crt + - CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/msp/peer/tls/ca.crt + # Mutual TLS + - CORE_PEER_TLS_CLIENTAUTHREQUIRED=true + - CORE_PEER_TLS_CLIENTROOTCAS_FILES=/etc/hyperledger/msp/caOrg1/ca.org1.example.com-cert.pem /etc/hyperledger/msp/caOrg2/ca.org2.example.com-cert.pem /etc/hyperledger/msp/caOrderer/ca.example.com-cert.pem + # setting up metrics + - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:9000 + - CORE_OPERATIONS_TLS_ENABLED=false + - CORE_METRICS_ENABLE=true + - CORE_METRICS_PROVIDER=prometheus + working_dir: /opt/gopath/src/github.com/hyperledger/fabric + command: peer node start +# ports: +# - 8051:7051 + volumes: + - /var/run/:/host/var/run/ + - ./config/crypto-config/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/:/etc/hyperledger/msp/peer + - ./config/crypto-config/peerOrganizations/org1.example.com/ca/:/etc/hyperledger/msp/caOrg1 + - ./config/crypto-config/peerOrganizations/org2.example.com/ca/:/etc/hyperledger/msp/caOrg2 + - ./config/crypto-config/ordererOrganizations/example.com/ca/:/etc/hyperledger/msp/caOrderer + depends_on: + - orderer0.example.com + - orderer1.example.com + - couchdb.peer0.org2.example.com + + couchdb.peer0.org2.example.com: + container_name: couchdb.peer0.org2.example.com + image: hyperledger/fabric-couchdb:0.4.14 +# ports: +# - 6984:5984 + environment: + DB_URL: http://localhost:5984/member_db + +################## +# CALIPER MASTER # +################## + + caliper-master: + image: caliper:test + container_name: caliper-master + command: launch master + volumes: + - ./:/hyperledger/caliper/workspace + depends_on: + - peer0.org1.example.com + - peer0.org2.example.com + diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/init.js b/packages/caliper-tests-integration/fabric_docker_local_tests/init.js new file mode 100644 index 000000000..56139eea1 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/init.js @@ -0,0 +1,47 @@ +/* +* 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 uuidv4 = require('uuid/v4'); + +module.exports.info = 'Creating marbles.'; + +let txIndex = 0; +let colors = ['red', 'blue', 'green', 'black', 'white', 'pink', 'rainbow']; +let owners = ['Alice', 'Bob', 'Claire', 'David']; +let bc, contx; + +module.exports.init = async function(blockchain, context, args) { + bc = blockchain; + contx = context; +}; + +module.exports.run = async function() { + txIndex++; + let marbleName = 'marble_' + txIndex.toString() + '_' + uuidv4(); + let marbleColor = colors[txIndex % colors.length]; + let marbleSize = (((txIndex % 10) + 1) * 10).toString(); // [10, 100] + let marbleOwner = owners[txIndex % owners.length]; + + let args = { + chaincodeFunction: 'initMarble', + chaincodeArguments: [marbleName, marbleColor, marbleSize, marbleOwner], + }; + + let targetCC = txIndex % 2 === 0 ? 'mymarbles' : 'yourmarbles'; + return bc.invokeSmartContract(contx, targetCC, 'v0', args, 5); +}; + +module.exports.end = async function() {}; diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/networkconfig.yaml b/packages/caliper-tests-integration/fabric_docker_local_tests/networkconfig.yaml new file mode 100644 index 000000000..7d2dff03c --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/networkconfig.yaml @@ -0,0 +1,164 @@ +# +# 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 + + chaincodes: + - 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 + + chaincodes: + - 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://orderer0.example.com: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://orderer1.example.com:7050 + 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://peer0.org1.example.com: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://peer0.org2.example.com:7051 + 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://ca.org1.example.com: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://ca.org2.example.com:7054 + 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_docker_local_tests/package.json b/packages/caliper-tests-integration/fabric_docker_local_tests/package.json new file mode 100644 index 000000000..010b55227 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/package.json @@ -0,0 +1,15 @@ +{ + "name": "fabric_docker_distributed_tests", + "version": "1.0.0", + "description": "Distributed Docker-based Caliper test for Fabric", + "main": "init.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "uuid": "^3.4.0" + } +} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/query.js b/packages/caliper-tests-integration/fabric_docker_local_tests/query.js new file mode 100644 index 000000000..5ca6b09c3 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/query.js @@ -0,0 +1,40 @@ +/* +* 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'; + +module.exports.info = 'Querying marbles.'; + +let txIndex = 0; +let owners = ['Alice', 'Bob', 'Claire', 'David']; +let bc, contx; + +module.exports.init = async function(blockchain, context, args) { + bc = blockchain; + contx = context; +}; + +module.exports.run = async function() { + txIndex++; + let marbleOwner = owners[txIndex % owners.length]; + let args = { + chaincodeFunction: 'queryMarblesByOwner', + chaincodeArguments: [marbleOwner] + }; + + let targetCC = txIndex % 2 === 0 ? 'mymarbles' : 'yourmarbles'; + return bc.querySmartContract(contx, targetCC, '', args, 20); +}; + +module.exports.end = async function() {}; diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh new file mode 100755 index 000000000..678866dcc --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +# Grab the parent (fabric_tests) directory. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "${DIR}" + +# generate the crypto materials +cd ./config +./generate.sh + +# back to this dir +cd ${DIR} + +# Publish the packages locally and build a local test image +cd ./../../caliper-publish/ +./publish.js verdaccio start +sleep 5s +./publish.js npm --registry http://localhost:4873 +./publish.js docker --registry http://localhost:4873 --image caliper --tag test +./publish.js verdaccio stop + +# back to this dir +cd ${DIR} + +npm i +docker-compose -p caliper up -d + diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh new file mode 100755 index 000000000..ce3d289a5 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# +# 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. +# + +# Print all commands. +set -v + +# Grab the parent (fabric_tests) directory. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd "${DIR}" + +# generate the crypto materials +cd ./config +./generate.sh + +# back to this dir +cd ${DIR} + +npm i +docker-compose -p caliper up -d + diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go new file mode 100644 index 000000000..4e932984b --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go @@ -0,0 +1,665 @@ +/* +* 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. +*/ + +/* +* NOTE: This implementation is a replica of the following: +* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +*/ + +// ====CHAINCODE EXECUTION SAMPLES (CLI) ================== + +// ==== Invoke marbles ==== +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble1","blue","35","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble2","red","50","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble3","blue","70","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarble","marble2","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarblesBasedOnColor","blue","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["delete","marble1"]}' + +// ==== Query marbles ==== +// peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getMarblesByRange","marble1","marble3"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' + +// Rich Query (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// INDEXES TO SUPPORT COUCHDB RICH QUERIES +// +// Indexes in CouchDB are required in order to make JSON queries efficient and are required for +// any JSON query with a sort. As of Hyperledger Fabric 1.1, indexes may be packaged alongside +// chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own +// text file with extension *.json with the index definition formatted in JSON following the +// CouchDB index JSON syntax as documented at: +// http://docs.couchdb.org/en/2.1.1/api/database/find.html#db-index +// +// This marbles02 example chaincode demonstrates a packaged +// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json. +// For deployment of chaincode to production environments, it is recommended +// to define any indexes alongside chaincode so that the chaincode and supporting indexes +// are deployed automatically as a unit, once the chaincode has been installed on a peer and +// instantiated on a channel. See Hyperledger Fabric documentation for more details. +// +// If you have access to the your peer's CouchDB state database in a development environment, +// you may want to iteratively test various indexes in support of your chaincode queries. You +// can use the CouchDB Fauxton interface or a command line curl utility to create and update +// indexes. Then once you finalize an index, include the index definition alongside your +// chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment +// to managed environments. +// +// In the examples below you can find index definitions that support marbles02 +// chaincode queries, along with the syntax that you can use in development environments +// to create the indexes in the CouchDB Fauxton interface or a curl command line utility. +// + +//Example hostname:port configurations to access CouchDB. +// +//To access CouchDB docker container from within another docker container or from vagrant environments: +// http://couchdb:5984/ +// +//Inside couchdb docker container +// http://127.0.0.1:5984/ + +// Index for docType, owner. +// Note that docType and owner fields must be prefixed with the "data" wrapper +// +// Index definition for use with Fauxton interface +// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} +// +// Example curl command line to define index in the CouchDB channel_chaincode database +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// + +// Index for docType, owner, size (descending order). +// Note that docType, owner and size fields must be prefixed with the "data" wrapper +// +// Index definition for use with Fauxton interface +// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} +// +// Example curl command line to define index in the CouchDB channel_chaincode database +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index + +// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' + +// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/hyperledger/fabric/core/chaincode/shim" + pb "github.com/hyperledger/fabric/protos/peer" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +type marble struct { + ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database + Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around + Color string `json:"color"` + Size int `json:"size"` + Owner string `json:"owner"` +} + +// =================================================================================== +// Main +// =================================================================================== +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting Simple chaincode: %s", err) + } +} + +// Init initializes chaincode +// =========================== +func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { + var err error + marbles := []marble{ + marble{ObjectType: "marble", Name: "marble1", Color: "Green", Size: 5, Owner: "Alice"}, + marble{ObjectType: "marble", Name: "marble2", Color: "Yellow", Size: 10, Owner: "Bob"}, + marble{ObjectType: "marble", Name: "marble3", Color: "Blue", Size: 15, Owner: "Tom"}, + } + for i, marble := range marbles { + marbleJSONasBytes, _ := json.Marshal(marble) + stub.PutState(marbles[i].Name, marbleJSONasBytes) + if err != nil { + return shim.Error("Failed to put to world state. %s") + } + } + return shim.Success(nil) +} + +// Invoke - Our entry point for Invocations +// ======================================== +func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + function, args := stub.GetFunctionAndParameters() + fmt.Println("invoke is running " + function) + + // Handle different functions + if function == "initMarble" { //create a new marble + return t.initMarble(stub, args) + } else if function == "transferMarble" { //change owner of a specific marble + return t.transferMarble(stub, args) + } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color + return t.transferMarblesBasedOnColor(stub, args) + } else if function == "delete" { //delete a marble + return t.delete(stub, args) + } else if function == "readMarble" { //read a marble + return t.readMarble(stub, args) + } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query + return t.queryMarblesByOwner(stub, args) + } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query + return t.queryMarbles(stub, args) + } else if function == "getHistoryForMarble" { //get history of values for a marble + return t.getHistoryForMarble(stub, args) + } else if function == "getMarblesByRange" { //get marbles based on range query + return t.getMarblesByRange(stub, args) + } + + fmt.Println("invoke did not find func: " + function) //error + return shim.Error("Received unknown function invocation") +} + +// ============================================================ +// initMarble - create a new marble, store into chaincode state +// ============================================================ +func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var err error + + // 0 1 2 3 + // "asdf", "blue", "35", "bob" + if len(args) != 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") + } + + // ==== Input sanitation ==== + fmt.Println("- start init marble") + if len(args[0]) <= 0 { + return shim.Error("1st argument must be a non-empty string") + } + if len(args[1]) <= 0 { + return shim.Error("2nd argument must be a non-empty string") + } + if len(args[2]) <= 0 { + return shim.Error("3rd argument must be a non-empty string") + } + if len(args[3]) <= 0 { + return shim.Error("4th argument must be a non-empty string") + } + marbleName := args[0] + color := strings.ToLower(args[1]) + owner := strings.ToLower(args[3]) + size, err := strconv.Atoi(args[2]) + if err != nil { + return shim.Error("3rd argument must be a numeric string") + } + + // ==== Check if marble already exists ==== + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble: " + err.Error()) + } else if marbleAsBytes != nil { + fmt.Println("This marble already exists: " + marbleName) + return shim.Error("This marble already exists: " + marbleName) + } + + // ==== Create marble object and marshal to JSON ==== + objectType := "marble" + marble := &marble{objectType, marbleName, color, size, owner} + marbleJSONasBytes, err := json.Marshal(marble) + if err != nil { + return shim.Error(err.Error()) + } + //Alternatively, build the marble json string manually if you don't want to use struct marshalling + //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` + //marbleJSONasBytes := []byte(str) + + // === Save marble to state === + err = stub.PutState(marbleName, marbleJSONasBytes) + if err != nil { + return shim.Error(err.Error()) + } + + // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== + // An 'index' is a normal key/value entry in state. + // The key is a composite key, with the elements that you want to range query on listed first. + // In our case, the composite key is based on indexName~color~name. + // This will enable very efficient state range queries based on composite keys matching indexName~color~* + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) + if err != nil { + return shim.Error(err.Error()) + } + // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. + // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value + value := []byte{0x00} + stub.PutState(colorNameIndexKey, value) + + // ==== Marble saved and indexed. Return success ==== + fmt.Println("- end init marble") + return shim.Success(nil) +} + +// =============================================== +// readMarble - read a marble from chaincode state +// =============================================== +func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var name, jsonResp string + var err error + + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting name of the marble to query") + } + + name = args[0] + valAsbytes, err := stub.GetState(name) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" + return shim.Error(jsonResp) + } + + return shim.Success(valAsbytes) +} + +// ================================================== +// delete - remove a marble key/value pair from state +// ================================================== +func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var jsonResp string + var marbleJSON marble + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + marbleName := args[0] + + // to maintain the color~name index, we need to read the marble first and get its color + valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) + if err != nil { + jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = stub.DelState(marbleName) //remove the marble from chaincode state + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + + // maintain the index + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) + if err != nil { + return shim.Error(err.Error()) + } + + // Delete index entry to state. + err = stub.DelState(colorNameIndexKey) + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + return shim.Success(nil) +} + +// =========================================================== +// transfer a marble by setting a new owner name on the marble +// =========================================================== +func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "name", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + marbleName := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarble ", marbleName, newOwner) + + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble:" + err.Error()) + } else if marbleAsBytes == nil { + return shim.Error("Marble does not exist") + } + + marbleToTransfer := marble{} + err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() + if err != nil { + return shim.Error(err.Error()) + } + marbleToTransfer.Owner = newOwner //change the owner + + marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) + err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble + if err != nil { + return shim.Error(err.Error()) + } + + fmt.Println("- end transferMarble (success)") + return shim.Success(nil) +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + // buffer is a JSON array containing QueryResults + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"Key\":") + buffer.WriteString("\"") + buffer.WriteString(queryResponse.Key) + buffer.WriteString("\"") + + buffer.WriteString(", \"Record\":") + // Record is a JSON object, so we write as-is + buffer.WriteString(string(queryResponse.Value)) + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} + +// ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= +// transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. +// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. +// Committing peers will re-execute range queries to guarantee that result sets are stable +// between endorsement time and commit time. The transaction is invalidated by the +// committing peers if the result set has changed between endorsement time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "color", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + color := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) + + // Query the color~name index by color + // This will execute a key range query on all keys starting with 'color' + coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) + if err != nil { + return shim.Error(err.Error()) + } + defer coloredMarbleResultsIterator.Close() + + // Iterate through result set and for each marble found, transfer to newOwner + var i int + for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { + // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key + responseRange, err := coloredMarbleResultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + + // get the color and name from color~name composite key + objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) + if err != nil { + return shim.Error(err.Error()) + } + returnedColor := compositeKeyParts[0] + returnedMarbleName := compositeKeyParts[1] + fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) + + // Now call the transfer function for the found marble. + // Re-use the same function that is used to transfer individual marbles + response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) + // if the transfer failed break out of loop and return error + if response.Status != shim.OK { + return shim.Error("Transfer failed: " + response.Message) + } + } + + responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) + fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) + return shim.Success([]byte(responsePayload)) +} + +// =======Rich queries ========================================================================= +// Two examples of rich queries are provided below (parameterized query and ad hoc query). +// Rich queries pass a query string to the state database. +// Rich queries are only supported by state database implementations +// that support rich query (e.g. CouchDB). +// The query string is in the syntax of the underlying state database. +// With rich queries there is no guarantee that the result set hasn't changed between +// endorsement time and commit time, aka 'phantom reads'. +// Therefore, rich queries should not be used in update transactions, unless the +// application handles the possibility of result set changes between endorsement and commit time. +// Rich queries can be used for point-in-time queries against a peer. +// ============================================================================================ + +// ===== Example: Parameterized rich query ================================================= +// queryMarblesByOwner queries for marbles based on a passed in owner. +// This is an example of a parameterized query where the query logic is baked into the chaincode, +// and accepting a single query parameter (owner). +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "bob" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + owner := strings.ToLower(args[0]) + + queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ===== Example: Ad hoc rich query ======================================================== +// queryMarbles uses a query string to perform a query for marbles. +// Query string matching state database syntax is passed in and executed as is. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + queryString := args[0] + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryString executes the passed in query string. +// Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, err := stub.GetQueryResult(queryString) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + // buffer is a JSON array containing QueryRecords + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"Key\":") + buffer.WriteString("\"") + buffer.WriteString(queryResponse.Key) + buffer.WriteString("\"") + + buffer.WriteString(", \"Record\":") + // Record is a JSON object, so we write as-is + buffer.WriteString(string(queryResponse.Value)) + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + + return buffer.Bytes(), nil +} + +func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + marbleName := args[0] + + fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) + + resultsIterator, err := stub.GetHistoryForKey(marbleName) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + // buffer is a JSON array containing historic values for the marble + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + response, err := resultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"TxId\":") + buffer.WriteString("\"") + buffer.WriteString(response.TxId) + buffer.WriteString("\"") + + buffer.WriteString(", \"Value\":") + // if it was a delete operation on given key, then we need to set the + //corresponding value null. Else, we will write the response.Value + //as-is (as the Value itself a JSON marble) + if response.IsDelete { + buffer.WriteString("null") + } else { + buffer.WriteString(string(response.Value)) + } + + buffer.WriteString(", \"Timestamp\":") + buffer.WriteString("\"") + buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) + buffer.WriteString("\"") + + buffer.WriteString(", \"IsDelete\":") + buffer.WriteString("\"") + buffer.WriteString(strconv.FormatBool(response.IsDelete)) + buffer.WriteString("\"") + + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json new file mode 100644 index 000000000..305f09044 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json @@ -0,0 +1 @@ +{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js new file mode 100644 index 000000000..f36228a19 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js @@ -0,0 +1,509 @@ +/* +* 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. +*/ + +/* +* NOTE: This implementation is a derivative work of the following: +* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +* The modifications include: bug fixes and refactoring for eslint compliance. +*/ + +/* eslint-disable no-console */ + +'use strict'; +const shim = require('fabric-shim'); +const util = require('util'); + +/** + * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. + * @type {SimpleChaincode} + * @extends {ChaincodeInterface} + */ +let Chaincode = class { + /** + * Called during chaincode instantiate and upgrade. This method can be used + * to initialize asset states. + * @async + * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim + * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub + * encapsulates the APIs between the chaincode implementation and the Fabric peer. + * @return {Promise} Returns a promise of a response indicating the result of the invocation. + */ + async Init(stub) { + const marbles = [ + { + name: 'marble1', + color: 'Green', + size: '5', + owner: 'Alice' + }, + { + name: 'marble2', + color: 'Yellow', + size: '15', + owner: 'Bob' + }, + { + name: 'marble3', + color: 'Blue', + size: '10', + owner: 'Tom' + } + ] + for (let i=0; i < marbles.length; i++) { + marbles[i].docType = 'marble'; + await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); + console.info('Added <--> ', marbles[i]); + } + console.info('=========== Instantiated Marbles Chaincode ==========='); + return shim.success(); + } + + /** + * Called throughout the life time of the chaincode to carry out business + * transaction logic and effect the asset states. + * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, + * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. + * @async + * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim + * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub + * encapsulates the APIs between the chaincode implementation and the Fabric peer. + * @return {Promise} Returns a promise of a response indicating the result of the invocation. + */ + async Invoke(stub) { + console.info('Transaction ID: ' + stub.getTxID()); + console.info(util.format('Args: %j', stub.getArgs())); + + let ret = stub.getFunctionAndParameters(); + console.info(ret); + + let method = this[ret.fcn]; + if (!method) { + console.log('no function of name:' + ret.fcn + ' found'); + throw new Error('Received unknown function ' + ret.fcn + ' invocation'); + } + try { + let payload = await method(stub, ret.params, this); + return shim.success(payload); + } catch (err) { + console.log(err); + return shim.error(err); + } + } + + /** + * Creates a new marble with the given attributes. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. + * Index 2: marble size. Index 3: marble owner. + */ + async initMarble(stub, args) { + if (args.length !== 4) { + throw new Error('Incorrect number of arguments. Expecting 4'); + } + // ==== Input sanitation ==== + console.info('--- start init marble ---'); + if (args[0].length <= 0) { + throw new Error('1st argument must be a non-empty string'); + } + if (args[1].length <= 0) { + throw new Error('2nd argument must be a non-empty string'); + } + if (args[2].length <= 0) { + throw new Error('3rd argument must be a non-empty string'); + } + if (args[3].length <= 0) { + throw new Error('4th argument must be a non-empty string'); + } + let marbleName = args[0]; + let color = args[1].toLowerCase(); + let owner = args[3].toLowerCase(); + let size = parseInt(args[2]); + if (isNaN(size)) { + throw new Error('3rd argument must be a numeric string'); + } + + // ==== Check if marble already exists ==== + let marbleState = await stub.getState(marbleName); + if (marbleState.toString()) { + throw new Error('This marble already exists: ' + marbleName); + } + + // ==== Create marble object and marshal to JSON ==== + let marble = {}; + marble.docType = 'marble'; + marble.name = marbleName; + marble.color = color; + marble.size = size; + marble.owner = owner; + + // === Save marble to state === + await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); + let indexName = 'color~name'; + let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); + console.info(colorNameIndexKey); + // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. + // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value + await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); + // ==== Marble saved and indexed. Return success ==== + console.info('- end init marble'); + } + + /** + * Retrieves the information about a marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + * @return {Promise} The byte representation of the marble. + */ + async readMarble(stub, args) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); + } + + let name = args[0]; + if (!name) { + throw new Error(' marble name must not be empty'); + } + let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state + if (!marbleAsBytes.toString()) { + let jsonResp = {}; + jsonResp.Error = 'Marble does not exist: ' + name; + throw new Error(JSON.stringify(jsonResp)); + } + console.info('======================================='); + console.log(marbleAsBytes.toString()); + console.info('======================================='); + return marbleAsBytes; + } + + /** + * Deletes the given marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + */ + async delete(stub, args) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); + } + let marbleName = args[0]; + if (!marbleName) { + throw new Error('marble name must not be empty'); + } + // to maintain the color~name index, we need to read the marble first and get its color + let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state + let jsonResp = {}; + if (!valAsBytes) { + jsonResp.error = 'marble does not exist: ' + marbleName; + throw new Error(jsonResp); + } + let marbleJSON = {}; + try { + marbleJSON = JSON.parse(valAsBytes.toString()); + } catch (err) { + jsonResp = {}; + jsonResp.error = 'Failed to decode JSON of: ' + marbleName; + throw new Error(jsonResp); + } + + await stub.deleteState(marbleName); //remove the marble from chaincode state + + // delete the index + let indexName = 'color~name'; + let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); + if (!colorNameIndexKey) { + throw new Error(' Failed to create the createCompositeKey'); + } + // Delete index entry to state. + await stub.deleteState(colorNameIndexKey); + } + + // =========================================================== + // transfer a marble by setting a new owner name on the marble + // =========================================================== + /** + * Transfers the given marble to a new owner. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. + */ + async transferMarble(stub, args) { + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting marble name and owner'); + } + + let marbleName = args[0]; + let newOwner = args[1].toLowerCase(); + console.info('- start transferMarble ', marbleName, newOwner); + + let marbleAsBytes = await stub.getState(marbleName); + if (!marbleAsBytes || !marbleAsBytes.toString()) { + throw new Error('marble does not exist'); + } + let marbleToTransfer = {}; + try { + marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal + } catch (err) { + let jsonResp = {}; + jsonResp.error = 'Failed to decode JSON of: ' + marbleName; + throw new Error(jsonResp); + } + console.info(marbleToTransfer); + marbleToTransfer.owner = newOwner; //change the owner + + let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); + await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble + + console.info('- end transferMarble (success)'); + } + + /** + * Performs a range query based on the start and end keys provided. + * + * Read-only function results are not typically submitted to ordering. If the read-only + * results are submitted to ordering, or if the query is used in an update transaction + * and submitted to ordering, then the committing peers will re-execute to guarantee that + * result sets are stable between endorsement time and commit time. The transaction is + * invalidated by the committing peers if the result set has changed between endorsement + * time and commit time. + * Therefore, range queries are a safe option for performing update transactions based on query results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The marbles in the given range. + */ + async getMarblesByRange(stub, args, thisObject) { + + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting 2'); + } + + let startKey = args[0]; + let endKey = args[1]; + + let resultsIterator = await stub.getStateByRange(startKey, endKey); + let results = await thisObject.getAllResults(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + /** + * Transfers marbles of a given color to a certain new owner. + * + * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. + * Committing peers will re-execute range queries to guarantee that result sets are stable + * between endorsement time and commit time. The transaction is invalidated by the + * committing peers if the result set has changed between endorsement time and commit time. + * Therefore, range queries are a safe option for performing update transactions based on query results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. + * @param {Chaincode} thisObject The chaincode object context. + */ + async transferMarblesBasedOnColor(stub, args, thisObject) { + if (args.length !== 2) { + throw new Error('Incorrect number of arguments. Expecting color and owner'); + } + + let color = args[0]; + let newOwner = args[1].toLowerCase(); + console.info('- start transferMarblesBasedOnColor ', color, newOwner); + + // Query the color~name index by color + // This will execute a key range query on all keys starting with 'color' + let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); + + let hasNext = true; + // Iterate through result set and for each marble found, transfer to newOwner + while (hasNext) { + let responseRange; + try { + responseRange = await coloredMarbleResultsIterator.next(); + } catch (err) { + hasNext = false; + continue; + } + + if (!responseRange || !responseRange.value || !responseRange.value.key) { + return; + } + console.log(responseRange.value.key); + + // let value = res.value.value.toString('utf8'); + let objectType; + let attributes; + ({ + objectType, + attributes + } = await stub.splitCompositeKey(responseRange.value.key)); + + let returnedColor = attributes[0]; + let returnedMarbleName = attributes[1]; + console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); + + // Now call the transfer function for the found marble. + // Re-use the same function that is used to transfer individual marbles + await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); + } + + let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); + console.info('- end transferMarblesBasedOnColor: ' + responsePayload); + } + + /** + * Queries for marbles based on a passed in owner. + * This is an example of a parameterized query where the query logic is baked into the chaincode, + * and accepting a single query parameter (owner). + * Only available on state databases that support rich query (e.g. CouchDB) + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble owner. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The marbles of the specified owner. + */ + async queryMarblesByOwner(stub, args, thisObject) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting owner name.'); + } + + let owner = args[0].toLowerCase(); + let queryString = {}; + queryString.selector = {}; + queryString.selector.docType = 'marble'; + queryString.selector.owner = owner; + return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); + } + + /** + * Uses a query string to perform a query for marbles. + * Query string matching state database syntax is passed in and executed as is. + * Supports ad hoc queries that can be defined at runtime by the client. + * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. + * Only available on state databases that support rich query (e.g. CouchDB) + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: query string. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The results of the specified query. + */ + async queryMarbles(stub, args, thisObject) { + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting queryString'); + } + let queryString = args[0]; + if (!queryString) { + throw new Error('queryString must not be empty'); + } + + return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); + } + + /** + * Gets the results of a specified iterator. + * @async + * @param {Object} iterator The iterator to use. + * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. + * @return {Promise} The array of results in JSON format. + */ + async getAllResults(iterator, isHistory) { + let allResults = []; + let hasNext = true; + while (hasNext) { + let res; + try { + res = await iterator.next(); + } catch (err) { + hasNext = false; + continue; + } + + if (res.value && res.value.value.toString()) { + let jsonRes = {}; + console.log(res.value.value.toString('utf8')); + + if (isHistory && isHistory === true) { + jsonRes.TxId = res.value.tx_id; + jsonRes.Timestamp = res.value.timestamp; + jsonRes.IsDelete = res.value.is_delete.toString(); + try { + jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); + } catch (err) { + console.log(err); + jsonRes.Value = res.value.value.toString('utf8'); + } + } else { + jsonRes.Key = res.value.key; + try { + jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); + } catch (err) { + console.log(err); + jsonRes.Record = res.value.value.toString('utf8'); + } + } + allResults.push(jsonRes); + } + if (res.done) { + console.log('end of data'); + await iterator.close(); + console.info(allResults); + return allResults; + } + } + } + + /** + * Executes the provided query string. + * Result set is built and returned as a byte array containing the JSON results. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String} queryString The query string to execute. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The results of the specified query. + */ + async getQueryResultForQueryString(stub, queryString, thisObject) { + + console.info('- getQueryResultForQueryString queryString:\n' + queryString); + let resultsIterator = await stub.getQueryResult(queryString); + + let results = await thisObject.getAllResults(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + /** + * Retrieves the history for a marble. + * @async + * @param {ChaincodeStub} stub The chaincode stub. + * @param {String[]} args The arguments of the function. Index 0: marble name. + * @param {Chaincode} thisObject The chaincode object context. + * @return {Promise} The history entries of the specified marble. + */ + async getHistoryForMarble(stub, args, thisObject) { + + if (args.length !== 1) { + throw new Error('Incorrect number of arguments. Expecting 1'); + } + let marbleName = args[0]; + console.info('- start getHistoryForMarble: %s\n', marbleName); + + let resultsIterator = await stub.getHistoryForKey(marbleName); + let results = await thisObject.getAllResults(resultsIterator, true); + + return Buffer.from(JSON.stringify(results)); + } +}; + +shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json new file mode 100644 index 000000000..305f09044 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json @@ -0,0 +1 @@ +{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json new file mode 100644 index 000000000..724fea100 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json @@ -0,0 +1,17 @@ +{ + "name": "marbles", + "version": "1.0.0", + "description": "marbles chaincode implemented in node.js", + "engines": { + "node": ">=8.4.0", + "npm": ">=5.3.0" + }, + "scripts": { + "start": "node marbles.js" + }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "~1.4.0" + } +} diff --git a/packages/caliper-tests-integration/fabric_tests/run.sh b/packages/caliper-tests-integration/fabric_tests/run.sh index 40ce64893..3d0b9eb60 100755 --- a/packages/caliper-tests-integration/fabric_tests/run.sh +++ b/packages/caliper-tests-integration/fabric_tests/run.sh @@ -31,11 +31,11 @@ cd ${DIR} export CALIPER_PROJECTCONFIG=../caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-workspace phase5 --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-workspace phase5 --caliper-flow-only-end } # PHASE 1: just starting the network -${CALL_METHOD} benchmark run --caliper-workspace phase1 --caliper-flow-only-start +${CALL_METHOD} launch master --caliper-workspace phase1 --caliper-flow-only-start rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 1"; @@ -45,7 +45,7 @@ fi # PHASE 2: just initialize the network # TODO: chaincodes shouldn't be required at this point -${CALL_METHOD} benchmark run --caliper-workspace phase2 --caliper-flow-only-init +${CALL_METHOD} launch master --caliper-workspace phase2 --caliper-flow-only-init rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 2"; @@ -54,7 +54,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 3: just init network and install the contracts (channels marked as created) -${CALL_METHOD} benchmark run --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end --caliper-flow-skip-test +${CALL_METHOD} launch master --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end --caliper-flow-skip-test rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 3"; @@ -63,7 +63,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 3 again: deployed contracts should be detected -${CALL_METHOD} benchmark run --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end --caliper-flow-skip-test +${CALL_METHOD} launch master --caliper-workspace phase3 --caliper-flow-skip-start --caliper-flow-skip-end --caliper-flow-skip-test rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 4"; @@ -72,7 +72,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 4: testing through the low-level API -${CALL_METHOD} benchmark run --caliper-workspace phase4 --caliper-flow-only-test +${CALL_METHOD} launch master --caliper-workspace phase4 --caliper-flow-only-test rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 5"; @@ -81,7 +81,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 4 again: testing through the gateway API -${CALL_METHOD} benchmark run --caliper-workspace phase4 --caliper-flow-only-test --caliper-fabric-usegateway +${CALL_METHOD} launch master --caliper-workspace phase4 --caliper-flow-only-test --caliper-fabric-usegateway rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 6"; @@ -90,7 +90,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 5: just disposing of the network -${CALL_METHOD} benchmark run --caliper-workspace phase5 --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-workspace phase5 --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 7"; diff --git a/packages/caliper-tests-integration/fisco-bcos_tests/run.sh b/packages/caliper-tests-integration/fisco-bcos_tests/run.sh index ea563815e..1723dc508 100755 --- a/packages/caliper-tests-integration/fisco-bcos_tests/run.sh +++ b/packages/caliper-tests-integration/fisco-bcos_tests/run.sh @@ -24,11 +24,11 @@ cd "${DIR}" export CALIPER_PROJECTCONFIG=caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-flow-only-end } # PHASE 1: just starting the network -${CALL_METHOD} benchmark run --caliper-flow-only-start +${CALL_METHOD} launch master --caliper-flow-only-start rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 1"; @@ -37,7 +37,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 2: init, install, test -${CALL_METHOD} benchmark run --caliper-flow-skip-start --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-flow-skip-start --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 2"; @@ -46,7 +46,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 5: just disposing of the network -${CALL_METHOD} benchmark run --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 3"; diff --git a/packages/caliper-tests-integration/generator_tests/fabric/run.sh b/packages/caliper-tests-integration/generator_tests/fabric/run.sh index 8a301d7c5..ffbcacefb 100755 --- a/packages/caliper-tests-integration/generator_tests/fabric/run.sh +++ b/packages/caliper-tests-integration/generator_tests/fabric/run.sh @@ -24,7 +24,7 @@ cd "${DIR}" export CALIPER_PROJECTCONFIG=../caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-workspace 'fabric/myWorkspace' --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-workspace 'fabric/myWorkspace' --caliper-flow-only-end } # Install yo @@ -41,7 +41,7 @@ cd ${DIR} ${GENERATOR_METHOD} -- --workspace 'myWorkspace' --chaincodeId 'mymarbles' --version 'v0' --chaincodeFunction 'queryMarblesByOwner' --chaincodeArguments '["Alice"]' --workers 'marbles' --benchmarkName 'A name for the marbles benchmark' --benchmarkDescription 'A description for the marbles benchmark' --label 'A label for the round' --rateController 'fixed-rate' --txType 'txDuration' --txDuration 'marbles' # start network and run benchmark test cd ../ -${CALL_METHOD} benchmark run --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed start network"; @@ -57,7 +57,7 @@ ${GENERATOR_METHOD} -- --workspace 'myWorkspace' --chaincodeId 'mymarbles' --ver # Run benchmark test cd ../ -${CALL_METHOD} benchmark run --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-only-test +${CALL_METHOD} launch master --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-only-test rc=$? if [[ ${rc} != 0 ]]; then echo "Failed run benchmark"; @@ -67,7 +67,7 @@ if [[ ${rc} != 0 ]]; then fi # dispose network -${CALL_METHOD} benchmark run --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-workspace 'fabric/myWorkspace' --caliper-networkconfig 'networkconfig.yaml' --caliper-benchconfig 'benchmarks/config.yaml' --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed end network"; diff --git a/packages/caliper-tests-integration/package.json b/packages/caliper-tests-integration/package.json index ae7f5487c..c8d8b67d9 100644 --- a/packages/caliper-tests-integration/package.json +++ b/packages/caliper-tests-integration/package.json @@ -55,6 +55,19 @@ "scripts/storage", "log", ".DS_Store", + "fabric_docker_distributed_tests/.gitignore", + "fabric_docker_distributed_tests/config/.gitignore", + "fabric_docker_distributed_tests/config/bin", + "fabric_docker_distributed_tests/config/config", + "fabric_docker_distributed_tests/config/crypto-config", + "fabric_docker_distributed_tests/mosquitto", + "fabric_docker_distributed_tests/node_modules", + "fabric_docker_local_tests/.gitignore", + "fabric_docker_local_tests/config/.gitignore", + "fabric_docker_local_tests/config/bin", + "fabric_docker_local_tests/config/config", + "fabric_docker_local_tests/config/crypto-config", + "fabric_docker_local_tests/node_modules", "fabric_tests/.gitignore", "fabric_tests/config/.gitignore", "fabric_tests/config/bin", diff --git a/packages/caliper-tests-integration/sawtooth_tests/run.sh b/packages/caliper-tests-integration/sawtooth_tests/run.sh index ea563815e..1723dc508 100755 --- a/packages/caliper-tests-integration/sawtooth_tests/run.sh +++ b/packages/caliper-tests-integration/sawtooth_tests/run.sh @@ -24,11 +24,11 @@ cd "${DIR}" export CALIPER_PROJECTCONFIG=caliper.yaml dispose () { - ${CALL_METHOD} benchmark run --caliper-flow-only-end + ${CALL_METHOD} launch master --caliper-flow-only-end } # PHASE 1: just starting the network -${CALL_METHOD} benchmark run --caliper-flow-only-start +${CALL_METHOD} launch master --caliper-flow-only-start rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 1"; @@ -37,7 +37,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 2: init, install, test -${CALL_METHOD} benchmark run --caliper-flow-skip-start --caliper-flow-skip-end +${CALL_METHOD} launch master --caliper-flow-skip-start --caliper-flow-skip-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 2"; @@ -46,7 +46,7 @@ if [[ ${rc} != 0 ]]; then fi # PHASE 5: just disposing of the network -${CALL_METHOD} benchmark run --caliper-flow-only-end +${CALL_METHOD} launch master --caliper-flow-only-end rc=$? if [[ ${rc} != 0 ]]; then echo "Failed CI step 3"; diff --git a/scripts/check-package-names.sh b/scripts/check-package-names.sh index 9ea787e3c..4460bb1f6 100755 --- a/scripts/check-package-names.sh +++ b/scripts/check-package-names.sh @@ -1,7 +1,7 @@ #!/bin/bash # publishNpmPackages.js contains the package dir names as caliper-*, those are fine -if grep -rnE --exclude-dir="caliper-publish" "['\"]caliper-(cli|core|burrow|ethereum|fabric|fisco-bcos|iroha|sawtooth)['\"]" . ; then +if grep -rnE --exclude-dir=".idea" --exclude-dir="caliper-publish" --exclude-dir="coverage" "['\"]caliper-(cli|core|burrow|ethereum|fabric|fisco-bcos|iroha|sawtooth)['\"]" . ; then echo "^^^ Found incorrect Caliper package names. Use the @hyperledger/ prefix for Caliper packages, e.g., @hyperledger/caliper-core" exit 1 else