From e8d68019e9202ad6fad96aa3cca56f60e0cf024c Mon Sep 17 00:00:00 2001 From: Fabian Vogelsteller <fabian@frozeman.de> Date: Thu, 16 Jun 2016 16:46:44 +0200 Subject: [PATCH 1/4] fixed eth start, but crash is not graceful --- main.js | 3 +-- modules/ethereumNode.js | 8 ++++---- modules/sockets.js | 27 +++++++++++++++++++++------ modules/windows.js | 3 ++- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/main.js b/main.js index 357257cb1..e6ddb1ba2 100644 --- a/main.js +++ b/main.js @@ -144,9 +144,8 @@ app.on('before-quit', function(event){ // delay quit, so the sockets can close setTimeout(function(){ - killedSockets = true; - ethereumNode.stop().then(function() { + killedSockets = true; app.quit(); }); }, 500); diff --git a/modules/ethereumNode.js b/modules/ethereumNode.js index cc4b31696..949522cea 100644 --- a/modules/ethereumNode.js +++ b/modules/ethereumNode.js @@ -143,7 +143,7 @@ class EthereumNode extends EventEmitter { return this._start(this.defaultNodeType, this.defaultNetwork) .catch((err) => { log.error('Failed to start node', err); - + throw err; }); }); @@ -289,8 +289,8 @@ class EthereumNode extends EventEmitter { this._saveUserData('network', this._network); return this._socket.connect({ path: ipcPath }, { - timeout: 30000 /* 30s */ - }) + timeout: 10000 /* 30s */ + }) .then(() => { this.state = STATES.CONNECTED; }) @@ -523,7 +523,7 @@ class EthereumNode extends EventEmitter { resolve(proc); } - }, 4000); + }, 1000); }) }); }); diff --git a/modules/sockets.js b/modules/sockets.js index 4d14010bd..8d770786c 100644 --- a/modules/sockets.js +++ b/modules/sockets.js @@ -51,6 +51,8 @@ class Socket extends EventEmitter { return this._resetSocket() .then(() => { + let timeoutId = null; + let intervalId = null; this._log.debug('Connecting...'); this._state = STATE.CONNECTING; @@ -60,33 +62,45 @@ class Socket extends EventEmitter { this._log.info('Connected!'); this._state = STATE.CONNECTED; + clearTimeout(timeoutId); + clearInterval(intervalId); this.emit('connect'); resolve(); }); - this._socket.once('error', (err) => { - if (STATE.CONNECTING === this._state) { + this._socket.on('error', (err) => { + // reject after the timeout is over + if (!options.timeout || STATE.CONNECTION_TIMEOUT === this._state) { this._log.error('Connection error', err); + clearTimeout(timeoutId); + clearInterval(intervalId); + this._state = STATE.ERROR; + this._socket.removeAllListeners('error'); return reject(new Error(`Unable to connect to socket: ${err.message}`)); } }); + // add timeout if (options.timeout) { this._log.debug(`Will wait ${options.timeout}ms for connection to happen.`); - setTimeout(() => { - if (STATE.CONNECTING === this._state) { - this._socket.emit('error', `Connection timeout (took longer than ${options.timeout} ms)`); + timeoutId = setTimeout(() => { + if (STATE.CONNECTED !== this._state) { + this._state = STATE.CONNECTION_TIMEOUT; + // this._socket.emit('error', `Connection timeout (took longer than ${options.timeout} ms)`); } }, options.timeout); } - this._socket.connect(connectConfig); + // try connecting + intervalId = setInterval(() => { + this._socket.connect(connectConfig); + }, 200); }); }); } @@ -388,6 +402,7 @@ const STATE = Socket.STATE = { DISCONNECTED: 4, ERROR: -1, DISCONNECTION_TIMEOUT: -2, + CONNECTION_TIMEOUT: -3, }; diff --git a/modules/windows.js b/modules/windows.js index 7301c01db..1f52b99bd 100644 --- a/modules/windows.js +++ b/modules/windows.js @@ -172,11 +172,12 @@ class Windows { alwaysOnTop: true, resizable: false, width: 100, - height: 50, + height: 80, center: true, frame: false, useContentSize: true, titleBarStyle: 'hidden', //hidden-inset: more space + skipTaskbar: true }, }); From 799703443f7d5d3a2c67fc0b144d25d5d34656ae Mon Sep 17 00:00:00 2001 From: Ramesh Nair <ram@hiddentao.com> Date: Fri, 17 Jun 2016 15:10:48 +0800 Subject: [PATCH 2/4] better socket connection logic, remove master ps logic for eth --- main.js | 5 -- modules/ethereumNode.js | 119 ++++++++++++++++++++-------------------- modules/settings.js | 26 +++++++++ modules/sockets.js | 72 ++++++++++++------------ 4 files changed, 124 insertions(+), 98 deletions(-) diff --git a/main.js b/main.js index e6ddb1ba2..57ea1e23c 100644 --- a/main.js +++ b/main.js @@ -152,11 +152,6 @@ app.on('before-quit', function(event){ }); - - -const NODE_TYPE = 'geth'; - - var mainWindow; var splashWindow; diff --git a/modules/ethereumNode.js b/modules/ethereumNode.js index 949522cea..14bf150ab 100644 --- a/modules/ethereumNode.js +++ b/modules/ethereumNode.js @@ -24,7 +24,7 @@ const DEFAULT_NETWORK = 'main'; const UNABLE_TO_BIND_PORT_ERROR = 'unableToBindPort'; const UNABLE_TO_SPAWN_ERROR = 'unableToSpan'; const PASSWORD_WRONG_ERROR = 'badPassword'; - +const NODE_START_WAIT_MS = 3000; /** @@ -336,59 +336,59 @@ class EthereumNode extends EventEmitter { log.debug(`Start node using ${binPath}`); return new Q((resolve, reject) => { - if ('eth' === nodeType) { - let modalWindow = Windows.createPopup('unlockMasterPassword', { - electronOptions: { - width: 400, - height: 220, - }, - useWeb3: false, - }); - - let called = false; - - modalWindow.on('closed', () => { - if (!called) { - app.quit(); - } - }); - - let popupCallback = function(err) { - if (err && _.get(modalWindow,'webContents')) { - log.error('unlockMasterPassword error', err); - - if(UNABLE_TO_SPAWN_ERROR === err) { - modalWindow.close(); - modalWindow = null; - } else { - modalWindow.webContents.send('data', { - masterPasswordWrong: true - }); - } - } else { - called = true; - modalWindow.close(); - modalWindow = null; - ipc.removeAllListeners('backendAction_unlockedMasterPassword'); - } - }; - - ipc.on('backendAction_unlockedMasterPassword', (ev, err, pw) => { - if (_.get(modalWindow, 'webContents') && ev.sender.getId() === modalWindow.webContents.getId()) { - if (!err) { - this.__startProcess(nodeType, network, binPath, pw, popupCallback) - .then(resolve, reject); - } else { - app.quit(); - } - - return; - } - }); - } else { + // if ('eth' === nodeType) { + // let modalWindow = Windows.createPopup('unlockMasterPassword', { + // electronOptions: { + // width: 400, + // height: 220, + // }, + // useWeb3: false, + // }); + + // let called = false; + + // modalWindow.on('closed', () => { + // if (!called) { + // app.quit(); + // } + // }); + + // let popupCallback = function(err) { + // if (err && _.get(modalWindow,'webContents')) { + // log.error('unlockMasterPassword error', err); + + // if(UNABLE_TO_SPAWN_ERROR === err) { + // modalWindow.close(); + // modalWindow = null; + // } else { + // modalWindow.webContents.send('data', { + // masterPasswordWrong: true + // }); + // } + // } else { + // called = true; + // modalWindow.close(); + // modalWindow = null; + // ipc.removeAllListeners('backendAction_unlockedMasterPassword'); + // } + // }; + + // ipc.on('backendAction_unlockedMasterPassword', (ev, err, pw) => { + // if (_.get(modalWindow, 'webContents') && ev.sender.getId() === modalWindow.webContents.getId()) { + // if (!err) { + // this.__startProcess(nodeType, network, binPath, pw, popupCallback) + // .then(resolve, reject); + // } else { + // app.quit(); + // } + + // return; + // } + // }); + // } else { this.__startProcess(nodeType, network, binPath) .then(resolve, reject); - } + // } }); } @@ -421,7 +421,7 @@ class EthereumNode extends EventEmitter { else { args = (nodeType === 'geth') ? ['--fast', '--cache', '512'] - : ['--unsafe-transactions', '--master', pw]; + : ['--unsafe-transactions'/*, '--master', pw*/]; pw = null; } @@ -510,12 +510,13 @@ class EthereumNode extends EventEmitter { // when data is first received this.once('data', () => { /* - Assume startup succeeded after 5 seconds. At this point - IPC connections are usually possible. + We wait a short while before marking startup as successful + because we may want to parse the initial node output for + errors, etc (see geth port-binding error above) */ setTimeout(() => { if (STATES.STARTING === this.state) { - log.info('4s elapsed, assuming node started up successfully'); + log.info(`${NODE_START_WAIT_MS}ms elapsed, assuming node started up successfully`); if (popupCallback) { popupCallback(); @@ -523,7 +524,7 @@ class EthereumNode extends EventEmitter { resolve(proc); } - }, 1000); + }, NODE_START_WAIT_MS); }) }); }); @@ -571,8 +572,8 @@ class EthereumNode extends EventEmitter { _loadDefaults () { log.trace('Load defaults'); - this.defaultNodeType = this._loadUserData('node') || DEFAULT_NODE_TYPE; - this.defaultNetwork = this._loadUserData('network') || DEFAULT_NETWORK; + this.defaultNodeType = Settings.nodeType || this._loadUserData('node') || DEFAULT_NODE_TYPE; + this.defaultNetwork = Settings.network || this._loadUserData('network') || DEFAULT_NETWORK; } diff --git a/modules/settings.js b/modules/settings.js index 4b0072115..ee166be02 100644 --- a/modules/settings.js +++ b/modules/settings.js @@ -27,6 +27,24 @@ const argv = require('yargs') type: 'string', group: 'Mist options:', }, + node: { + demand: false, + default: null, + describe: 'Node to use: geth, eth', + requiresArg: true, + nargs: 1, + type: 'string', + group: 'Mist options:', + }, + network: { + demand: false, + default: null, + describe: 'Network to connect to: main, test', + requiresArg: true, + nargs: 1, + type: 'string', + group: 'Mist options:', + }, ipcpath: { demand: false, describe: 'Path to node IPC socket file (this will automatically get passed as an option to Geth).', @@ -162,6 +180,14 @@ class Settings { return argv.ipcpath; } + get nodeType () { + return argv.node; + } + + get network () { + return argv.network; + } + get nodeOptions () { return argv.nodeOptions; } diff --git a/modules/sockets.js b/modules/sockets.js index 8d770786c..895948adf 100644 --- a/modules/sockets.js +++ b/modules/sockets.js @@ -10,6 +10,9 @@ const dechunker = require('./ipc/dechunker.js'); +const CONNECT_INTERVAL_MS = 1000; +const CONNECT_TIMEOUT_MS = 3000; + /** * Socket connecting to Ethereum Node. @@ -41,66 +44,67 @@ class Socket extends EventEmitter { * Connect to host. * @param {Object} connectConfig * @param {Object} [options] - * @param {Number} [options.timeout] Milliseconds to wait before timeout. + * @param {Number} [options.timeout] Milliseconds to wait before timeout (default is 5000). * @return {Promise} */ connect (connectConfig, options) { this._log.info(`Connect to ${JSON.stringify(connectConfig)}`); - options = options || {}; + options = _.extend({ + timeout: CONNECT_TIMEOUT_MS, + }, options); return this._resetSocket() .then(() => { - let timeoutId = null; - let intervalId = null; + let connectTimerId = null; + let timeoutTimerId = null; + this._log.debug('Connecting...'); + this._log.debug(`Will wait ${options.timeout}ms for connection to happen.`); + this._state = STATE.CONNECTING; return new Q((resolve, reject) => { this._socket.once('connect', () => { - this._log.info('Connected!'); + if (STATE.CONNECTING === this._state) { + this._log.info('Connected!'); - this._state = STATE.CONNECTED; - clearTimeout(timeoutId); - clearInterval(intervalId); + this._state = STATE.CONNECTED; - this.emit('connect'); + clearTimeout(connectTimerId); + clearTimeout(timeoutTimerId); - resolve(); + this.emit('connect'); + + resolve(); + } }); this._socket.on('error', (err) => { - // reject after the timeout is over - if (!options.timeout || STATE.CONNECTION_TIMEOUT === this._state) { - this._log.error('Connection error', err); - - clearTimeout(timeoutId); - clearInterval(intervalId); + if (STATE.CONNECTING === this._state) { + this._log.warn(`Connection failed, retrying after ${CONNECT_INTERVAL_MS}ms...`); - this._state = STATE.ERROR; - - this._socket.removeAllListeners('error'); - return reject(new Error(`Unable to connect to socket: ${err.message}`)); + connectTimerId = setTimeout(() => { + this._socket.connect(connectConfig); + }, CONNECT_INTERVAL_MS); } }); - // add timeout - if (options.timeout) { - this._log.debug(`Will wait ${options.timeout}ms for connection to happen.`); + timeoutTimerId = setTimeout(() => { + if (STATE.CONNECTING === this._state) { + this._log.error(`Connection failed (${options.timeout}ms elapsed)`); - timeoutId = setTimeout(() => { - if (STATE.CONNECTED !== this._state) { - this._state = STATE.CONNECTION_TIMEOUT; - // this._socket.emit('error', `Connection timeout (took longer than ${options.timeout} ms)`); - } - }, options.timeout); - } + this._state = STATE.CONNECTION_TIMEOUT; + + clearTimeout(connectTimerId); + + return reject(new Error(`Unable to connect to socket: timeout`)); + } + }, options.timeout); - // try connecting - intervalId = setInterval(() => { - this._socket.connect(connectConfig); - }, 200); + // initial kick-off + this._socket.connect(connectConfig); }); }); } From dcab76f5afcb4d92e36fd83bc4c2565551b3813c Mon Sep 17 00:00:00 2001 From: Ramesh Nair <ram@hiddentao.com> Date: Fri, 17 Jun 2016 17:13:01 +0800 Subject: [PATCH 3/4] fix splash screen display of state text for eth --- .../popupWindows/onboardingScreen.js | 2 +- .../templates/popupWindows/splashScreen.html | 4 ++- .../templates/popupWindows/splashScreen.js | 29 +++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/interface/client/templates/popupWindows/onboardingScreen.js b/interface/client/templates/popupWindows/onboardingScreen.js index b912dab4f..140b1a4dd 100644 --- a/interface/client/templates/popupWindows/onboardingScreen.js +++ b/interface/client/templates/popupWindows/onboardingScreen.js @@ -111,7 +111,7 @@ Template['popupWindows_onboardingScreen'].helpers({ // Saves the data back to the object TemplateVar.set(template, 'syncing', syncing); - console.log('number', Number(syncing.statesPercent), Number(syncing.statesPercent) < 90) + console.trace('number', Number(syncing.statesPercent), Number(syncing.statesPercent) < 90) // Only show states if they are less than 50% downloaded if (Number(syncing.statesPercent) > 0 && Number(syncing.statesPercent) < 90) { diff --git a/interface/client/templates/popupWindows/splashScreen.html b/interface/client/templates/popupWindows/splashScreen.html index 1cf165530..008408034 100644 --- a/interface/client/templates/popupWindows/splashScreen.html +++ b/interface/client/templates/popupWindows/splashScreen.html @@ -21,7 +21,9 @@ <h1> {{#if TemplateVar.get "showProgressBar"}} <progress class="{{mode}}" max="100" value="{{TemplateVar.get 'progress'}}"></progress> - <progress class="state {{mode}}" max="100" value="{{TemplateVar.get 'stateProgress'}}"></progress> + {{#if TemplateVar.get "showStateProgressBar"}} + <progress class="state {{mode}}" max="100" value="{{TemplateVar.get 'stateProgress'}}"></progress> + {{/if}} {{/if}} </div> </template> \ No newline at end of file diff --git a/interface/client/templates/popupWindows/splashScreen.js b/interface/client/templates/popupWindows/splashScreen.js index b588ce249..8d47c9e0e 100644 --- a/interface/client/templates/popupWindows/splashScreen.js +++ b/interface/client/templates/popupWindows/splashScreen.js @@ -100,8 +100,10 @@ Template['popupWindows_splashScreen'].onCreated(function(){ if(web3.net.peerCount > 0) { // Check which state we are - if ( lastSyncData.pulledStates != Math.round(lastSyncData._displayState) - || lastSyncData.knownStates != Math.round(lastSyncData._displayKnownStates)) { + if ( 0 < lastSyncData._displayKnownStates && ( + lastSyncData.pulledStates != Math.round(lastSyncData._displayState) + || lastSyncData.knownStates != Math.round(lastSyncData._displayKnownStates)) + ) { // Mostly downloading new states translationString = 'mist.startScreen.nodeSyncInfoStates'; @@ -181,16 +183,13 @@ Template['popupWindows_splashScreen'].helpers({ if (!(syncData._displayBlock > -1)) { // initialize the display numbers syncData._displayBlock = Number(syncData.currentBlock); - syncData._displayState = Number(syncData.pulledStates); - syncData._displayKnownStates = Number(syncData.knownStates); - + syncData._displayState = Number(syncData.pulledStates || 0); + syncData._displayKnownStates = Number(syncData.knownStates || 0); } else { // Increment each them slowly to match target number - syncData._displayBlock = syncData._displayBlock + (Number(syncData.currentBlock) - syncData._displayBlock) / 10; - - syncData._displayState = syncData._displayState + (Number(syncData.pulledStates) - syncData._displayState) / 10; - - syncData._displayKnownStates = syncData._displayKnownStates + (Number(syncData.knownStates) - syncData._displayKnownStates) / 10; + syncData._displayBlock += (Number(syncData.currentBlock) - syncData._displayBlock) / 10; + syncData._displayState += (Number(syncData.pulledStates || 0) - syncData._displayState) / 10; + syncData._displayKnownStates += (Number(syncData.knownStates || 0) - syncData._displayKnownStates) / 10; }; // Create the fancy strings @@ -202,7 +201,10 @@ Template['popupWindows_splashScreen'].helpers({ var translatedMessage = TAPi18n.__(translationString, syncData); // Calculates both progress bars - var stateProgress = (lastSyncData._displayState / lastSyncData._displayKnownStates) * 100; + var stateProgress = null; + if (0 < lastSyncData._displayKnownStates) { + stateProgress = (lastSyncData._displayState / lastSyncData._displayKnownStates) * 100; + } var progress = ((lastSyncData._displayBlock - Number(lastSyncData.startingBlock)) / (Number(lastSyncData._highestBlock) - Number(lastSyncData.startingBlock))) * 100 ; @@ -214,7 +216,10 @@ Template['popupWindows_splashScreen'].helpers({ if(_.isFinite(progress)) { TemplateVar.set(template, 'showProgressBar', true); TemplateVar.set(template, 'progress', progress); - TemplateVar.set(template, 'stateProgress', stateProgress); + if (null !== stateProgress) { + TemplateVar.set(template, 'showStateProgressBar', true); + TemplateVar.set(template, 'stateProgress', stateProgress); + } } }, 100); From ec1f4f9857e97864e22c9e7471876ca8f7682911 Mon Sep 17 00:00:00 2001 From: Ramesh Nair <ram@hiddentao.com> Date: Mon, 20 Jun 2016 12:34:19 +0800 Subject: [PATCH 4/4] better gulp download plugin, remove master passwd stuff once and for all --- gulpfile.js | 2 +- modules/ethereumNode.js | 82 +++-------------------------------------- package.json | 2 +- 3 files changed, 7 insertions(+), 79 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index d81634973..db415d2bc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,7 @@ var packager = require('electron-packager'); var spawn = require('child_process').spawn; var merge = require('merge-stream'); var rename = require("gulp-rename"); -var download = require('gulp-download'); +var download = require('gulp-download-stream'); var decompress = require('gulp-decompress'); var tap = require("gulp-tap"); // const zip = require('gulp-zip'); diff --git a/modules/ethereumNode.js b/modules/ethereumNode.js index 14bf150ab..8cf2a3f8f 100644 --- a/modules/ethereumNode.js +++ b/modules/ethereumNode.js @@ -289,7 +289,7 @@ class EthereumNode extends EventEmitter { this._saveUserData('network', this._network); return this._socket.connect({ path: ipcPath }, { - timeout: 10000 /* 30s */ + timeout: 30000 /* 30s */ }) .then(() => { this.state = STATES.CONNECTED; @@ -336,60 +336,8 @@ class EthereumNode extends EventEmitter { log.debug(`Start node using ${binPath}`); return new Q((resolve, reject) => { - // if ('eth' === nodeType) { - // let modalWindow = Windows.createPopup('unlockMasterPassword', { - // electronOptions: { - // width: 400, - // height: 220, - // }, - // useWeb3: false, - // }); - - // let called = false; - - // modalWindow.on('closed', () => { - // if (!called) { - // app.quit(); - // } - // }); - - // let popupCallback = function(err) { - // if (err && _.get(modalWindow,'webContents')) { - // log.error('unlockMasterPassword error', err); - - // if(UNABLE_TO_SPAWN_ERROR === err) { - // modalWindow.close(); - // modalWindow = null; - // } else { - // modalWindow.webContents.send('data', { - // masterPasswordWrong: true - // }); - // } - // } else { - // called = true; - // modalWindow.close(); - // modalWindow = null; - // ipc.removeAllListeners('backendAction_unlockedMasterPassword'); - // } - // }; - - // ipc.on('backendAction_unlockedMasterPassword', (ev, err, pw) => { - // if (_.get(modalWindow, 'webContents') && ev.sender.getId() === modalWindow.webContents.getId()) { - // if (!err) { - // this.__startProcess(nodeType, network, binPath, pw, popupCallback) - // .then(resolve, reject); - // } else { - // app.quit(); - // } - - // return; - // } - // }); - // } else { - this.__startProcess(nodeType, network, binPath) - .then(resolve, reject); - // } - + this.__startProcess(nodeType, network, binPath) + .then(resolve, reject); }); } @@ -397,7 +345,7 @@ class EthereumNode extends EventEmitter { /** * @return {Promise} */ - __startProcess (nodeType, network, binPath, pw, popupCallback) { + __startProcess (nodeType, network, binPath) { return new Q((resolve, reject) => { log.trace('Rotate log file'); @@ -421,8 +369,7 @@ class EthereumNode extends EventEmitter { else { args = (nodeType === 'geth') ? ['--fast', '--cache', '512'] - : ['--unsafe-transactions'/*, '--master', pw*/]; - pw = null; + : ['--unsafe-transactions']; } let nodeOptions = Settings.nodeOptions; @@ -442,10 +389,6 @@ class EthereumNode extends EventEmitter { if (STATES.STARTING === this.state) { this.state = STATES.ERROR; - if (popupCallback) { - popupCallback(UNABLE_TO_SPAWN_ERROR); - } - log.info('Node startup error'); // TODO: detect this properly @@ -455,17 +398,6 @@ class EthereumNode extends EventEmitter { } }); - // node quit, e.g. master pw wrong - proc.once('exit', () => { - if ('eth' === nodeType) { - log.warn('Password wrong!'); - - if (popupCallback) { - popupCallback(PASSWORD_WRONG_ERROR); - } - } - }); - // we need to read the buff to prevent node from not working proc.stderr.pipe( fs.createWriteStream(this._buildFilePath('node.log'), { flags: 'a' }) @@ -518,10 +450,6 @@ class EthereumNode extends EventEmitter { if (STATES.STARTING === this.state) { log.info(`${NODE_START_WAIT_MS}ms elapsed, assuming node started up successfully`); - if (popupCallback) { - popupCallback(); - } - resolve(proc); } }, NODE_START_WAIT_MS); diff --git a/package.json b/package.json index 915a67fac..0c04fce9b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "electron-packager": "^7.0.4", "gulp": "^3.9.0", "gulp-decompress": "^2.0.0", - "gulp-download": "0.0.1", + "gulp-download-stream": "0.0.13", "gulp-rename": "^1.2.2", "gulp-replace": "^0.5.4", "gulp-tap": "^0.1.3",