/ lib / modules / blockchain_process / parityClient.js
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;