Merge branch 'refactor_5_0_0' of github.com:embark-framework/embark into refactor_5_0_0

This commit is contained in:
Iuri Matias 2019-08-19 17:34:00 -04:00
commit a8050cf7d2
22 changed files with 1922 additions and 232 deletions

View File

@ -26,21 +26,20 @@
},
"main": "./dist/index.js",
"scripts": {
"build": "cross-env BABEL_ENV=node babel src --extensions \".js\" --out-dir dist --root-mode upward --source-maps",
"build": "cross-env BABEL_ENV=node babel src --extensions \".js,.ts\" --out-dir dist --root-mode upward --source-maps",
"ci": "npm run qa",
"clean": "npm run reset",
"lint": "npm-run-all lint:*",
"lint:js": "eslint src/",
"// lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"lint:ts": "tslint -c tslint.json \"src/**/*.ts\"",
"package": "npm pack",
"// qa": "npm-run-all lint typecheck build package",
"qa": "npm-run-all lint build package",
"qa": "npm-run-all lint typecheck build package",
"reset": "npx rimraf dist embark-*.tgz package",
"start": "npm run watch",
"// typecheck": "tsc",
"typecheck": "tsc",
"watch": "run-p watch:*",
"watch:build": "npm run build -- --verbose --watch",
"// watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch"
"watch:typecheck": "npm run typecheck -- --preserveWatchOutput --watch"
},
"eslintConfig": {
"extends": "../../.eslintrc.json"

View File

@ -0,0 +1,49 @@
import { ContractConfig, Logger } from "embark";
const { sha3 } = require("embark-utils");
import { ABIDefinition } from "web3/eth/abi";
export default class Contract {
private logger: Logger;
public abiDefinition?: ABIDefinition[];
public deployedAddress?: string;
public className: string = "";
public address?: string;
public args?: any[] = [];
public instanceOf?: string;
public gas?: number;
public gasPrice?: number;
public silent?: boolean = false;
public track?: boolean = true;
public deploy?: boolean = true;
public realRuntimeBytecode: string = "";
public realArgs: any[] = [];
constructor(logger: Logger, contractConfig: ContractConfig) {
this.logger = logger;
this.address = contractConfig.address;
this.args = contractConfig.args;
this.instanceOf = contractConfig.instanceOf;
this.gas = contractConfig.gas;
this.gasPrice = contractConfig.gasPrice;
this.silent = contractConfig.silent;
this.track = contractConfig.track;
this.deploy = contractConfig.deploy;
}
/**
* Calculates a hash from runtime bytecode, classname, and deploy arguments.
* Used for uniquely identifying a contract, ie in chains.json.
*/
get hash() {
return sha3(this.realRuntimeBytecode + this.className + (this.realArgs || this.args).join(","));
}
/**
* Logs a message to the console. Logs with loglevel trace if contract has it's silent
* property set (in the config or internally, ie ENS contracts). Otherwise, logs with
* info log level.
* @param {string} message message to log to the console
*/
public log(message: string) {
this.silent ? this.logger.trace(message) : this.logger.info(message);
}
}

View File

@ -1,4 +1,5 @@
import {__} from 'embark-i18n';
import Contract from './contract';
const async = require('async');
const constants = require('embark-core/constants');
const {dappPath, proposeAlternative, toposort} = require('embark-utils');
@ -263,6 +264,7 @@ class ContractsManager {
self.events.emit("status", __("Building..."));
async.eachOf(contractsConfig.contracts, (contract, className, eachCb) => {
contract = new Contract(self.logger, contract);
if (!contract.artifact) {
contract.className = className;
contract.args = contract.args || [];

View File

@ -0,0 +1,84 @@
import {__} from 'embark-i18n';
import {toChecksumAddress} from 'embark-utils';
import Web3 from "web3";
export default class DeploymentChecks {
constructor({trackingFunctions, logger, events, plugins}) {
this.trackingFunctions = trackingFunctions;
this.logger = logger;
this.events = events;
this.plugins = plugins;
this._web3 = null;
}
get web3() {
return (async () => {
if (!this._web3) {
const provider = await this.events.request2("blockchain:client:provider", "ethereum");
this._web3 = new Web3(provider);
}
return this._web3;
})();
}
async checkContractConfig(params, cb) {
const {contract} = params;
// previous event action check
if (!params.shouldDeploy) {
return cb(null, params);
}
// contract config address field set - do not deploy
if (contract.address !== undefined) {
try {
toChecksumAddress(contract.address);
} catch (e) {
return cb(e);
}
contract.deployedAddress = contract.address;
contract.log(contract.className.bold.cyan + __(" already deployed at ").green + contract.deployedAddress.bold.cyan);
params.shouldDeploy = false;
return cb(null, params);
}
cb(null, params);
}
async checkIfAlreadyDeployed(params, cb) {
const {contract} = params;
const trackedContract = this.trackingFunctions.getContract(contract);
// previous event action check
if (!params.shouldDeploy) {
return cb(null, params);
}
// contract is not already tracked - deploy
if (!trackedContract || !trackedContract.address) {
return cb(null, params);
}
// tracked contract has track field set - deploy anyway, but tell user
if (trackedContract.track === false || this.trackingFunctions.trackContracts === false) {
contract.log(contract.className.bold.cyan + __(" will be redeployed").green);
return cb(null, params);
}
// if bytecode for the contract in chains.json exists on chain - don't deploy
const web3 = await this.web3;
let codeInChain = "";
try {
codeInChain = await web3.eth.getCode(trackedContract.address);
}
catch (err) {
return cb(err);
}
if (codeInChain.length > 3) { // it is "0x" or "0x0" for empty code, depending on web3 version
contract.deployedAddress = trackedContract.address;
contract.log(contract.className.bold.cyan + __(" already deployed at ").green + contract.deployedAddress.bold.cyan);
params.shouldDeploy = false;
}
cb(null, params);
}
}

View File

@ -1,130 +1,24 @@
import { __ } from 'embark-i18n';
import { dappPath, sha3 } from 'embark-utils';
const Web3 = require('web3');
import DeploymentChecks from "./deploymentChecks";
import TrackingFunctions from "./trackingFunctions";
class DeployTracker {
constructor(embark, options) {
this.logger = embark.logger;
this.events = embark.events;
this.plugins = options.plugins;
this.fs = embark.fs;
constructor(embark, {trackContracts, env, plugins}) {
const {logger, events, fs, config} = embark;
this.embark = embark;
this.trackContracts = (options.trackContracts !== false);
// TODO: unclear where env comes from
// TODO: we should be getting the env from a request to the config
this.env = options.env;
this.chainConfig = {};
this.chainFile = embark.config.contractsConfig.tracking;
this.events.on("blockchain:started", this.loadChainTrackerFile.bind(this));
this.embark.registerActionForEvent('deployment:deployContracts:beforeAll', this.setCurrentChain.bind(this));
this.embark.registerActionForEvent("deployment:contract:deployed", this.trackAndSaveContract.bind(this));
this.embark.registerActionForEvent("deploy:contract:shouldDeploy", this.checkIfDeploymentIsNeeded.bind(this));
}
const trackingFunctions = new TrackingFunctions({config, fs, logger, events, env, trackContracts});
const deploymentChecks = new DeploymentChecks({trackingFunctions, logger, events, plugins});
trackAndSaveContract(params, cb) {
if (!this.embark.config.contractsConfig.tracking) return cb();
let contract = params.contract;
this.trackContract(contract.className, contract.realRuntimeBytecode, contract.realArgs, contract.deployedAddress);
this.save();
cb();
this.embark.events.on("blockchain:started", trackingFunctions.loadChainTrackerFile.bind(trackingFunctions));
this.embark.registerActionForEvent('deployment:deployContracts:beforeAll', trackingFunctions.setCurrentChain.bind(trackingFunctions));
this.embark.registerActionForEvent("deployment:contract:deployed", trackingFunctions.trackAndSaveContract.bind(trackingFunctions));
this.embark.registerActionForEvent("deployment:contract:shouldDeploy", deploymentChecks.checkContractConfig.bind(deploymentChecks));
this.embark.registerActionForEvent("deployment:contract:shouldDeploy", deploymentChecks.checkIfAlreadyDeployed.bind(deploymentChecks));
}
checkIfDeploymentIsNeeded(params, cb) {
if (!this.embark.config.contractsConfig.tracking) return;
if (!this.trackContracts) {
return cb(null, params);
}
let contract = params.contract;
let trackedContract = this.getContract(contract.className, contract.realRuntimeBytecode, contract.realArgs);
if (trackedContract) {
params.contract.address = trackedContract.address;
}
if (params.shouldDeploy && trackedContract) {
params.shouldDeploy = true;
}
cb(null, params);
}
loadChainTrackerFile() {
if (this.chainFile === false) return;
if (this.chainFile === undefined) this.chainFile = ".embark/chains.json";
this.chainFile = dappPath(this.chainFile);
if (!this.fs.existsSync(this.chainFile)) {
this.logger.info(this.chainFile + ' ' + __('file not found, creating it...'));
this.fs.outputJSONSync(this.chainFile, {});
}
this.chainConfig = this.fs.readJSONSync(this.chainFile);
}
setCurrentChain(_params, callback) {
if (!this.embark.config.contractsConfig.tracking) return callback();
if (this.chainFile === false) return callback();
if (this.chainConfig === false) {
this.currentChain = {contracts: []};
return callback();
}
this.getBlock(0, (err) => {
if (err) {
// Retry with block 1 (Block 0 fails with Ganache-cli using the --fork option)
return this.getBlock(1, callback);
}
callback();
});
}
async getBlock(blockNum, cb) {
let provider = await this.events.request2("blockchain:client:provider", "ethereum");
var web3 = new Web3(provider);
try {
let block = await web3.eth.getBlock(blockNum, true);
let chainId = block.hash;
if (self.chainConfig[chainId] === undefined) {
self.chainConfig[chainId] = { contracts: {} };
}
self.currentChain = self.chainConfig[chainId];
self.currentChain.name = self.env;
cb();
} catch (err) {
return cb(err);
}
}
loadConfig(config) {
this.chainConfig = config;
return this;
}
trackContract(name, code, args, address) {
if (!this.currentChain) return false;
this.currentChain.contracts[sha3(code + name + args.join(','))] = { name, address };
}
getContract(name, code, args) {
if (!this.currentChain) return false;
let contract = this.currentChain.contracts[sha3(code + name + args.join(','))];
if (contract && contract.address === undefined) {
return false;
}
return contract;
}
save() {
if (this.chainConfig === false) {
return;
}
this.fs.writeJSONSync(this.chainFile, this.chainConfig, {spaces: 2});
}
}
module.exports = DeployTracker;

View File

@ -0,0 +1,111 @@
import {__} from 'embark-i18n';
import {dappPath} from 'embark-utils';
import Web3 from 'web3';
export default class TrackingFunctions {
constructor({config, env, fs, events, logger, trackContracts}) {
this.config = config;
this.chainConfig = {};
this.chainFile = config.contractsConfig.tracking;
this.currentChain = null;
this.env = env;
this.fs = fs;
this.events = events;
this.logger = logger;
this._web3 = null;
this.trackContracts = (trackContracts !== false);
}
get web3() {
return (async () => {
if (!this._web3) {
const provider = await this.events.request2("blockchain:client:provider", "ethereum");
this._web3 = new Web3(provider);
}
return this._web3;
})();
}
getContract(contract) {
if (!this.currentChain) return false;
let contractInFile = this.currentChain.contracts[contract.hash];
if (contractInFile && contractInFile.address === undefined) {
return false;
}
return contractInFile;
}
trackAndSaveContract(params, cb) {
const {contract} = params;
if (!this.chainFile || !this.trackContracts || contract.track === false) return cb();
this.trackContract(contract);
this.save();
cb();
}
loadChainTrackerFile() {
if (this.chainFile === false) return;
if (this.chainFile === undefined) this.chainFile = ".embark/chains.json";
this.chainFile = dappPath(this.chainFile);
if (!this.fs.existsSync(this.chainFile)) {
this.logger.info(this.chainFile + ' ' + __('file not found, creating it...'));
this.fs.outputJSONSync(this.chainFile, {});
this.chainConfig = {};
return;
}
this.chainConfig = this.fs.readJSONSync(this.chainFile);
}
setCurrentChain(_params, callback) {
if (this.chainFile === false) return callback();
if (this.chainConfig === false) {
this.currentChain = {contracts: []};
return callback();
}
this.getBlock(0, (err) => {
if (err) {
// Retry with block 1 (Block 0 fails with Ganache-cli using the --fork option)
return this.getBlock(1, callback);
}
callback();
});
}
async getBlock(blockNum, cb) {
try {
const web3 = await this.web3;
let block = await web3.eth.getBlock(blockNum, true);
let chainId = block.hash;
if (this.chainConfig[chainId] === undefined) {
this.chainConfig[chainId] = {contracts: {}};
}
this.currentChain = this.chainConfig[chainId];
this.currentChain.name = this.env;
cb();
} catch (err) {
return cb(err);
}
}
loadConfig(config) {
this.chainConfig = config;
return this;
}
trackContract(contract) {
if (!this.currentChain) return false;
this.currentChain.contracts[contract.hash] = {name: contract.className, address: contract.deployedAddress};
}
save() {
if (this.chainConfig === false) {
return;
}
this.fs.writeJSONSync(this.chainFile, this.chainConfig, {spaces: 2});
}
}

View File

@ -8,7 +8,7 @@ class ContractDeployer {
this.plugins = options.plugins;
this.deployer = {};
this.events.setCommandHandler("deployment:deployer:register", (blockchainType, deployerCb) => {
this.deployer[blockchainType] = deployerCb
this.deployer[blockchainType] = deployerCb;
});
this.events.setCommandHandler('deployment:contract:deploy', this.deployContract.bind(this));
@ -28,33 +28,33 @@ class ContractDeployer {
},
(next) => {
// self.plugins.emitAndRunActionsForEvent('deployment:contract:arguments', {contract: contract}, (_params) => {
this.plugins.emitAndRunActionsForEvent('deployment:contract:shouldDeploy', {contract: contract, shouldDeploy: true}, (_params) => {
next();
this.plugins.emitAndRunActionsForEvent('deployment:contract:shouldDeploy', {contract: contract, shouldDeploy: true}, (err, params) => {
next(err, params);
});
},
(next) => {
if (contract.deploy === false) {
(params, next) => {
if (!params.shouldDeploy) {
this.events.emit("deployment:contract:undeployed", contract);
return next();
return next(null, null);
}
console.dir("deploying contract");
console.dir(contract.className);
// this.deployer[contract.blockchainType].apply(this.deployer, [contract, next])
this.deployer["ethereum"].apply(this.deployer, [contract, next])
// next();
// TODO: implement `blockchainType` a la `this.deployer[contract.blockchainType].apply(this.deployer, [contract, next])`
this.deployer["ethereum"].apply(this.deployer, [contract, next]);
},
(next) => {
console.dir("-------> contract deployed")
if (contract.deploy === false) return next();
console.dir("-------> contract deployed 2")
this.plugins.emitAndRunActionsForEvent('deployment:contract:deployed', {contract: contract}, (_params) => {
next();
(receipt, next) => {
if (!receipt) return next();
this.plugins.emitAndRunActionsForEvent('deployment:contract:deployed', {contract, receipt}, (err, _params) => {
next(err);
});
}
], callback);
], (err) => {
if (err) {
this.events.emit("deploy:contract:error", contract);
}
callback(err);
});
}
}
module.exports = ContractDeployer;

View File

@ -15,7 +15,8 @@ class Deployment {
this.contractDeployer = new ContractDeployer({
events: this.events,
plugins: this.plugins
plugins: this.plugins,
logger: this.logger
});
this.events.setCommandHandler('deployment:contracts:deploy', (contractsList, contractDependencies, cb) => {
@ -28,7 +29,7 @@ class Deployment {
async.waterfall([
// TODO used to be called this.plugins.emitAndRunActionsForEvent("deploy:beforeAll", (err) => {
(next) => {this.plugins.emitAndRunActionsForEvent('deployment:deployContracts:beforeAll', {}, () => {next()});},
(next) => { this.deployAll(contracts, contractDependencies, () => { next() }); },
(next) => {this.deployAll(contracts, contractDependencies, next);},
(next) => {
this.events.emit('contractsDeployed');
this.plugins.emitAndRunActionsForEvent('deployment:deployContracts:afterAll', {}, () => {next()});
@ -37,7 +38,7 @@ class Deployment {
], done);
}
deployContract(contract, callback) {
deployContract(contract, errors, callback) {
console.dir("requesting to deploy contract")
this.events.request('deployment:contract:deploy', contract, (err) => {
if (err) {
@ -63,7 +64,7 @@ class Deployment {
function deploy(result, callback) {
console.dir("== deploy")
if (typeof result === 'function') callback = result;
self.deployContract(contract, callback);
self.deployContract(contract, errors, callback);
}
const className = contract.className;
@ -75,23 +76,22 @@ class Deployment {
contractDeploys[className].push(deploy);
})
async.auto(contractDeploys, (_err, _results) => {
if (_err) {
async.auto(contractDeploys, (err, _results) => {
if (err) {
console.dir("error deploying contracts")
console.dir(_err)
console.dir(err)
}
if (errors.length) {
_err = __("Error deploying contracts. Please fix errors to continue.");
this.logger.error(_err);
err = __("Error deploying contracts. Please fix errors to continue.");
this.events.emit("outputError", __("Error deploying contracts, please check console"));
return done(_err);
return done(err);
}
if (contracts.length === 0) {
this.logger.info(__("no contracts found"));
return done();
}
this.logger.info(__("finished deploying contracts"));
done(_err);
done(err);
});
}

View File

@ -4,6 +4,7 @@ import "./src/remix-debug-debugtest";
export * from "./src/callbacks";
export * from "./src/contract";
export * from "./src/embark";
export * from "./src/contractsConfig";
export * from "./src/embarkConfig";
export * from "./src/logger";
export * from "./src/maybe";

View File

@ -0,0 +1,16 @@
export interface ContractsConfig {
deploy: { [name: string]: ContractConfig }
gas: string | number;
tracking: boolean | string;
}
export interface ContractConfig {
address?: string;
args?: Array<any>;
instanceOf?: string;
gas?: number;
gasPrice?: number;
silent?: boolean;
track?: boolean;
deploy?: boolean;
}

View File

@ -1,5 +1,6 @@
export interface Logger {
info(text: string): void;
warn(text: string): void;
trace(text: string): void;
error(text: string, ...args: Array<string | Error>): void;
}

View File

@ -219,7 +219,12 @@ class EmbarkController {
let _contractsConfig = await engine.events.request2("config:contractsConfig");
let contractsConfig = cloneDeep(_contractsConfig);
let [contractsList, contractDependencies] = await engine.events.request2("contracts:build", contractsConfig, compiledContracts);
try {
await engine.events.request2("deployment:contracts:deploy", contractsList, contractDependencies);
}
catch (err) {
engine.logger.error(err);
}
} else if (fileType === 'asset') {
engine.events.request('pipeline:generateAll', () => {
console.dir("outputDone")
@ -238,7 +243,12 @@ class EmbarkController {
let _contractsConfig = await engine.events.request2("config:contractsConfig");
let contractsConfig = cloneDeep(_contractsConfig);
let [contractsList, contractDependencies] = await engine.events.request2("contracts:build", contractsConfig, compiledContracts);
try {
await engine.events.request2("deployment:contracts:deploy", contractsList, contractDependencies);
}
catch (err) {
engine.logger.error(err);
}
console.dir("deployment done")
await engine.events.request2("watcher:start")
@ -805,7 +815,12 @@ class EmbarkController {
let _contractsConfig = await engine.events.request2("config:contractsConfig");
let contractsConfig = cloneDeep(_contractsConfig);
let [contractsList, contractDependencies] = await engine.events.request2("contracts:build", contractsConfig, compiledContracts);
try {
await engine.events.request2("deployment:contracts:deploy", contractsList, contractDependencies);
}
catch (err) {
engine.logger.error(err);
}
console.dir("deployment done")
await engine.events.request2('pipeline:generateAll');

View File

@ -174,7 +174,14 @@ class Engine {
isDev: this.isDev,
plugins: this.plugins,
ipc: this.ipc
})
});
this.registerModule('parity', {
client: this.client,
locale: this.locale,
isDev: this.isDev,
plugins: this.plugins,
ipc: this.ipc
});
}
testComponents() {
@ -257,6 +264,10 @@ class Engine {
return service.apply(this, [options]);
}
embarkListenerService(_options) {
this.registerModulePackage('embark-listener');
}
blockchainListenerService(_options) {
this.registerModulePackage('embark-blockchain-listener', {
ipc: this.ipc

View File

@ -1,3 +1,5 @@
import {__} from 'embark-i18n';
const async = require('async');
const Web3 = require('web3');
const embarkJsUtils = require('embarkjs').Utils;
@ -7,6 +9,7 @@ class EthereumBlockchainClient {
constructor(embark, options) {
this.embark = embark;
this.events = embark.events;
this.logger = embark.logger;
this.embark.registerActionForEvent("deployment:contract:deployed", this.addContractJSONToPipeline.bind(this));
this.embark.registerActionForEvent('deployment:contract:beforeDeploy', this.determineArguments.bind(this));
@ -52,7 +55,8 @@ class EthereumBlockchainClient {
}, true, (err, receipt) => {
contract.deployedAddress = receipt.contractAddress;
contract.transactionHash = receipt.transactionHash;
done();
contract.log(`${contract.className.bold.cyan} ${__('deployed at').green} ${receipt.contractAddress.bold.cyan} ${__("using").green} ${receipt.gasUsed} ${__("gas").green} (txHash: ${receipt.transactionHash.bold.cyan})`);
done(err, receipt);
}, (hash) => {
console.dir('hash is ' + hash);
});

View File

@ -1,6 +1,7 @@
const {normalizeInput} = require('embark-utils');
import {BlockchainProcessLauncher} from './blockchainProcessLauncher';
import {ws, rpc} from './check.js';
const constants = require('embark-core/constants');
class Geth {
@ -16,27 +17,43 @@ class Geth {
this.plugins = options.plugins;
// let plugin = this.plugins.createPlugin('gethplugin', {});
this.events.request("blockchain:node:register", "geth", (readyCb) => {
console.dir('registering blockchain node')
console.dir(readyCb)
if (!this.shouldInit()) {
return;
}
this.events.request("blockchain:node:register", constants.blockchain.clients.geth, (readyCb) => {
console.dir('registering blockchain node');
console.dir(readyCb);
this.events.request('processes:register', 'blockchain', {
launchFn: (cb) => {
// this.startBlockchainNode(readyCb);
this.startBlockchainNode(cb);
},
stopFn: (cb) => { this.stopBlockchainNode(cb); }
stopFn: (cb) => {
this.stopBlockchainNode(cb);
}
});
this.events.request("processes:launch", "blockchain", (err) => {
readyCb()
if (err) {
this.logger.error(`Error launching blockchain process: ${err.message || err}`);
}
readyCb();
});
this.registerServiceCheck()
})
this.registerServiceCheck();
});
}
shouldInit() {
return (
this.blockchainConfig.client === constants.blockchain.clients.geth &&
this.blockchainConfig.enabled
);
}
_getNodeState(err, version, cb) {
if (err) return cb({name: "Ethereum node not found", status: 'off'});
let nodeName = "go-ethereum"
let nodeName = "go-ethereum";
let versionNumber = version.split("-")[0];
let name = nodeName + " " + versionNumber + " (Ethereum)";
return cb({name, status: 'on'});

View File

@ -0,0 +1,470 @@
import {__} from 'embark-i18n';
const fs = require('fs-extra');
const async = require('async');
const {spawn, exec} = require('child_process');
const path = require('path');
const constants = require('embark-core/constants');
const ParityClient = require('./parityClient.js');
// const ParityClient = require('./parityClient.js');
// import { Proxy } from './proxy';
import {IPC} from 'embark-core';
import {compact, dappPath, defaultHost, dockerHostSwap, embarkPath, AccountParser} from 'embark-utils';
const Logger = require('embark-logger');
// time between IPC connection attempts (in ms)
const IPC_CONNECT_INTERVAL = 2000;
/*eslint complexity: ["error", 50]*/
var Blockchain = function (userConfig, clientClass) {
this.userConfig = userConfig;
this.env = userConfig.env || 'development';
this.isDev = userConfig.isDev;
this.onReadyCallback = userConfig.onReadyCallback || (() => {});
this.onExitCallback = userConfig.onExitCallback;
this.logger = userConfig.logger || new Logger({logLevel: 'debug', context: constants.contexts.blockchain}); // do not pass in events as we don't want any log events emitted
this.events = userConfig.events;
this.proxyIpc = null;
this.isStandalone = userConfig.isStandalone;
this.certOptions = userConfig.certOptions;
let defaultWsApi = clientClass.DEFAULTS.WS_API;
if (this.isDev) defaultWsApi = clientClass.DEFAULTS.DEV_WS_API;
this.config = {
silent: this.userConfig.silent,
client: this.userConfig.client,
ethereumClientBin: this.userConfig.ethereumClientBin || this.userConfig.client,
networkType: this.userConfig.networkType || clientClass.DEFAULTS.NETWORK_TYPE,
networkId: this.userConfig.networkId || clientClass.DEFAULTS.NETWORK_ID,
genesisBlock: this.userConfig.genesisBlock || false,
datadir: this.userConfig.datadir,
mineWhenNeeded: this.userConfig.mineWhenNeeded || false,
rpcHost: dockerHostSwap(this.userConfig.rpcHost) || defaultHost,
rpcPort: this.userConfig.rpcPort || 8545,
rpcCorsDomain: this.userConfig.rpcCorsDomain || false,
rpcApi: this.userConfig.rpcApi || clientClass.DEFAULTS.RPC_API,
port: this.userConfig.port || 30303,
nodiscover: this.userConfig.nodiscover || false,
mine: this.userConfig.mine || false,
account: {},
whisper: (this.userConfig.whisper !== false),
maxpeers: ((this.userConfig.maxpeers === 0) ? 0 : (this.userConfig.maxpeers || 25)),
bootnodes: this.userConfig.bootnodes || "",
wsRPC: (this.userConfig.wsRPC !== false),
wsHost: dockerHostSwap(this.userConfig.wsHost) || defaultHost,
wsPort: this.userConfig.wsPort || 8546,
wsOrigins: this.userConfig.wsOrigins || false,
wsApi: this.userConfig.wsApi || defaultWsApi,
vmdebug: this.userConfig.vmdebug || false,
targetGasLimit: this.userConfig.targetGasLimit || false,
syncMode: this.userConfig.syncMode || this.userConfig.syncmode,
verbosity: this.userConfig.verbosity,
proxy: this.userConfig.proxy
};
this.devFunds = null;
if (this.userConfig.accounts) {
const nodeAccounts = this.userConfig.accounts.find(account => account.nodeAccounts);
if (nodeAccounts) {
this.config.account = {
numAccounts: nodeAccounts.numAddresses || 1,
password: nodeAccounts.password,
balance: nodeAccounts.balance
};
}
}
if (this.userConfig === {} || this.userConfig.default || JSON.stringify(this.userConfig) === '{"client":"parity"}') {
if (this.env === 'development') {
this.isDev = true;
} else {
this.config.genesisBlock = embarkPath("templates/boilerplate/config/privatenet/genesis.json");
}
this.config.datadir = dappPath(".embark/development/datadir");
this.config.wsOrigins = this.config.wsOrigins || "http://localhost:8000";
this.config.rpcCorsDomain = this.config.rpcCorsDomain || "http://localhost:8000";
this.config.targetGasLimit = 8000000;
}
this.config.account.devPassword = path.join(this.config.datadir, "devPassword");
const spaceMessage = 'The path for %s in blockchain config contains spaces, please remove them';
if (this.config.datadir && this.config.datadir.indexOf(' ') > 0) {
this.logger.error(__(spaceMessage, 'datadir'));
process.exit(1);
}
if (this.config.account.password && this.config.account.password.indexOf(' ') > 0) {
this.logger.error(__(spaceMessage, 'accounts.password'));
process.exit(1);
}
if (this.config.genesisBlock && this.config.genesisBlock.indexOf(' ') > 0) {
this.logger.error(__(spaceMessage, 'genesisBlock'));
process.exit(1);
}
this.initProxy();
this.client = new clientClass({config: this.config, env: this.env, isDev: this.isDev});
this.initStandaloneProcess();
};
/**
* Polls for a connection to an IPC server (generally this is set up
* in the Embark process). Once connected, any logs logged to the
* Logger will be shipped off to the IPC server. In the case of `embark
* run`, the BlockchainListener module is listening for these logs.
*
* @returns {void}
*/
Blockchain.prototype.initStandaloneProcess = function () {
if (this.isStandalone) {
let logQueue = [];
// on every log logged in logger (say that 3x fast), send the log
// to the IPC serve listening (only if we're connected of course)
this.logger.events.on('log', (logLevel, message) => {
if (this.ipc.connected) {
this.ipc.request('blockchain:log', {logLevel, message});
} else {
logQueue.push({logLevel, message});
}
});
this.ipc = new IPC({ipcRole: 'client'});
// Wait for an IPC server to start (ie `embark run`) by polling `.connect()`.
// Do not kill this interval as the IPC server may restart (ie restart
// `embark run` without restarting `embark blockchain`)
setInterval(() => {
if (!this.ipc.connected) {
this.ipc.connect(() => {
if (this.ipc.connected) {
logQueue.forEach(message => {this.ipc.request('blockchain:log', message);});
logQueue = [];
this.ipc.client.on('process:blockchain:stop', () => {
this.kill();
process.exit(0);
});
}
});
}
}, IPC_CONNECT_INTERVAL);
}
};
Blockchain.prototype.initProxy = function () {
if (this.config.proxy) {
this.config.rpcPort += constants.blockchain.servicePortOnProxy;
this.config.wsPort += constants.blockchain.servicePortOnProxy;
}
};
Blockchain.prototype.setupProxy = async function () {
// if (!this.proxyIpc) this.proxyIpc = new IPC({ipcRole: 'client'});
// const addresses = AccountParser.parseAccountsConfig(this.userConfig.accounts, false, dappPath(), this.logger);
// let wsProxy;
// if (this.config.wsRPC) {
// wsProxy = new Proxy(this.proxyIpc).serve(this.config.wsHost, this.config.wsPort, true, this.config.wsOrigins, addresses, this.certOptions);
// }
// [this.rpcProxy, this.wsProxy] = await Promise.all([new Proxy(this.proxyIpc).serve(this.config.rpcHost, this.config.rpcPort, false, null, addresses, this.certOptions), wsProxy]);
};
Blockchain.prototype.shutdownProxy = function () {
// if (!this.config.proxy) {
// return;
// }
// if (this.rpcProxy) this.rpcProxy.close();
// if (this.wsProxy) this.wsProxy.close();
};
Blockchain.prototype.runCommand = function (cmd, options, callback) {
this.logger.info(__("running: %s", cmd.underline).green);
if (this.config.silent) {
options.silent = true;
}
return exec(cmd, options, callback);
};
Blockchain.prototype.run = function () {
var self = this;
this.logger.info("===============================================================================".magenta);
this.logger.info("===============================================================================".magenta);
this.logger.info(__("Embark Blockchain using %s", self.client.prettyName.underline).magenta);
this.logger.info("===============================================================================".magenta);
this.logger.info("===============================================================================".magenta);
let address = '';
async.waterfall([
function checkInstallation(next) {
self.isClientInstalled((err) => {
if (err) {
return next({message: err});
}
next();
});
},
function init(next) {
if (self.isDev) {
return self.initDevChain((err) => {
next(err);
});
}
return self.initChainAndGetAddress((err, addr) => {
address = addr;
next(err);
});
},
function getMainCommand(next) {
self.client.mainCommand(address, function (cmd, args) {
next(null, cmd, args);
}, true);
}
], function (err, cmd, args) {
if (err) {
self.logger.error(err.message);
return;
}
args = compact(args);
let full_cmd = cmd + " " + args.join(' ');
self.logger.info(__("running: %s", full_cmd.underline).green);
self.child = spawn(cmd, args, {cwd: process.cwd()});
self.child.on('error', (err) => {
err = err.toString();
self.logger.error('Blockchain error: ', err);
if (self.env === 'development' && err.indexOf('Failed to unlock') > 0) {
self.logger.error('\n' + __('Development blockchain has changed to use the --dev option.').yellow);
self.logger.error(__('You can reset your workspace to fix the problem with').yellow + ' embark reset'.cyan);
self.logger.error(__('Otherwise, you can change your data directory in blockchain.json (datadir)').yellow);
}
});
// TOCHECK I don't understand why stderr and stdout are reverted.
// This happens with Geth and Parity, so it does not seems a client problem
self.child.stdout.on('data', (data) => {
self.logger.info(`${self.client.name} error: ${data}`);
});
self.child.stderr.on('data', async (data) => {
data = data.toString();
if (!self.readyCalled && self.client.isReady(data)) {
self.readyCalled = true;
// if (self.config.proxy) {
// await self.setupProxy();
// }
self.readyCallback();
}
self.logger.info(`${self.client.name}: ${data}`);
});
self.child.on('exit', (code) => {
let strCode;
if (code) {
strCode = 'with error code ' + code;
} else {
strCode = 'with no error code (manually killed?)';
}
self.logger.error(self.client.name + ' exited ' + strCode);
if (self.onExitCallback) {
self.onExitCallback();
}
});
self.child.on('uncaughtException', (err) => {
self.logger.error('Uncaught ' + self.client.name + ' exception', err);
if (self.onExitCallback) {
self.onExitCallback();
}
});
});
};
Blockchain.prototype.readyCallback = function () {
if (this.onReadyCallback) {
this.onReadyCallback();
}
if (this.config.mineWhenNeeded && !this.isDev) {
this.miner = this.client.getMiner();
}
};
Blockchain.prototype.kill = function () {
this.shutdownProxy();
if (this.child) {
this.child.kill();
}
};
Blockchain.prototype.isClientInstalled = function (callback) {
let versionCmd = this.client.determineVersionCommand();
this.runCommand(versionCmd, {}, (err, stdout, stderr) => {
if (err || !stdout || stderr.indexOf("not found") >= 0 || stdout.indexOf("not found") >= 0) {
return callback(__('Ethereum client bin not found:') + ' ' + this.client.getBinaryPath());
}
const parsedVersion = this.client.parseVersion(stdout);
const supported = this.client.isSupportedVersion(parsedVersion);
if (supported === undefined) {
this.logger.error((__('WARNING! Ethereum client version could not be determined or compared with version range') + ' ' + this.client.versSupported + __(', for best results please use a supported version')).yellow);
} else if (!supported) {
this.logger.error((__('WARNING! Ethereum client version unsupported, for best results please use a version in range') + ' ' + this.client.versSupported).yellow);
}
callback();
});
};
Blockchain.prototype.initDevChain = function (callback) {
const self = this;
const ACCOUNTS_ALREADY_PRESENT = 'accounts_already_present';
// Init the dev chain
self.client.initDevChain(self.config.datadir, (err) => {
if (err) {
return callback(err);
}
const accountsToCreate = self.config.account && self.config.account.numAccounts;
if (!accountsToCreate) return callback();
// Create other accounts
async.waterfall([
function listAccounts(next) {
self.runCommand(self.client.listAccountsCommand(), {}, (err, stdout, _stderr) => {
if (err || stdout === undefined || stdout.indexOf("Fatal") >= 0) {
console.log(__("no accounts found").green);
return next();
}
// List current addresses
self.config.unlockAddressList = self.client.parseListAccountsCommandResultToAddressList(stdout);
// Count current addresses and remove the default account from the count (because password can be different)
let addressCount = self.config.unlockAddressList.length;
if (addressCount < accountsToCreate) {
next(null, accountsToCreate - addressCount);
} else {
next(ACCOUNTS_ALREADY_PRESENT);
}
});
},
function newAccounts(accountsToCreate, next) {
var accountNumber = 0;
async.whilst(
function () {
return accountNumber < accountsToCreate;
},
function (callback) {
accountNumber++;
self.runCommand(self.client.newAccountCommand(), {}, (err, stdout, _stderr) => {
if (err) {
return callback(err, accountNumber);
}
self.config.unlockAddressList.push(self.client.parseNewAccountCommandResultToAddress(stdout));
callback(null, accountNumber);
});
},
function (err) {
next(err);
}
);
}
], (err) => {
if (err && err !== ACCOUNTS_ALREADY_PRESENT) {
console.log(err);
return callback(err);
}
callback();
});
});
};
Blockchain.prototype.initChainAndGetAddress = function (callback) {
const self = this;
let address = null;
const ALREADY_INITIALIZED = 'already';
// ensure datadir exists, bypassing the interactive liabilities prompt.
self.datadir = self.config.datadir;
async.waterfall([
function makeDir(next) {
fs.mkdirp(self.datadir, (err, _result) => {
next(err);
});
},
function listAccounts(next) {
self.runCommand(self.client.listAccountsCommand(), {}, (err, stdout, _stderr) => {
if (err || stdout === undefined || stdout.indexOf("Fatal") >= 0) {
self.logger.info(__("no accounts found").green);
return next();
}
let firstAccountFound = self.client.parseListAccountsCommandResultToAddress(stdout);
if (firstAccountFound === undefined || firstAccountFound === "") {
console.log(__("no accounts found").green);
return next();
}
self.logger.info(__("already initialized").green);
address = firstAccountFound;
next(ALREADY_INITIALIZED);
});
},
function genesisBlock(next) {
//There's no genesis init with Parity. Custom network are set in the chain property at startup
if (!self.config.genesisBlock || self.client.name === constants.blockchain.clients.parity) {
return next();
}
self.logger.info(__("initializing genesis block").green);
self.runCommand(self.client.initGenesisCommmand(), {}, (err, _stdout, _stderr) => {
next(err);
});
},
function newAccount(next) {
self.runCommand(self.client.newAccountCommand(), {}, (err, stdout, _stderr) => {
if (err) {
return next(err);
}
address = self.client.parseNewAccountCommandResultToAddress(stdout);
next();
});
}
], (err) => {
if (err === ALREADY_INITIALIZED) {
err = null;
}
callback(err, address);
});
};
export function BlockchainClient(userConfig, options) {
if ((userConfig === {} || JSON.stringify(userConfig) === '{"enabled":true}') && options.env !== 'development') {
options.logger.info("===> " + __("warning: running default config on a non-development environment"));
}
// if client is not set in preferences, default is parity
if (!userConfig.client) userConfig.client = constants.blockchain.clients.parity;
// if clientName is set, it overrides preferences
if (options.clientName) userConfig.client = options.clientName;
// Choose correct client instance based on clientName
let clientClass;
switch (userConfig.client) {
case constants.blockchain.clients.parity:
clientClass = ParityClient;
break;
// case constants.blockchain.clients.parity:
// clientClass = ParityClient;
// break;
default:
console.error(__('Unknown client "%s". Please use one of the following: %s', userConfig.client, Object.keys(constants.blockchain.clients).join(', ')));
process.exit(1);
}
userConfig.isDev = (userConfig.isDev || userConfig.default);
userConfig.env = options.env;
userConfig.onReadyCallback = options.onReadyCallback;
userConfig.onExitCallback = options.onExitCallback;
userConfig.logger = options.logger;
userConfig.certOptions = options.certOptions;
userConfig.isStandalone = options.isStandalone;
return new Blockchain(userConfig, clientClass);
}

View File

@ -0,0 +1,57 @@
import * as i18n from 'embark-i18n';
import { ProcessWrapper } from 'embark-core';
const constants = require('embark-core/constants');
import { BlockchainClient } from './blockchain';
let blockchainProcess;
class BlockchainProcess extends ProcessWrapper {
constructor(options) {
super();
this.blockchainConfig = options.blockchainConfig;
this.client = options.client;
this.env = options.env;
this.isDev = options.isDev;
this.certOptions = options.certOptions;
i18n.setOrDetectLocale(options.locale);
this.blockchainConfig.silent = true;
this.blockchain = BlockchainClient(
this.blockchainConfig,
{
clientName: this.client,
env: this.env,
certOptions: this.certOptions,
onReadyCallback: this.blockchainReady.bind(this),
onExitCallback: this.blockchainExit.bind(this),
logger: console
}
);
this.blockchain.run();
}
blockchainReady() {
blockchainProcess.send({result: constants.blockchain.blockchainReady});
}
blockchainExit() {
// tell our parent process that ethereum client has exited
blockchainProcess.send({result: constants.blockchain.blockchainExit});
}
kill() {
this.blockchain.kill();
}
}
process.on('message', (msg) => {
if (msg === 'exit') {
return blockchainProcess.kill();
}
if (msg.action === constants.blockchain.init) {
blockchainProcess = new BlockchainProcess(msg.options);
return blockchainProcess.send({result: constants.blockchain.initiated});
}
});

View File

@ -0,0 +1,83 @@
import { __ } from 'embark-i18n';
import { ProcessLauncher } from 'embark-core';
import { joinPath } from 'embark-utils';
const constants = require('embark-core/constants');
export class BlockchainProcessLauncher {
constructor (options) {
this.events = options.events;
this.logger = options.logger;
this.normalizeInput = options.normalizeInput;
this.blockchainConfig = options.blockchainConfig;
this.locale = options.locale;
this.isDev = options.isDev;
this.client = options.client;
this.embark = options.embark;
}
processEnded(code) {
this.logger.error(__('Blockchain process ended before the end of this process. Try running blockchain in a separate process using `$ embark blockchain`. Code: %s', code));
}
startBlockchainNode(readyCb) {
this.logger.info(__('Starting Blockchain node in another process').cyan);
this.blockchainProcess = new ProcessLauncher({
name: 'blockchain',
modulePath: joinPath(__dirname, './blockchainProcess.js'),
logger: this.logger,
events: this.events,
silent: this.logger.logLevel !== 'trace',
exitCallback: this.processEnded.bind(this),
embark: this.embark
});
this.blockchainProcess.send({
action: constants.blockchain.init, options: {
blockchainConfig: this.blockchainConfig,
client: this.client,
env: this.env,
isDev: this.isDev,
locale: this.locale,
certOptions: this.embark.config.webServerConfig.certOptions,
events: this.events
}
});
this.blockchainProcess.once('result', constants.blockchain.blockchainReady, () => {
this.logger.info(__('Blockchain node is ready').cyan);
readyCb()
// this.events.emit(constants.blockchain.blockchainReady);
});
this.blockchainProcess.once('result', constants.blockchain.blockchainExit, () => {
// tell everyone that our blockchain process (ie geth) died
this.events.emit(constants.blockchain.blockchainExit);
// then kill off the blockchain process
this.blockchainProcess.kill();
});
this.events.on('logs:ethereum:enable', () => {
this.blockchainProcess.silent = false;
});
this.events.on('logs:ethereum:disable', () => {
this.blockchainProcess.silent = true;
});
this.events.on('exit', () => {
this.blockchainProcess.send('exit');
});
}
stopBlockchainNode(cb) {
if(this.blockchainProcess) {
this.events.once(constants.blockchain.blockchainExit, cb);
this.blockchainProcess.exitCallback = () => {}; // don't show error message as the process was killed on purpose
this.blockchainProcess.send('exit');
}
}
}

View File

@ -0,0 +1,47 @@
const WebSocket = require("ws");
const http = require("http");
const LIVENESS_CHECK=`{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":42}`;
const parseAndRespond = (data, cb) => {
const resp = JSON.parse(data);
const [_, version, __] = resp.result.split('/');
cb(null, version);
}
const rpc = (host, port, cb) => {
const options = {
hostname: host, // TODO(andremedeiros): get from config
port: port, // TODO(andremedeiros): get from config
method: "POST",
timeout: 1000,
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(LIVENESS_CHECK)
}
};
const req = http.request(options, (res) => {
let data = "";
res.on("data", chunk => data = data + chunk);
res.on("end", () => parseAndRespond(data, cb));
});
req.on("error", (e) => cb(e));
req.write(LIVENESS_CHECK);
req.end();
}
const ws = (host, port, cb) => {
const conn = new WebSocket("ws://" + host + ":" + port);
conn.on("message", (data) => {
parseAndRespond(data, cb)
conn.close();
});
conn.on("open", () => conn.send(LIVENESS_CHECK));
conn.on("error", (e) => cb(e));
}
module.exports = {
ws,
rpc
}

View File

@ -0,0 +1,103 @@
const {normalizeInput} = require('embark-utils');
import {BlockchainProcessLauncher} from './blockchainProcessLauncher';
import {ws, rpc} from './check.js';
const constants = require('embark-core/constants');
class Parity {
constructor(embark, options) {
this.embark = embark;
this.embarkConfig = embark.config.embarkConfig;
this.blockchainConfig = embark.config.blockchainConfig;
this.locale = options.locale;
this.logger = embark.logger;
this.client = options.client;
this.isDev = options.isDev;
this.events = embark.events;
this.plugins = options.plugins;
// let plugin = this.plugins.createPlugin('gethplugin', {});
if (!this.shouldInit()) {
return;
}
this.events.request("blockchain:node:register", constants.blockchain.clients.parity, (readyCb) => {
console.dir('registering blockchain node');
console.dir(readyCb);
this.events.request('processes:register', 'blockchain', {
launchFn: (cb) => {
// this.startBlockchainNode(readyCb);
this.startBlockchainNode(cb);
},
stopFn: (cb) => {
this.stopBlockchainNode(cb);
}
});
this.events.request("processes:launch", "blockchain", (err) => {
if (err) {
this.logger.error(`Error launching blockchain process: ${err.message || err}`);
}
readyCb();
});
this.registerServiceCheck();
});
}
shouldInit() {
return (
this.blockchainConfig.client === constants.blockchain.clients.geth &&
this.blockchainConfig.enabled
);
}
_getNodeState(err, version, cb) {
if (err) return cb({name: "Ethereum node not found", status: 'off'});
let nodeName = "parity";
let versionNumber = version.split("-")[0];
let name = nodeName + " " + versionNumber + " (Ethereum)";
return cb({name, status: 'on'});
}
// TODO: need to get correct port taking into account the proxy
registerServiceCheck() {
this.events.request("services:register", 'Ethereum', (cb) => {
const {rpcHost, rpcPort, wsRPC, wsHost, wsPort} = this.blockchainConfig;
if (wsRPC) {
return ws(wsHost, wsPort + 10, (err, version) => this._getNodeState(err, version, cb));
}
rpc(rpcHost, rpcPort + 10, (err, version) => this._getNodeState(err, version, cb));
}, 5000, 'off');
}
startBlockchainNode(callback) {
this.blockchainProcess = new BlockchainProcessLauncher({
events: this.events,
logger: this.logger,
normalizeInput,
blockchainConfig: this.blockchainConfig,
locale: this.locale,
client: this.client,
isDev: this.isDev,
embark: this.embark
});
this.blockchainProcess.startBlockchainNode(callback);
}
stopBlockchainNode(cb) {
const message = __(`The blockchain process has been stopped. It can be restarted by running ${"service blockchain on".bold} in the Embark console.`);
if (!this.blockchainProcess) {
return cb();
}
this.blockchainProcess.stopBlockchainNode(() => {
this.logger.info(message);
cb();
});
}
}
module.exports = Parity;

View File

@ -0,0 +1,319 @@
const async = require('async');
const NetcatClient = require('netcat/client');
import { ipcPath } from 'embark-utils';
//Constants
const minerStart = 'miner_start';
const minerStop = 'miner_stop';
const getHashRate = 'miner_getHashrate';
const getCoinbase = 'eth_coinbase';
const getBalance = 'eth_getBalance';
const newBlockFilter = 'eth_newBlockFilter';
const pendingBlockFilter = 'eth_newPendingTransactionFilter';
const getChanges = 'eth_getFilterChanges';
const getBlockCount = 'eth_getBlockTransactionCountByNumber';
class GethMiner {
constructor(options) {
const self = this;
// TODO: Find a way to load mining config from YML.
// In the meantime, just set an empty config object
this.config = {};
this.datadir = options.datadir;
self.interval = null;
self.callback = null;
self.started = null;
self.commandQueue = async.queue((task, callback) => {
self.callback = callback;
self.client.send(JSON.stringify({"jsonrpc": "2.0", "method": task.method, "params": task.params || [], "id": 1}));
}, 1);
const defaults = {
interval_ms: 15000,
initial_ether: 15000000000000000000,
mine_pending_txns: true,
mine_periodically: false,
mine_normally: false,
threads: 1
};
for (let key in defaults) {
if (this.config[key] === undefined) {
this.config[key] = defaults[key];
}
}
this.client = new NetcatClient();
this.client.unixSocket(ipcPath('geth.ipc', true))
.enc('utf8')
.connect()
.on('data', (response) => {
try {
response = JSON.parse(response);
} catch (e) {
console.error(e);
return;
}
if (self.callback) {
self.callback(response.error, response.result);
}
});
if (this.config.mine_normally) {
this.startMiner();
return;
}
self.stopMiner(() => {
self.fundAccount(function (err) {
if (err) {
console.error(err);
return;
}
if (self.config.mine_periodically) self.start_periodic_mining();
if (self.config.mine_pending_txns) self.start_transaction_mining();
});
});
}
sendCommand(method, params, callback) {
if (typeof params === 'function') {
callback = params;
params = [];
}
if (!callback) {
callback = function () {
};
}
this.commandQueue.push({method, params: params || []}, callback);
}
startMiner(callback) {
if (this.started) {
if (callback) {
callback();
}
return;
}
this.started = true;
this.sendCommand(minerStart, callback);
}
stopMiner(callback) {
if (!this.started) {
if (callback) {
callback();
}
return;
}
this.started = false;
this.sendCommand(minerStop, callback);
}
getCoinbase(callback) {
if (this.coinbase) {
return callback(null, this.coinbase);
}
this.sendCommand(getCoinbase, (err, result) => {
if (err) {
return callback(err);
}
this.coinbase = result;
if (!this.coinbase) {
return callback('Failed getting coinbase account');
}
callback(null, this.coinbase);
});
}
accountFunded(callback) {
const self = this;
self.getCoinbase((err, coinbase) => {
if (err) {
return callback(err);
}
self.sendCommand(getBalance, [coinbase, 'latest'], (err, result) => {
if (err) {
return callback(err);
}
callback(null, parseInt(result, 16) >= self.config.initial_ether);
});
});
}
watchBlocks(filterCommand, callback, delay) {
const self = this;
self.sendCommand(filterCommand, (err, filterId) => {
if (err) {
return callback(err);
}
self.interval = setInterval(() => {
self.sendCommand(getChanges, [filterId], (err, changes) => {
if (err) {
console.error(err);
return;
}
if (!changes || !changes.length) {
return;
}
callback(null, changes);
});
}, delay || 1000);
});
}
mineUntilFunded(callback) {
const self = this;
this.startMiner();
self.watchBlocks(newBlockFilter, (err) => {
if (err) {
console.error(err);
return;
}
self.accountFunded((err, funded) => {
if (funded) {
clearTimeout(self.interval);
self.stopMiner();
callback();
}
});
});
}
fundAccount(callback) {
const self = this;
self.accountFunded((err, funded) => {
if (err) {
return callback(err);
}
if (funded) {
return callback();
}
console.log("== Funding account");
self.mineUntilFunded(callback);
});
}
pendingTransactions(callback) {
const self = this;
self.sendCommand(getBlockCount, ['pending'], (err, hexCount) => {
if (err) {
return callback(err);
}
callback(null, parseInt(hexCount, 16));
});
}
start_periodic_mining() {
const self = this;
const WAIT = 'wait';
let last_mined_ms = Date.now();
let timeout_set = false;
let next_block_in_ms;
self.startMiner();
self.watchBlocks(newBlockFilter, (err) => {
if (err) {
console.error(err);
return;
}
if (timeout_set) {
return;
}
async.waterfall([
function checkPendingTransactions(next) {
if (!self.config.mine_pending_txns) {
return next();
}
self.pendingTransactions((err, count) => {
if (err) {
return next(err);
}
if (count) {
return next(WAIT);
}
next();
});
},
function stopMiner(next) {
timeout_set = true;
const now = Date.now();
const ms_since_block = now - last_mined_ms;
last_mined_ms = now;
if (ms_since_block > self.config.interval_ms) {
next_block_in_ms = 0;
} else {
next_block_in_ms = (self.config.interval_ms - ms_since_block);
}
self.stopMiner();
console.log("== Looking for next block in " + next_block_in_ms + "ms");
next();
},
function startAfterTimeout(next) {
setTimeout(function () {
console.log("== Looking for next block");
timeout_set = false;
self.startMiner();
next();
}, next_block_in_ms);
}
], (err) => {
if (err === WAIT) {
return;
}
if (err) {
console.error(err);
}
});
});
}
start_transaction_mining() {
const self = this;
const pendingTrasactionsMessage = "== Pending transactions! Looking for next block...";
self.watchBlocks(pendingBlockFilter, (err) => {
if (err) {
console.error(err);
return;
}
self.sendCommand(getHashRate, (err, result) => {
if (result > 0) return;
console.log(pendingTrasactionsMessage);
self.startMiner();
});
}, 2000);
if (self.config.mine_periodically) return;
self.watchBlocks(newBlockFilter, (err) => {
if (err) {
console.error(err);
return;
}
self.pendingTransactions((err, count) => {
if (err) {
console.error(err);
return;
}
if (!count) {
console.log("== No transactions left. Stopping miner...");
self.stopMiner();
} else {
console.log(pendingTrasactionsMessage);
self.startMiner();
}
});
}, 2000);
}
}
module.exports = GethMiner;

View File

@ -0,0 +1,407 @@
import {__} from 'embark-i18n';
import {dappPath} from 'embark-utils';
import * as fs from 'fs-extra';
const async = require('async');
const path = require('path');
const os = require('os');
const semver = require('semver');
const constants = require('embark-core/constants');
const DEFAULTS = {
"BIN": "parity",
"VERSIONS_SUPPORTED": ">=2.0.0",
"NETWORK_TYPE": "dev",
"NETWORK_ID": 17,
"RPC_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"],
"WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"],
"DEV_WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub", "personal"],
"TARGET_GAS_LIMIT": 8000000,
"DEV_ACCOUNT": "0x00a329c0648769a73afac7f9381e08fb43dbea72",
"DEV_WALLET": {
"id": "d9460e00-6895-8f58-f40c-bb57aebe6c00",
"version": 3,
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": {"iv": "74245f453143f9d06a095c6e6e309d5d"},
"ciphertext": "2fa611c4aa66452ef81bd1bd288f9d1ed14edf61aa68fc518093f97c791cf719",
"kdf": "pbkdf2",
"kdfparams": {"c": 10240, "dklen": 32, "prf": "hmac-sha256", "salt": "73b74e437a1144eb9a775e196f450a23ab415ce2c17083c225ddbb725f279b98"},
"mac": "f5882ae121e4597bd133136bf15dcbcc1bb2417a25ad205041a50c59def812a8"
},
"address": "00a329c0648769a73afac7f9381e08fb43dbea72",
"name": "Development Account",
"meta": "{\"description\":\"Never use this account outside of development chain!\",\"passwordHint\":\"Password is empty string\"}"
}
};
const safePush = function (set, value) {
if (set.indexOf(value) === -1) {
set.push(value);
}
};
class ParityClient {
static get DEFAULTS() {
return DEFAULTS;
}
constructor(options) {
this.config = options && options.hasOwnProperty('config') ? options.config : {};
this.env = options && options.hasOwnProperty('env') ? options.env : 'development';
this.isDev = options && options.hasOwnProperty('isDev') ? options.isDev : (this.env === 'development');
this.name = constants.blockchain.clients.parity;
this.prettyName = "Parity-Ethereum (https://github.com/paritytech/parity-ethereum)";
this.bin = this.config.ethereumClientBin || DEFAULTS.BIN;
this.versSupported = DEFAULTS.VERSIONS_SUPPORTED;
}
isReady(data) {
return data.indexOf('Public node URL') > -1;
}
/**
* Check if the client needs some sort of 'keep alive' transactions to avoid freezing by inactivity
* @returns {boolean} if keep alive is needed
*/
needKeepAlive() {
return false;
}
commonOptions() {
let config = this.config;
let cmd = [];
cmd.push(this.determineNetworkType(config));
if (config.networkId) {
cmd.push(`--network-id=${config.networkId}`);
}
if (config.datadir) {
cmd.push(`--base-path=${config.datadir}`);
}
if (config.syncMode === 'light') {
cmd.push("--light");
} else if (config.syncMode === 'fast') {
cmd.push("--pruning=fast");
} else if (config.syncMode === 'full') {
cmd.push("--pruning=archive");
}
// In dev mode we store all users passwords in the devPassword file, so Parity can unlock all users from the start
if (this.isDev) cmd.push(`--password=${config.account.devPassword}`);
else if (config.account && config.account.password) {
cmd.push(`--password=${config.account.password}`);
}
if (Number.isInteger(config.verbosity) && config.verbosity >= 0 && config.verbosity <= 5) {
switch (config.verbosity) {
case 0: // No option to silent Parity, go to less verbose
case 1:
cmd.push("--logging=error");
break;
case 2:
cmd.push("--logging=warn");
break;
case 3:
cmd.push("--logging=info");
break;
case 4: // Debug is the max verbosity for Parity
case 5:
cmd.push("--logging=debug");
break;
default:
cmd.push("--logging=info");
break;
}
}
if (this.runAsArchival(config)) {
cmd.push("--pruning=archive");
}
return cmd;
}
getMiner() {
console.warn(__("Miner requested, but Parity does not embed a miner! Use Geth or install ethminer (https://github.com/ethereum-mining/ethminer)").yellow);
return;
}
getBinaryPath() {
return this.bin;
}
determineVersionCommand() {
return this.bin + " --version";
}
parseVersion(rawVersionOutput) {
let parsed;
const match = rawVersionOutput.match(/version Parity(?:-Ethereum)?\/(.*?)\//);
if (match) {
parsed = match[1].trim();
}
return parsed;
}
runAsArchival(config) {
return config.networkId === 1337 || config.archivalMode;
}
isSupportedVersion(parsedVersion) {
let test;
try {
let v = semver(parsedVersion);
v = `${v.major}.${v.minor}.${v.patch}`;
test = semver.Range(this.versSupported).test(semver(v));
if (typeof test !== 'boolean') {
test = undefined;
}
} finally {
// eslint-disable-next-line no-unsafe-finally
return test;
}
}
determineNetworkType(config) {
if (this.isDev) {
return "--chain=dev";
}
if (config.networkType === 'rinkeby') {
console.warn(__('Parity does not support the Rinkeby PoA network, switching to Kovan PoA network'));
config.networkType = 'kovan';
} else if (config.networkType === 'testnet') {
console.warn(__('Parity "testnet" corresponds to Kovan network, switching to Ropsten to be compliant with Geth parameters'));
config.networkType = "ropsten";
}
if (config.genesisBlock) {
config.networkType = config.genesisBlock;
}
return "--chain=" + config.networkType;
}
newAccountCommand() {
return this.bin + " " + this.commonOptions().join(' ') + " account new ";
}
parseNewAccountCommandResultToAddress(data = "") {
return data.replace(/^\n|\n$/g, "");
}
listAccountsCommand() {
return this.bin + " " + this.commonOptions().join(' ') + " account list ";
}
parseListAccountsCommandResultToAddress(data = "") {
return data.replace(/^\n|\n$/g, "").split('\n')[0];
}
parseListAccountsCommandResultToAddressList(data = "") {
const list = data.split('\n');
return list.filter(acc => acc);
}
parseListAccountsCommandResultToAddressCount(data = "") {
const count = this.parseListAccountsCommandResultToAddressList(data).length;
return (count > 0 ? count : 0);
}
determineRpcOptions(config) {
let cmd = [];
cmd.push("--port=" + config.port);
cmd.push("--jsonrpc-port=" + config.rpcPort);
cmd.push("--jsonrpc-interface=" + (config.rpcHost === 'localhost' ? 'local' : config.rpcHost));
if (config.rpcCorsDomain) {
if (config.rpcCorsDomain === '*') {
console.warn('==================================');
console.warn(__('rpcCorsDomain set to "all"'));
console.warn(__('make sure you know what you are doing'));
console.warn('==================================');
}
cmd.push("--jsonrpc-cors=" + (config.rpcCorsDomain === '*' ? 'all' : config.rpcCorsDomain));
} else {
console.warn('==================================');
console.warn(__('warning: cors is not set'));
console.warn('==================================');
}
cmd.push("--jsonrpc-hosts=all");
return cmd;
}
determineWsOptions(config) {
let cmd = [];
if (config.wsRPC) {
cmd.push("--ws-port=" + config.wsPort);
cmd.push("--ws-interface=" + (config.wsHost === 'localhost' ? 'local' : config.wsHost));
if (config.wsOrigins) {
const origins = config.wsOrigins.split(',');
if (origins.includes('*') || origins.includes("all")) {
console.warn('==================================');
console.warn(__('wsOrigins set to "all"'));
console.warn(__('make sure you know what you are doing'));
console.warn('==================================');
cmd.push("--ws-origins=all");
} else {
cmd.push("--ws-origins=" + config.wsOrigins);
}
} else {
console.warn('==================================');
console.warn(__('warning: wsOrigins is not set'));
console.warn('==================================');
}
cmd.push("--ws-hosts=all");
}
return cmd;
}
initDevChain(datadir, callback) {
// Parity requires specific initialization also for the dev chain
const self = this;
const keysDataDir = datadir + '/keys/DevelopmentChain';
async.waterfall([
function makeDir(next) {
fs.mkdirp(keysDataDir, (err, _result) => {
next(err);
});
},
function createDevAccount(next) {
self.createDevAccount(keysDataDir, next);
},
function mkDevPasswordDir(next) {
fs.mkdirp(path.dirname(self.config.account.devPassword), (err, _result) => {
next(err);
});
},
function getText(next) {
if (!self.config.account.password) {
return next(null, os.EOL + 'dev_password');
}
fs.readFile(dappPath(self.config.account.password), {encoding: 'utf8'}, (err, content) => {
next(err, os.EOL + content);
});
},
function updatePasswordFile(passwordList, next) {
fs.writeFile(self.config.account.devPassword, passwordList, next);
}
], (err) => {
callback(err);
});
}
createDevAccount(keysDataDir, cb) {
const devAccountWallet = keysDataDir + '/dev.wallet';
fs.writeFile(devAccountWallet, JSON.stringify(DEFAULTS.DEV_WALLET), function (err) {
if (err) {
return cb(err);
}
cb();
});
}
mainCommand(address, done) {
let self = this;
let config = this.config;
let rpc_api = this.config.rpcApi;
let ws_api = this.config.wsApi;
let args = [];
async.series([
function commonOptions(callback) {
let cmd = self.commonOptions();
args = args.concat(cmd);
callback(null, cmd);
},
function rpcOptions(callback) {
let cmd = self.determineRpcOptions(self.config);
args = args.concat(cmd);
callback(null, cmd);
},
function wsOptions(callback) {
let cmd = self.determineWsOptions(self.config);
args = args.concat(cmd);
callback(null, cmd);
},
function dontGetPeers(callback) {
if (config.nodiscover) {
args.push("--no-discovery");
return callback(null, "--no-discovery");
}
callback(null, "");
},
function vmDebug(callback) {
if (config.vmdebug) {
args.push("--tracing on");
return callback(null, "--tracing on");
}
callback(null, "");
},
function maxPeers(callback) {
let cmd = "--max-peers=" + config.maxpeers;
args.push(cmd);
callback(null, cmd);
},
function bootnodes(callback) {
if (config.bootnodes && config.bootnodes !== "" && config.bootnodes !== []) {
args.push("--bootnodes=" + config.bootnodes);
return callback(null, "--bootnodes=" + config.bootnodes);
}
callback("");
},
function whisper(callback) {
if (config.whisper) {
safePush(rpc_api, 'shh');
safePush(rpc_api, 'shh_pubsub');
safePush(ws_api, 'shh');
safePush(ws_api, 'shh_pubsub');
args.push("--whisper");
return callback(null, "--whisper");
}
callback("");
},
function rpcApi(callback) {
args.push('--jsonrpc-apis=' + rpc_api.join(','));
callback(null, '--jsonrpc-apis=' + rpc_api.join(','));
},
function wsApi(callback) {
args.push('--ws-apis=' + ws_api.join(','));
callback(null, '--ws-apis=' + ws_api.join(','));
},
function accountToUnlock(callback) {
if (self.isDev) {
let unlockAddressList = self.config.unlockAddressList ? self.config.unlockAddressList : DEFAULTS.DEV_ACCOUNT;
args.push("--unlock=" + unlockAddressList);
return callback(null, "--unlock=" + unlockAddressList);
}
let accountAddress = "";
if (config.account && config.account.address) {
accountAddress = config.account.address;
} else {
accountAddress = address;
}
if (accountAddress && !self.isDev) {
args.push("--unlock=" + accountAddress);
return callback(null, "--unlock=" + accountAddress);
}
callback(null, "");
},
function gasLimit(callback) {
if (config.targetGasLimit) {
args.push("--gas-floor-target=" + config.targetGasLimit);
return callback(null, "--gas-floor-target=" + config.targetGasLimit);
}
// Default Parity gas limit is 4700000: let's set to the geth default
args.push("--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT);
return callback(null, "--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT);
}
], function (err) {
if (err) {
throw new Error(err.message);
}
return done(self.bin, args);
});
}
}
module.exports = ParityClient;