2018-05-22 18:13:56 +00:00
const async = require ( 'async' ) ;
2018-05-28 15:54:31 +00:00
const child _process = require ( 'child_process' ) ;
2016-08-21 14:42:42 +00:00
2018-05-22 18:13:56 +00:00
const fs = require ( '../../core/fs.js' ) ;
2018-06-11 20:43:08 +00:00
const constants = require ( '../../constants.json' ) ;
2018-07-24 12:29:06 +00:00
const utils = require ( '../../utils/utils.js' ) ;
2017-02-19 17:51:32 +00:00
2018-05-22 18:13:56 +00:00
const GethCommands = require ( './geth_commands.js' ) ;
2018-07-13 12:56:59 +00:00
const DevFunds = require ( './dev_funds.js' ) ;
2017-02-19 17:51:32 +00:00
2018-07-15 17:30:51 +00:00
const { defaultHost , dockerHostSwap } = require ( '../../utils/host' ) ;
2018-05-28 16:49:44 +00:00
/*eslint complexity: ["error", 36]*/
2017-03-31 11:34:43 +00:00
var Blockchain = function ( options ) {
this . blockchainConfig = options . blockchainConfig ;
this . env = options . env || 'development' ;
this . client = options . client ;
2018-05-14 18:32:19 +00:00
this . isDev = options . isDev ;
2018-07-13 12:56:59 +00:00
this . onReadyCallback = options . onReadyCallback || ( ( ) => { } ) ;
2018-06-22 12:52:15 +00:00
this . onExitCallback = options . onExitCallback ;
2018-09-20 05:08:02 +00:00
this . proxyIpc = null ;
2017-03-31 11:34:43 +00:00
2018-01-12 22:16:46 +00:00
if ( ( this . blockchainConfig === { } || JSON . stringify ( this . blockchainConfig ) === '{"enabled":true}' ) && this . env !== 'development' ) {
2018-05-08 21:49:46 +00:00
console . log ( "===> " + _ _ ( "warning: running default config on a non-development environment" ) ) ;
2018-01-12 22:16:46 +00:00
}
2018-06-29 21:09:19 +00:00
let defaultWsApi = [ 'eth' , 'web3' , 'net' , 'shh' , 'debug' , 'pubsub' ] ;
2018-07-19 07:58:47 +00:00
if ( this . isDev ) {
defaultWsApi . push ( 'personal' ) ;
}
2017-03-31 11:34:43 +00:00
this . config = {
geth _bin : this . blockchainConfig . geth _bin || 'geth' ,
networkType : this . blockchainConfig . networkType || 'custom' ,
genesisBlock : this . blockchainConfig . genesisBlock || false ,
datadir : this . blockchainConfig . datadir || false ,
mineWhenNeeded : this . blockchainConfig . mineWhenNeeded || false ,
2018-07-15 17:30:51 +00:00
rpcHost : dockerHostSwap ( this . blockchainConfig . rpcHost ) || defaultHost ,
2017-03-31 11:34:43 +00:00
rpcPort : this . blockchainConfig . rpcPort || 8545 ,
rpcCorsDomain : this . blockchainConfig . rpcCorsDomain || false ,
2018-05-11 14:23:52 +00:00
networkId : this . blockchainConfig . networkId || 1337 ,
2017-03-31 11:34:43 +00:00
port : this . blockchainConfig . port || 30303 ,
nodiscover : this . blockchainConfig . nodiscover || false ,
mine : this . blockchainConfig . mine || false ,
account : this . blockchainConfig . account || { } ,
whisper : ( this . blockchainConfig . whisper === undefined ) || this . blockchainConfig . whisper ,
maxpeers : ( ( this . blockchainConfig . maxpeers === 0 ) ? 0 : ( this . blockchainConfig . maxpeers || 25 ) ) ,
bootnodes : this . blockchainConfig . bootnodes || "" ,
2018-07-06 18:48:33 +00:00
rpcApi : ( this . blockchainConfig . rpcApi || [ 'eth' , 'web3' , 'net' , 'debug' ] ) ,
2018-01-11 13:55:28 +00:00
wsRPC : ( this . blockchainConfig . wsRPC === undefined ) || this . blockchainConfig . wsRPC ,
2018-07-15 17:30:51 +00:00
wsHost : dockerHostSwap ( this . blockchainConfig . wsHost ) || defaultHost ,
2017-10-19 22:55:49 +00:00
wsPort : this . blockchainConfig . wsPort || 8546 ,
wsOrigins : this . blockchainConfig . wsOrigins || false ,
2018-07-19 07:58:47 +00:00
wsApi : ( this . blockchainConfig . wsApi || defaultWsApi ) ,
2018-01-17 16:23:32 +00:00
vmdebug : this . blockchainConfig . vmdebug || false ,
2018-01-22 07:54:49 +00:00
targetGasLimit : this . blockchainConfig . targetGasLimit || false ,
2018-06-15 20:33:59 +00:00
syncMode : this . blockchainConfig . syncMode ,
2018-07-06 13:31:28 +00:00
syncmode : this . blockchainConfig . syncmode ,
2018-06-08 21:02:45 +00:00
verbosity : this . blockchainConfig . verbosity
2017-03-31 11:34:43 +00:00
} ;
2018-01-12 22:16:46 +00:00
if ( this . blockchainConfig === { } || JSON . stringify ( this . blockchainConfig ) === '{"enabled":true}' ) {
this . config . account = { } ;
2018-03-29 23:23:24 +00:00
this . config . account . password = fs . embarkPath ( "templates/boilerplate/config/development/password" ) ;
this . config . genesisBlock = fs . embarkPath ( "templates/boilerplate/config/development/genesis.json" ) ;
2018-09-13 12:37:44 +00:00
this . config . datadir = fs . dappPath ( ".embark/development/datadir" ) ;
2018-01-12 22:16:46 +00:00
}
2018-05-28 15:55:16 +00:00
const spaceMessage = 'The path for %s in blockchain config contains spaces, please remove them' ;
2018-05-28 19:56:03 +00:00
if ( this . config . datadir && this . config . datadir . indexOf ( ' ' ) > 0 ) {
2018-05-28 15:55:16 +00:00
console . error ( _ _ ( spaceMessage , 'datadir' ) ) ;
process . exit ( ) ;
}
2018-05-28 19:56:03 +00:00
if ( this . config . account . password && this . config . account . password . indexOf ( ' ' ) > 0 ) {
2018-05-28 15:55:16 +00:00
console . error ( _ _ ( spaceMessage , 'account.password' ) ) ;
process . exit ( ) ;
}
2018-05-28 19:56:03 +00:00
if ( this . config . genesisBlock && this . config . genesisBlock . indexOf ( ' ' ) > 0 ) {
2018-05-28 15:55:16 +00:00
console . error ( _ _ ( spaceMessage , 'genesisBlock' ) ) ;
process . exit ( ) ;
}
2018-09-18 11:12:17 +00:00
this . initProxy ( ) ;
2018-05-09 13:17:48 +00:00
this . client = new options . client ( { config : this . config , env : this . env , isDev : this . isDev } ) ;
2016-08-21 14:42:42 +00:00
} ;
2018-09-18 11:12:17 +00:00
Blockchain . prototype . initProxy = function ( ) {
2018-06-11 20:26:32 +00:00
this . config . proxy = true ;
2018-06-11 16:02:00 +00:00
if ( this . blockchainConfig . proxy === false ) {
2018-06-11 20:26:32 +00:00
this . config . proxy = false ;
2018-06-11 16:02:00 +00:00
return ;
}
2018-06-11 20:26:32 +00:00
2018-09-18 11:12:17 +00:00
this . config . rpcPort += constants . blockchain . servicePortOnProxy ;
this . config . wsPort += constants . blockchain . servicePortOnProxy ;
} ;
2018-09-20 05:08:02 +00:00
Blockchain . prototype . setupProxy = async function ( type ) {
2018-08-07 22:57:49 +00:00
const proxy = require ( './proxy' ) ;
2018-06-11 16:02:00 +00:00
const Ipc = require ( '../../core/ipc' ) ;
2018-06-11 20:40:14 +00:00
2018-09-20 05:08:02 +00:00
if ( ! this . proxyIpc ) this . proxyIpc = new Ipc ( { ipcRole : 'client' } ) ;
if ( type === 'rpc' ) {
this . rpcProxy = await proxy . serve ( this . proxyIpc , this . config . rpcHost , this . config . rpcPort , false ) ;
}
else if ( type === 'ws' ) {
this . wsProxy = await proxy . serve ( this . proxyIpc , this . config . wsHost , this . config . wsPort , true , this . config . wsOrigins ) ;
}
2018-06-11 20:40:14 +00:00
} ;
2018-06-07 19:13:35 +00:00
2018-07-11 15:38:58 +00:00
Blockchain . prototype . shutdownProxy = function ( ) {
if ( ! this . config . proxy ) {
return ;
}
2018-09-20 05:08:02 +00:00
if ( this . rpcProxy ) this . rpcProxy . close ( ) ;
if ( this . wsProxy ) this . wsProxy . close ( ) ;
2018-07-11 15:44:06 +00:00
} ;
2018-07-11 15:38:58 +00:00
2018-05-22 18:13:56 +00:00
Blockchain . prototype . runCommand = function ( cmd , options , callback ) {
2018-05-08 21:49:46 +00:00
console . log ( _ _ ( "running: %s" , cmd . underline ) . green ) ;
2018-05-22 18:13:56 +00:00
if ( this . blockchainConfig . silent ) {
options . silent = true ;
}
2018-05-28 15:54:31 +00:00
return child _process . exec ( cmd , options , callback ) ;
2017-03-31 11:34:43 +00:00
} ;
2016-08-21 14:42:42 +00:00
2017-03-31 11:34:43 +00:00
Blockchain . prototype . run = function ( ) {
var self = this ;
console . log ( "===============================================================================" . magenta ) ;
console . log ( "===============================================================================" . magenta ) ;
2018-05-08 21:49:46 +00:00
console . log ( _ _ ( "Embark Blockchain Using: %s" , this . client . name . underline ) . magenta ) ;
2017-03-31 11:34:43 +00:00
console . log ( "===============================================================================" . magenta ) ;
console . log ( "===============================================================================" . magenta ) ;
2018-05-23 16:04:00 +00:00
this . checkPathLength ( ) ;
2018-05-08 20:25:48 +00:00
let address = '' ;
2018-05-22 18:13:56 +00:00
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 ) {
2018-05-28 15:54:31 +00:00
self . client . mainCommand ( address , function ( cmd , args ) {
next ( null , cmd , args ) ;
} , true ) ;
2018-05-22 18:13:56 +00:00
}
2018-09-20 02:13:19 +00:00
] , function ( err , cmd , args ) {
2018-05-22 18:13:56 +00:00
if ( err ) {
2018-08-23 14:29:39 +00:00
console . error ( err . message ) ;
2018-05-22 18:13:56 +00:00
return ;
}
2018-07-24 12:29:06 +00:00
args = utils . compact ( args ) ;
2018-06-12 17:58:21 +00:00
let full _cmd = cmd + " " + args . join ( ' ' ) ;
console . log ( _ _ ( "running: %s" , full _cmd . underline ) . green ) ;
2018-05-30 14:52:15 +00:00
self . child = child _process . spawn ( cmd , args , { cwd : process . cwd ( ) } ) ;
self . child . on ( 'error' , ( err ) => {
2018-05-28 15:54:31 +00:00
err = err . toString ( ) ;
2018-05-28 20:02:44 +00:00
console . error ( 'Blockchain error: ' , err ) ;
2018-05-28 15:54:31 +00:00
if ( self . env === 'development' && err . indexOf ( 'Failed to unlock' ) > 0 ) {
2018-05-25 03:42:18 +00:00
console . error ( '\n' + _ _ ( 'Development blockchain has changed to use the --dev option.' ) . yellow ) ;
console . error ( _ _ ( 'You can reset your workspace to fix the problem with' ) . yellow + ' embark reset' . cyan ) ;
console . error ( _ _ ( 'Otherwise, you can change your data directory in blockchain.json (datadir)' ) . yellow ) ;
2018-05-22 18:13:56 +00:00
}
} ) ;
2018-05-30 14:52:15 +00:00
self . child . stdout . on ( 'data' , ( data ) => {
2018-06-27 18:29:31 +00:00
console . error ( ` Geth error: ${ data } ` ) ;
2018-05-28 15:54:31 +00:00
} ) ;
2018-05-28 20:02:44 +00:00
// Geth logs appear in stderr somehow
2018-09-20 01:11:36 +00:00
self . child . stderr . on ( 'data' , async ( data ) => {
2018-05-28 15:54:31 +00:00
data = data . toString ( ) ;
2018-09-20 05:08:02 +00:00
if ( ! self . readyCalled && data . indexOf ( 'HTTP endpoint opened' ) > - 1 && self . config . proxy ) {
await self . setupProxy ( 'rpc' ) ;
}
2018-06-27 18:32:13 +00:00
if ( ! self . readyCalled && data . indexOf ( 'WebSocket endpoint opened' ) > - 1 ) {
2018-09-20 03:31:14 +00:00
if ( self . config . proxy ) {
2018-09-20 05:08:02 +00:00
await self . setupProxy ( 'ws' ) ;
2018-09-20 03:31:14 +00:00
}
2018-09-17 12:48:24 +00:00
if ( self . isDev ) {
self . createFundAndUnlockAccounts ( ( err ) => {
// TODO: this is never called!
if ( err ) console . error ( 'Error creating, unlocking, and funding accounts' , err ) ;
} ) ;
}
2018-05-28 15:54:31 +00:00
self . readyCalled = true ;
2018-06-27 18:32:13 +00:00
self . readyCallback ( ) ;
2018-05-28 15:54:31 +00:00
}
console . log ( 'Geth: ' + data ) ;
} ) ;
2018-05-30 14:52:15 +00:00
self . child . on ( 'exit' , ( code ) => {
2018-06-26 02:59:14 +00:00
let strCode ;
2018-05-28 15:54:31 +00:00
if ( code ) {
2018-06-22 12:52:15 +00:00
strCode = ' with error code ' + code ;
} else {
strCode = ' with no error code (manually killed?)' ;
}
console . error ( 'Geth exited' + strCode ) ;
if ( self . onExitCallback ) {
self . onExitCallback ( ) ;
}
} ) ;
self . child . on ( 'uncaughtException' , ( err ) => {
console . error ( 'Uncaught geth exception' , err ) ;
if ( self . onExitCallback ) {
self . onExitCallback ( ) ;
2018-05-28 15:54:31 +00:00
}
} ) ;
2017-03-31 11:34:43 +00:00
} ) ;
} ;
2016-08-21 14:42:42 +00:00
2018-07-13 12:56:59 +00:00
Blockchain . prototype . createFundAndUnlockAccounts = function ( cb ) {
2018-08-01 01:35:42 +00:00
DevFunds . new ( { blockchainConfig : this . config } ) . then ( devFunds => {
devFunds . createFundAndUnlockAccounts ( ( err ) => {
cb ( err ) ;
} ) ;
2018-07-13 12:56:59 +00:00
} ) ;
2018-08-21 20:12:57 +00:00
} ;
2018-06-27 18:32:13 +00:00
Blockchain . prototype . readyCallback = function ( ) {
if ( this . onReadyCallback ) {
this . onReadyCallback ( ) ;
}
2018-06-28 14:37:10 +00:00
if ( this . config . mineWhenNeeded && ! this . isDev ) {
2018-06-27 18:14:58 +00:00
const GethMiner = require ( './miner' ) ;
this . miner = new GethMiner ( ) ;
}
2018-07-16 16:48:32 +00:00
} ;
2018-07-13 12:56:59 +00:00
2018-05-30 14:52:15 +00:00
Blockchain . prototype . kill = function ( ) {
2018-07-11 15:38:58 +00:00
this . shutdownProxy ( ) ;
2018-05-30 14:52:15 +00:00
if ( this . child ) {
this . child . kill ( ) ;
}
} ;
2018-05-23 16:04:00 +00:00
Blockchain . prototype . checkPathLength = function ( ) {
2018-05-23 15:08:32 +00:00
let dappPath = fs . dappPath ( '' ) ;
2018-05-25 03:42:18 +00:00
if ( dappPath . length > 66 ) {
// console.error is captured and sent to the console output regardless of silent setting
console . error ( "===============================================================================" . yellow ) ;
console . error ( "===========> " . yellow + _ _ ( 'WARNING! DApp path length is too long: ' ) . yellow + dappPath . yellow ) ;
console . error ( "===========> " . yellow + _ _ ( 'This is known to cause issues with starting geth, please consider reducing your DApp path\'s length to 66 characters or less.' ) . yellow ) ;
console . error ( "===============================================================================" . yellow ) ;
2018-05-23 15:08:32 +00:00
}
} ;
2018-05-22 18:13:56 +00:00
Blockchain . prototype . isClientInstalled = function ( callback ) {
2018-05-28 15:55:16 +00:00
let versionCmd = this . client . determineVersionCommand ( ) ;
2018-07-04 13:40:46 +00:00
this . runCommand ( versionCmd , { } , ( err , stdout , stderr ) => {
if ( err || ! stdout || stderr . indexOf ( "not found" ) >= 0 || stdout . indexOf ( "not found" ) >= 0 ) {
2018-05-22 18:13:56 +00:00
return callback ( 'Geth not found' ) ;
}
callback ( ) ;
} ) ;
2017-12-13 22:58:07 +00:00
} ;
2018-05-22 18:13:56 +00:00
Blockchain . prototype . initChainAndGetAddress = function ( callback ) {
const self = this ;
2018-05-22 19:36:31 +00:00
let address = null ;
2018-07-03 18:55:04 +00:00
const ALREADY _INITIALIZED = 'already' ;
2017-03-31 11:34:43 +00:00
// ensure datadir exists, bypassing the interactive liabilities prompt.
2018-05-22 18:13:56 +00:00
self . datadir = '.embark/' + self . env + '/datadir' ;
async . waterfall ( [
function makeDir ( next ) {
2018-05-22 19:36:31 +00:00
fs . mkdirp ( self . datadir , ( err , _result ) => {
next ( err ) ;
} ) ;
2018-05-22 18:13:56 +00:00
} ,
function listAccounts ( next ) {
2018-07-03 18:55:04 +00:00
self . runCommand ( self . client . listAccountsCommand ( ) , { } , ( err , stdout , _stderr ) => {
if ( err || stdout === undefined || stdout . match ( /{(\w+)}/ ) === null || stdout . indexOf ( "Fatal" ) >= 0 ) {
2018-05-22 18:13:56 +00:00
console . log ( _ _ ( "no accounts found" ) . green ) ;
return next ( ) ;
}
2018-05-22 19:36:31 +00:00
console . log ( _ _ ( "already initialized" ) . green ) ;
address = stdout . match ( /{(\w+)}/ ) [ 1 ] ;
2018-07-03 18:55:04 +00:00
next ( ALREADY _INITIALIZED ) ;
2018-05-22 18:13:56 +00:00
} ) ;
} ,
function genesisBlock ( next ) {
if ( ! self . config . genesisBlock ) {
return next ( ) ;
}
2018-05-08 21:49:46 +00:00
console . log ( _ _ ( "initializing genesis block" ) . green ) ;
2018-05-22 18:13:56 +00:00
self . runCommand ( self . client . initGenesisCommmand ( ) , { } , ( err , _stdout , _stderr ) => {
next ( err ) ;
} ) ;
} ,
function newAccount ( next ) {
2018-05-22 19:36:31 +00:00
self . runCommand ( self . client . newAccountCommand ( ) , { } , ( err , stdout , _stderr ) => {
2018-05-22 18:13:56 +00:00
if ( err ) {
return next ( err ) ;
}
address = stdout . match ( /{(\w+)}/ ) [ 1 ] ;
2018-05-22 19:36:31 +00:00
next ( ) ;
2018-05-22 18:13:56 +00:00
} ) ;
2017-03-30 11:12:39 +00:00
}
2018-05-22 18:13:56 +00:00
] , ( err ) => {
2018-07-03 18:55:04 +00:00
if ( err === ALREADY _INITIALIZED ) {
err = null ;
}
2018-05-22 18:13:56 +00:00
callback ( err , address ) ;
} ) ;
2017-03-31 11:34:43 +00:00
} ;
2018-07-10 12:49:08 +00:00
var BlockchainClient = function ( blockchainConfig , client , env , onReadyCallback , onExitCallback ) {
const isDev = blockchainConfig . isDev || blockchainConfig . default ;
2018-05-18 17:27:01 +00:00
// TODO add other clients at some point
2017-03-31 11:34:43 +00:00
if ( client === 'geth' ) {
2018-06-22 12:52:15 +00:00
return new Blockchain ( { blockchainConfig , client : GethCommands , env , isDev , onReadyCallback , onExitCallback } ) ;
2016-08-21 14:42:42 +00:00
}
2018-08-30 13:53:04 +00:00
throw new Error ( 'unknown client' ) ;
2017-03-31 11:34:43 +00:00
} ;
2016-08-21 14:42:42 +00:00
module . exports = BlockchainClient ;