2018-05-17 19:32:57 +00:00
const async = require ( 'async' ) ;
2018-10-06 16:05:37 +00:00
const GethMiner = require ( './miner' ) ;
const os = require ( 'os' ) ;
const DEFAULTS = {
"BIN" : "geth" ,
"NETWORK_TYPE" : "custom" ,
"NETWORK_ID" : 1337 ,
"RPC_API" : [ 'eth' , 'web3' , 'net' , 'debug' ] ,
"WS_API" : [ 'eth' , 'web3' , 'net' , 'shh' , 'debug' , 'pubsub' ] ,
"DEV_WS_API" : [ 'eth' , 'web3' , 'net' , 'shh' , 'debug' , 'pubsub' , 'personal' ] ,
"TARGET_GAS_LIMIT" : 8000000
} ;
2016-09-25 06:23:33 +00:00
2017-02-22 02:01:38 +00:00
// TODO: make all of this async
2018-10-06 16:05:37 +00:00
class GethClient {
static get DEFAULTS ( ) {
return DEFAULTS ;
}
2017-03-30 11:12:39 +00:00
constructor ( options ) {
2017-03-30 13:16:46 +00:00
this . config = options && options . hasOwnProperty ( 'config' ) ? options . config : { } ;
this . env = options && options . hasOwnProperty ( 'env' ) ? options . env : 'development' ;
2018-05-09 13:17:48 +00:00
this . isDev = options && options . hasOwnProperty ( 'isDev' ) ? options . isDev : ( this . env === 'development' ) ;
2018-10-06 16:05:37 +00:00
this . name = "geth" ;
this . prettyName = "Go-Ethereum (https://github.com/ethereum/go-ethereum)" ;
this . bin = this . config . ethereumClientBin || DEFAULTS . BIN ;
this . httpReady = false ;
this . wsReady = ! this . config . wsRPC ;
}
isReady ( data ) {
if ( data . indexOf ( 'HTTP endpoint opened' ) > - 1 ) {
this . httpReady = true ;
}
if ( data . indexOf ( 'WebSocket endpoint opened' ) > - 1 ) {
this . wsReady = true ;
}
return this . httpReady && this . wsReady ;
}
/ * *
* Check if the client needs some sort of 'keep alive' transactions to avoid freezing by inactivity
* @ returns { boolean } if keep alive is needed
* /
needKeepAlive ( ) {
// TODO: check version also (geth version < 1.8.15)
if ( this . isDev ) {
// Trigger regular txs due to a bug in geth (< 1.8.15) and stuck transactions in --dev mode.
return true ;
}
return false ;
2017-03-30 11:12:39 +00:00
}
2016-09-25 06:23:33 +00:00
2018-10-06 16:05:37 +00:00
commonOptions ( firstAccount = false ) {
2017-03-30 11:12:39 +00:00
let config = this . config ;
2018-05-28 15:54:31 +00:00
let cmd = [ ] ;
2016-09-25 06:23:33 +00:00
2018-05-28 15:54:31 +00:00
cmd . push ( this . determineNetworkType ( config ) ) ;
2016-09-25 06:23:33 +00:00
2017-03-30 11:12:39 +00:00
if ( config . datadir ) {
2018-05-28 15:55:16 +00:00
cmd . push ( ` --datadir= ${ config . datadir } ` ) ;
2017-03-30 11:12:39 +00:00
}
2016-09-25 06:23:33 +00:00
2018-10-06 16:05:37 +00:00
if ( config . syncMode ) {
cmd . push ( "--syncmode=" + config . syncMode ) ;
2017-03-30 11:12:39 +00:00
}
2017-02-20 20:53:55 +00:00
2018-10-06 16:05:37 +00:00
// geth in dev mode needs the first account to have a blank password, so we use for convenience the same Parity's devpassword
2017-03-30 11:12:39 +00:00
if ( config . account && config . account . password ) {
2018-10-06 16:05:37 +00:00
if ( firstAccount ) cmd . push ( ` --password= ${ config . account . devPassword } ` ) ;
else cmd . push ( ` --password= ${ config . account . password } ` ) ;
2017-03-30 11:12:39 +00:00
}
2016-09-25 06:23:33 +00:00
2018-10-06 16:05:37 +00:00
if ( Number . isInteger ( config . verbosity ) && config . verbosity >= 0 && config . verbosity <= 5 ) {
2018-05-28 15:54:31 +00:00
cmd . push ( "--verbosity=" + config . verbosity ) ;
2018-05-22 13:11:38 +00:00
}
2017-03-30 11:12:39 +00:00
return cmd ;
2017-02-22 02:01:38 +00:00
}
2016-09-25 06:23:33 +00:00
2018-10-06 16:05:37 +00:00
getMiner ( ) {
return new GethMiner ( { datadir : this . config . datadir } ) ;
}
getBinaryPath ( ) {
return this . bin ;
}
2018-05-28 15:55:16 +00:00
determineVersionCommand ( ) {
2018-10-06 16:05:37 +00:00
return this . bin + " version" ;
2017-12-13 22:58:07 +00:00
}
2017-03-30 11:12:39 +00:00
determineNetworkType ( config ) {
2018-05-28 15:54:31 +00:00
let cmd ;
2017-03-30 11:12:39 +00:00
if ( config . networkType === 'testnet' ) {
2018-06-14 19:22:01 +00:00
cmd = "--testnet" ;
2018-06-15 18:18:37 +00:00
} else if ( config . networkType === 'rinkeby' ) {
cmd = "--rinkeby" ;
2017-03-30 11:12:39 +00:00
} else if ( config . networkType === 'custom' ) {
2018-05-28 15:54:31 +00:00
cmd = "--networkid=" + config . networkId ;
2017-03-30 11:12:39 +00:00
}
return cmd ;
2016-09-25 06:23:33 +00:00
}
2017-03-30 11:12:39 +00:00
initGenesisCommmand ( ) {
let config = this . config ;
2018-10-06 16:05:37 +00:00
let cmd = this . bin + " " + this . commonOptions ( ) . join ( ' ' ) ;
2017-03-30 11:12:39 +00:00
if ( config . genesisBlock ) {
2018-05-28 15:55:16 +00:00
cmd += " init \"" + config . genesisBlock + "\" " ;
2017-03-30 11:12:39 +00:00
}
return cmd ;
}
2016-10-31 00:21:28 +00:00
2018-10-06 16:05:37 +00:00
newAccountCommand ( firstAccount = false ) {
if ( ! ( this . config . account && this . config . account . password ) ) {
2018-09-18 06:28:23 +00:00
console . warn ( _ _ ( 'Your blockchain is missing a password and creating an account may fail. Please consider updating ' ) . yellow + _ _ ( 'config/blockchain > account > password' ) . cyan + _ _ ( ' then re-run the command' ) . yellow ) ;
}
2018-10-06 16:05:37 +00:00
return this . bin + " " + this . commonOptions ( firstAccount ) . join ( ' ' ) + " account new " ;
}
parseNewAccountCommandResultToAddress ( data = "" ) {
if ( data . match ( /{(\w+)}/ ) ) return "0x" + data . match ( /{(\w+)}/ ) [ 1 ] ;
return "" ;
2017-03-30 11:12:39 +00:00
}
2016-09-25 06:23:33 +00:00
2017-03-30 11:12:39 +00:00
listAccountsCommand ( ) {
2018-10-06 16:05:37 +00:00
return this . bin + " " + this . commonOptions ( ) . join ( ' ' ) + " account list " ;
}
parseListAccountsCommandResultToAddress ( data = "" ) {
if ( data . match ( /{(\w+)}/ ) ) return "0x" + data . match ( /{(\w+)}/ ) [ 1 ] ;
return "" ;
}
parseListAccountsCommandResultToAddressList ( data = "" ) {
let list = data . split ( os . EOL ) ;
list . pop ( ) ; // Remove empty value
return list . map ( el => "0x" + el . match ( /{(\w+)}/ ) [ 1 ] ) ;
}
parseListAccountsCommandResultToAddressCount ( data = "" ) {
const count = this . parseListAccountsCommandResultToAddressList ( data ) . length ;
return ( count > 0 ? count : 0 ) ;
2017-03-30 11:12:39 +00:00
}
2016-09-25 06:23:33 +00:00
2017-03-30 11:12:39 +00:00
determineRpcOptions ( config ) {
2018-05-28 15:54:31 +00:00
let cmd = [ ] ;
cmd . push ( "--port=" + config . port ) ;
cmd . push ( "--rpc" ) ;
cmd . push ( "--rpcport=" + config . rpcPort ) ;
cmd . push ( "--rpcaddr=" + config . rpcHost ) ;
2017-03-30 11:12:39 +00:00
if ( config . rpcCorsDomain ) {
if ( config . rpcCorsDomain === '*' ) {
2018-10-06 16:05:37 +00:00
console . warn ( '==================================' ) ;
console . warn ( _ _ ( 'rpcCorsDomain set to *' ) ) ;
console . warn ( _ _ ( 'make sure you know what you are doing' ) ) ;
console . warn ( '==================================' ) ;
2017-03-30 11:12:39 +00:00
}
2018-05-28 14:16:07 +00:00
cmd . push ( "--rpccorsdomain=" + config . rpcCorsDomain ) ;
2017-03-30 11:12:39 +00:00
} else {
2018-10-06 16:05:37 +00:00
console . warn ( '==================================' ) ;
console . warn ( _ _ ( 'warning: cors is not set' ) ) ;
console . warn ( '==================================' ) ;
2016-10-07 11:15:29 +00:00
}
2017-03-30 11:12:39 +00:00
return cmd ;
2016-09-26 00:51:00 +00:00
}
2016-09-25 06:23:33 +00:00
2017-10-19 22:55:49 +00:00
determineWsOptions ( config ) {
2018-05-28 15:54:31 +00:00
let cmd = [ ] ;
2018-01-11 13:55:28 +00:00
if ( config . wsRPC ) {
2018-05-28 15:54:31 +00:00
cmd . push ( "--ws" ) ;
cmd . push ( "--wsport=" + config . wsPort ) ;
cmd . push ( "--wsaddr=" + config . wsHost ) ;
2018-01-11 13:55:28 +00:00
if ( config . wsOrigins ) {
if ( config . wsOrigins === '*' ) {
2018-10-06 16:05:37 +00:00
console . warn ( '==================================' ) ;
console . warn ( _ _ ( 'wsOrigins set to *' ) ) ;
console . warn ( _ _ ( 'make sure you know what you are doing' ) ) ;
console . warn ( '==================================' ) ;
2018-01-11 13:55:28 +00:00
}
2018-05-28 14:16:07 +00:00
cmd . push ( "--wsorigins=" + config . wsOrigins ) ;
2018-01-11 13:55:28 +00:00
} else {
2018-10-06 16:05:37 +00:00
console . warn ( '==================================' ) ;
console . warn ( _ _ ( 'warning: wsOrigins is not set' ) ) ;
console . warn ( '==================================' ) ;
2017-10-19 22:55:49 +00:00
}
}
return cmd ;
}
2018-10-06 16:05:37 +00:00
initDevChain ( datadir , callback ) {
// No specific configuration needed for the dev chain
return callback ( ) ;
}
2017-03-30 11:12:39 +00:00
mainCommand ( address , done ) {
let self = this ;
let config = this . config ;
2018-10-06 16:05:37 +00:00
let rpc _api = this . config . rpcApi ;
let ws _api = this . config . wsApi ;
2018-05-28 15:54:31 +00:00
let args = [ ] ;
2017-03-30 11:12:39 +00:00
async . series ( [
function commonOptions ( callback ) {
let cmd = self . commonOptions ( ) ;
2018-05-28 15:54:31 +00:00
args = args . concat ( cmd ) ;
2017-03-30 11:12:39 +00:00
callback ( null , cmd ) ;
} ,
function rpcOptions ( callback ) {
let cmd = self . determineRpcOptions ( self . config ) ;
2018-05-28 15:54:31 +00:00
args = args . concat ( cmd ) ;
2017-03-30 11:12:39 +00:00
callback ( null , cmd ) ;
} ,
2017-10-19 22:55:49 +00:00
function wsOptions ( callback ) {
let cmd = self . determineWsOptions ( self . config ) ;
2018-05-28 15:54:31 +00:00
args = args . concat ( cmd ) ;
2017-10-19 22:55:49 +00:00
callback ( null , cmd ) ;
} ,
2017-03-30 11:12:39 +00:00
function dontGetPeers ( callback ) {
if ( config . nodiscover ) {
2018-05-28 15:54:31 +00:00
args . push ( "--nodiscover" ) ;
2017-03-30 11:12:39 +00:00
return callback ( null , "--nodiscover" ) ;
}
callback ( null , "" ) ;
} ,
function vmDebug ( callback ) {
if ( config . vmdebug ) {
2018-05-28 15:54:31 +00:00
args . push ( "--vmdebug" ) ;
2017-03-30 11:12:39 +00:00
return callback ( null , "--vmdebug" ) ;
}
callback ( null , "" ) ;
} ,
function maxPeers ( callback ) {
2018-05-28 15:54:31 +00:00
let cmd = "--maxpeers=" + config . maxpeers ;
args . push ( cmd ) ;
2017-03-30 11:12:39 +00:00
callback ( null , cmd ) ;
} ,
function mining ( callback ) {
if ( config . mineWhenNeeded || config . mine ) {
2018-05-28 15:54:31 +00:00
args . push ( "--mine" ) ;
return callback ( null , "--mine" ) ;
2017-03-30 11:12:39 +00:00
}
callback ( "" ) ;
} ,
function bootnodes ( callback ) {
if ( config . bootnodes && config . bootnodes !== "" && config . bootnodes !== [ ] ) {
2018-05-28 15:54:31 +00:00
args . push ( "--bootnodes=" + config . bootnodes ) ;
return callback ( null , "--bootnodes=" + config . bootnodes ) ;
2017-03-30 11:12:39 +00:00
}
callback ( "" ) ;
} ,
function whisper ( callback ) {
if ( config . whisper ) {
rpc _api . push ( 'shh' ) ;
2017-10-21 12:42:29 +00:00
if ( ws _api . indexOf ( 'shh' ) === - 1 ) {
ws _api . push ( 'shh' ) ;
}
2018-05-28 15:54:31 +00:00
args . push ( "--shh" ) ;
2017-03-30 11:12:39 +00:00
return callback ( null , "--shh " ) ;
}
callback ( "" ) ;
} ,
function rpcApi ( callback ) {
2018-05-28 14:16:07 +00:00
args . push ( '--rpcapi=' + rpc _api . join ( ',' ) ) ;
callback ( null , '--rpcapi=' + rpc _api . join ( ',' ) ) ;
2017-03-30 11:12:39 +00:00
} ,
2017-10-19 22:55:49 +00:00
function wsApi ( callback ) {
2018-05-28 14:16:07 +00:00
args . push ( '--wsapi=' + ws _api . join ( ',' ) ) ;
callback ( null , '--wsapi=' + ws _api . join ( ',' ) ) ;
2017-10-19 22:55:49 +00:00
} ,
2017-03-30 11:12:39 +00:00
function accountToUnlock ( callback ) {
2018-10-06 16:05:37 +00:00
if ( self . isDev && self . config . unlockAddressList ) {
// The first address is the dev account, that is automatically unlocked by the client using blank password
args . push ( "--unlock=" + self . config . unlockAddressList . slice ( 1 ) ) ;
return callback ( null , "--unlock=" + self . config . unlockAddressList . slice ( 1 ) ) ;
}
2017-03-30 13:16:46 +00:00
let accountAddress = "" ;
2018-10-06 16:05:37 +00:00
if ( config . account && config . account . address ) {
2017-03-30 13:16:46 +00:00
accountAddress = config . account . address ;
} else {
accountAddress = address ;
}
2018-10-06 16:05:37 +00:00
if ( accountAddress ) {
2018-10-15 03:21:26 +00:00
if ( ! ( self . config && self . config . account && self . config . account . password ) ) {
console . warn ( _ _ ( "\n===== Password needed =====\nPassword for account {{account}} not found. Unlocking this account may fail. Please ensure a password is specified in config/blockchain.js > {{env}} > account > password.\n" , { account : address , env : self . env } ) ) ;
}
2018-05-28 15:54:31 +00:00
args . push ( "--unlock=" + accountAddress ) ;
2017-03-30 11:12:39 +00:00
return callback ( null , "--unlock=" + accountAddress ) ;
}
callback ( null , "" ) ;
} ,
2018-01-17 16:23:32 +00:00
function gasLimit ( callback ) {
if ( config . targetGasLimit ) {
2018-10-06 16:05:37 +00:00
args . push ( "--miner.gastarget=" + config . targetGasLimit ) ;
return callback ( null , "--miner.gastarget=" + config . targetGasLimit ) ;
2018-01-17 16:23:32 +00:00
}
callback ( null , "" ) ;
} ,
2018-05-08 20:25:48 +00:00
function isDev ( callback ) {
2018-05-09 13:17:48 +00:00
if ( self . isDev ) {
2018-05-28 15:54:31 +00:00
args . push ( '--dev' ) ;
2018-05-08 20:25:48 +00:00
return callback ( null , '--dev' ) ;
}
callback ( null , '' ) ;
2017-03-02 13:15:35 +00:00
}
2018-10-06 16:05:37 +00:00
] , function ( err ) {
2017-03-30 11:12:39 +00:00
if ( err ) {
throw new Error ( err . message ) ;
2017-03-02 13:15:35 +00:00
}
2018-10-06 16:05:37 +00:00
return done ( self . bin , args ) ;
2017-03-30 11:12:39 +00:00
} ) ;
}
}
2016-09-25 06:23:33 +00:00
2018-10-06 16:05:37 +00:00
module . exports = GethClient ;