mirror of https://github.com/embarklabs/embark.git
Merge branch 'refactor_5_0_0' of github.com:embark-framework/embark into refactor_5_0_0
This commit is contained in:
commit
a8050cf7d2
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 || [];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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'});
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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});
|
||||
}
|
||||
});
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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;
|
Loading…
Reference in New Issue