diff --git a/dapps/templates/boilerplate/config/blockchain.js b/dapps/templates/boilerplate/config/blockchain.js index 56e87e4a9d..f5b09b101c 100644 --- a/dapps/templates/boilerplate/config/blockchain.js +++ b/dapps/templates/boilerplate/config/blockchain.js @@ -4,10 +4,11 @@ module.exports = { // default applies to all environments default: { enabled: true, - client: "geth" // Can be geth or parity (default:geth) + client: "geth" // Can be ganache-cli, geth or parity (default: geth) }, development: { + client: 'ganache-cli', clientConfig: { miningMode: 'dev' // Mode in which the node mines. Options: dev, auto, always, off } diff --git a/dapps/templates/demo/config/blockchain.js b/dapps/templates/demo/config/blockchain.js index 56e87e4a9d..f5b09b101c 100644 --- a/dapps/templates/demo/config/blockchain.js +++ b/dapps/templates/demo/config/blockchain.js @@ -4,10 +4,11 @@ module.exports = { // default applies to all environments default: { enabled: true, - client: "geth" // Can be geth or parity (default:geth) + client: "geth" // Can be ganache-cli, geth or parity (default: geth) }, development: { + client: 'ganache-cli', clientConfig: { miningMode: 'dev' // Mode in which the node mines. Options: dev, auto, always, off } diff --git a/dapps/tests/app/config/blockchain.js b/dapps/tests/app/config/blockchain.js index 43ab9ffd0d..6caeb9e4a8 100644 --- a/dapps/tests/app/config/blockchain.js +++ b/dapps/tests/app/config/blockchain.js @@ -4,10 +4,11 @@ module.exports = { // default applies to all environments default: { enabled: true, - client: "geth" // Can be geth or parity (default:geth) + client: "geth" // Can be ganache-cli, geth or parity (default: geth) }, development: { + client: 'ganache-cli', clientConfig: { miningMode: 'dev' // Mode in which the node mines. Options: dev, auto, always, off }, diff --git a/dapps/tests/app/test/another_storage_spec.js b/dapps/tests/app/test/another_storage_spec.js index 8d7fdb3a45..a6f7c13d26 100644 --- a/dapps/tests/app/test/another_storage_spec.js +++ b/dapps/tests/app/test/another_storage_spec.js @@ -12,6 +12,7 @@ config({ { "mnemonic": "example exile argue silk regular smile grass bomb merge arm assist farm", balance: "5ether", + hdpath: "m/44'/1'/0'/0/", numAddresses: 10 } ] @@ -49,7 +50,6 @@ contract("AnotherStorage", function() { for (let i = 1; i < numAddresses - 3; i++) { balance = await web3.eth.getBalance(accounts[i]); - console.log('Account', i , balance); assert.strictEqual(parseInt(balance, 10), 5000000000000000000, `Account ${i} doesn't have the balance set`); } }); diff --git a/packages/core/core/constants.json b/packages/core/core/constants.json index b465da4874..92459807f6 100644 --- a/packages/core/core/constants.json +++ b/packages/core/core/constants.json @@ -40,8 +40,10 @@ "call": "eth_call", "clients": { "geth": "geth", - "parity": "parity" + "parity": "parity", + "ganache": "ganache-cli" }, + "defaultMnemonic": "example exile argue silk regular smile grass bomb merge arm assist farm", "blockchainReady": "blockchainReady", "blockchainExit": "blockchainExit", "defaults": { diff --git a/packages/core/core/src/configDefaults.ts b/packages/core/core/src/configDefaults.ts index 10ae7251fb..ec020996ee 100644 --- a/packages/core/core/src/configDefaults.ts +++ b/packages/core/core/src/configDefaults.ts @@ -26,7 +26,7 @@ export function getBlockchainDefaults(env) { miningMode: 'dev' // Mode in which the node mines. Options: dev, auto, always, off }, enabled: true, - client: constants.blockchain.clients.geth, + client: constants.blockchain.clients.ganache, proxy: true, datadir: `.embark/${env}/datadir`, rpcHost: "localhost", diff --git a/packages/core/engine/package.json b/packages/core/engine/package.json index 250d5b53c4..64961d5952 100644 --- a/packages/core/engine/package.json +++ b/packages/core/engine/package.json @@ -51,7 +51,6 @@ "embark-authenticator": "^5.2.0-nightly.3", "embark-basic-pipeline": "^5.2.0-nightly.3", "embark-blockchain": "^5.2.0-nightly.3", - "embark-blockchain-client": "^5.1.1", "embark-code-runner": "^5.2.0-nightly.3", "embark-communication": "^5.2.0-nightly.3", "embark-compiler": "^5.2.0-nightly.3", diff --git a/packages/core/engine/src/index.ts b/packages/core/engine/src/index.ts index c91e54b8fe..133666f53f 100644 --- a/packages/core/engine/src/index.ts +++ b/packages/core/engine/src/index.ts @@ -246,7 +246,6 @@ export class Engine { blockchainStackComponents() { this.registerModulePackage('embark-blockchain', { plugins: this.plugins, ipc: this.ipc }); - this.registerModulePackage('embark-blockchain-client'); this.registerModulePackage('embark-process-logs-api-manager'); } @@ -259,7 +258,6 @@ export class Engine { this.registerModulePackage('embark-compiler', { plugins: this.plugins, isCoverage: options.isCoverage }); this.registerModulePackage('embark-contracts-manager', { plugins: this.plugins, compileOnceOnly: options.compileOnceOnly }); this.registerModulePackage('embark-deployment', { plugins: this.plugins, onlyCompile: options.onlyCompile }); - this.registerModulePackage('embark-blockchain-client'); this.registerModulePackage('embark-storage'); this.registerModulePackage('embark-communication'); this.registerModulePackage('embark-namesystem'); @@ -269,6 +267,7 @@ export class Engine { blockchainComponents() { // plugins + this.registerModulePackage('embark-ganache'); this.registerModulePackage('embark-geth'); this.registerModulePackage('embark-parity'); } @@ -287,7 +286,6 @@ export class Engine { } contractsComponents(_options) { - this.registerModulePackage('embark-ganache'); this.registerModulePackage('embark-ethereum-blockchain-client'); this.registerModulePackage('embark-web3'); this.registerModulePackage('embark-accounts-manager'); diff --git a/packages/core/engine/tsconfig.json b/packages/core/engine/tsconfig.json index 3c7234d697..1abf02dc45 100644 --- a/packages/core/engine/tsconfig.json +++ b/packages/core/engine/tsconfig.json @@ -94,9 +94,6 @@ { "path": "../../stack/blockchain" }, - { - "path": "../../stack/blockchain-client" - }, { "path": "../../stack/communication" }, diff --git a/packages/embark/src/test/config.js b/packages/embark/src/test/config.js index f25ca903c8..99acaeff6a 100644 --- a/packages/embark/src/test/config.js +++ b/packages/embark/src/test/config.js @@ -17,7 +17,7 @@ describe('embark.Config', function () { config.loadBlockchainConfigFile(); let expectedConfig = { "enabled": true, - "client": "geth", + "client": "ganache-cli", "proxy": true, "clientConfig": { "miningMode": "dev" @@ -54,7 +54,7 @@ describe('embark.Config', function () { it('should convert Ether units', function () { let expectedConfig = { "enabled": true, - "client": "geth", + "client": "ganache-cli", "proxy": true, "clientConfig": { "miningMode": "dev" @@ -107,7 +107,7 @@ describe('embark.Config', function () { it('should accept unitless gas values', function () { let expectedConfig = { "enabled": true, - "client": "geth", + "client": "ganache-cli", "proxy": true, "clientConfig": { "miningMode": "dev" @@ -160,7 +160,7 @@ describe('embark.Config', function () { it('should use the specified endpoint', () => { let expectedConfig = { "enabled": true, - "client": "geth", + "client": "ganache-cli", "proxy": true, "clientConfig": { "miningMode": "dev" diff --git a/packages/plugins/ethereum-blockchain-client/src/index.js b/packages/plugins/ethereum-blockchain-client/src/index.js index a1e740642f..651604de2a 100644 --- a/packages/plugins/ethereum-blockchain-client/src/index.js +++ b/packages/plugins/ethereum-blockchain-client/src/index.js @@ -6,7 +6,7 @@ const embarkJsUtils = require('embarkjs').Utils; import checkContractSize from "./checkContractSize"; const {ZERO_ADDRESS} = AddressUtils; import EthereumAPI from "./api"; - +import constants from "embark-core/constants"; class EthereumBlockchainClient { @@ -24,7 +24,7 @@ class EthereumBlockchainClient { this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.doLinking.bind(this)); this.embark.registerActionForEvent('deployment:contract:beforeDeploy', checkContractSize.bind(this)); this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.determineAccounts.bind(this)); - this.events.request("blockchain:client:register", "ethereum", this.getClient.bind(this)); + this.events.request("blockchain:client:register", "ethereum", this.getEthereumClient.bind(this)); this.events.request("deployment:deployer:register", "ethereum", this.deployer.bind(this)); this.events.on("blockchain:started", () => { @@ -50,8 +50,14 @@ class EthereumBlockchainClient { ethereumApi.registerAPIs(); } - getClient() { - return {}; + getEthereumClient(endpoint) { + if (endpoint.startsWith('ws')) { + return new Web3.providers.WebsocketProvider(endpoint, { + headers: { Origin: constants.embarkResourceOrigin } + }); + } + const web3 = new Web3(endpoint); + return web3.currentProvider; } async deployer(contract, done) { diff --git a/packages/plugins/ganache/package.json b/packages/plugins/ganache/package.json index 619d007afb..dcf638b4f9 100644 --- a/packages/plugins/ganache/package.json +++ b/packages/plugins/ganache/package.json @@ -46,6 +46,8 @@ "dependencies": { "@babel/runtime-corejs3": "7.7.4", "core-js": "3.4.3", + "embark-core": "^5.2.0-nightly.0", + "embark-i18n": "^5.1.1", "ganache-cli": "6.8.2" }, "devDependencies": { diff --git a/packages/plugins/ganache/src/index.js b/packages/plugins/ganache/src/index.js index c6f6d83b22..390baf348a 100644 --- a/packages/plugins/ganache/src/index.js +++ b/packages/plugins/ganache/src/index.js @@ -1,10 +1,80 @@ +import {__} from 'embark-i18n'; +const constants = require('embark-core/constants'); + class Ganache { constructor(embark) { - embark.events.request('blockchain:vm:register', () => { - const ganache = require('ganache-cli'); + this.embark = embark; + this.currentProvider = null; + + this.embark.events.request("blockchain:node:register", constants.blockchain.clients.ganache, { + isStartedFn: (cb) => { + cb(null, !!this.currentProvider); // Always assume it's started, because it's just a provider (nothing to start) + }, + launchFn: (cb) => { + this._getProvider(); // No need to return anything, we just want to populate currentProvider + this.embark.logger.info(__('Blockchain node is ready').cyan); + cb(); + }, + stopFn: (cb) => { + this.currentProvider = null; + cb(); + }, + provider: async (_endpoint) => { + return this._getProvider(); + } + }); + + this._registerStatusCheck(); + } + + _getProvider() { + if (this.currentProvider) { + return this.currentProvider; + } + const ganache = require('ganache-cli'); + const blockchainConfig = this.embark.config.blockchainConfig; + + // Ensure the dir exists before initiating Ganache, because Ganache has a bug + // => https://github.com/trufflesuite/ganache-cli/issues/558 + this.embark.fs.ensureDirSync(blockchainConfig.datadir); + + const hasAccounts = blockchainConfig.accounts && blockchainConfig.accounts.length; + + const isTest = this.embark.currentContext.includes('test'); + + this.currentProvider = ganache.provider({ // Default to 8000000, which is the server default // Somehow, the provider default is 6721975 - return ganache.provider({gasLimit: '0x7A1200'}); + gasLimit: blockchainConfig.targetGasLimit || '0x7A1200', + blockTime: blockchainConfig.simulatorBlocktime, + network_id: blockchainConfig.networkId || 1337, + db_path: isTest ? '' : blockchainConfig.datadir, + default_balance_ether: '99999', + mnemonic: hasAccounts || isTest ? '' : constants.blockchain.defaultMnemonic + }); + return this.currentProvider; + } + + _registerStatusCheck() { + this.embark.events.request("services:register", 'Ethereum (VM)', (cb) => { + if (!this.currentProvider) { + return cb({name: "Ethereum provider stopped", status: 'off'}); + } + + const sendMethod = (this.currentProvider.sendAsync) ? this.currentProvider.sendAsync.bind(this.currentProvider) : this.currentProvider.send.bind(this.currentProvider); + sendMethod({ + jsonrpc: '2.0', + method: 'web3_clientVersion', + params: [], + id: Date.now().toString().substring(9) + }, + (error, res) => { + if (error || !res.result) { + return cb({name: "Ethereum provider failure", status: 'off'}); + } + const versionParts = res.result.split('/'); + cb({name: `Ganache - ${versionParts[1] || ''}`, status: 'on'}); + }); }); } } diff --git a/packages/plugins/ganache/tsconfig.json b/packages/plugins/ganache/tsconfig.json index 5bd1a1db4e..6f7cf51693 100644 --- a/packages/plugins/ganache/tsconfig.json +++ b/packages/plugins/ganache/tsconfig.json @@ -8,5 +8,13 @@ "extends": "../../../tsconfig.base.json", "include": [ "src/**/*" + ], + "references": [ + { + "path": "../../core/core" + }, + { + "path": "../../core/i18n" + } ] } diff --git a/packages/plugins/geth/src/devtxs.ts b/packages/plugins/geth/src/devtxs.ts index 1b9ac681b2..5cc448094f 100644 --- a/packages/plugins/geth/src/devtxs.ts +++ b/packages/plugins/geth/src/devtxs.ts @@ -22,7 +22,7 @@ export default class DevTxs { if (!this.shouldStartDevTxs()) { return; } - const provider = await this.events.request2("blockchain:client:provider", "ethereum", this.blockchainConfig.endpoint); + const provider = await this.events.request2("blockchain:node:provider", "ethereum"); this.web3 = new Web3(provider); const accounts = await this.web3.eth.getAccounts(); diff --git a/packages/plugins/geth/src/index.js b/packages/plugins/geth/src/index.js index 4f7968dc46..375b5aa5f4 100644 --- a/packages/plugins/geth/src/index.js +++ b/packages/plugins/geth/src/index.js @@ -4,7 +4,7 @@ import { BlockchainProcessLauncher } from './blockchainProcessLauncher'; import { BlockchainClient } from './blockchain'; import { ws, rpcWithEndpoint } from './check.js'; import DevTxs from "./devtxs"; -const constants = require('embark-core/constants'); +import constants from 'embark-core/constants'; class Geth { constructor(embark) { @@ -66,8 +66,8 @@ class Geth { if (err) { this.logger.error(`Error launching blockchain process: ${err.message || err}`); } - this.setupDevTxs(); readyCb(); + this.setupDevTxs(); }); this.registerServiceCheck(); }, @@ -112,7 +112,6 @@ class Geth { rpcWithEndpoint(this.blockchainConfig.endpoint, (err, version) => this._getNodeState(err, version, cb)); } - // TODO: need to get correct port taking into account the proxy registerServiceCheck() { this.events.request("services:register", 'Ethereum', (cb) => { this._doCheck(cb); diff --git a/packages/plugins/parity/src/index.js b/packages/plugins/parity/src/index.js index b5c84beec3..5a61664ca6 100644 --- a/packages/plugins/parity/src/index.js +++ b/packages/plugins/parity/src/index.js @@ -3,7 +3,7 @@ import {BlockchainClient} from "./blockchain"; const {normalizeInput} = require('embark-utils'); import {BlockchainProcessLauncher} from './blockchainProcessLauncher'; import {ws, rpcWithEndpoint} from './check.js'; -const constants = require('embark-core/constants'); +import constants from 'embark-core/constants'; class Parity { diff --git a/packages/stack/blockchain-client/.npmrc b/packages/stack/blockchain-client/.npmrc deleted file mode 100644 index e031d34328..0000000000 --- a/packages/stack/blockchain-client/.npmrc +++ /dev/null @@ -1,4 +0,0 @@ -engine-strict = true -package-lock = false -save-exact = true -scripts-prepend-node-path = true diff --git a/packages/stack/blockchain-client/CHANGELOG.md b/packages/stack/blockchain-client/CHANGELOG.md deleted file mode 100644 index d81f01aa44..0000000000 --- a/packages/stack/blockchain-client/CHANGELOG.md +++ /dev/null @@ -1,131 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [5.1.1](https://github.com/embarklabs/embark/compare/v5.1.1-nightly.4...v5.1.1) (2020-02-03) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -## [5.1.1-nightly.2](https://github.com/embarklabs/embark/compare/v5.1.1-nightly.1...v5.1.1-nightly.2) (2020-01-31) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -# [5.1.0](https://github.com/embarklabs/embark/compare/v5.1.0-nightly.6...v5.1.0) (2020-01-27) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -# [5.1.0-nightly.1](https://github.com/embarklabs/embark/compare/v5.1.0-nightly.0...v5.1.0-nightly.1) (2020-01-20) - - -### Features - -* support Node.js v12.x and newer ([c093cf8](https://github.com/embarklabs/embark/commit/c093cf8)) - - - - - -# [5.1.0-nightly.0](https://github.com/embarklabs/embark/compare/v5.0.0...v5.1.0-nightly.0) (2020-01-17) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -# [5.0.0](https://github.com/embarklabs/embark/compare/v5.0.0-beta.0...v5.0.0) (2020-01-07) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -# [5.0.0-alpha.9](https://github.com/embarklabs/embark/compare/v5.0.0-alpha.8...v5.0.0-alpha.9) (2019-12-20) - - -### Build System - -* **deps:** bump web3[-*] from 1.2.1 to 1.2.4 ([7e550f0](https://github.com/embarklabs/embark/commit/7e550f0)) - - -### BREAKING CHANGES - -* **deps:** bump embark's minimum supported version of parity from -`>=2.0.0` to `>=2.2.1`. This is necessary since web3 1.2.4 makes use of the -`eth_chainId` RPC method (EIP 695) and that parity version is the earliest one -to implement it. - -[bug]: https://github.com/ethereum/web3.js/issues/3283 - - - - - -# [5.0.0-alpha.5](https://github.com/embarklabs/embark/compare/v5.0.0-alpha.4...v5.0.0-alpha.5) (2019-12-16) - -**Note:** Version bump only for package embark-blockchain-client - - - - - -# [5.0.0-alpha.2](https://github.com/embarklabs/embark/compare/v5.0.0-alpha.1...v5.0.0-alpha.2) (2019-12-05) - - -### Bug Fixes - -* **@embark/proxy:** Fix unsubsribe handling and add new provider ([f6f4507](https://github.com/embarklabs/embark/commit/f6f4507)) - - - - - -# [5.0.0-alpha.1](https://github.com/embarklabs/embark/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2019-11-05) - - -### Bug Fixes - -* fix ws providers to have the patch for a bigger threshold ([#2017](https://github.com/embarklabs/embark/issues/2017)) ([9e654c5](https://github.com/embarklabs/embark/commit/9e654c5)) - - - - - -# [5.0.0-alpha.0](https://github.com/embarklabs/embark/compare/v4.1.1...v5.0.0-alpha.0) (2019-10-28) - - -### Build System - -* bump all packages' engines settings ([#1985](https://github.com/embarklabs/embark/issues/1985)) ([ed02cc8](https://github.com/embarklabs/embark/commit/ed02cc8)) - - -### BREAKING CHANGES - -* node: >=10.17.0 <12.0.0 -npm: >=6.11.3 -yarn: >=1.19.1 - -node v10.17.0 is the latest in the 10.x series and is still in the Active LTS -lifecycle. Embark is still not compatible with node's 12.x and 13.x -series (because of some dependencies), otherwise it would probably make sense -to bump our minimum supported node version all the way to the most recent 12.x -release. - -npm v6.11.3 is the version that's bundled with node v10.17.0. - -yarn v1.19.1 is the most recent version as of the time node v10.17.0 was -released. diff --git a/packages/stack/blockchain-client/README.md b/packages/stack/blockchain-client/README.md deleted file mode 100644 index a99ea44304..0000000000 --- a/packages/stack/blockchain-client/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# `embark-blockchain-client` - -> Provides ability to register a blockchain technology in Embark, ie Ethereum. - -Visit [framework.embarklabs.io](https://framework.embarklabs.io/) to get started with -[Embark](https://github.com/embarklabs/embark). diff --git a/packages/stack/blockchain-client/package.json b/packages/stack/blockchain-client/package.json deleted file mode 100644 index 1953e744fc..0000000000 --- a/packages/stack/blockchain-client/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "embark-blockchain-client", - "version": "5.1.1", - "author": "Iuri Matias ", - "contributors": [], - "description": "Provides ability to register a blockchain technology in Embark, ie Ethereum.", - "homepage": "https://github.com/embarklabs/embark/tree/master/packages/blockchain-client/proxy#readme", - "bugs": "https://github.com/embarklabs/embark/issues", - "keywords": [ - "blockchain", - "dapps", - "ethereum", - "ipfs", - "serverless", - "solc", - "solidity" - ], - "files": [ - "dist" - ], - "license": "MIT", - "repository": { - "directory": "packages/stack/blockchain-client", - "type": "git", - "url": "https://github.com/embarklabs/embark.git" - }, - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "embark-collective": { - "build:node": true, - "typecheck": true - }, - "scripts": { - "_build": "npm run solo -- build", - "_typecheck": "npm run solo -- typecheck", - "ci": "npm run qa", - "clean": "npm run reset", - "lint": "eslint src/", - "qa": "npm-run-all lint _typecheck _build", - "reset": "npx rimraf dist embark-*.tgz package", - "solo": "embark-solo" - }, - "eslintConfig": { - "extends": "../../../.eslintrc.json" - }, - "dependencies": { - "@babel/runtime-corejs3": "7.7.4", - "core-js": "3.4.3", - "web3": "1.2.6" - }, - "devDependencies": { - "embark-solo": "^5.1.1", - "eslint": "5.7.0", - "npm-run-all": "4.1.5", - "rimraf": "3.0.0" - }, - "engines": { - "node": ">=10.17.0", - "npm": ">=6.11.3", - "yarn": ">=1.19.1" - } -} diff --git a/packages/stack/blockchain-client/src/index.js b/packages/stack/blockchain-client/src/index.js deleted file mode 100644 index e269dc1084..0000000000 --- a/packages/stack/blockchain-client/src/index.js +++ /dev/null @@ -1,78 +0,0 @@ -const Web3 = require('web3'); -const constants = require('embark-core/constants'); - -class BlockchainClient { - - constructor(embark, _options) { - this.embark = embark; - this.events = embark.events; - - this.blockchainClients = {}; - this.client = null; - this.vms = []; - this.events.setCommandHandler("blockchain:client:register", (clientName, blockchainClient) => { - this.blockchainClients[clientName] = blockchainClient; - this.client = blockchainClient; - }); - this.events.setCommandHandler("blockchain:vm:register", (handler) => { - this.vms.push(handler()); - }); - - // TODO: unclear currently if this belongs here so it's a bit hardcoded for now - this.events.setCommandHandler("blockchain:client:vmProvider", async (cb) => { - if (!this.vms.length) { - return cb(`Failed to get the VM provider. Please register one using 'blockchain:vm:register', or by ensuring the 'embark-ganache' package is registered.`); - } - return cb(null, this.vms[this.vms.length - 1]); - }); - this.events.setCommandHandler("blockchain:client:provider", async (clientName, endpoint, cb) => { - if (!cb && typeof endpoint === "function") { - cb = endpoint; - endpoint = null; - } - let provider; - try { - provider = await this._getProvider(clientName, endpoint); - } - catch (err) { - return cb(`Error getting provider: ${err.message || err}`); - } - cb(null, provider); - }); - - // TODO: maybe not the ideal event to listen to? - // for e.g, could wait for all stack components to be ready - // TODO: probably better to have 2 stages in engine, services start, then connections, etc.. - this.events.on("blockchain:started", (_clientName) => { - // make connections - // this.client.initAndConnect(); // and config options - // should do stuff like - // connect to endpoint given - // set default account - }); - } - async _getProvider(clientName, endpoint) { - // Passing in an endpoint allows us to customise which URL the provider connects to. - // If no endpoint is provided, the provider will connect to the proxy. - // Explicity setting an endpoint is useful for cases where we want to connect directly - // to the node (ie in the proxy). - if (!endpoint) { - // will return the proxy URL - endpoint = await this.events.request2("proxy:endpoint:get"); - } - if (endpoint.startsWith('ws')) { - return new Web3.providers.WebsocketProvider(endpoint, { - headers: { Origin: constants.embarkResourceOrigin }, - // TODO remove this when Geth fixes this: https://github.com/ethereum/go-ethereum/issues/16846 - // Edit: This has been fixed in Geth 1.9, but we don't support 1.9 yet and still support 1.8 - clientConfig: { - fragmentationThreshold: 81920 - } - }); - } - const web3 = new Web3(endpoint); - return web3.currentProvider; - } -} - -module.exports = BlockchainClient; diff --git a/packages/stack/blockchain-client/tsconfig.json b/packages/stack/blockchain-client/tsconfig.json deleted file mode 100644 index 988a48b665..0000000000 --- a/packages/stack/blockchain-client/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "declarationDir": "./dist", - "rootDir": "./src", - "tsBuildInfoFile": "./node_modules/.cache/tsc/tsconfig.embark-blockchain-client.tsbuildinfo" - }, - "extends": "../../../tsconfig.base.json", - "include": [ - "src/**/*" - ] -} diff --git a/packages/stack/blockchain/src/index.js b/packages/stack/blockchain/src/index.js index 0c9ccbd68d..b3e11fb955 100644 --- a/packages/stack/blockchain/src/index.js +++ b/packages/stack/blockchain/src/index.js @@ -1,9 +1,10 @@ import async from 'async'; import { warnIfPackageNotDefinedLocally } from 'embark-utils'; const { __ } = require('embark-i18n'); -const constants = require('embark-core/constants'); import BlockchainAPI from "./api"; +import Web3 from "web3"; +import constants from "embark-core/constants"; export default class Blockchain { constructor(embark, options) { @@ -16,6 +17,7 @@ export default class Blockchain { this.blockchainApi = new BlockchainAPI(embark); this.startedClient = null; this.plugins = options.plugins; + this.blockchainClients = {}; this.registerConsoleCommands(); @@ -28,7 +30,8 @@ export default class Blockchain { } this.blockchainNodes = {}; - this.events.setCommandHandler("blockchain:node:register", (clientName, { isStartedFn, launchFn, stopFn }) => { + this.events.setCommandHandler("blockchain:node:register", (clientName, clientFunctions) => { + const {isStartedFn, launchFn, stopFn, provider} = clientFunctions; if (!isStartedFn) { throw new Error(`Blockchain client '${clientName}' must be registered with an 'isStarted' function, client not registered.`); @@ -39,8 +42,20 @@ export default class Blockchain { if (!stopFn) { throw new Error(`Blockchain client '${clientName}' must be registered with a 'stopFn' function, client not registered.`); } + if (!provider) { + // Set default provider function + clientFunctions.provider = async () => { + if (this.blockchainConfig.endpoint.startsWith('ws')) { + return new Web3.providers.WebsocketProvider(this.blockchainConfig.endpoint, { + headers: { Origin: constants.embarkResourceOrigin } + }); + } + const web3 = new Web3(this.blockchainConfig.endpoint); + return web3.currentProvider; + }; + } - this.blockchainNodes[clientName] = { isStartedFn, launchFn, stopFn }; + this.blockchainNodes[clientName] = clientFunctions; }); this.events.setCommandHandler("blockchain:node:start", (blockchainConfig, cb) => { @@ -53,10 +68,6 @@ export default class Blockchain { this.startedClient = clientName; this.events.emit("blockchain:started", clientName); }; - if (clientName === constants.blockchain.vm) { - started(); - return cb(); - } const client = this.blockchainNodes[clientName]; @@ -80,24 +91,20 @@ export default class Blockchain { }); }); + const noStartedClientMsg = __('No blockchain client is currently started'); + const noClientFoundMsgFunction = (clientName) => __("Client %s not found in registered plugins", clientName); this.events.setCommandHandler("blockchain:node:stop", (clientName, cb) => { if (typeof clientName === 'function') { cb = clientName; clientName = this.startedClient; if (!this.startedClient) { - return cb(__('No blockchain client is currently started')); + return cb(noStartedClientMsg); } } - if (clientName === constants.blockchain.vm) { - this.startedClient = null; - this.events.emit("blockchain:stopped", clientName); - return cb(); - } - const clientFunctions = this.blockchainNodes[clientName]; if (!clientFunctions) { - return cb(__("Client %s not found in registered plugins", clientName)); + return cb(noClientFoundMsgFunction(clientName)); } clientFunctions.stopFn.apply(clientFunctions, [ @@ -108,6 +115,44 @@ export default class Blockchain { ]); this.startedClient = null; }); + // Returns the provider of the node, hence having a connection directly with the node + this.events.setCommandHandler('blockchain:node:provider', async (clientName, cb) => { + if (typeof clientName === 'function') { + cb = clientName; + clientName = this.startedClient; + if (!this.startedClient) { + return cb(noStartedClientMsg); + } + } + const clientFunctions = this.blockchainNodes[this.startedClient]; + if (!clientFunctions) { + return cb(__(noClientFoundMsgFunction(clientName))); + } + + try { + const provider = await clientFunctions.provider.apply(clientFunctions); + cb(null, provider); + } catch (e) { + cb(e); + } + }); + + this.events.setCommandHandler("blockchain:client:register", (clientName, getProviderFunction) => { + this.blockchainClients[clientName] = getProviderFunction; + }); + + this.events.setCommandHandler("blockchain:client:provider", async (clientName, cb) => { + try { + if (!this.blockchainClients[clientName]) { + throw new Error(__('No registered client of the name %s.\nRegister one using the `blockchain:client:register` request', clientName)); + } + const endpoint = await this.events.request2('proxy:endpoint:get'); + const provider = this.blockchainClients[clientName](endpoint); + cb(null, provider); + } catch (err) { + return cb(`Error getting provider: ${err.message || err}`); + } + }); this.blockchainApi.registerAPIs("ethereum"); this.blockchainApi.registerRequests("ethereum"); diff --git a/packages/stack/proxy/src/index.ts b/packages/stack/proxy/src/index.ts index 5cc87c31b9..7a059c12c2 100644 --- a/packages/stack/proxy/src/index.ts +++ b/packages/stack/proxy/src/index.ts @@ -18,7 +18,6 @@ export default class ProxyManager { private wsPort = 0; private ready = false; private isWs = false; - private isVm = false; private _endpoint: string = ""; private inited: boolean = false; @@ -29,6 +28,15 @@ export default class ProxyManager { this.host = "localhost"; + if (!this.embark.config.blockchainConfig.proxy) { + this.logger.warn(__("The proxy has been disabled -- some Embark features will not work.")); + this.logger.warn(__("Configured wallet accounts will be ignored and cannot be used in the DApp, and transactions will not be logged.")); + } + + this.setupEvents(); + } + + setupEvents() { this.events.on("blockchain:started", async (clientName: string) => { try { await this.setupProxy(clientName); @@ -40,18 +48,13 @@ export default class ProxyManager { this.logger.debug(`Error during proxy setup:\n${error.stack}`); } }); + this.events.on("blockchain:stopped", async (clientName: string, node?: string) => { this.ready = false; await this.stopProxy(); }); - if (!this.embark.config.blockchainConfig.proxy) { - this.logger.warn(__("The proxy has been disabled -- some Embark features will not work.")); - this.logger.warn(__("Configured wallet accounts will be ignored and cannot be used in the DApp, and transactions will not be logged.")); - } - this.events.setCommandHandler("proxy:endpoint:get", async (cb) => { - await this.onReady(); cb(null, (await this.endpoint)); }); } @@ -65,6 +68,7 @@ export default class ProxyManager { this._endpoint = this.embark.config.blockchainConfig.endpoint; return this._endpoint; } + await this.onReady(); await this.init(); // TODO Check if the proxy can support HTTPS, though it probably doesn't matter since it's local if (this.isWs) { @@ -109,9 +113,8 @@ export default class ProxyManager { this.rpcPort = rpcPort; this.wsPort = wsPort; - // setup proxy details - this.isVm = this.embark.config.blockchainConfig.client === constants.blockchain.vm; - this.isWs = this.isVm || (/wss?/).test(this.embark.config.blockchainConfig.endpoint); + // setup proxy details - default to WS if no endpoint + this.isWs = this.embark.config.blockchainConfig.endpoint ? (/wss?/).test(this.embark.config.blockchainConfig.endpoint) : true; } private async setupProxy(clientName: string) { @@ -126,35 +129,30 @@ export default class ProxyManager { const endpoint = this.embark.config.blockchainConfig.endpoint; // HTTP - if (!this.isVm) { - this.httpProxy = await new Proxy({ - endpoint, - events: this.events, - isWs: false, - logger: this.logger, - plugins: this.plugins, - isVm: this.isVm, - }) - .serve( - this.host, - this.rpcPort, - ); - this.logger.info(`HTTP Proxy for node endpoint ${endpoint} listening on ${buildUrl("http", this.host, this.rpcPort, "rpc")}`); - } + this.httpProxy = await new Proxy({ + endpoint, + events: this.events, + isWs: false, + logger: this.logger, + plugins: this.plugins + }) + .serve( + this.host, + this.rpcPort, + ); + this.logger.info(`HTTP Proxy for node endpoint ${endpoint} listening on ${buildUrl("http", this.host, this.rpcPort, "rpc")}`); if (this.isWs) { this.wsProxy = await new Proxy({ - endpoint, events: this.events, isWs: true, logger: this.logger, - plugins: this.plugins, - isVm: this.isVm, + plugins: this.plugins }) .serve( this.host, this.wsPort, ); - this.logger.info(`WS Proxy for node endpoint ${this.isVm ? 'vm' : endpoint} listening on ${buildUrl("ws", this.host, this.wsPort, "ws")}`); + this.logger.info(`WS Proxy for node endpoint ${endpoint} listening on ${buildUrl("ws", this.host, this.wsPort, "ws")}`); } } private stopProxy() { diff --git a/packages/stack/proxy/src/proxy.js b/packages/stack/proxy/src/proxy.js index dd57a457fc..f7ded04dad 100644 --- a/packages/stack/proxy/src/proxy.js +++ b/packages/stack/proxy/src/proxy.js @@ -4,28 +4,20 @@ import express from 'express'; import expressWs from 'express-ws'; import cors from 'cors'; const Web3RequestManager = require('web3-core-requestmanager'); -const constants = require("embark-core/constants"); const ACTION_TIMEOUT = 5000; export class Proxy { constructor(options) { - this.commList = {}; - this.receipts = {}; this.transactions = {}; - this.timeouts = {}; this.plugins = options.plugins; this.logger = options.logger; this.app = null; - this.endpoint = options.endpoint; this.events = options.events; this.isWs = options.isWs; - this.isVm = options.isVm; this.nodeSubscriptions = {}; this._requestManager = null; - this.clientName = options.isVm ? constants.blockchain.vm : constants.blockchain.ethereum; - this.events.setCommandHandler("proxy:websocket:subscribe", this.handleSubscribe.bind(this)); this.events.setCommandHandler("proxy:websocket:unsubscribe", this.handleUnsubscribe.bind(this)); } @@ -36,20 +28,15 @@ export class Proxy { get requestManager() { return (async () => { if (!this._requestManager) { - const provider = await this._createWebSocketProvider(this.endpoint); + const provider = await this._createWebSocketProvider(); this._requestManager = this._createWeb3RequestManager(provider); } return this._requestManager; })(); } - async _createWebSocketProvider(endpoint) { - // if we are using a VM (ie for tests), then try to get the VM provider - if (this.isVm) { - return this.events.request2("blockchain:client:vmProvider"); - } - // pass in endpoint to ensure we get a provider with a connection to the node - return this.events.request2("blockchain:client:provider", this.clientName, endpoint); + async _createWebSocketProvider() { + return this.events.request2("blockchain:node:provider"); } _createWeb3RequestManager(provider) { @@ -59,9 +46,10 @@ export class Proxy { async nodeReady() { try { const reqMgr = await this.requestManager; - await reqMgr.send({ method: 'eth_accounts' }); + // Using net_version instead of eth_accounts, because eth_accounts can fail if EIP1102 is not approved first + await reqMgr.send({ method: 'net_version' }); } catch (e) { - throw new Error(__(`Unable to connect to the blockchain endpoint on ${this.endpoint}`)); + throw new Error(__(`Unable to connect to the blockchain endpoint`)); } } @@ -137,8 +125,7 @@ export class Proxy { if (modifiedRequest.sendToNode !== false) { try { - const result = await this.forwardRequestToNode(modifiedRequest.request); - response.result = result; + response.result = await this.forwardRequestToNode(modifiedRequest.request); } catch (fwdReqErr) { // The node responded with an error. Set up the error so that it can be // stripped out by modifying the response (via actions for blockchain:proxy:response) @@ -181,16 +168,7 @@ export class Proxy { async handleSubscribe(clientSocket, request, response, cb) { let currentReqManager = await this.requestManager; - if (!this.isVm) { - const provider = await this._createWebSocketProvider(this.endpoint); - // creates a new long-living connection to the node - currentReqManager = this._createWeb3RequestManager(provider); - - // kill WS connetion to the node when the client connection closes - clientSocket.on('close', () => currentReqManager.provider.disconnect()); - } - - + // do the actual forward request to the node currentReqManager.send(request, (error, subscriptionId) => { if (error) { @@ -203,14 +181,10 @@ export class Proxy { this.logger.debug(`Created subscription: ${subscriptionId} for ${JSON.stringify(request.params)}`); this.logger.debug(`Subscription request: ${JSON.stringify(request)} `); - // add the websocket req manager for this subscription to memory so it - // can be referenced later - this.nodeSubscriptions[subscriptionId] = currentReqManager; - // Watch for `eth_subscribe` subscription data coming from the node. // Send the subscription data back across the originating client // connection. - currentReqManager.provider.on('data', async (subscriptionResponse, deprecatedResponse) => { + const onWsData = async (subscriptionResponse, deprecatedResponse) => { subscriptionResponse = subscriptionResponse || deprecatedResponse; // filter out any subscription data that is not meant to be passed back to the client @@ -227,7 +201,12 @@ export class Proxy { // allow modification of the node subscription data sent to the client subscriptionResponse = await this.emitActionsForResponse(subscriptionResponse, subscriptionResponse, clientSocket); this.respondWs(clientSocket, subscriptionResponse.response); - }); + }; + + // when the node sends subscription message, we can forward that to originating socket + currentReqManager.provider.on('data', onWsData); + // kill WS connetion to the node when the client connection closes + clientSocket.on('close', () => currentReqManager.provider.removeListener('data', onWsData)); // send a response to the original requesting inbound client socket // (ie the browser or embark) with the result of the subscription @@ -236,17 +215,12 @@ export class Proxy { cb(null, response); }); - + } async handleUnsubscribe(request, response, cb) { - // kill our manually created long-living connection for eth_subscribe if we have one - const subscriptionId = request.params[0]; - const currentReqManager = this.nodeSubscriptions[subscriptionId]; + const currentReqManager = await this.requestManager; - if (!currentReqManager) { - return this.logger.error(`Failed to unsubscribe from subscription '${subscriptionId}' because the proxy failed to find an active connection to the node.`); - } // forward unsubscription request to the node currentReqManager.send(request, (error, result) => { if (error) { @@ -256,14 +230,6 @@ export class Proxy { this.logger.debug(`Unsubscription result for subscription '${JSON.stringify(request.params)}': ${result} `); this.logger.debug(`Unsubscription request: ${JSON.stringify(request)} `); - - // if unsubscribe succeeded, disconnect connection and remove connection from memory - if (result === true) { - if (currentReqManager.provider && currentReqManager.provider.disconnect && !this.isVm) { - currentReqManager.provider.disconnect(); - } - delete this.nodeSubscriptions[subscriptionId]; - } // result should be true/false response.result = result; @@ -323,7 +289,6 @@ export class Proxy { this.logger.error(__('Error parsing the request in the proxy')); this.logger.error(err); // Reset the data to the original request so that it can be used anyway - result = data; calledBack = true; return reject(err); } @@ -360,7 +325,6 @@ export class Proxy { this.logger.error(err); calledBack = true; // Reset the data to the original response so that it can be used anyway - result = data; return reject(err); } calledBack = true; @@ -376,9 +340,6 @@ export class Proxy { this.server.close(); this.server = null; this.app = null; - this.commList = {}; - this.receipts = {}; this.transactions = {}; - this.timeouts = {}; } } diff --git a/packages/stack/test-runner/src/lib/index.js b/packages/stack/test-runner/src/lib/index.js index 62295cad4e..8bac68505b 100644 --- a/packages/stack/test-runner/src/lib/index.js +++ b/packages/stack/test-runner/src/lib/index.js @@ -298,7 +298,7 @@ class TestRunner { let node = options.node; if (!this.simOptions.host && (node && node === constants.blockchain.vm)) { this.simOptions.type = constants.blockchain.vm; - this.simOptions.client = constants.blockchain.vm; + this.simOptions.client = constants.blockchain.clients.ganache; } else if (this.simOptions.host || (node && node !== constants.blockchain.vm && node !== EMBARK_OPTION)) { let options = this.simOptions; if (node && node !== constants.blockchain.vm) { diff --git a/tsconfig.json b/tsconfig.json index 9a7d652c02..df745aef67 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -154,9 +154,6 @@ { "path": "packages/stack/blockchain" }, - { - "path": "packages/stack/blockchain-client" - }, { "path": "packages/stack/communication" },