parityClient.js
1 const async = require('async'); 2 const fs = require('../../core/fs.js'); 3 const os = require('os'); 4 5 const DEFAULTS = { 6 "BIN": "parity", 7 "NETWORK_TYPE": "dev", 8 "NETWORK_ID": 17, 9 "RPC_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"], 10 "WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub"], 11 "DEV_WS_API": ["web3", "eth", "pubsub", "net", "parity", "private", "parity_pubsub", "traces", "rpc", "shh", "shh_pubsub", "personal"], 12 "TARGET_GAS_LIMIT": 8000000, 13 "DEV_ACCOUNT": "0x00a329c0648769a73afac7f9381e08fb43dbea72", 14 "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\"}"} 15 }; 16 17 const safePush = function(set, value) { 18 if (set.indexOf(value) === -1) { 19 set.push(value); 20 } 21 }; 22 23 class ParityClient { 24 25 static get DEFAULTS() { 26 return DEFAULTS; 27 } 28 29 constructor(options) { 30 this.config = options && options.hasOwnProperty('config') ? options.config : {}; 31 this.env = options && options.hasOwnProperty('env') ? options.env : 'development'; 32 this.isDev = options && options.hasOwnProperty('isDev') ? options.isDev : (this.env === 'development'); 33 this.name = "parity"; 34 this.prettyName = "Parity-Ethereum (https://github.com/paritytech/parity-ethereum)"; 35 this.bin = this.config.ethereumClientBin || DEFAULTS.BIN; 36 } 37 38 isReady(data) { 39 return data.indexOf('Public node URL') > -1; 40 } 41 42 /** 43 * Check if the client needs some sort of 'keep alive' transactions to avoid freezing by inactivity 44 * @returns {boolean} if keep alive is needed 45 */ 46 needKeepAlive() { 47 return false; 48 } 49 50 commonOptions() { 51 let config = this.config; 52 let cmd = []; 53 54 cmd.push(this.determineNetworkType(config)); 55 56 if (config.networkId) { 57 cmd.push(`--network-id=${config.networkId}`); 58 } 59 60 if (config.datadir) { 61 cmd.push(`--base-path=${config.datadir}`); 62 } 63 64 if (config.syncMode === 'light') { 65 cmd.push("--light"); 66 } else if (config.syncMode === 'fast') { 67 cmd.push("--pruning=fast"); 68 } else if (config.syncMode === 'full') { 69 cmd.push("--pruning=archive"); 70 } 71 72 // In dev mode we store all users passwords in the devPassword file, so Parity can unlock all users from the start 73 if (this.isDev) cmd.push(`--password=${config.account.devPassword}`); 74 else if (config.account && config.account.password) { 75 cmd.push(`--password=${config.account.password}`); 76 } 77 78 if (Number.isInteger(config.verbosity) && config.verbosity >= 0 && config.verbosity <= 5) { 79 switch (config.verbosity) { 80 case 0: // No option to silent Parity, go to less verbose 81 case 1: 82 cmd.push("--logging=error"); 83 break; 84 case 2: 85 cmd.push("--logging=warn"); 86 break; 87 case 3: 88 cmd.push("--logging=info"); 89 break; 90 case 4: // Debug is the max verbosity for Parity 91 case 5: 92 cmd.push("--logging=debug"); 93 break; 94 default: 95 cmd.push("--logging=info"); 96 break; 97 } 98 } 99 100 return cmd; 101 } 102 103 getMiner() { 104 console.warn(__("Miner requested, but Parity does not embed a miner! Use Geth or install ethminer (https://github.com/ethereum-mining/ethminer)").yellow); 105 return; 106 } 107 108 getBinaryPath() { 109 return this.bin; 110 } 111 112 determineVersionCommand() { 113 return this.bin + " --version"; 114 } 115 116 determineNetworkType(config) { 117 if (this.isDev) { 118 return "--chain=dev"; 119 } 120 if (config.networkType === 'rinkeby') { 121 console.warn(__('Parity does not support the Rinkeby PoA network, switching to Kovan PoA network')); 122 config.networkType = 'kovan'; 123 } else if (config.networkType === 'testnet') { 124 console.warn(__('Parity "testnet" corresponds to Kovan network, switching to Ropsten to be compliant with Geth parameters')); 125 config.networkType = "ropsten"; 126 } 127 if (config.genesisBlock) { 128 config.networkType = config.genesisBlock; 129 } 130 return "--chain=" + config.networkType; 131 } 132 133 newAccountCommand() { 134 return this.bin + " " + this.commonOptions().join(' ') + " account new "; 135 } 136 137 parseNewAccountCommandResultToAddress(data = "") { 138 return data.replace(/^\n|\n$/g, ""); 139 } 140 141 listAccountsCommand() { 142 return this.bin + " " + this.commonOptions().join(' ') + " account list "; 143 } 144 145 parseListAccountsCommandResultToAddress(data = "") { 146 return data.replace(/^\n|\n$/g, "").split('\n')[0]; 147 } 148 149 parseListAccountsCommandResultToAddressList(data = "") { 150 let list = data.split(os.EOL); 151 list.pop(); 152 return list; 153 } 154 155 parseListAccountsCommandResultToAddressCount(data = "") { 156 const count = this.parseListAccountsCommandResultToAddressList(data).length; 157 return (count > 0 ? count : 0); 158 } 159 160 determineRpcOptions(config) { 161 let cmd = []; 162 cmd.push("--port=" + config.port); 163 cmd.push("--jsonrpc-port=" + config.rpcPort); 164 cmd.push("--jsonrpc-interface=" + (config.rpcHost === 'localhost' ? 'local' : config.rpcHost)); 165 if (config.rpcCorsDomain) { 166 if (config.rpcCorsDomain === '*') { 167 console.warn('=================================='); 168 console.warn(__('rpcCorsDomain set to "all"')); 169 console.warn(__('make sure you know what you are doing')); 170 console.warn('=================================='); 171 } 172 cmd.push("--jsonrpc-cors=" + (config.rpcCorsDomain === '*' ? 'all' : config.rpcCorsDomain)); 173 } else { 174 console.warn('=================================='); 175 console.warn(__('warning: cors is not set')); 176 console.warn('=================================='); 177 } 178 cmd.push("--jsonrpc-hosts=all"); 179 return cmd; 180 } 181 182 determineWsOptions(config) { 183 let cmd = []; 184 if (config.wsRPC) { 185 cmd.push("--ws-port=" + config.wsPort); 186 cmd.push("--ws-interface=" + (config.wsHost === 'localhost' ? 'local' : config.wsHost)); 187 if (config.wsOrigins) { 188 if (config.wsOrigins === '*') { 189 console.warn('=================================='); 190 console.warn(__('wsOrigins set to "all"')); 191 console.warn(__('make sure you know what you are doing')); 192 console.warn('=================================='); 193 } 194 cmd.push("--ws-origins=" + (config.wsOrigins === '*' ? 'all' : config.wsOrigins)); 195 } else { 196 console.warn('=================================='); 197 console.warn(__('warning: wsOrigins is not set')); 198 console.warn('=================================='); 199 } 200 cmd.push("--ws-hosts=all"); 201 } 202 return cmd; 203 } 204 205 initDevChain(datadir, callback) { 206 // Parity requires specific initialization also for the dev chain 207 const self = this; 208 const keysDataDir = '.embark/development/datadir/keys/DevelopmentChain'; 209 async.series([ 210 function makeDir(next) { 211 fs.mkdirp(keysDataDir, (err, _result) => { 212 next(err); 213 }); 214 }, 215 function createDevAccount(next) { 216 self.createDevAccount(keysDataDir, next); 217 }, 218 function updatePasswordFile(next) { 219 if (self.config.account.password) { 220 let passwordList = os.EOL + (self.config.account.password ? fs.readFileSync(fs.dappPath(self.config.account.password), 'utf8').replace('\n', '') : 'dev_password'); 221 fs.writeFileSync(self.config.account.devPassword, passwordList, function(err) { 222 return next(err); 223 }); 224 } 225 next(); 226 } 227 ], (err) => { 228 callback(err); 229 }); 230 } 231 232 createDevAccount(keysDataDir, cb) { 233 const devAccountWallet = keysDataDir + '/dev.wallet'; 234 fs.writeFile(devAccountWallet, JSON.stringify(DEFAULTS.DEV_WALLET), function(err) { 235 if (err) { 236 return cb(err); 237 } 238 cb(); 239 }); 240 } 241 242 mainCommand(address, done) { 243 let self = this; 244 let config = this.config; 245 let rpc_api = this.config.rpcApi; 246 let ws_api = this.config.wsApi; 247 let args = []; 248 async.series([ 249 function commonOptions(callback) { 250 let cmd = self.commonOptions(); 251 args = args.concat(cmd); 252 callback(null, cmd); 253 }, 254 function rpcOptions(callback) { 255 let cmd = self.determineRpcOptions(self.config); 256 args = args.concat(cmd); 257 callback(null, cmd); 258 }, 259 function wsOptions(callback) { 260 let cmd = self.determineWsOptions(self.config); 261 args = args.concat(cmd); 262 callback(null, cmd); 263 }, 264 function dontGetPeers(callback) { 265 if (config.nodiscover) { 266 args.push("--no-discovery"); 267 return callback(null, "--no-discovery"); 268 } 269 callback(null, ""); 270 }, 271 function vmDebug(callback) { 272 if (config.vmdebug) { 273 args.push("--tracing on"); 274 return callback(null, "--tracing on"); 275 } 276 callback(null, ""); 277 }, 278 function maxPeers(callback) { 279 let cmd = "--max-peers=" + config.maxpeers; 280 args.push(cmd); 281 callback(null, cmd); 282 }, 283 function bootnodes(callback) { 284 if (config.bootnodes && config.bootnodes !== "" && config.bootnodes !== []) { 285 args.push("--bootnodes=" + config.bootnodes); 286 return callback(null, "--bootnodes=" + config.bootnodes); 287 } 288 callback(""); 289 }, 290 function whisper(callback) { 291 if (config.whisper) { 292 safePush(rpc_api, 'shh'); 293 safePush(rpc_api, 'shh_pubsub'); 294 safePush(ws_api, 'shh'); 295 safePush(ws_api, 'shh_pubsub'); 296 args.push("--whisper"); 297 return callback(null, "--whisper"); 298 } 299 callback(""); 300 }, 301 function rpcApi(callback) { 302 args.push('--jsonrpc-apis=' + rpc_api.join(',')); 303 callback(null, '--jsonrpc-apis=' + rpc_api.join(',')); 304 }, 305 function wsApi(callback) { 306 args.push('--ws-apis=' + ws_api.join(',')); 307 callback(null, '--ws-apis=' + ws_api.join(',')); 308 }, 309 function accountToUnlock(callback) { 310 if (self.isDev) { 311 let unlockAddressList = self.config.unlockAddressList ? self.config.unlockAddressList : DEFAULTS.DEV_ACCOUNT; 312 args.push("--unlock=" + unlockAddressList); 313 return callback(null, "--unlock=" + unlockAddressList); 314 } 315 let accountAddress = ""; 316 if (config.account && config.account.address) { 317 accountAddress = config.account.address; 318 } else { 319 accountAddress = address; 320 } 321 if (accountAddress && !self.isDev) { 322 args.push("--unlock=" + accountAddress); 323 return callback(null, "--unlock=" + accountAddress); 324 } 325 callback(null, ""); 326 }, 327 function gasLimit(callback) { 328 if (config.targetGasLimit) { 329 args.push("--gas-floor-target=" + config.targetGasLimit); 330 return callback(null, "--gas-floor-target=" + config.targetGasLimit); 331 } 332 // Default Parity gas limit is 4700000: let's set to the geth default 333 args.push("--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT); 334 return callback(null, "--gas-floor-target=" + DEFAULTS.TARGET_GAS_LIMIT); 335 } 336 ], function(err) { 337 if (err) { 338 throw new Error(err.message); 339 } 340 return done(self.bin, args); 341 }); 342 } 343 } 344 345 module.exports = ParityClient;