diff --git a/lib/cmds/blockchain/blockchain.js b/lib/cmds/blockchain/blockchain.js index 51d6bf202..f784a1278 100644 --- a/lib/cmds/blockchain/blockchain.js +++ b/lib/cmds/blockchain/blockchain.js @@ -1,8 +1,9 @@ -var shelljs = require('shelljs'); +const async = require('async'); +const shelljs = require('shelljs'); -var fs = require('../../core/fs.js'); +const fs = require('../../core/fs.js'); -var GethCommands = require('./geth_commands.js'); +const GethCommands = require('./geth_commands.js'); /*eslint complexity: ["error", 35]*/ var Blockchain = function(options) { @@ -10,6 +11,7 @@ var Blockchain = function(options) { this.env = options.env || 'development'; this.client = options.client; this.isDev = options.isDev; + this.onReadyCallback = options.onReadyCallback; if ((this.blockchainConfig === {} || JSON.stringify(this.blockchainConfig) === '{"enabled":true}') && this.env !== 'development') { console.log("===> " + __("warning: running default config on a non-development environment")); @@ -55,15 +57,12 @@ var Blockchain = function(options) { this.client = new options.client({config: this.config, env: this.env, isDev: this.isDev}); }; -Blockchain.prototype.runCommand = function(cmd, options) { +Blockchain.prototype.runCommand = function(cmd, options, callback) { console.log(__("running: %s", cmd.underline).green); - return shelljs.exec(cmd, options, (err, stdout, _stderr) => { - if (err && this.env === 'development' && stdout.indexOf('Failed to unlock') > 0) { - console.warn('\n' + __('Development blockchain has changed to use the --dev option.').yellow); - console.warn(__('You can reset your workspace to fix the problem with').yellow + ' embark reset'.cyan); - console.warn(__('Otherwise, you can change your data directory in blockchain.json (datadir)').yellow); - } - }); + if (this.blockchainConfig.silent) { + options.silent = true; + } + return shelljs.exec(cmd, options, callback); }; Blockchain.prototype.run = function() { @@ -73,62 +72,121 @@ Blockchain.prototype.run = function() { console.log(__("Embark Blockchain Using: %s", this.client.name.underline).magenta); console.log("===============================================================================".magenta); console.log("===============================================================================".magenta); - if (!this.isClientInstalled()) { - console.log(__("could not find {{geth_bin}} command; is {{client_name}} installed or in the PATH?", {geth_bin: this.config.geth_bin, client_name: this.client.name}).green); - return; - } + let address = ''; - if (!this.isDev) { - address = this.initChainAndGetAddress(); - } - this.client.mainCommand(address, function(cmd) { - self.runCommand(cmd, {async: true}); + async.waterfall([ + function checkInstallation(next) { + self.isClientInstalled((err) => { + if (err) { + console.log(__("could not find {{geth_bin}} command; is {{client_name}} installed or in the PATH?", {geth_bin: this.config.geth_bin, client_name: this.client.name}).green); + return next(err); + } + next(); + }); + }, + function init(next) { + if (!self.isDev) { + return self.initChainAndGetAddress((err, addr) => { + address = addr; + next(err); + }); + } + next(); + }, + function getMainCommand(next) { + self.client.mainCommand(address, function(cmd) { + console.log(cmd); + next(null, cmd); + }); + } + ], function (err, cmd) { + if (err) { + console.error(err); + return; + } + const child = self.runCommand(cmd, {}, (err, stdout, _stderr) => { + if (err && self.env === 'development' && stdout.indexOf('Failed to unlock') > 0) { + console.warn('\n' + __('Development blockchain has changed to use the --dev option.').yellow); + console.warn(__('You can reset your workspace to fix the problem with').yellow + ' embark reset'.cyan); + console.warn(__('Otherwise, you can change your data directory in blockchain.json (datadir)').yellow); + } + }); + if (self.onReadyCallback) { + // Geth logs appear in stderr somehow + child.stderr.on('data', (data) => { + if (!self.readyCalled && data.indexOf('Mapped network port') > -1) { + self.readyCalled = true; + return self.onReadyCallback(); + } + console.log('Geth:' + data); + }); + } }); }; -Blockchain.prototype.isClientInstalled = function() { +Blockchain.prototype.isClientInstalled = function(callback) { let versionCmd = this.client.determineVersion(); - let result = this.runCommand(versionCmd); - - if (result.output === undefined || result.output.indexOf("not found") >= 0) { - return false; - } - return true; + this.runCommand(versionCmd, {}, (err, stdout, stderr) => { + if (err || stderr || !stdout || stdout.indexOf("not found") >= 0) { + return callback('Geth not found'); + } + callback(); + }); }; -Blockchain.prototype.initChainAndGetAddress = function() { +Blockchain.prototype.initChainAndGetAddress = function(callback) { + const self = this; var address = null, result; // ensure datadir exists, bypassing the interactive liabilities prompt. - this.datadir = '.embark/' + this.env + '/datadir'; - fs.mkdirpSync(this.datadir); + self.datadir = '.embark/' + self.env + '/datadir'; - // copy mining script - fs.copySync(fs.embarkPath("js"), ".embark/" + this.env + "/js", {overwrite: true}); + async.waterfall([ + function makeDir(next) { + fs.mkdirp(self.datadir, next); + }, + function copy(next) { + // copy mining script + fs.copy(fs.embarkPath("js"), ".embark/" + self.env + "/js", {overwrite: true}, next); + }, + function listAccounts(next) { + self.runCommand(self.client.listAccountsCommand(), {}, (err, stdout, stderr) => { + if (err || stderr || stdout === undefined || stdout.match(/{(\w+)}/) === null || stdout.indexOf("Fatal") >= 0) { + console.log(__("no accounts found").green); + return next(); - // check if an account already exists, create one if not, return address - result = this.runCommand(this.client.listAccountsCommand()); - if (result.output === undefined || result.output.match(/{(\w+)}/) === null || result.output.indexOf("Fatal") >= 0) { - console.log(__("no accounts found").green); - if (this.config.genesisBlock) { + } else { + console.log(__("already initialized").green); + address = result.output.match(/{(\w+)}/)[1]; + } + }); + }, + function genesisBlock(next) { + if (!self.config.genesisBlock) { + return next(); + } console.log(__("initializing genesis block").green); - result = this.runCommand(this.client.initGenesisCommmand()); + self.runCommand(self.client.initGenesisCommmand(), {}, (err, _stdout, _stderr) => { + next(err); + }); + }, + function newAccount(next) { + result = self.runCommand(self.client.newAccountCommand(), {}, (err, stdout, _stderr) => { + if (err) { + return next(err); + } + address = stdout.match(/{(\w+)}/)[1]; + }); } - - result = this.runCommand(this.client.newAccountCommand()); - address = result.output.match(/{(\w+)}/)[1]; - } else { - console.log(__("already initialized").green); - address = result.output.match(/{(\w+)}/)[1]; - } - - return address; + ], (err) => { + callback(err, address); + }); }; -var BlockchainClient = function(blockchainConfig, client, env, isDev) { +var BlockchainClient = function(blockchainConfig, client, env, isDev, onReadyCallback) { // TODO add other clients at some point if (client === 'geth') { - return new Blockchain({blockchainConfig, client: GethCommands, env, isDev}); + return new Blockchain({blockchainConfig, client: GethCommands, env, isDev, onReadyCallback}); } else { throw new Error('unknown client'); } diff --git a/lib/cmds/blockchain/blockchainProcess.js b/lib/cmds/blockchain/blockchainProcess.js index bc4f50c0a..421703e07 100644 --- a/lib/cmds/blockchain/blockchainProcess.js +++ b/lib/cmds/blockchain/blockchainProcess.js @@ -1,6 +1,9 @@ const ProcessWrapper = require('../../process/processWrapper'); const BlockchainClient = require('./blockchain'); const i18n = require('../../i18n/i18n.js'); +const constants = require('../../constants'); + +let blockchainProcess; class BlockchainProcess extends ProcessWrapper { constructor(options) { @@ -12,15 +15,26 @@ class BlockchainProcess extends ProcessWrapper { i18n.setOrDetectLocale(options.locale); - this.blockchainConfig.verbosity = 0; - this.blockchain = BlockchainClient(this.blockchainConfig, this.client, this.env, this.isDev); + this.blockchainConfig.silent = true; + this.blockchain = BlockchainClient( + this.blockchainConfig, + this.client, + this.env, + this.isDev, + this.blockchainReady.bind(this) + ); + this.blockchain.run(); } + + blockchainReady() { + blockchainProcess.send({result: constants.blockchain.blockchainReady}); + } } process.on('message', (msg) => { - if (msg.action === 'init') { - const blockchainProcess = new BlockchainProcess(msg.options); - return process.send({result: 'initiated'}); + if (msg.action === constants.blockchain.init) { + blockchainProcess = new BlockchainProcess(msg.options); + return blockchainProcess.send({result: constants.blockchain.initiated}); } }); diff --git a/lib/constants.json b/lib/constants.json index b99d13873..16e13e308 100644 --- a/lib/constants.json +++ b/lib/constants.json @@ -29,5 +29,10 @@ "build": "build", "initiated": "initiated", "built": "built" + }, + "blockchain": { + "blockchainReady": "blockchainReady", + "init": "init", + "initiated": "initiated" } } diff --git a/lib/core/engine.js b/lib/core/engine.js index a99127d8b..347aa5b4d 100644 --- a/lib/core/engine.js +++ b/lib/core/engine.js @@ -17,6 +17,7 @@ const request = require('request'); const ProcessLauncher = require('../process/processLauncher'); const utils = require('../utils/utils'); +const constants = require('../constants'); class Engine { constructor(options) { @@ -339,7 +340,8 @@ class Engine { } startBlockchainNode() { - self.isRunningBlockchain = true; + this.logger.info('Starting Blockchain node in another process'.cyan); + this.isRunningBlockchain = true; this.blockchainProcess = new ProcessLauncher({ modulePath: utils.joinPath(__dirname, '../cmds/blockchain/blockchainProcess.js'), @@ -349,7 +351,7 @@ class Engine { silent: true }); this.blockchainProcess.send({ - action: 'init', options: { + action: constants.blockchain.init, options: { blockchainConfig: this.config.blockchainConfig, client: this.client, env: this.env, @@ -358,11 +360,10 @@ class Engine { } }); - // TODO use event from process to know when node is ready - setTimeout(() => { - // Wait a couple seconds for process to start - this.startService("web3"); - }, 2000); + this.blockchainProcess.once('result', constants.blockchain.blockchainReady, () => { + this.logger.info('Blockchain node is ready'.cyan); + this.startService('web3'); + }); } libraryManagerService(_options) { diff --git a/lib/process/processWrapper.js b/lib/process/processWrapper.js index a22f15568..4bb093c93 100644 --- a/lib/process/processWrapper.js +++ b/lib/process/processWrapper.js @@ -45,6 +45,10 @@ class ProcessWrapper { } process.send({result: constants.process.log, message: messages, type}); } + + send() { + process.send(...arguments); + } } process.on('exit', () => {