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' ) ;
const _ = require ( 'underscore' ) ;
2016-08-21 14:42:42 +00:00
2018-05-22 18:13:56 +00:00
const fs = require ( '../../core/fs.js' ) ;
2017-02-19 17:51:32 +00:00
2018-05-22 18:13:56 +00:00
const GethCommands = require ( './geth_commands.js' ) ;
2017-02-19 17:51:32 +00:00
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-05-22 18:13:56 +00:00
this . onReadyCallback = options . onReadyCallback ;
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
}
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 ,
rpcHost : this . blockchainConfig . rpcHost || 'localhost' ,
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 || "" ,
rpcApi : ( this . blockchainConfig . rpcApi || [ 'eth' , 'web3' , 'net' ] ) ,
2018-01-11 13:55:28 +00:00
wsRPC : ( this . blockchainConfig . wsRPC === undefined ) || this . blockchainConfig . wsRPC ,
2017-10-19 22:55:49 +00:00
wsHost : this . blockchainConfig . wsHost || 'localhost' ,
wsPort : this . blockchainConfig . wsPort || 8546 ,
wsOrigins : this . blockchainConfig . wsOrigins || false ,
wsApi : ( this . blockchainConfig . wsApi || [ 'eth' , 'web3' , 'net' , 'shh' ] ) ,
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 ,
light : this . blockchainConfig . light || false ,
2018-05-22 13:11:38 +00:00
fast : this . blockchainConfig . fast || false ,
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-01-12 22:16:46 +00:00
this . config . datadir = fs . embarkPath ( ".embark/development/datadir" ) ;
}
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-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-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-22 18:13:56 +00:00
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-05-28 15:54:31 +00:00
] , function ( err , cmd , args ) {
2018-05-22 18:13:56 +00:00
if ( err ) {
console . error ( err ) ;
return ;
}
2018-05-28 15:54:31 +00:00
args = _ . compact ( args ) ;
2018-06-01 03:12:17 +00:00
console . trace ( ` Geth command: ${ cmd } ${ args . join ( ' ' ) } ` ) ;
2018-05-30 14:52:15 +00:00
self . child = child _process . spawn ( cmd , args , { cwd : process . cwd ( ) } ) ;
2018-05-28 15:54:31 +00:00
2018-05-30 14:52:15 +00:00
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-05-28 15:54:31 +00:00
console . log ( ` Geth error: ${ data } ` ) ;
} ) ;
2018-05-28 20:02:44 +00:00
// Geth logs appear in stderr somehow
2018-05-30 14:52:15 +00:00
self . child . stderr . on ( 'data' , ( data ) => {
2018-05-28 15:54:31 +00:00
data = data . toString ( ) ;
2018-05-30 14:52:15 +00:00
if ( self . onReadyCallback && ! self . readyCalled && data . indexOf ( 'WebSocket endpoint opened' ) > - 1 ) {
2018-05-28 15:54:31 +00:00
self . readyCalled = true ;
self . onReadyCallback ( ) ;
}
console . log ( 'Geth: ' + data ) ;
} ) ;
2018-05-30 14:52:15 +00:00
self . child . on ( 'exit' , ( code ) => {
2018-05-28 15:54:31 +00:00
if ( code ) {
console . error ( 'Geth exited with error code ' + code ) ;
}
} ) ;
2017-03-31 11:34:43 +00:00
} ) ;
} ;
2016-08-21 14:42:42 +00:00
2018-05-30 14:52:15 +00:00
Blockchain . prototype . kill = function ( ) {
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-05-22 18:13:56 +00:00
this . runCommand ( versionCmd , { } , ( err , stdout , stderr ) => {
if ( err || stderr || ! stdout || stdout . indexOf ( "not found" ) >= 0 ) {
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 ;
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 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 ( ) ;
}
2018-05-22 19:36:31 +00:00
console . log ( _ _ ( "already initialized" ) . green ) ;
address = stdout . match ( /{(\w+)}/ ) [ 1 ] ;
next ( ) ;
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 ) => {
callback ( err , address ) ;
} ) ;
2017-03-31 11:34:43 +00:00
} ;
2018-05-22 18:13:56 +00:00
var BlockchainClient = function ( blockchainConfig , client , env , isDev , onReadyCallback ) {
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-05-22 18:13:56 +00:00
return new Blockchain ( { blockchainConfig , client : GethCommands , env , isDev , onReadyCallback } ) ;
2017-03-31 11:34:43 +00:00
} else {
throw new Error ( 'unknown client' ) ;
2016-08-21 14:42:42 +00:00
}
2017-03-31 11:34:43 +00:00
} ;
2016-08-21 14:42:42 +00:00
module . exports = BlockchainClient ;