diff --git a/bin/node b/bin/node index bc9693a5..6fb094ad 100755 --- a/bin/node +++ b/bin/node @@ -50,6 +50,26 @@ process.on('SIGINT', async () => { await node.close(); }); +node.on('abort', async (err) => { + const timeout = setTimeout(() => { + console.error('Shutdown is taking a long time. Exitting.'); + process.exit(3); + }, 5000); + + timeout.unref(); + + try { + console.error('Shutting down...'); + await node.close(); + clearTimeout(timeout); + console.error(err.stack); + process.exit(2); + } catch (e) { + console.error(`Error occurred during shutdown: ${e.message}`); + process.exit(3); + } +}); + (async () => { await node.ensure(); await node.open(); diff --git a/bin/spvnode b/bin/spvnode index e24a9d02..50ebc4f6 100755 --- a/bin/spvnode +++ b/bin/spvnode @@ -67,6 +67,26 @@ process.on('SIGINT', async () => { await node.close(); }); +node.on('abort', async (err) => { + const timeout = setTimeout(() => { + console.error('Shutdown is taking a long time. Exitting.'); + process.exit(3); + }, 5000); + + timeout.unref(); + + try { + console.error('Shutting down...'); + await node.close(); + clearTimeout(timeout); + console.error(err.stack); + process.exit(2); + } catch (e) { + console.error(`Error occurred during shutdown: ${e.message}`); + process.exit(3); + } +}); + (async () => { await node.ensure(); await node.open(); diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index dd26947f..f61508dd 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -998,8 +998,9 @@ class Chain extends AsyncEmitter { if (ns.isNull()) { if (!covenant.isClaim() && !covenant.isOpen()) { - this.emit('abort', 'Unexpected null NameState.'); - throw new CriticalError('Database inconsistency.'); + const error = new CriticalError('Database inconsistency.'); + this.emit('abort', error); + throw error; } const name = covenant.get(2); @@ -1899,8 +1900,9 @@ class Chain extends AsyncEmitter { try { await this.db.save(entry, block, view); } catch (e) { - this.emit('abort', e.message); - throw new CriticalError(e); + const error = new CriticalError(e.message); + this.emit('abort', error); + throw error; } // Expose the new state. @@ -1963,8 +1965,9 @@ class Chain extends AsyncEmitter { try { await this.db.save(entry, block); } catch (e) { - this.emit('abort', e.message); - throw new CriticalError(e); + const error = new CriticalError(e.message); + this.emit('abort', error); + throw error; } this.logger.warning('Heads up: Competing chain at height %d:' diff --git a/lib/errors.js b/lib/errors.js index 3d0b9ffa..31ffbc78 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -10,43 +10,24 @@ * @module errors */ -const assert = require('bsert'); - /** * Critical Error * An error severe enough to warrant shutting down the node. * @extends Error - * @param {Block|TX} msg - * @param {String} code - Reject packet code. - * @param {String} reason - Reject packet reason. - * @param {Number} score - Ban score increase - * (can be -1 for no reject packet). - * @param {Boolean} malleated */ class CriticalError extends Error { /** * Create a verify error. * @constructor - * @param {Block|TX} msg - * @param {String} code - Reject packet code. - * @param {String} reason - Reject packet reason. - * @param {Number} score - Ban score increase - * (can be -1 for no reject packet). - * @param {Boolean} malleated + * @param {String} msg */ - constructor(err) { + constructor(msg) { super(); this.type = 'CriticalError'; - - if (err instanceof Error) { - this.message = `Critical Error: ${err.message}`; - } else { - assert(typeof err === 'string'); - this.message = `Critical Error: ${err}`; - } + this.message = `Critical Error: ${msg}`; if (Error.captureStackTrace) Error.captureStackTrace(this, CriticalError); diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 4fd7245d..1754180d 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -197,7 +197,7 @@ class FullNode extends Node { init() { // Bind to errors this.chain.on('error', err => this.error(err)); - this.chain.on('abort', msg => this.abort(msg)); + this.chain.on('abort', err => this.abort(err)); this.mempool.on('error', err => this.error(err)); this.pool.on('error', err => this.error(err)); @@ -350,23 +350,6 @@ class FullNode extends Node { this.emit('closed'); } - /** - * Emergency shutdown. - * @param {String} msg - error message - * @returns {Promise} - */ - - async abort(msg) { - this.logger.error(`Critical error, shutting down: ${msg}`); - try { - this.emit('abort', msg); - await this.close(); - } catch (e) { - this.logger.error(`Error occurred during shutdown: ${e.message}`); - process.exit(-2); - } - } - /** * Rescan for any missed transactions. * @param {Number|Hash} start - Start block. diff --git a/lib/node/node.js b/lib/node/node.js index 315d9c05..dea3d5ba 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -305,6 +305,17 @@ class Node extends EventEmitter { this.emit('error', err); } + /** + * Emit and log an abort error. + * @private + * @param {Error} err + */ + + abort(err) { + this.logger.error(err); + this.emit('abort', err); + } + /** * Get node uptime in seconds. * @returns {Number} diff --git a/lib/node/spvnode.js b/lib/node/spvnode.js index cf67da9d..87548ab4 100644 --- a/lib/node/spvnode.js +++ b/lib/node/spvnode.js @@ -130,7 +130,7 @@ class SPVNode extends Node { init() { // Bind to errors this.chain.on('error', err => this.error(err)); - this.chain.on('abort', msg => this.abort(msg)); + this.chain.on('abort', err => this.abort(err)); this.pool.on('error', err => this.error(err)); @@ -221,23 +221,6 @@ class SPVNode extends Node { this.emit('closed'); } - /** - * Emergency shutdown. - * @param {String} msg - error message - * @returns {Promise} - */ - - async abort(msg) { - this.logger.error(`Critical error, shutting down: ${msg}`); - try { - this.emit('abort', msg); - await this.close(); - } catch (e) { - this.logger.error(`Error occurred during shutdown: ${e.message}`); - process.exit(-2); - } - } - /** * Scan for any missed transactions. * Note that this will replay the blockchain sync. diff --git a/test/node-critical-error-test.js b/test/node-critical-error-test.js index 68df3449..7722d432 100644 --- a/test/node-critical-error-test.js +++ b/test/node-critical-error-test.js @@ -88,6 +88,14 @@ describe('Node Critical Error', function() { node.once('closed', () => resolve()); }); + node.on('abort', async () => { + try { + await node.close(); + } catch (e) { + ; + } + }); + await mineBlocks(node, 99); node.chain.db.db.batch = () => { return { @@ -112,6 +120,14 @@ describe('Node Critical Error', function() { node.once('closed', () => resolve()); }); + node.on('abort', async () => { + try { + await node.close(); + } catch (e) { + ; + } + }); + await mineBlocks(node, 50); node.chain.db.tree.store.commit = () => { throw new Error('Disk full!');