317 lines
10 KiB
JavaScript
317 lines
10 KiB
JavaScript
const async = require('async');
|
|
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
|
|
};
|
|
|
|
// TODO: make all of this async
|
|
class GethClient {
|
|
|
|
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 = "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;
|
|
}
|
|
|
|
commonOptions(firstAccount = false) {
|
|
let config = this.config;
|
|
let cmd = [];
|
|
|
|
cmd.push(this.determineNetworkType(config));
|
|
|
|
if (config.datadir) {
|
|
cmd.push(`--datadir=${config.datadir}`);
|
|
}
|
|
|
|
if (config.syncMode) {
|
|
cmd.push("--syncmode=" + config.syncMode);
|
|
}
|
|
|
|
// geth in dev mode needs the first account to have a blank password, so we use for convenience the same Parity's devpassword
|
|
if (config.account && config.account.password) {
|
|
if (firstAccount) cmd.push(`--password=${config.account.devPassword}`);
|
|
else cmd.push(`--password=${config.account.password}`);
|
|
}
|
|
|
|
if (Number.isInteger(config.verbosity) && config.verbosity >= 0 && config.verbosity <= 5) {
|
|
cmd.push("--verbosity=" + config.verbosity);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
getMiner() {
|
|
return new GethMiner({datadir: this.config.datadir});
|
|
}
|
|
|
|
getBinaryPath() {
|
|
return this.bin;
|
|
}
|
|
|
|
determineVersionCommand() {
|
|
return this.bin + " version";
|
|
}
|
|
|
|
determineNetworkType(config) {
|
|
let cmd;
|
|
if (config.networkType === 'testnet') {
|
|
cmd = "--testnet";
|
|
} else if (config.networkType === 'rinkeby') {
|
|
cmd = "--rinkeby";
|
|
} else if (config.networkType === 'custom') {
|
|
cmd = "--networkid=" + config.networkId;
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
initGenesisCommmand() {
|
|
let config = this.config;
|
|
let cmd = this.bin + " " + this.commonOptions().join(' ');
|
|
if (config.genesisBlock) {
|
|
cmd += " init \"" + config.genesisBlock + "\" ";
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
newAccountCommand(firstAccount = false) {
|
|
if (!(this.config.account && this.config.account.password)) {
|
|
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);
|
|
}
|
|
return this.bin + " " + this.commonOptions(firstAccount).join(' ') + " account new ";
|
|
}
|
|
|
|
parseNewAccountCommandResultToAddress(data = "") {
|
|
if (data.match(/{(\w+)}/)) return "0x" + data.match(/{(\w+)}/)[1];
|
|
return "";
|
|
}
|
|
|
|
listAccountsCommand() {
|
|
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);
|
|
}
|
|
|
|
determineRpcOptions(config) {
|
|
let cmd = [];
|
|
cmd.push("--port=" + config.port);
|
|
cmd.push("--rpc");
|
|
cmd.push("--rpcport=" + config.rpcPort);
|
|
cmd.push("--rpcaddr=" + config.rpcHost);
|
|
if (config.rpcCorsDomain) {
|
|
if (config.rpcCorsDomain === '*') {
|
|
console.warn('==================================');
|
|
console.warn(__('rpcCorsDomain set to *'));
|
|
console.warn(__('make sure you know what you are doing'));
|
|
console.warn('==================================');
|
|
}
|
|
cmd.push("--rpccorsdomain=" + config.rpcCorsDomain);
|
|
} else {
|
|
console.warn('==================================');
|
|
console.warn(__('warning: cors is not set'));
|
|
console.warn('==================================');
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
determineWsOptions(config) {
|
|
let cmd = [];
|
|
if (config.wsRPC) {
|
|
cmd.push("--ws");
|
|
cmd.push("--wsport=" + config.wsPort);
|
|
cmd.push("--wsaddr=" + config.wsHost);
|
|
if (config.wsOrigins) {
|
|
if (config.wsOrigins === '*') {
|
|
console.warn('==================================');
|
|
console.warn(__('wsOrigins set to *'));
|
|
console.warn(__('make sure you know what you are doing'));
|
|
console.warn('==================================');
|
|
}
|
|
cmd.push("--wsorigins=" + config.wsOrigins);
|
|
} else {
|
|
console.warn('==================================');
|
|
console.warn(__('warning: wsOrigins is not set'));
|
|
console.warn('==================================');
|
|
}
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
initDevChain(datadir, callback) {
|
|
// No specific configuration needed for the dev chain
|
|
return callback();
|
|
}
|
|
|
|
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("--nodiscover");
|
|
return callback(null, "--nodiscover");
|
|
}
|
|
callback(null, "");
|
|
},
|
|
function vmDebug(callback) {
|
|
if (config.vmdebug) {
|
|
args.push("--vmdebug");
|
|
return callback(null, "--vmdebug");
|
|
}
|
|
callback(null, "");
|
|
},
|
|
function maxPeers(callback) {
|
|
let cmd = "--maxpeers=" + config.maxpeers;
|
|
args.push(cmd);
|
|
callback(null, cmd);
|
|
},
|
|
function mining(callback) {
|
|
if (config.mineWhenNeeded || config.mine) {
|
|
args.push("--mine");
|
|
return callback(null, "--mine");
|
|
}
|
|
callback("");
|
|
},
|
|
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) {
|
|
rpc_api.push('shh');
|
|
if (ws_api.indexOf('shh') === -1) {
|
|
ws_api.push('shh');
|
|
}
|
|
args.push("--shh");
|
|
return callback(null, "--shh ");
|
|
}
|
|
callback("");
|
|
},
|
|
function rpcApi(callback) {
|
|
args.push('--rpcapi=' + rpc_api.join(','));
|
|
callback(null, '--rpcapi=' + rpc_api.join(','));
|
|
},
|
|
function wsApi(callback) {
|
|
args.push('--wsapi=' + ws_api.join(','));
|
|
callback(null, '--wsapi=' + ws_api.join(','));
|
|
},
|
|
function accountToUnlock(callback) {
|
|
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));
|
|
}
|
|
let accountAddress = "";
|
|
if (config.account && config.account.address) {
|
|
accountAddress = config.account.address;
|
|
} else {
|
|
accountAddress = address;
|
|
}
|
|
if (accountAddress) {
|
|
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}));
|
|
}
|
|
args.push("--unlock=" + accountAddress);
|
|
return callback(null, "--unlock=" + accountAddress);
|
|
}
|
|
callback(null, "");
|
|
},
|
|
function gasLimit(callback) {
|
|
if (config.targetGasLimit) {
|
|
args.push("--miner.gastarget=" + config.targetGasLimit);
|
|
return callback(null, "--miner.gastarget=" + config.targetGasLimit);
|
|
}
|
|
callback(null, "");
|
|
},
|
|
function isDev(callback) {
|
|
if (self.isDev) {
|
|
args.push('--dev');
|
|
return callback(null, '--dev');
|
|
}
|
|
callback(null, '');
|
|
}
|
|
], function(err) {
|
|
if (err) {
|
|
throw new Error(err.message);
|
|
}
|
|
return done(self.bin, args);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = GethClient;
|