feat(@embark/blockchain): make GanacheCLI the default dev blockchain

Set Ganache as a blockchain client that doesn't need to be started.
Set it as the default client, at least for development.
Move all blockchain related stuff in the blockchain component
Includes a fix by @emmizle to fix the WS connection in the proxy
This commit is contained in:
Jonathan Rainville 2020-02-14 08:31:26 -05:00
parent b2f670ba46
commit cd934f8157
28 changed files with 218 additions and 426 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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
},

View File

@ -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`);
}
});

View File

@ -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": {

View File

@ -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",

View File

@ -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",

View File

@ -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');

View File

@ -94,9 +94,6 @@
{
"path": "../../stack/blockchain"
},
{
"path": "../../stack/blockchain-client"
},
{
"path": "../../stack/communication"
},

View File

@ -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"

View File

@ -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) {

View File

@ -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": {

View File

@ -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'});
});
});
}
}

View File

@ -8,5 +8,13 @@
"extends": "../../../tsconfig.base.json",
"include": [
"src/**/*"
],
"references": [
{
"path": "../../core/core"
},
{
"path": "../../core/i18n"
}
]
}

View File

@ -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();

View File

@ -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);

View File

@ -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 {

View File

@ -1,4 +0,0 @@
engine-strict = true
package-lock = false
save-exact = true
scripts-prepend-node-path = true

View File

@ -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.

View File

@ -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).

View File

@ -1,62 +0,0 @@
{
"name": "embark-blockchain-client",
"version": "5.1.1",
"author": "Iuri Matias <iuri.matias@gmail.com>",
"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"
}
}

View File

@ -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;

View File

@ -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/**/*"
]
}

View File

@ -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");

View File

@ -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() {

View File

@ -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 = {};
}
}

View File

@ -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) {

View File

@ -154,9 +154,6 @@
{
"path": "packages/stack/blockchain"
},
{
"path": "packages/stack/blockchain-client"
},
{
"path": "packages/stack/communication"
},