const async = require('async'); const fs = require('../../core/fs.js'); const os = require('os'); const semver = require('semver'); const DEFAULTS = { "BIN": "parity", "VERSIONS_SUPPORTED": ">=2.0.0", "NETWORK_TYPE": "dev", "NETWORK_ID": 17, "RPC_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"], "WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"], "DEV_WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub", "personal"], "TARGET_GAS_LIMIT": 8000000, "DEV_ACCOUNT": "0x00a329c0648769a73afac7f9381e08fb43dbea72", "DEV_WALLET": {"id": "d9460e00-6895-8f58-f40c-bb57aebe6c00", "version": 3, "crypto": {"cipher": "aes-128-ctr", "cipherparams": {"iv": "74245f453143f9d06a095c6e6e309d5d"}, "ciphertext": "2fa611c4aa66452ef81bd1bd288f9d1ed14edf61aa68fc518093f97c791cf719", "kdf": "pbkdf2", "kdfparams": {"c": 10240, "dklen": 32, "prf": "hmac-sha256", "salt": "73b74e437a1144eb9a775e196f450a23ab415ce2c17083c225ddbb725f279b98"}, "mac": "f5882ae121e4597bd133136bf15dcbcc1bb2417a25ad205041a50c59def812a8"}, "address": "00a329c0648769a73afac7f9381e08fb43dbea72", "name": "Development Account", "meta": "{\"description\":\"Never use this account outside of development chain!\",\"passwordHint\":\"Password is empty string\"}"} }; const safePush = function(set, value) { if (set.indexOf(value) === -1) { set.push(value); } }; class ParityClient { 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 = "parity"; this.prettyName = "Parity-Ethereum (https://github.com/paritytech/parity-ethereum)"; this.bin = this.config.ethereumClientBin || DEFAULTS.BIN; this.versSupported = DEFAULTS.VERSIONS_SUPPORTED; } isReady(data) { return data.indexOf('Public node URL') > -1; } /** * Check if the client needs some sort of 'keep alive' transactions to avoid freezing by inactivity * @returns {boolean} if keep alive is needed */ needKeepAlive() { return false; } commonOptions() { let config = this.config; let cmd = []; cmd.push(this.determineNetworkType(config)); if (config.networkId) { cmd.push(`--network-id=${config.networkId}`); } if (config.datadir) { cmd.push(`--base-path=${config.datadir}`); } if (config.syncMode === 'light') { cmd.push("--light"); } else if (config.syncMode === 'fast') { cmd.push("--pruning=fast"); } else if (config.syncMode === 'full') { cmd.push("--pruning=archive"); } // In dev mode we store all users passwords in the devPassword file, so Parity can unlock all users from the start if (this.isDev) cmd.push(`--password=${config.account.devPassword}`); else if (config.account && config.account.password) { cmd.push(`--password=${config.account.password}`); } if (Number.isInteger(config.verbosity) && config.verbosity >= 0 && config.verbosity <= 5) { switch (config.verbosity) { case 0: // No option to silent Parity, go to less verbose case 1: cmd.push("--logging=error"); break; case 2: cmd.push("--logging=warn"); break; case 3: cmd.push("--logging=info"); break; case 4: // Debug is the max verbosity for Parity case 5: cmd.push("--logging=debug"); break; default: cmd.push("--logging=info"); break; } } return cmd; } getMiner() { console.warn(__("Miner requested, but Parity does not embed a miner! Use Geth or install ethminer (https://github.com/ethereum-mining/ethminer)").yellow); return; } getBinaryPath() { return this.bin; } determineVersionCommand() { return this.bin + " --version"; } parseVersion(rawVersionOutput) { let parsed; const match = rawVersionOutput.match(/version Parity-Ethereum\/(.*?)\//); if (match) { parsed = match[1].trim(); } return parsed; } isSupportedVersion(parsedVersion) { let test; try { let v = semver(parsedVersion); v = `${v.major}.${v.minor}.${v.patch}`; test = semver.Range(this.versSupported).test(semver(v)); if (typeof test !== 'boolean') { test = undefined; } } finally { // eslint-disable-next-line no-unsafe-finally return test; } } determineNetworkType(config) { if (this.isDev) { return "--chain=dev"; } if (config.networkType === 'rinkeby') { console.warn(__('Parity does not support the Rinkeby PoA network, switching to Kovan PoA network')); config.networkType = 'kovan'; } else if (config.networkType === 'testnet') { console.warn(__('Parity "testnet" corresponds to Kovan network, switching to Ropsten to be compliant with Geth parameters')); config.networkType = "ropsten"; } if (config.genesisBlock) { config.networkType = config.genesisBlock; } return "--chain=" + config.networkType; } newAccountCommand() { return this.bin + " " + this.commonOptions().join(' ') + " account new "; } parseNewAccountCommandResultToAddress(data = "") { return data.replace(/^\n|\n$/g, ""); } listAccountsCommand() { return this.bin + " " + this.commonOptions().join(' ') + " account list "; } parseListAccountsCommandResultToAddress(data = "") { return data.replace(/^\n|\n$/g, "").split('\n')[0]; } parseListAccountsCommandResultToAddressList(data = "") { let list = data.split(os.EOL); list.pop(); return list; } parseListAccountsCommandResultToAddressCount(data = "") { const count = this.parseListAccountsCommandResultToAddressList(data).length; return (count > 0 ? count : 0); } determineRpcOptions(config) { let cmd = []; cmd.push("--port=" + config.port); cmd.push("--jsonrpc-port=" + config.rpcPort); cmd.push("--jsonrpc-interface=" + (config.rpcHost === 'localhost' ? 'local' : config.rpcHost)); if (config.rpcCorsDomain) { if (config.rpcCorsDomain === '*') { console.warn('=================================='); console.warn(__('rpcCorsDomain set to "all"')); console.warn(__('make sure you know what you are doing')); console.warn('=================================='); } cmd.push("--jsonrpc-cors=" + (config.rpcCorsDomain === '*' ? 'all' : config.rpcCorsDomain)); } else { console.warn('=================================='); console.warn(__('warning: cors is not set')); console.warn('=================================='); } cmd.push("--jsonrpc-hosts=all"); return cmd; } determineWsOptions(config) { let cmd = []; if (config.wsRPC) { cmd.push("--ws-port=" + config.wsPort); cmd.push("--ws-interface=" + (config.wsHost === 'localhost' ? 'local' : config.wsHost)); if (config.wsOrigins) { if (config.wsOrigins === '*') { console.warn('=================================='); console.warn(__('wsOrigins set to "all"')); console.warn(__('make sure you know what you are doing')); console.warn('=================================='); } cmd.push("--ws-origins=" + (config.wsOrigins === '*' ? 'all' : config.wsOrigins)); } else { console.warn('=================================='); console.warn(__('warning: wsOrigins is not set')); console.warn('=================================='); } cmd.push("--ws-hosts=all"); } return cmd; } initDevChain(datadir, callback) { // Parity requires specific initialization also for the dev chain const self = this; const keysDataDir = '.embark/development/datadir/keys/DevelopmentChain'; async.series([ function makeDir(next) { fs.mkdirp(keysDataDir, (err, _result) => { next(err); }); }, function createDevAccount(next) { self.createDevAccount(keysDataDir, next); }, function updatePasswordFile(next) { if (self.config.account.password) { let passwordList = os.EOL + (self.config.account.password ? fs.readFileSync(fs.dappPath(self.config.account.password), 'utf8').replace('\n', '') : 'dev_password'); fs.writeFileSync(self.config.account.devPassword, passwordList, function(err) { return next(err); }); } next(); } ], (err) => { callback(err); }); } createDevAccount(keysDataDir, cb) { const devAccountWallet = keysDataDir + '/dev.wallet'; fs.writeFile(devAccountWallet, JSON.stringify(DEFAULTS.DEV_WALLET), function(err) { if (err) { return cb(err); } cb(); }); } 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("--no-discovery"); return callback(null, "--no-discovery"); } callback(null, ""); }, function vmDebug(callback) { if (config.vmdebug) { args.push("--tracing on"); return callback(null, "--tracing on"); } callback(null, ""); }, function maxPeers(callback) { let cmd = "--max-peers=" + config.maxpeers; args.push(cmd); callback(null, cmd); }, 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) { safePush(rpc_api, 'shh'); safePush(rpc_api, 'shh_pubsub'); safePush(ws_api, 'shh'); safePush(ws_api, 'shh_pubsub'); args.push("--whisper"); return callback(null, "--whisper"); } callback(""); }, function rpcApi(callback) { args.push('--jsonrpc-apis=' + rpc_api.join(',')); callback(null, '--jsonrpc-apis=' + rpc_api.join(',')); }, function wsApi(callback) { args.push('--ws-apis=' + ws_api.join(',')); callback(null, '--ws-apis=' + ws_api.join(',')); }, function accountToUnlock(callback) { if (self.isDev) { let unlockAddressList = self.config.unlockAddressList ? self.config.unlockAddressList : DEFAULTS.DEV_ACCOUNT; args.push("--unlock=" + unlockAddressList); return callback(null, "--unlock=" + unlockAddressList); } let accountAddress = ""; if (config.account && config.account.address) { accountAddress = config.account.address; } else { accountAddress = address; } if (accountAddress && !self.isDev) { args.push("--unlock=" + accountAddress); return callback(null, "--unlock=" + accountAddress); } callback(null, ""); }, function gasLimit(callback) { if (config.targetGasLimit) { args.push("--gas-floor-target=" + config.targetGasLimit); return callback(null, "--gas-floor-target=" + config.targetGasLimit); } // Default Parity gas limit is 4700000: let's set to the geth default args.push("--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT); return callback(null, "--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT); } ], function(err) { if (err) { throw new Error(err.message); } return done(self.bin, args); }); } } module.exports = ParityClient;