From a61e1d9b8fa9c04471b8d4e3ff49b438bc832818 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 20 Aug 2015 17:50:14 -0400 Subject: [PATCH 1/8] start and stop services --- bin/start.js | 30 +++++++++++++ lib/chain.js | 9 ++++ lib/daemon.js | 40 ++++------------- lib/db.js | 9 +++- lib/module.js | 8 ++++ lib/node.js | 112 ++++++++++++++++++++++++++++++++-------------- test/node.unit.js | 16 +++++++ 7 files changed, 156 insertions(+), 68 deletions(-) diff --git a/bin/start.js b/bin/start.js index 7b65c58c..9b9f716a 100644 --- a/bin/start.js +++ b/bin/start.js @@ -141,3 +141,33 @@ node.chain.on('addblock', function(block) { }, 10000); } }); + +process.stdin.resume();//so the program will not close instantly + +function exitHandler(options, err) { + if (err) { + log.error('uncaught exception:', err); + if(err.stack) { + console.log(err.stack); + } + process.exit(-1); + } + if (options.sigint) { + log.info('Stopping Services'); + node.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + return process.exit(1); + } + + log.info('Halted'); + process.exit(0); + }); + } +} + +//catches ctrl+c event +process.on('SIGINT', exitHandler.bind(null, {sigint:true})); + +//catches uncaught exceptions +process.on('uncaughtException', exitHandler.bind(null, {exit:true})); diff --git a/lib/chain.js b/lib/chain.js index f9e7c200..7e93d38b 100644 --- a/lib/chain.js +++ b/lib/chain.js @@ -50,6 +50,15 @@ function Chain(options) { util.inherits(Chain, BaseChain); +Chain.prototype.start = function(callback) { + this.on('initialized', callback); + this.initialize(); +}; + +Chain.prototype.stop = function(callback) { + setImmediate(callback); +}; + Chain.prototype._writeBlock = function(block, callback) { // Update hashes this.cache.hashes[block.hash] = block.prevHash; diff --git a/lib/daemon.js b/lib/daemon.js index 429efa9f..23cc18d0 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -35,7 +35,7 @@ function Daemon(options) { this.on('newListener', function(name) { if (name === 'open') { - self.start(); + //self.start(); } }); } @@ -67,22 +67,9 @@ Daemon.prototype.__defineGetter__('global', function() { return Daemon.global; }); -Daemon.prototype.start = function(options, callback) { +Daemon.prototype.start = function(callback) { var self = this; - if (!callback) { - callback = options; - options = null; - } - - if (!options) { - options = {}; - } - - if (!callback) { - callback = function() {}; - } - if (this.instances[this.datadir]) { return; } @@ -97,12 +84,12 @@ Daemon.prototype.start = function(options, callback) { var errorCaught = none; Object.keys(this.options).forEach(function(key) { - if (options[key] == null) { - options[key] = self.options[key]; + if (self.options[key] == null) { + self.options[key] = self.options[key]; } }); - bitcoind.start(options, function(err, status) { + bitcoind.start(this.options, function(err, status) { self._started = true; // Poll for queued packet @@ -179,23 +166,12 @@ Daemon.prototype.start = function(options, callback) { }); setTimeout(function callee() { - // Wait until wallet is loaded: - if (callback) { - callback(err ? err : null); - } - if (err) { self.emit('error', err); + callback(err); } else { - if (callback) { - self.emit('open', status); - } else { - self.emit('status', status); - } - } - - if (callback) { - callback = null; + self.emit('open', status); + callback(); } }, 100); }); diff --git a/lib/db.js b/lib/db.js index cdb01423..43733b02 100644 --- a/lib/db.js +++ b/lib/db.js @@ -40,7 +40,7 @@ function DB(options) { util.inherits(DB, BaseDB); -DB.prototype.initialize = function() { +DB.prototype.start = function(callback) { // Add all db option modules if(this._modules && this._modules.length) { for(var i = 0; i < this._modules.length; i++) { @@ -49,7 +49,12 @@ DB.prototype.initialize = function() { } this.bitcoind.on('tx', this.transactionHandler.bind(this)); this.emit('ready'); -} + callback(); +}; + +DB.prototype.stop = function(callback) { + this.store.close(callback); +}; DB.prototype.getBlock = function(hash, callback) { var self = this; diff --git a/lib/module.js b/lib/module.js index b9e39e66..0f15fb3b 100644 --- a/lib/module.js +++ b/lib/module.js @@ -45,4 +45,12 @@ Module.prototype.getAPIMethods = function() { // // }; +Module.prototype.start = function() { + +}; + +Module.prototype.stop = function() { + +}; + module.exports = Module; diff --git a/lib/node.js b/lib/node.js index a4f6bee7..d3f5c804 100644 --- a/lib/node.js +++ b/lib/node.js @@ -25,6 +25,12 @@ function Node(config) { util.inherits(Node, BaseNode); +var defaultServices = { + 'bitcoind': [], + 'db': ['bitcoind'], + 'chain': ['db'], +}; + Node.prototype.openBus = function() { return new Bus({db: this.db}); }; @@ -372,15 +378,23 @@ Node.prototype._loadConsensus = function(config) { this.chain = new Chain(config.consensus); }; -Node.prototype._initializeBitcoind = function() { +Node.prototype._initialize = function() { var self = this; + // DB References + this.db.chain = this.chain; + this.db.Block = this.Block; + this.db.bitcoind = this.bitcoind; + + // Chain References + this.chain.db = this.db; + + // Bitcoind this.bitcoind.on('ready', function(status) { log.info('Bitcoin Daemon Ready'); // Set the current chain height var info = self.bitcoind.getInfo(); self.bitcoindHeight = info.blocks; - self.db.initialize(); }); this.bitcoind.on('open', function(status) { @@ -400,27 +414,6 @@ Node.prototype._initializeBitcoind = function() { self.emit('error', err); }); -}; - -Node.prototype._initializeDatabase = function() { - var self = this; - - // Database - this.db.on('ready', function() { - log.info('Bitcoin Database Ready'); - self.chain.initialize(); - }); - - this.db.on('error', function(err) { - Error.captureStackTrace(err); - self.emit('error', err); - }); - -}; - -Node.prototype._initializeChain = function() { - var self = this; - // Chain this.chain.on('ready', function() { log.info('Bitcoin Chain Ready'); @@ -432,23 +425,74 @@ Node.prototype._initializeChain = function() { Error.captureStackTrace(err); self.emit('error', err); }); + + // Database + this.db.on('ready', function() { + log.info('Bitcoin Database Ready'); + }); + + this.db.on('error', function(err) { + Error.captureStackTrace(err); + self.emit('error', err); + }); + + this.start(function(err) { + if(err) { + log.error('Failed starting services: ' + err); + } + }); }; -Node.prototype._initialize = function() { +Node.prototype.getServiceOrder = function(services, keys, stack) { + if(!services) { + services = defaultServices; + } - // DB References - this.db.chain = this.chain; - this.db.Block = this.Block; - this.db.bitcoind = this.bitcoind; + if(!keys) { + keys = Object.keys(services); + } - // Chain References - this.chain.db = this.db; + if(!stack) { + stack = []; + } - // Setup Chain of Events - this._initializeBitcoind(); - this._initializeDatabase(); - this._initializeChain(); + for(var i = 0; i < keys.length; i++) { + this.getServiceOrder(services, services[keys[i]], stack); + if(stack.indexOf(keys[i]) === -1) { + stack.push(keys[i]); + } + } + return stack; }; +Node.prototype.start = function(callback) { + var self = this; + var services = this.getServiceOrder(); + + async.eachSeries( + services, + function(service, next) { + log.info('Starting ' + service); + self[service].start(next); + }, + callback + ); +}; + +Node.prototype.stop = function(callback) { + var self = this; + var services = this.getServiceOrder().reverse(); + + async.eachSeries( + services, + function(service, next) { + log.info('Stopping ' + service); + self[service].stop(next); + }, + callback + ); +}; + + module.exports = Node; diff --git a/test/node.unit.js b/test/node.unit.js index 674f3ed4..203476ad 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -533,4 +533,20 @@ describe('Bitcoind Node', function() { }); }); + + describe('#getServiceOrder', function() { + var services = { + 'chain': ['db'], + 'db': ['daemon', 'p2p'], + 'daemon': [], + 'p2p': [] + }; + + it('should return the services in the correct order', function() { + var node = new Node({}); + var order = node.getServiceOrder(services); + + order.should.deep.equal(['daemon', 'p2p', 'db', 'chain']); + }); + }); }); From 164a2cad75c1ecacbf722a787c9e01733ea67ae2 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Fri, 21 Aug 2015 11:53:20 -0400 Subject: [PATCH 2/8] cleanup daemon --- bin/start.js | 17 +++++ lib/daemon.js | 167 +++++---------------------------------------- lib/node.js | 15 ++-- src/libbitcoind.cc | 26 ------- src/libbitcoind.h | 2 - 5 files changed, 41 insertions(+), 186 deletions(-) diff --git a/bin/start.js b/bin/start.js index 9b9f716a..72f8dd04 100644 --- a/bin/start.js +++ b/bin/start.js @@ -142,6 +142,10 @@ node.chain.on('addblock', function(block) { } }); +node.on('stopping', function() { + clearInterval(interval); +}); + process.stdin.resume();//so the program will not close instantly function exitHandler(options, err) { @@ -169,5 +173,18 @@ function exitHandler(options, err) { //catches ctrl+c event process.on('SIGINT', exitHandler.bind(null, {sigint:true})); +/*setTimeout(function() { + log.info('Stopping Services'); + node.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + return process.exit(1); + } + + log.info('Halted'); + process.exit(0); + }); +}, 10000);*/ + //catches uncaught exceptions process.on('uncaughtException', exitHandler.bind(null, {exit:true})); diff --git a/lib/daemon.js b/lib/daemon.js index 23cc18d0..54717ad3 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -33,29 +33,10 @@ function Daemon(options) { self[key] = exports[key]; }); - this.on('newListener', function(name) { - if (name === 'open') { - //self.start(); - } - }); } util.inherits(Daemon, EventEmitter); -// Make sure signal handlers are not overwritten -Daemon._signalQueue = []; -Daemon._processOn = process.on; -process.addListener = -process.on = function(name, listener) { - if (~['SIGINT', 'SIGHUP', 'SIGQUIT'].indexOf(name.toUpperCase())) { - if (!Daemon.global || !Daemon.global._started) { - Daemon._signalQueue.push([name, listener]); - return; - } - } - return Daemon._processOn.apply(this, arguments); -}; - Daemon.instances = {}; Daemon.prototype.instances = Daemon.instances; @@ -71,77 +52,17 @@ Daemon.prototype.start = function(callback) { var self = this; if (this.instances[this.datadir]) { - return; + return callback(new Error('Daemon already started')); } this.instances[this.datadir] = true; - var none = {}; - var isSignal = {}; - var sigint = { name: 'SIGINT', signal: isSignal }; - var sighup = { name: 'SIGHUP', signal: isSignal }; - var sigquit = { name: 'SIGQUIT', signal: isSignal }; - var exitCaught = none; - var errorCaught = none; - - Object.keys(this.options).forEach(function(key) { - if (self.options[key] == null) { - self.options[key] = self.options[key]; + bitcoind.start(this.options, function(err) { + if(err) { + return callback(err); } - }); - bitcoind.start(this.options, function(err, status) { self._started = true; - // Poll for queued packet - [sigint, sighup, sigquit].forEach(function(signal) { - process.on(signal.name, signal.listener = function() { - if (process.listeners(signal.name).length > 1) { - return; - } - if (!self._shutdown) { - process.exit(0); - } else { - self.stop(); - exitCaught = signal; - } - }); - }); - - // Finally set signal handlers - process.on = process.addListener = Daemon._processOn; - Daemon._signalQueue.forEach(function(event) { - process.on(event[0], event[1]); - }); - - var exit = process.exit; - self._exit = function() { - return exit.apply(process, arguments); - }; - - process.exit = function(code) { - exitCaught = code || 0; - if (!self._shutdown) { - return self._exit(code); - } - self.stop(); - }; - - process.on('uncaughtException', function(err) { - if (process.listeners('uncaughtException').length > 1) { - return; - } - errorCaught = err; - log.error('Uncaught error: shutting down safely before throwing...'); - if (!self._shutdown) { - if (err && err.stack) { - log.error(err.stack); - } - self._exit(1); - return; - } - self.stop(); - }); - bitcoind.onBlocksReady(function(err, result) { function onTipUpdateListener(result) { @@ -155,58 +76,18 @@ Daemon.prototype.start = function(callback) { bitcoind.onTipUpdate(onTipUpdateListener); - self.emit('ready', result); - bitcoind.startTxMon(function(txs) { for(var i = 0; i < txs.length; i++) { self.emit('tx', txs[i]); } }); - }); - - setTimeout(function callee() { - if (err) { - self.emit('error', err); - callback(err); - } else { - self.emit('open', status); + setImmediate(function() { + self.emit('ready', result); callback(); - } - }, 100); + }); + }); }); - - // bitcoind's boost threads aren't in the thread pool - // or on node's event loop, so we need to keep node open. - this._shutdown = setInterval(function() { - if (!self._stoppingSaid && bitcoind.stopping()) { - self._stoppingSaid = true; - } - - if (bitcoind.stopped()) { - - clearInterval(self._shutdown); - delete self._shutdown; - - if (exitCaught !== none) { - if (exitCaught.signal === isSignal) { - process.removeListener(exitCaught.name, exitCaught.listener); - setImmediate(function() { - process.kill(process.pid, exitCaught.name); - }); - return; - } - return self._exit(exitCaught); - } - - if (errorCaught !== none) { - if (errorCaught && errorCaught.stack) { - log.error(errorCaught.stack); - } - return self._exit(0); - } - } - }, 1000); }; Daemon.prototype.isSynced = function() { @@ -258,33 +139,17 @@ Daemon.prototype.getInfo = function() { }; Daemon.prototype.stop = function(callback) { - if (Daemon.stopping) return []; var self = this; return bitcoind.stop(function(err, status) { - if (err) { - self.error(err.message); - } else { - log.info(status); - } - if (!callback) return; - return callback(err, status); + setImmediate(function() { + if (err) { + return callback(err); + } else { + log.info(status); + return callback(); + } + }); }); }; -Daemon.prototype.__defineGetter__('stopping', function() { - return bitcoind.stopping() || bitcoind.stopped(); -}); - -Daemon.prototype.__defineGetter__('stopped', function() { - return bitcoind.stopped(); -}); - -Daemon.__defineGetter__('stopping', function() { - return bitcoind.stopping() || bitcoind.stopped(); -}); - -Daemon.__defineGetter__('stopped', function() { - return bitcoind.stopped(); -}); - module.exports = Daemon; diff --git a/lib/node.js b/lib/node.js index d3f5c804..30466269 100644 --- a/lib/node.js +++ b/lib/node.js @@ -236,12 +236,13 @@ Node.prototype._syncBitcoind = function() { async.whilst(function() { height = self.chain.tip.__height; - return height < self.bitcoindHeight; + return height < self.bitcoindHeight && !self.stopping; }, function(done) { self.bitcoind.getBlock(height + 1, function(err, blockBuffer) { if (err) { return done(err); } + var block = self.Block.fromBuffer(blockBuffer); if (block.prevHash === self.chain.tip.hash) { @@ -259,17 +260,17 @@ Node.prototype._syncBitcoind = function() { if (err) { return done(err); } + delete self.chain.tip.__transactions; self.chain.tip = block; log.debug('Saving metadata'); self.chain.saveMetadata(); log.debug('Chain added block to main chain'); self.chain.emit('addblock', block); - done(); + setImmediate(done); }); } else { - // This block doesn't progress the current tip, so we'll attempt // to rewind the chain to the common ancestor of the block and // then we can resume syncing. @@ -397,10 +398,6 @@ Node.prototype._initialize = function() { self.bitcoindHeight = info.blocks; }); - this.bitcoind.on('open', function(status) { - log.info('Bitcoin Core Daemon Status:', status); - }); - // Notify that there is a new tip this.bitcoind.on('tip', function(height) { var percentage = self.bitcoind.syncPercentage(); @@ -481,9 +478,13 @@ Node.prototype.start = function(callback) { }; Node.prototype.stop = function(callback) { + log.info('Stopping Bitcore Node'); var self = this; var services = this.getServiceOrder().reverse(); + this.stopping = true; + this.emit('stopping'); + async.eachSeries( services, function(service, next) { diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index f2fce2ac..5c3c0add 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -885,30 +885,6 @@ async_stop_node_after(uv_work_t *req) { delete req; } -/** - * IsStopping() - * bitcoind.stopping() - * Check whether bitcoind is in the process of shutting down. This is polled - * from javascript. - */ - -NAN_METHOD(IsStopping) { - NanScope(); - NanReturnValue(NanNew(ShutdownRequested())); -} - -/** - * IsStopped() - * bitcoind.stopped() - * Check whether bitcoind has shutdown completely. This will be polled by - * javascript to check whether the libuv event loop is safe to stop. - */ - -NAN_METHOD(IsStopped) { - NanScope(); - NanReturnValue(NanNew(shutdown_complete)); -} - /** * GetBlock() * bitcoind.getBlock([blockhash,blockheight], callback) @@ -1650,8 +1626,6 @@ init(Handle target) { NODE_SET_METHOD(target, "onBlocksReady", OnBlocksReady); NODE_SET_METHOD(target, "onTipUpdate", OnTipUpdate); NODE_SET_METHOD(target, "stop", StopBitcoind); - NODE_SET_METHOD(target, "stopping", IsStopping); - NODE_SET_METHOD(target, "stopped", IsStopped); NODE_SET_METHOD(target, "getBlock", GetBlock); NODE_SET_METHOD(target, "getTransaction", GetTransaction); NODE_SET_METHOD(target, "getTransactionWithBlockInfo", GetTransactionWithBlockInfo); diff --git a/src/libbitcoind.h b/src/libbitcoind.h index 9744f828..e35e40b6 100644 --- a/src/libbitcoind.h +++ b/src/libbitcoind.h @@ -20,8 +20,6 @@ NAN_METHOD(StartBitcoind); NAN_METHOD(OnBlocksReady); NAN_METHOD(OnTipUpdate); -NAN_METHOD(IsStopping); -NAN_METHOD(IsStopped); NAN_METHOD(StopBitcoind); NAN_METHOD(GetBlock); NAN_METHOD(GetTransaction); From b0ab45f3f7734b7283d12d43ff6fef0165951937 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Fri, 21 Aug 2015 15:39:46 -0400 Subject: [PATCH 3/8] handle signals and stop correctly --- bin/start.js | 24 +++++------------------- lib/node.js | 12 +++++++----- src/libbitcoind.cc | 6 ++++-- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/bin/start.js b/bin/start.js index 72f8dd04..5579e310 100644 --- a/bin/start.js +++ b/bin/start.js @@ -146,8 +146,6 @@ node.on('stopping', function() { clearInterval(interval); }); -process.stdin.resume();//so the program will not close instantly - function exitHandler(options, err) { if (err) { log.error('uncaught exception:', err); @@ -157,7 +155,6 @@ function exitHandler(options, err) { process.exit(-1); } if (options.sigint) { - log.info('Stopping Services'); node.stop(function(err) { if(err) { log.error('Failed to stop services: ' + err); @@ -170,21 +167,10 @@ function exitHandler(options, err) { } } -//catches ctrl+c event -process.on('SIGINT', exitHandler.bind(null, {sigint:true})); - -/*setTimeout(function() { - log.info('Stopping Services'); - node.stop(function(err) { - if(err) { - log.error('Failed to stop services: ' + err); - return process.exit(1); - } - - log.info('Halted'); - process.exit(0); - }); -}, 10000);*/ - //catches uncaught exceptions + + process.on('uncaughtException', exitHandler.bind(null, {exit:true})); +//catches ctrl+c event +process.on('exit', exitHandler.bind(null, {exit: true})); +process.on('SIGINT', exitHandler.bind(null, {sigint:true})); diff --git a/lib/node.js b/lib/node.js index 30466269..6495dcfd 100644 --- a/lib/node.js +++ b/lib/node.js @@ -400,10 +400,12 @@ Node.prototype._initialize = function() { // Notify that there is a new tip this.bitcoind.on('tip', function(height) { - var percentage = self.bitcoind.syncPercentage(); - log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage); - self.bitcoindHeight = height; - self._syncBitcoind(); + if(!self.stopping) { + var percentage = self.bitcoind.syncPercentage(); + log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage); + self.bitcoindHeight = height; + self._syncBitcoind(); + } }); this.bitcoind.on('error', function(err) { @@ -478,7 +480,7 @@ Node.prototype.start = function(callback) { }; Node.prototype.stop = function(callback) { - log.info('Stopping Bitcore Node'); + log.info('Beginning shutdown'); var self = this; var services = this.getServiceOrder().reverse(); diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 5c3c0add..35b72c6e 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -104,6 +104,8 @@ static bool g_testnet = false; static bool g_regtest = false; static bool g_txindex = false; +static boost::thread_group threadGroup; + /** * Private Structs * Used for async functions and necessary linked lists at points. @@ -685,7 +687,6 @@ start_node(void) { static void start_node_thread(void) { - boost::thread_group threadGroup; CScheduler scheduler; // Workaround for AppInit2() arg parsing. Not ideal, but it works. @@ -798,7 +799,6 @@ start_node_thread(void) { */ NAN_METHOD(StopBitcoind) { - fprintf(stderr, "Stopping Bitcoind please wait!\n"); Isolate* isolate = Isolate::GetCurrent(); HandleScope scope(isolate); @@ -840,7 +840,9 @@ NAN_METHOD(StopBitcoind) { static void async_stop_node(uv_work_t *req) { async_node_data *data = static_cast(req->data); + StartShutdown(); + while(!shutdown_complete) { usleep(1E6); } From a9e5ee6f1a2645f8775c0ca5b1473da3702864b5 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Fri, 21 Aug 2015 16:13:11 -0400 Subject: [PATCH 4/8] add tests --- lib/node.js | 15 ++-- test/chain.unit.js | 18 +++++ test/db.unit.js | 27 +++++++- test/node.unit.js | 167 +++++++++++++++++++-------------------------- 4 files changed, 121 insertions(+), 106 deletions(-) diff --git a/lib/node.js b/lib/node.js index 6495dcfd..04fce0bd 100644 --- a/lib/node.js +++ b/lib/node.js @@ -279,14 +279,18 @@ Node.prototype._syncBitcoind = function() { } }); }, function(err) { - - self.bitcoindSyncing = false; - self.chain.lastSavedMetadataThreshold = 0; if (err) { Error.captureStackTrace(err); return self.emit('error', err); } + if(self.stopping) { + return; + } + + self.bitcoindSyncing = false; + self.chain.lastSavedMetadataThreshold = 0; + // If bitcoind is completely synced if (self.bitcoind.isSynced()) { self.emit('synced'); @@ -417,7 +421,6 @@ Node.prototype._initialize = function() { this.chain.on('ready', function() { log.info('Bitcoin Chain Ready'); self._syncBitcoind(); - self.emit('ready'); }); this.chain.on('error', function(err) { @@ -437,8 +440,10 @@ Node.prototype._initialize = function() { this.start(function(err) { if(err) { - log.error('Failed starting services: ' + err); + return self.emit('error', err); } + + self.emit('ready'); }); }; diff --git a/test/chain.unit.js b/test/chain.unit.js index 9d463005..ff432419 100644 --- a/test/chain.unit.js +++ b/test/chain.unit.js @@ -25,6 +25,24 @@ describe('Bitcoin Chain', function() { }); + describe('#start', function() { + it('should call the callback when base chain is initialized', function(done) { + var chain = new Chain(); + chain.initialize = function() { + chain.emit('initialized'); + }; + + chain.start(done); + }); + }); + + describe('#stop', function() { + it('should call the callback', function(done) { + var chain = new Chain(); + chain.stop(done); + }); + }); + describe('#_writeBlock', function() { it('should update hashes and call putBlock', function(done) { var chain = new Chain(); diff --git a/test/db.unit.js b/test/db.unit.js index e4081a8e..13977f71 100644 --- a/test/db.unit.js +++ b/test/db.unit.js @@ -16,7 +16,7 @@ var Transaction = bitcore.Transaction; describe('Bitcoin DB', function() { var coinbaseAmount = 50 * 1e8; - describe('#initialize', function() { + describe('#start', function() { it('should emit ready', function(done) { var db = new DB({store: memdown}); db._modules = ['mod1', 'mod2']; @@ -24,8 +24,29 @@ describe('Bitcoin DB', function() { on: sinon.spy() }; db.addModule = sinon.spy(); - db.on('ready', done); - db.initialize(); + var readyFired = false; + db.on('ready', function() { + readyFired = true; + }); + db.start(function() { + readyFired.should.equal(true); + done(); + }); + }); + }); + + describe('#stop', function() { + it('should close the store', function(done) { + var db = new DB({store: memdown}); + db.store = { + close: sinon.stub().callsArg(0) + }; + + db.stop(function(err) { + should.not.exist(err); + db.store.close.calledOnce.should.equal(true); + done(); + }); }); }); diff --git a/test/node.unit.js b/test/node.unit.js index 203476ad..deb81863 100644 --- a/test/node.unit.js +++ b/test/node.unit.js @@ -273,7 +273,50 @@ describe('Bitcoind Node', function() { }); node._syncBitcoind(); }); + it('will stop syncing when the node is stopping', function(done) { + var node = new Node({}); + node.Block = Block; + node.bitcoindHeight = 1; + var blockBuffer = new Buffer(blockData); + var block = Block.fromBuffer(blockBuffer); + node.bitcoind = { + getBlock: sinon.stub().callsArgWith(1, null, blockBuffer), + isSynced: sinon.stub().returns(true) + }; + node.chain = { + tip: { + __height: 0, + hash: block.prevHash + }, + saveMetadata: sinon.stub(), + emit: sinon.stub(), + cache: { + hashes: {} + } + }; + node.db = { + _onChainAddBlock: function(block, callback) { + node.chain.tip.__height += 1; + callback(); + } + }; + node.stopping = true; + + var synced = false; + + node.on('synced', function() { + synced = true; + }); + + node._syncBitcoind(); + + setTimeout(function() { + synced.should.equal(false); + done(); + }, 10); + }); }); + describe('#_loadNetwork', function() { it('should use the testnet network if testnet is specified', function() { var config = { @@ -414,101 +457,26 @@ describe('Bitcoind Node', function() { }); }); - describe('#_initializeBitcoind', function() { - it('will call db.initialize() on ready event', function(done) { - var node = new Node({}); - node.bitcoind = new EventEmitter(); - node.bitcoind.getInfo = sinon.stub().returns({blocks: 10}); - node.db = { - initialize: sinon.spy() - }; - sinon.stub(chainlib.log, 'info'); - node.bitcoind.on('ready', function() { - setImmediate(function() { - chainlib.log.info.callCount.should.equal(1); - chainlib.log.info.restore(); - node.db.initialize.callCount.should.equal(1); - node.bitcoindHeight.should.equal(10); - done(); - }); - }); - node._initializeBitcoind(); - node.bitcoind.emit('ready'); - }); - it('will call emit an error from libbitcoind', function(done) { - var node = new Node({}); - node.bitcoind = new EventEmitter(); - node.on('error', function(err) { - should.exist(err); - err.message.should.equal('test error'); - done(); - }); - node._initializeBitcoind(); - node.bitcoind.emit('error', new Error('test error')); - }); - }); - - describe('#_initializeDatabase', function() { - it('will call chain.initialize() on ready event', function(done) { - var node = new Node({}); - node.db = new EventEmitter(); - node.db.addModule = sinon.spy(); - var module = {}; - node.db._modules = [module]; - node.chain = { - initialize: sinon.spy() - }; - sinon.stub(chainlib.log, 'info'); - node.db.on('ready', function() { - setImmediate(function() { - chainlib.log.info.callCount.should.equal(1); - chainlib.log.info.restore(); - node.chain.initialize.callCount.should.equal(1); - done(); - }); - }); - node._initializeDatabase(); - node.db.emit('ready'); - }); - it('will call emit an error from db', function(done) { - var node = new Node({}); - node.db = new EventEmitter(); - node.on('error', function(err) { - should.exist(err); - err.message.should.equal('test error'); - done(); - }); - node._initializeDatabase(); - node.db.emit('error', new Error('test error')); - }); - }); - - describe('#_initializeChain', function() { - it('will call emit an error from chain', function(done) { - var node = new Node({}); - node.chain = new EventEmitter(); - node.on('error', function(err) { - should.exist(err); - err.message.should.equal('test error'); - done(); - }); - node._initializeChain(); - node.chain.emit('error', new Error('test error')); - }); - }); - describe('#_initialize', function() { + var node = new Node({}); + node.chain = { + on: sinon.spy() + }; + node.Block = 'Block'; + node.bitcoind = { + on: sinon.spy() + }; + node.db = { + on: sinon.spy() + }; it('should initialize', function(done) { - var node = new Node({}); - node.chain = {}; - node.Block = 'Block'; - node.bitcoind = 'bitcoind'; - node.db = {}; + node.once('ready', function() { + done(); + }); + + node.start = sinon.stub().callsArg(0); - node._initializeBitcoind = sinon.spy(); - node._initializeDatabase = sinon.spy(); - node._initializeChain = sinon.spy(); node._initialize(); // references @@ -518,18 +486,21 @@ describe('Bitcoind Node', function() { node.chain.db.should.equal(node.db); node.chain.db.should.equal(node.db); - // events - node._initializeBitcoind.callCount.should.equal(1); - node._initializeDatabase.callCount.should.equal(1); - node._initializeChain.callCount.should.equal(1); - // start syncing node.setSyncStrategy = sinon.spy(); - node.on('ready', function() { + + }); + + it('should emit an error if an error occurred starting services', function(done) { + node.once('error', function(err) { + should.exist(err); + err.message.should.equal('error'); done(); }); - node.emit('ready'); + node.start = sinon.stub().callsArgWith(0, new Error('error')); + + node._initialize(); }); }); From db65d6476f323ed2a143b62f860633ed1678a932 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Mon, 24 Aug 2015 10:13:45 -0400 Subject: [PATCH 5/8] add ctrl-c behavior to daemon --- bin/start-libbitcoind.js | 31 ++++++++++++++++++++++++++++++- bin/start.js | 1 - 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/bin/start-libbitcoind.js b/bin/start-libbitcoind.js index a6299a33..a7f7299d 100644 --- a/bin/start-libbitcoind.js +++ b/bin/start-libbitcoind.js @@ -15,7 +15,7 @@ var daemon = require('../').daemon({ network: process.env.BITCORENODE_NETWORK || 'livenet' }); -daemon.on('ready', function() { +daemon.start(function() { log.info('ready'); }); @@ -26,3 +26,32 @@ daemon.on('error', function(err) { daemon.on('open', function(status) { log.info('status="%s"', status); }); + +function exitHandler(options, err) { + log.info('Stopping daemon'); + if (err) { + log.error('uncaught exception:', err); + if(err.stack) { + console.log(err.stack); + } + process.exit(-1); + } + if (options.sigint) { + daemon.stop(function(err) { + if(err) { + log.error('Failed to stop services: ' + err); + return process.exit(1); + } + + log.info('Halted'); + process.exit(0); + }); + } +} + +//catches uncaught exceptions + + +process.on('uncaughtException', exitHandler.bind(null, {exit:true})); +//catches ctrl+c event +process.on('SIGINT', exitHandler.bind(null, {sigint:true})); \ No newline at end of file diff --git a/bin/start.js b/bin/start.js index 5579e310..9673da85 100644 --- a/bin/start.js +++ b/bin/start.js @@ -172,5 +172,4 @@ function exitHandler(options, err) { process.on('uncaughtException', exitHandler.bind(null, {exit:true})); //catches ctrl+c event -process.on('exit', exitHandler.bind(null, {exit: true})); process.on('SIGINT', exitHandler.bind(null, {sigint:true})); From f4a1f7b12a895ad768ab452ab2401045fd9f9f94 Mon Sep 17 00:00:00 2001 From: Chris Kleeschulte Date: Mon, 24 Aug 2015 12:45:42 -0400 Subject: [PATCH 6/8] Removed signal handlers from bitcoind. - when using bitcoin as a shared lib, there should not be any need for bitcoind to be handling its own signals. --- etc/bitcoin.patch | 26 ++++++++++++++++++++++++++ src/libbitcoind.cc | 6 ------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/etc/bitcoin.patch b/etc/bitcoin.patch index 1a6b6ca6..99b36feb 100644 --- a/etc/bitcoin.patch +++ b/etc/bitcoin.patch @@ -280,6 +280,32 @@ index cce687a..0f162ff 100644 return (AppInit(argc, argv) ? 0 : 1); } +#endif +diff --git a/src/init.cpp b/src/init.cpp +index d127d55..fafd4fd 100644 +--- a/src/init.cpp ++++ b/src/init.cpp +@@ -638,21 +638,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) + umask(077); + } + +- // Clean shutdown on SIGTERM +- struct sigaction sa; +- sa.sa_handler = HandleSIGTERM; +- sigemptyset(&sa.sa_mask); +- sa.sa_flags = 0; +- sigaction(SIGTERM, &sa, NULL); +- sigaction(SIGINT, &sa, NULL); +- +- // Reopen debug.log on SIGHUP +- struct sigaction sa_hup; +- sa_hup.sa_handler = HandleSIGHUP; +- sigemptyset(&sa_hup.sa_mask); +- sa_hup.sa_flags = 0; +- sigaction(SIGHUP, &sa_hup, NULL); +- + #if defined (__SVR4) && defined (__sun) + // ignore SIGPIPE on Solaris + signal(SIGPIPE, SIG_IGN); diff --git a/src/init.h b/src/init.h index dcb2b29..5ce68ba 100644 --- a/src/init.h diff --git a/src/libbitcoind.cc b/src/libbitcoind.cc index 35b72c6e..045c57e3 100644 --- a/src/libbitcoind.cc +++ b/src/libbitcoind.cc @@ -676,12 +676,6 @@ start_node(void) { noui_connect(); new boost::thread(boost::bind(&start_node_thread)); - - // Drop the bitcoind signal handlers: we want our own. - signal(SIGINT, SIG_DFL); - signal(SIGHUP, SIG_DFL); - signal(SIGQUIT, SIG_DFL); - return 0; } From efdf38d5c2af465c4c05611516e719e6a3f77a4e Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Mon, 24 Aug 2015 12:52:52 -0400 Subject: [PATCH 7/8] don't call store.close() due to issues --- lib/db.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/db.js b/lib/db.js index 43733b02..ea287a5b 100644 --- a/lib/db.js +++ b/lib/db.js @@ -53,7 +53,8 @@ DB.prototype.start = function(callback) { }; DB.prototype.stop = function(callback) { - this.store.close(callback); + // TODO Figure out how to call this.store.close() without issues + setImmediate(callback); }; DB.prototype.getBlock = function(hash, callback) { From faf93a18a7af05722a508e56bd490510a2d27000 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Mon, 24 Aug 2015 13:32:13 -0400 Subject: [PATCH 8/8] get regtest to work --- integration/regtest.js | 4 ++++ test/db.unit.js | 6 +----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/regtest.js b/integration/regtest.js index d309da6b..97df6d4f 100644 --- a/integration/regtest.js +++ b/integration/regtest.js @@ -66,6 +66,10 @@ describe('Daemon Binding Functionality', function() { network: 'regtest' }); + bitcoind.start(function() { + log.info('Bitcoind started'); + }); + bitcoind.on('error', function(err) { log.error('error="%s"', err.message); }); diff --git a/test/db.unit.js b/test/db.unit.js index 13977f71..372ec016 100644 --- a/test/db.unit.js +++ b/test/db.unit.js @@ -36,15 +36,11 @@ describe('Bitcoin DB', function() { }); describe('#stop', function() { - it('should close the store', function(done) { + it('should immediately call the callback', function(done) { var db = new DB({store: memdown}); - db.store = { - close: sinon.stub().callsArg(0) - }; db.stop(function(err) { should.not.exist(err); - db.store.close.calledOnce.should.equal(true); done(); }); });