diff --git a/.travis.yml b/.travis.yml index 911510a94..24f9a6bf7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ matrix: include: - env: BENCHMARK=composer - env: BENCHMARK=fabric + - env: BENCHMARK=ethereum dist: trusty before_install: | set -ev diff --git a/.travis/script.sh b/.travis/script.sh index 388dcbd18..74558be77 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -44,6 +44,8 @@ if [[ "${BENCHMARK}" == "composer" ]]; then npx caliper bind --caliper-bind-sut composer elif [[ "${BENCHMARK}" == "fabric" ]]; then npx caliper bind --caliper-bind-sut fabric +elif [[ "${BENCHMARK}" == "ethereum" ]]; then + npx caliper bind --caliper-bind-sut ethereum else echo "Unknown target benchmark ${BENCHMARK}" npm run cleanup diff --git a/packages/caliper-cli/lib/bind/config.yaml b/packages/caliper-cli/lib/bind/config.yaml index 6b880a699..7024fdba2 100644 --- a/packages/caliper-cli/lib/bind/config.yaml +++ b/packages/caliper-cli/lib/bind/config.yaml @@ -81,3 +81,7 @@ sut: settings: - *new-node-old-grpc latest: *composer-latest + ethereum: + 1.2.1: ðereum-latest + packages: ['web3@1.2.1'] + latest: *ethereum-latest diff --git a/packages/caliper-cli/package.json b/packages/caliper-cli/package.json index e8bc99632..32963e200 100644 --- a/packages/caliper-cli/package.json +++ b/packages/caliper-cli/package.json @@ -29,6 +29,7 @@ "@hyperledger/caliper-fabric": "^0.1.0", "@hyperledger/caliper-iroha": "^0.1.0", "@hyperledger/caliper-sawtooth": "^0.1.0", + "@hyperledger/caliper-ethereum": "^0.1.0", "chalk": "1.1.3", "yargs": "10.0.3" }, diff --git a/packages/caliper-ethereum/.editorconfig b/packages/caliper-ethereum/.editorconfig new file mode 100644 index 000000000..1a1ede6c8 --- /dev/null +++ b/packages/caliper-ethereum/.editorconfig @@ -0,0 +1,26 @@ +# +# 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. +# + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/caliper-ethereum/.eslintignore b/packages/caliper-ethereum/.eslintignore new file mode 100644 index 000000000..fbbb968ec --- /dev/null +++ b/packages/caliper-ethereum/.eslintignore @@ -0,0 +1,16 @@ +# +# 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. +# + +coverage +node_modules diff --git a/packages/caliper-ethereum/.eslintrc.yml b/packages/caliper-ethereum/.eslintrc.yml new file mode 100644 index 000000000..6ef5e947f --- /dev/null +++ b/packages/caliper-ethereum/.eslintrc.yml @@ -0,0 +1,48 @@ +env: + es6: true + node: true + mocha: true +extends: 'eslint:recommended' +parserOptions: + ecmaVersion: 8 + sourceType: + - script +rules: + indent: + - error + - 4 + linebreak-style: + - error + - unix + quotes: + - error + - single + semi: + - error + - always + no-unused-vars: + - error + - args: none + no-console: warn + curly: error + eqeqeq: error + no-throw-literal: error + strict: error + no-var: error + dot-notation: error + no-tabs: error + no-trailing-spaces: error + no-use-before-define: error + no-useless-call: error + no-with: error + operator-linebreak: error + require-jsdoc: + - error + - require: + ClassDeclaration: true + MethodDefinition: true + FunctionDeclaration: true + valid-jsdoc: + - error + - requireReturn: false + yoda: error diff --git a/packages/caliper-ethereum/index.js b/packages/caliper-ethereum/index.js new file mode 100644 index 000000000..eadf46894 --- /dev/null +++ b/packages/caliper-ethereum/index.js @@ -0,0 +1,18 @@ +/* + * 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.AdminClient = require('./lib/ethereum'); +module.exports.ClientFactory = require('./lib/ethereumClientFactory'); diff --git a/packages/caliper-ethereum/lib/ethereum.js b/packages/caliper-ethereum/lib/ethereum.js new file mode 100644 index 000000000..50fb1e12d --- /dev/null +++ b/packages/caliper-ethereum/lib/ethereum.js @@ -0,0 +1,244 @@ +/** + * 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. + * + * @file, definition of the Ethereum class, which implements the Caliper's NBI for Ethereum Web3 interface. + */ + +'use strict'; + +const Web3 = require('web3'); +const {BlockchainInterface, CaliperUtils, TxStatus} = require('@hyperledger/caliper-core'); +const logger = CaliperUtils.getLogger('ethereum.js'); + +/** + * @typedef {Object} EthereumInvoke + * + * @property {string} verb Required. The name of the smart contract function + * @property {string} args Required. Arguments of the smart contract function in the order in which they are defined + * @property {boolean} isView Optional. If method to call is a view. + */ + +/** + * Implements {BlockchainInterface} for a web3 Ethereum backend. + */ +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. + */ + constructor(config_path, workspace_root) { + super(config_path); + this.bcType = 'ethereum'; + this.workspaceRoot = workspace_root; + this.ethereumConfig = require(config_path).ethereum; + this.web3 = new Web3(this.ethereumConfig.url); + this.web3.transactionConfirmationBlocks = this.ethereumConfig.transactionConfirmationBlocks; + } + + /** + * Initialize the {Ethereum} object. + * @return {object} Promise True if the account got unlocked successful otherwise false. + */ + init() { + return this.web3.eth.personal.unlockAccount(this.ethereumConfig.contractDeployerAddress, this.ethereumConfig.contractDeployerAddressPassword, 1000); + } + + /** + * Deploy smart contracts specified in the network configuration file. + * @return {object} Promise execution for all the contract creations. + */ + async installSmartContract() { + let promises = []; + 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 + this.ethereumConfig.contracts[key].abi = contractData.abi; + promises.push(new Promise(async function(resolve, reject) { + let contractInstance = await self.deployContract(contractData); + logger.info('Deployed contract ' + contractData.name + ' at ' + contractInstance.options.address); + self.ethereumConfig.contracts[key].address = contractInstance.options.address; + resolve(contractInstance); + })); + } + return Promise.all(promises); + } + + /** + * Return the Ethereum context associated with the given callback module name. + * @param {string} name The name of the callback module as defined in the configuration files. + * @param {object} args Unused. + * @return {object} The assembled Ethereum context. + * @async + */ + async getContext(name, args) { + let context = {fromAddress: this.ethereumConfig.fromAddress}; + context.web3 = this.web3; + context.contracts = {}; + for (const key of Object.keys(args.contracts)) { + context.contracts[key] = new this.web3.eth.Contract(args.contracts[key].abi, args.contracts[key].address); + } + await context.web3.eth.personal.unlockAccount(this.ethereumConfig.fromAddress, this.ethereumConfig.fromAddressPassword, 1000); + return context; + } + + /** + * Release the given Ethereum context. + * @param {object} context The Ethereum context to release. + * @async + */ + async releaseContext(context) { + // nothing to do + } + + /** + * Invoke a smart contract. + * @param {Object} context Context object. + * @param {String} contractID Identity of the contract. + * @param {String} contractVer Version of the contract. + * @param {EthereumInvoke|EthereumInvoke[]} invokeData Smart contract methods calls. + * @param {Number} timeout Request timeout, in seconds. + * @return {Promise} The promise for the result of the execution. + */ + async invokeSmartContract(context, contractID, contractVer, invokeData, timeout) { + let invocations; + if (!Array.isArray(invokeData)) { + invocations = [invokeData]; + } else { + invocations = invokeData; + } + let promises = []; + invocations.forEach((item, index) => { + promises.push(this.sendTransaction(context, contractID, contractVer, item, timeout)); + }); + return Promise.all(promises); + } + + /** + * Query a smart contract. + * @param {Object} context Context object. + * @param {String} contractID Identity of the contract. + * @param {String} contractVer Version of the contract. + * @param {EthereumInvoke|EthereumInvoke[]} invokeData Smart contract methods calls. + * @param {Number} timeout Request timeout, in seconds. + * @return {Promise} The promise for the result of the execution. + */ + async querySmartContract(context, contractID, contractVer, invokeData, timeout) { + let invocations; + if (!Array.isArray(invokeData)) { + invocations = [invokeData]; + } else { + invocations = invokeData; + } + let promises = []; + invocations.forEach((item, index) => { + item.isView = true; + promises.push(this.sendTransaction(context, contractID, contractVer, item, timeout)); + }); + return Promise.all(promises); + } + + /** + * Submit a transaction to the ethereum context. + * @param {Object} context Context object. + * @param {String} contractID Identity of the contract. + * @param {String} contractVer Version of the contract. + * @param {EthereumInvoke} methodCall Methods call data. + * @param {Number} timeout Request timeout, in seconds. + * @return {Promise} Result and stats of the transaction invocation. + */ + async sendTransaction(context, contractID, contractVer, methodCall, timeout) { + let status = new TxStatus(); + try { + context.engine.submitCallback(1); + let receipt = null; + let methodType = 'send'; + if (methodCall.isView) { + methodType = 'call'; + } + if (methodCall.args) { + receipt = await context.contracts[contractID].methods[methodCall.verb](...methodCall.args)[methodType]({from: context.fromAddress}); + } else { + receipt = await context.contracts[contractID].methods[methodCall.verb]()[methodType]({from: context.fromAddress}); + } + status.SetID(receipt.transactionHash); + status.SetResult(receipt); + status.SetVerification(true); + status.SetStatusSuccess(); + } catch (err) { + status.SetStatusFail(); + logger.error('Failed tx on ' + contractID + ' calling method ' + methodCall.verb); + logger.error(err); + } + return Promise.resolve(status); + } + + /** + * Query the given smart contract according to the specified options. + * @param {object} context The Ethereum context returned by {getContext}. + * @param {string} contractID The name of the contract. + * @param {string} contractVer The version of the contract. + * @param {string} key The argument to pass to the smart contract query. + * @param {string} [fcn=query] The contract query function name. + * @return {Promise} The promise for the result of the execution. + */ + async queryState(context, contractID, contractVer, key, fcn = 'query') { + let methodCall = { + verb: fcn, + args: [key], + isView: true + }; + return this.sendTransaction(context, contractID, contractVer, methodCall, 60); + } + + /** + * Deploys a new contract using the given web3 instance + * @param {JSON} contractData Contract data with abi, bytecode and gas properties + * @returns {Promise} The deployed contract instance + */ + deployContract(contractData) { + let web3 = this.web3; + let contractDeployerAddress = this.ethereumConfig.contractDeployerAddress; + return new Promise(function(resolve, reject) { + let contract = new web3.eth.Contract(contractData.abi); + let contractDeploy = contract.deploy({ + data: contractData.bytecode + }); + contractDeploy.send({ + from: contractDeployerAddress, + gas: contractData.gas + }).on('error', (error) => { + reject(error); + }).then((newContractInstance) => { + resolve(newContractInstance); + }); + }); + } + + /** + * It passes deployed contracts addresses to all clients + * @param {Number} number of clients to prepare + * @returns {Array} client args + */ + async prepareClients(number) { + let result = []; + for (let i = 0 ; i< number ; i++) { + result[i] = {contracts: this.ethereumConfig.contracts}; + } + return result; + } +} + +module.exports = Ethereum; diff --git a/packages/caliper-ethereum/lib/ethereumClientFactory.js b/packages/caliper-ethereum/lib/ethereumClientFactory.js new file mode 100644 index 000000000..073573763 --- /dev/null +++ b/packages/caliper-ethereum/lib/ethereumClientFactory.js @@ -0,0 +1,56 @@ +/** + * 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. + * + * @file, definition of the Ethereum client factory + */ + +'use strict'; + +const childProcess = require('child_process'); +const path = require('path'); + +/** + * Class used to spawn fabric client workers + */ +class EthereumClientFactory { + + /** + * Require paths to configuration data used when calling new on fabric.js + * @param {String} absNetworkFile absolute workerPath + * @param {Sting} workspace_root root location + */ + constructor(absNetworkFile, workspace_root){ + this.absNetworkFile = absNetworkFile; + this.workspaceRoot = workspace_root; + } + + + /** + * Spawn the worker and perform required init + * @returns {Object} the child process + */ + spawnWorker() { + const child = childProcess.fork(path.join(__dirname, './ethereumClientWorker.js'), process.argv.slice(1), { env: process.env}); + + const msg = { + type: 'init', + absNetworkFile: this.absNetworkFile, + networkRoot: this.workspaceRoot + }; + child.send(msg); + + return child; + } +} + +module.exports = EthereumClientFactory; diff --git a/packages/caliper-ethereum/lib/ethereumClientWorker.js b/packages/caliper-ethereum/lib/ethereumClientWorker.js new file mode 100644 index 000000000..c81c25840 --- /dev/null +++ b/packages/caliper-ethereum/lib/ethereumClientWorker.js @@ -0,0 +1,56 @@ +/** + * 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. + * + * @file, definition of the Ethereum client worker + */ + +'use strict'; + +const {CaliperLocalClient, CaliperUtils} = require('@hyperledger/caliper-core'); +const EthereumClient = require('./ethereum'); + +let caliperClient; +/** + * Message handler + */ +process.on('message', async (message) => { + + if (!message.hasOwnProperty('type')) { + process.send({type: 'error', data: 'unknown message type'}); + return; + } + + try { + switch (message.type) { + case 'init': { + const blockchain = new EthereumClient(message.absNetworkFile, message.networkRoot); + caliperClient = new CaliperLocalClient(blockchain); + process.send({type: 'ready', data: {pid: process.pid, complete: true}}); + break; + } + case 'test': { + let result = await caliperClient.doTest(message); + + await CaliperUtils.sleep(200); + process.send({type: 'testResult', data: result}); + break; + } + default: { + process.send({type: 'error', data: 'unknown message type [' + message.type + ']'}); + } + } + } + catch (err) { + process.send({type: 'error', data: err.toString()}); + } +}); diff --git a/packages/caliper-ethereum/package.json b/packages/caliper-ethereum/package.json new file mode 100644 index 000000000..46cf1d726 --- /dev/null +++ b/packages/caliper-ethereum/package.json @@ -0,0 +1,88 @@ +{ + "name": "@hyperledger/caliper-ethereum", + "version": "0.1.0", + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/caliper" + }, + "scripts": { + "test": "npm run lint", + "lint": "npx eslint .", + "nyc": "nyc mocha --recursive -t 10000" + }, + "engines": { + "node": ">=8.10.0", + "npm": ">=5.6.0" + }, + "engine-strict": true, + "engineStrict": true, + "files": [ + "*", + "src/comm/template/report.html" + ], + "dependencies": { + "@hyperledger/caliper-core": "^0.1.0" + }, + "devDependencies": { + "web3": "^1.2.0", + "chai": "^3.5.0", + "eslint": "^4.19.1", + "mocha": "3.4.2", + "nyc": "11.1.0", + "rewire": "^4.0.0", + "sinon": "2.3.8" + }, + "license-check-and-add-config": { + "folder": ".", + "license": "../../LICENSE.txt", + "exact_paths_method": "EXCLUDE", + "exact_paths": [ + "node_modules", + ".nyc_output", + "coverage" + ], + "file_type_method": "EXCLUDE", + "file_types": [ + ".yml" + ], + "insert_license": false, + "license_formats": { + "js": { + "prepend": "/*", + "append": "*/", + "eachLine": { + "prepend": "* " + } + }, + "editorconfig": { + "prepend": "#", + "append": "#", + "eachLine": { + "prepend": "# " + } + } + } + }, + "nyc": { + "exclude": [ + "lib/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": false, + "statements": 5, + "branches": 8, + "functions": 7, + "lines": 5 + }, + "license": "Apache-2.0", + "licenses": [ + { + "type": "Apache-2.0", + "url": "https://github.com/hyperledger/caliper/blob/master/LICENSE" + } + ] +} diff --git a/packages/caliper-samples/benchmark/simple/open.js b/packages/caliper-samples/benchmark/simple/open.js index 2a392f8d2..330ace593 100644 --- a/packages/caliper-samples/benchmark/simple/open.js +++ b/packages/caliper-samples/benchmark/simple/open.js @@ -79,6 +79,11 @@ function generateWorkload() { chaincodeFunction: 'open', chaincodeArguments: [acc_id, initMoney.toString()], }); + } else if (bc.bcType === 'ethereum') { + workload.push({ + verb: 'open', + args: [acc_id, initMoney] + }); } else { workload.push({ 'verb': 'open', diff --git a/packages/caliper-samples/benchmark/simple/transfer.js b/packages/caliper-samples/benchmark/simple/transfer.js index e7082af6f..90f4919e7 100644 --- a/packages/caliper-samples/benchmark/simple/transfer.js +++ b/packages/caliper-samples/benchmark/simple/transfer.js @@ -44,6 +44,11 @@ module.exports.run = function () { chaincodeFunction: 'transfer', chaincodeArguments: [account1, account2, initmoney.toString()], }; + } else if (bc.bcType === 'ethereum') { + args = { + verb: 'transfer', + args: [account1, account2, initmoney] + }; } else { args = { 'verb': 'transfer', diff --git a/packages/caliper-samples/network/ethereum/1node-clique/Dockerfile b/packages/caliper-samples/network/ethereum/1node-clique/Dockerfile new file mode 100644 index 000000000..b7bfb99ed --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/Dockerfile @@ -0,0 +1,17 @@ +# 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. + +FROM ethereum/client-go:stable +COPY ./data/ /root/ +VOLUME [ "/root/.ethereum/keystore/" ] +RUN geth --nousb init /root/genesis.json && geth --nousb import /root/bc.dat +ENTRYPOINT [ "geth" ] \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node-clique/data/bc.dat b/packages/caliper-samples/network/ethereum/1node-clique/data/bc.dat new file mode 100755 index 000000000..140eb9c8b Binary files /dev/null and b/packages/caliper-samples/network/ethereum/1node-clique/data/bc.dat differ diff --git a/packages/caliper-samples/network/ethereum/1node-clique/data/genesis.json b/packages/caliper-samples/network/ethereum/1node-clique/data/genesis.json new file mode 100644 index 000000000..02445215d --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/data/genesis.json @@ -0,0 +1,31 @@ +{ + "config": { + "chainId": 48122, + "homesteadBlock": 1, + "eip150Block": 2, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 3, + "eip158Block": 3, + "byzantiumBlock": 4, + "constantinopleBlock": 5, + "clique": { + "period": 5, + "epoch": 30000 + } + }, + "nonce": "0x0", + "timestamp": "0x5ca916c6", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000c0A8e4D217eB85b812aeb1226fAb6F588943C2C20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x47b760", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2", + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "alloc": { + "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + } + } +} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node-clique/docker-compose.yml b/packages/caliper-samples/network/ethereum/1node-clique/docker-compose.yml new file mode 100644 index 000000000..f17cdaab5 --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/docker-compose.yml @@ -0,0 +1,25 @@ +# +# 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: + node: + build: . + image: caliper-ethereum-clique + container_name: ethereum_clique + volumes: + - ./keys:/root/.ethereum/keystore + ports: + - 8545:8545 + command: --unlock 0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2 --password /root/.ethereum/keystore/password --mine --minerthreads 2 --etherbase 0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2 --rpc --rpcaddr 0.0.0.0 --rpcvhosts=* --rpcapi admin,eth,miner,personal,web3 --allow-insecure-unlock --nodiscover --gasprice 1 \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node-clique/ethereum.json b/packages/caliper-samples/network/ethereum/1node-clique/ethereum.json new file mode 100644 index 000000000..fcb249632 --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/ethereum.json @@ -0,0 +1,22 @@ +{ + "caliper": { + "blockchain": "ethereum", + "command" : { + "start": "docker-compose -f network/ethereum/1node-clique/docker-compose.yml up -d && sleep 3", + "end" : "docker-compose -f network/ethereum/1node-clique/docker-compose.yml down" + } + }, + "ethereum": { + "url": "http://localhost:8545", + "contractDeployerAddress": "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2", + "contractDeployerAddressPassword": "password", + "fromAddress": "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2", + "fromAddressPassword": "password", + "transactionConfirmationBlocks": 2, + "contracts": { + "simple": { + "path": "src/contract/ethereum/simple/simple.json" + } + } + } +} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node-clique/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 b/packages/caliper-samples/network/ethereum/1node-clique/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 new file mode 100644 index 000000000..237ff7e2b --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 @@ -0,0 +1 @@ +{"address":"c0a8e4d217eb85b812aeb1226fab6f588943c2c2","crypto":{"cipher":"aes-128-ctr","ciphertext":"521588833e66d0e052120c30080e37e847ae7877eb09dc6760eb10382c2e6d4f","cipherparams":{"iv":"cee02dbf21538798041c99f49bef9afa"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"916a08841725aba0bf2a2d39f489f0d5d3f2b5032067e44655b8ff542c492f03"},"mac":"f90667ea4de3e977b1cdd23ed722ddffc3f6888b21ea68cc7d5ef77463022e94"},"id":"7543fa05-dac5-4ae9-856d-e96ddea28c41","version":3} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node-clique/keys/password b/packages/caliper-samples/network/ethereum/1node-clique/keys/password new file mode 100644 index 000000000..7aa311adf --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node-clique/keys/password @@ -0,0 +1 @@ +password \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/Dockerfile b/packages/caliper-samples/network/ethereum/1node/Dockerfile new file mode 100644 index 000000000..a48ac8acc --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/Dockerfile @@ -0,0 +1,17 @@ +# 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. + +FROM ethereum/client-go:stable +COPY ./data/ /root/ +VOLUME [ "/root/.ethereum/keystore/" ] +RUN geth --nousb init /root/genesis.json && geth --nousb import /root/bc.dat && geth --nousb makedag 0 /root/.ethash && geth --nousb makedag 30000 /root/.ethash +ENTRYPOINT [ "geth" ] \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/data/bc.dat b/packages/caliper-samples/network/ethereum/1node/data/bc.dat new file mode 100755 index 000000000..54a7fd889 Binary files /dev/null and b/packages/caliper-samples/network/ethereum/1node/data/bc.dat differ diff --git a/packages/caliper-samples/network/ethereum/1node/data/genesis.json b/packages/caliper-samples/network/ethereum/1node/data/genesis.json new file mode 100644 index 000000000..400af6d6d --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/data/genesis.json @@ -0,0 +1,28 @@ +{ + "config": { + "chainId": 21194, + "homesteadBlock": 1, + "eip150Block": 2, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 3, + "eip158Block": 3, + "byzantiumBlock": 4, + "constantinopleBlock": 5, + "ethash": {} + }, + "nonce": "0x0", + "timestamp": "0x5cd09c57", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x47b760", + "difficulty": "0x80000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "c0a8e4d217eb85b812aeb1226fab6f588943c2c2": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/docker-compose.yml b/packages/caliper-samples/network/ethereum/1node/docker-compose.yml new file mode 100644 index 000000000..501f9a82b --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/docker-compose.yml @@ -0,0 +1,25 @@ +# +# 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: + node: + build: . + image: caliper-ethereum + container_name: ethereum + volumes: + - ./keys:/root/.ethereum/keystore + ports: + - 8545:8545 + command: --unlock 0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2 --password /root/.ethereum/keystore/password --mine --minerthreads 2 --etherbase 0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2 --rpc --rpcaddr 0.0.0.0 --rpcvhosts=* --rpcapi admin,eth,miner,personal,web3 --allow-insecure-unlock --nodiscover --gasprice 1 \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/ethereum.json b/packages/caliper-samples/network/ethereum/1node/ethereum.json new file mode 100644 index 000000000..36290166b --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/ethereum.json @@ -0,0 +1,22 @@ +{ + "caliper": { + "blockchain": "ethereum", + "command" : { + "start": "docker-compose -f network/ethereum/1node/docker-compose.yml up -d && sleep 3", + "end" : "docker-compose -f network/ethereum/1node/docker-compose.yml down" + } + }, + "ethereum": { + "url": "http://localhost:8545", + "contractDeployerAddress": "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2", + "contractDeployerAddressPassword": "password", + "fromAddress": "0xc0A8e4D217eB85b812aeb1226fAb6F588943C2C2", + "fromAddressPassword": "password", + "transactionConfirmationBlocks": 2, + "contracts": { + "simple": { + "path": "src/contract/ethereum/simple/simple.json" + } + } + } +} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 b/packages/caliper-samples/network/ethereum/1node/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 new file mode 100644 index 000000000..237ff7e2b --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/keys/UTC--2019-05-05T20-07-38.958128475Z--c0a8e4d217eb85b812aeb1226fab6f588943c2c2 @@ -0,0 +1 @@ +{"address":"c0a8e4d217eb85b812aeb1226fab6f588943c2c2","crypto":{"cipher":"aes-128-ctr","ciphertext":"521588833e66d0e052120c30080e37e847ae7877eb09dc6760eb10382c2e6d4f","cipherparams":{"iv":"cee02dbf21538798041c99f49bef9afa"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"916a08841725aba0bf2a2d39f489f0d5d3f2b5032067e44655b8ff542c492f03"},"mac":"f90667ea4de3e977b1cdd23ed722ddffc3f6888b21ea68cc7d5ef77463022e94"},"id":"7543fa05-dac5-4ae9-856d-e96ddea28c41","version":3} \ No newline at end of file diff --git a/packages/caliper-samples/network/ethereum/1node/keys/password b/packages/caliper-samples/network/ethereum/1node/keys/password new file mode 100644 index 000000000..7aa311adf --- /dev/null +++ b/packages/caliper-samples/network/ethereum/1node/keys/password @@ -0,0 +1 @@ +password \ No newline at end of file diff --git a/packages/caliper-samples/package.json b/packages/caliper-samples/package.json index 502a7dd11..c1a50381a 100644 --- a/packages/caliper-samples/package.json +++ b/packages/caliper-samples/package.json @@ -32,6 +32,10 @@ "benchmark/smallbank/.Rhistory", "network/burrow/simple/chain/keys/names", "network/sawtooth/simplenetwork/Dockerfile", + "network/ethereum/1node/keys", + "network/ethereum/1node/Dockerfile", + "network/ethereum/1node-clique/keys", + "network/ethereum/1node-clique/Dockerfile", "network/fabric-v1.0/config/.gitignore", "network/fabric-v1.1/config/.gitignore", "network/fabric-v1.2/config/.gitignore", @@ -67,7 +71,8 @@ ".list", ".sol", ".proto", - ".cfg" + ".cfg", + ".dat" ], "insert_license": false, "license_formats": { diff --git a/packages/caliper-samples/src/contract/ethereum/simple/simple.json b/packages/caliper-samples/src/contract/ethereum/simple/simple.json new file mode 100644 index 000000000..8353266de --- /dev/null +++ b/packages/caliper-samples/src/contract/ethereum/simple/simple.json @@ -0,0 +1,73 @@ +{ + "name": "simple", + "abi": [ + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "acc_from", + "type": "string" + }, + { + "internalType": "string", + "name": "acc_to", + "type": "string" + }, + { + "internalType": "int256", + "name": "amount", + "type": "int256" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "acc_id", + "type": "string" + } + ], + "name": "query", + "outputs": [ + { + "internalType": "int256", + "name": "amount", + "type": "int256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "acc_id", + "type": "string" + }, + { + "internalType": "int256", + "name": "amount", + "type": "int256" + } + ], + "name": "open", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x608060405234801561001057600080fd5b50610542806100206000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80631de45b10146100465780637c261929146101a25780639064129314610271575b600080fd5b6101a06004803603606081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561011057600080fd5b82018360208201111561012257600080fd5b8035906020019184600183028401116401000000008311171561014457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610336565b005b61025b600480360360208110156101b857600080fd5b81019080803590602001906401000000008111156101d557600080fd5b8201836020820111156101e757600080fd5b8035906020019184600183028401116401000000008311171561020957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610429565b6040518082815260200191505060405180910390f35b6103346004803603604081101561028757600080fd5b81019080803590602001906401000000008111156102a457600080fd5b8201836020820111156102b657600080fd5b803590602001918460018302840111640100000000831117156102d857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019092919050505061049b565b005b806000846040518082805190602001908083835b6020831061036d578051825260208201915060208101905060208303925061034a565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008282540392505081905550806000836040518082805190602001908083835b602083106103e457805182526020820191506020810190506020830392506103c1565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008282540192505081905550505050565b600080826040518082805190602001908083835b60208310610460578051825260208201915060208101905060208303925061043d565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020549050919050565b806000836040518082805190602001908083835b602083106104d257805182526020820191506020810190506020830392506104af565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902081905550505056fea265627a7a72315820f89fa48c6c4d5d7df165b5e3dba04cbe258ce57a3a4ef3bab1377546b84b699f64736f6c634300050b0032", + "gas": 4700000 +} \ No newline at end of file diff --git a/packages/caliper-samples/src/contract/ethereum/simple/simple.sol b/packages/caliper-samples/src/contract/ethereum/simple/simple.sol new file mode 100644 index 000000000..59397b601 --- /dev/null +++ b/packages/caliper-samples/src/contract/ethereum/simple/simple.sol @@ -0,0 +1,18 @@ +pragma solidity >=0.4.22 <0.6.0; + +contract simple { + mapping(string => int) private accounts; + + function open(string memory acc_id, int amount) public { + accounts[acc_id] = amount; + } + + function query(string memory acc_id) public view returns (int amount) { + amount = accounts[acc_id]; + } + + function transfer(string memory acc_from, string memory acc_to, int amount) public { + accounts[acc_from] -= amount; + accounts[acc_to] += amount; + } +} \ No newline at end of file diff --git a/packages/caliper-tests-integration/scripts/publishNpmPackages.js b/packages/caliper-tests-integration/scripts/publishNpmPackages.js index c70d7d65c..8685aa30b 100644 --- a/packages/caliper-tests-integration/scripts/publishNpmPackages.js +++ b/packages/caliper-tests-integration/scripts/publishNpmPackages.js @@ -27,6 +27,7 @@ const packages = [ 'caliper-core', 'caliper-burrow', 'caliper-composer', + 'caliper-ethereum', 'caliper-fabric', 'caliper-iroha', 'caliper-sawtooth', diff --git a/packages/caliper-tests-integration/scripts/run-tests.sh b/packages/caliper-tests-integration/scripts/run-tests.sh index 861a5841e..d2dac2ef7 100755 --- a/packages/caliper-tests-integration/scripts/run-tests.sh +++ b/packages/caliper-tests-integration/scripts/run-tests.sh @@ -52,6 +52,10 @@ elif [[ "${BENCHMARK}" == "fabric" ]]; then rc=$? exit $rc; fi +elif [[ "${BENCHMARK}" == "ethereum" ]]; then + ${CALL_METHOD} benchmark run --caliper-benchconfig benchmark/simple/config.yaml --caliper-networkconfig network/ethereum/1node-clique/ethereum.json --caliper-workspace ../caliper-samples/ + rc=$? + exit $rc; else echo "Unknown target benchmark ${BENCHMARK}" exit 1