Merge pull request #140 from pnagurny/enhance/ctrl-c

Better Ctrl-C Behavior
This commit is contained in:
Braydon Fuller 2015-08-24 13:56:39 -04:00
commit 4a50df9d4c
14 changed files with 387 additions and 390 deletions

View File

@ -15,7 +15,7 @@ var daemon = require('../').daemon({
network: process.env.BITCORENODE_NETWORK || 'livenet' network: process.env.BITCORENODE_NETWORK || 'livenet'
}); });
daemon.on('ready', function() { daemon.start(function() {
log.info('ready'); log.info('ready');
}); });
@ -26,3 +26,32 @@ daemon.on('error', function(err) {
daemon.on('open', function(status) { daemon.on('open', function(status) {
log.info('status="%s"', 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}));

View File

@ -141,3 +141,35 @@ node.chain.on('addblock', function(block) {
}, 10000); }, 10000);
} }
}); });
node.on('stopping', function() {
clearInterval(interval);
});
function exitHandler(options, err) {
if (err) {
log.error('uncaught exception:', err);
if(err.stack) {
console.log(err.stack);
}
process.exit(-1);
}
if (options.sigint) {
node.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}));

View File

@ -280,6 +280,32 @@ index cce687a..0f162ff 100644
return (AppInit(argc, argv) ? 0 : 1); return (AppInit(argc, argv) ? 0 : 1);
} }
+#endif +#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 diff --git a/src/init.h b/src/init.h
index dcb2b29..5ce68ba 100644 index dcb2b29..5ce68ba 100644
--- a/src/init.h --- a/src/init.h

View File

@ -66,6 +66,10 @@ describe('Daemon Binding Functionality', function() {
network: 'regtest' network: 'regtest'
}); });
bitcoind.start(function() {
log.info('Bitcoind started');
});
bitcoind.on('error', function(err) { bitcoind.on('error', function(err) {
log.error('error="%s"', err.message); log.error('error="%s"', err.message);
}); });

View File

@ -50,6 +50,15 @@ function Chain(options) {
util.inherits(Chain, BaseChain); 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) { Chain.prototype._writeBlock = function(block, callback) {
// Update hashes // Update hashes
this.cache.hashes[block.hash] = block.prevHash; this.cache.hashes[block.hash] = block.prevHash;

View File

@ -33,29 +33,10 @@ function Daemon(options) {
self[key] = exports[key]; self[key] = exports[key];
}); });
this.on('newListener', function(name) {
if (name === 'open') {
self.start();
}
});
} }
util.inherits(Daemon, EventEmitter); 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.instances = {};
Daemon.prototype.instances = Daemon.instances; Daemon.prototype.instances = Daemon.instances;
@ -67,94 +48,21 @@ Daemon.prototype.__defineGetter__('global', function() {
return Daemon.global; return Daemon.global;
}); });
Daemon.prototype.start = function(options, callback) { Daemon.prototype.start = function(callback) {
var self = this; var self = this;
if (!callback) {
callback = options;
options = null;
}
if (!options) {
options = {};
}
if (!callback) {
callback = function() {};
}
if (this.instances[this.datadir]) { if (this.instances[this.datadir]) {
return; return callback(new Error('Daemon already started'));
} }
this.instances[this.datadir] = true; this.instances[this.datadir] = true;
var none = {}; bitcoind.start(this.options, function(err) {
var isSignal = {}; if(err) {
var sigint = { name: 'SIGINT', signal: isSignal }; return callback(err);
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 (options[key] == null) {
options[key] = self.options[key];
} }
});
bitcoind.start(options, function(err, status) {
self._started = true; 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) { bitcoind.onBlocksReady(function(err, result) {
function onTipUpdateListener(result) { function onTipUpdateListener(result) {
@ -168,69 +76,18 @@ Daemon.prototype.start = function(options, callback) {
bitcoind.onTipUpdate(onTipUpdateListener); bitcoind.onTipUpdate(onTipUpdateListener);
self.emit('ready', result);
bitcoind.startTxMon(function(txs) { bitcoind.startTxMon(function(txs) {
for(var i = 0; i < txs.length; i++) { for(var i = 0; i < txs.length; i++) {
self.emit('tx', txs[i]); self.emit('tx', txs[i]);
} }
}); });
setImmediate(function() {
self.emit('ready', result);
callback();
});
}); });
setTimeout(function callee() {
// Wait until wallet is loaded:
if (callback) {
callback(err ? err : null);
}
if (err) {
self.emit('error', err);
} else {
if (callback) {
self.emit('open', status);
} else {
self.emit('status', status);
}
}
if (callback) {
callback = null;
}
}, 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() { Daemon.prototype.isSynced = function() {
@ -282,33 +139,17 @@ Daemon.prototype.getInfo = function() {
}; };
Daemon.prototype.stop = function(callback) { Daemon.prototype.stop = function(callback) {
if (Daemon.stopping) return [];
var self = this; var self = this;
return bitcoind.stop(function(err, status) { return bitcoind.stop(function(err, status) {
if (err) { setImmediate(function() {
self.error(err.message); if (err) {
} else { return callback(err);
log.info(status); } else {
} log.info(status);
if (!callback) return; return callback();
return callback(err, status); }
});
}); });
}; };
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; module.exports = Daemon;

View File

@ -40,7 +40,7 @@ function DB(options) {
util.inherits(DB, BaseDB); util.inherits(DB, BaseDB);
DB.prototype.initialize = function() { DB.prototype.start = function(callback) {
// Add all db option modules // Add all db option modules
if(this._modules && this._modules.length) { if(this._modules && this._modules.length) {
for(var i = 0; i < this._modules.length; i++) { for(var i = 0; i < this._modules.length; i++) {
@ -49,7 +49,13 @@ DB.prototype.initialize = function() {
} }
this.bitcoind.on('tx', this.transactionHandler.bind(this)); this.bitcoind.on('tx', this.transactionHandler.bind(this));
this.emit('ready'); this.emit('ready');
} callback();
};
DB.prototype.stop = function(callback) {
// TODO Figure out how to call this.store.close() without issues
setImmediate(callback);
};
DB.prototype.getBlock = function(hash, callback) { DB.prototype.getBlock = function(hash, callback) {
var self = this; var self = this;

View File

@ -45,4 +45,12 @@ Module.prototype.getAPIMethods = function() {
// //
// }; // };
Module.prototype.start = function() {
};
Module.prototype.stop = function() {
};
module.exports = Module; module.exports = Module;

View File

@ -25,6 +25,12 @@ function Node(config) {
util.inherits(Node, BaseNode); util.inherits(Node, BaseNode);
var defaultServices = {
'bitcoind': [],
'db': ['bitcoind'],
'chain': ['db'],
};
Node.prototype.openBus = function() { Node.prototype.openBus = function() {
return new Bus({db: this.db}); return new Bus({db: this.db});
}; };
@ -230,12 +236,13 @@ Node.prototype._syncBitcoind = function() {
async.whilst(function() { async.whilst(function() {
height = self.chain.tip.__height; height = self.chain.tip.__height;
return height < self.bitcoindHeight; return height < self.bitcoindHeight && !self.stopping;
}, function(done) { }, function(done) {
self.bitcoind.getBlock(height + 1, function(err, blockBuffer) { self.bitcoind.getBlock(height + 1, function(err, blockBuffer) {
if (err) { if (err) {
return done(err); return done(err);
} }
var block = self.Block.fromBuffer(blockBuffer); var block = self.Block.fromBuffer(blockBuffer);
if (block.prevHash === self.chain.tip.hash) { if (block.prevHash === self.chain.tip.hash) {
@ -253,17 +260,17 @@ Node.prototype._syncBitcoind = function() {
if (err) { if (err) {
return done(err); return done(err);
} }
delete self.chain.tip.__transactions; delete self.chain.tip.__transactions;
self.chain.tip = block; self.chain.tip = block;
log.debug('Saving metadata'); log.debug('Saving metadata');
self.chain.saveMetadata(); self.chain.saveMetadata();
log.debug('Chain added block to main chain'); log.debug('Chain added block to main chain');
self.chain.emit('addblock', block); self.chain.emit('addblock', block);
done(); setImmediate(done);
}); });
} else { } else {
// This block doesn't progress the current tip, so we'll attempt // This block doesn't progress the current tip, so we'll attempt
// to rewind the chain to the common ancestor of the block and // to rewind the chain to the common ancestor of the block and
// then we can resume syncing. // then we can resume syncing.
@ -272,14 +279,18 @@ Node.prototype._syncBitcoind = function() {
} }
}); });
}, function(err) { }, function(err) {
self.bitcoindSyncing = false;
self.chain.lastSavedMetadataThreshold = 0;
if (err) { if (err) {
Error.captureStackTrace(err); Error.captureStackTrace(err);
return self.emit('error', err); return self.emit('error', err);
} }
if(self.stopping) {
return;
}
self.bitcoindSyncing = false;
self.chain.lastSavedMetadataThreshold = 0;
// If bitcoind is completely synced // If bitcoind is completely synced
if (self.bitcoind.isSynced()) { if (self.bitcoind.isSynced()) {
self.emit('synced'); self.emit('synced');
@ -372,69 +383,8 @@ Node.prototype._loadConsensus = function(config) {
this.chain = new Chain(config.consensus); this.chain = new Chain(config.consensus);
}; };
Node.prototype._initializeBitcoind = function() {
var self = this;
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) {
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();
log.info('Bitcoin Core Daemon New Height:', height, 'Percentage:', percentage);
self.bitcoindHeight = height;
self._syncBitcoind();
});
this.bitcoind.on('error', function(err) {
Error.captureStackTrace(err);
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');
self._syncBitcoind();
self.emit('ready');
});
this.chain.on('error', function(err) {
Error.captureStackTrace(err);
self.emit('error', err);
});
};
Node.prototype._initialize = function() { Node.prototype._initialize = function() {
var self = this;
// DB References // DB References
this.db.chain = this.chain; this.db.chain = this.chain;
@ -444,11 +394,113 @@ Node.prototype._initialize = function() {
// Chain References // Chain References
this.chain.db = this.db; this.chain.db = this.db;
// Setup Chain of Events // Bitcoind
this._initializeBitcoind(); this.bitcoind.on('ready', function(status) {
this._initializeDatabase(); log.info('Bitcoin Daemon Ready');
this._initializeChain(); // Set the current chain height
var info = self.bitcoind.getInfo();
self.bitcoindHeight = info.blocks;
});
// Notify that there is a new tip
this.bitcoind.on('tip', function(height) {
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) {
Error.captureStackTrace(err);
self.emit('error', err);
});
// Chain
this.chain.on('ready', function() {
log.info('Bitcoin Chain Ready');
self._syncBitcoind();
});
this.chain.on('error', function(err) {
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) {
return self.emit('error', err);
}
self.emit('ready');
});
}; };
Node.prototype.getServiceOrder = function(services, keys, stack) {
if(!services) {
services = defaultServices;
}
if(!keys) {
keys = Object.keys(services);
}
if(!stack) {
stack = [];
}
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) {
log.info('Beginning shutdown');
var self = this;
var services = this.getServiceOrder().reverse();
this.stopping = true;
this.emit('stopping');
async.eachSeries(
services,
function(service, next) {
log.info('Stopping ' + service);
self[service].stop(next);
},
callback
);
};
module.exports = Node; module.exports = Node;

View File

@ -104,6 +104,8 @@ static bool g_testnet = false;
static bool g_regtest = false; static bool g_regtest = false;
static bool g_txindex = false; static bool g_txindex = false;
static boost::thread_group threadGroup;
/** /**
* Private Structs * Private Structs
* Used for async functions and necessary linked lists at points. * Used for async functions and necessary linked lists at points.
@ -674,18 +676,11 @@ start_node(void) {
noui_connect(); noui_connect();
new boost::thread(boost::bind(&start_node_thread)); 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; return 0;
} }
static void static void
start_node_thread(void) { start_node_thread(void) {
boost::thread_group threadGroup;
CScheduler scheduler; CScheduler scheduler;
// Workaround for AppInit2() arg parsing. Not ideal, but it works. // Workaround for AppInit2() arg parsing. Not ideal, but it works.
@ -798,7 +793,6 @@ start_node_thread(void) {
*/ */
NAN_METHOD(StopBitcoind) { NAN_METHOD(StopBitcoind) {
fprintf(stderr, "Stopping Bitcoind please wait!\n");
Isolate* isolate = Isolate::GetCurrent(); Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate); HandleScope scope(isolate);
@ -840,7 +834,9 @@ NAN_METHOD(StopBitcoind) {
static void static void
async_stop_node(uv_work_t *req) { async_stop_node(uv_work_t *req) {
async_node_data *data = static_cast<async_node_data*>(req->data); async_node_data *data = static_cast<async_node_data*>(req->data);
StartShutdown(); StartShutdown();
while(!shutdown_complete) { while(!shutdown_complete) {
usleep(1E6); usleep(1E6);
} }
@ -885,30 +881,6 @@ async_stop_node_after(uv_work_t *req) {
delete 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<Boolean>(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<Boolean>(shutdown_complete));
}
/** /**
* GetBlock() * GetBlock()
* bitcoind.getBlock([blockhash,blockheight], callback) * bitcoind.getBlock([blockhash,blockheight], callback)
@ -1650,8 +1622,6 @@ init(Handle<Object> target) {
NODE_SET_METHOD(target, "onBlocksReady", OnBlocksReady); NODE_SET_METHOD(target, "onBlocksReady", OnBlocksReady);
NODE_SET_METHOD(target, "onTipUpdate", OnTipUpdate); NODE_SET_METHOD(target, "onTipUpdate", OnTipUpdate);
NODE_SET_METHOD(target, "stop", StopBitcoind); 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, "getBlock", GetBlock);
NODE_SET_METHOD(target, "getTransaction", GetTransaction); NODE_SET_METHOD(target, "getTransaction", GetTransaction);
NODE_SET_METHOD(target, "getTransactionWithBlockInfo", GetTransactionWithBlockInfo); NODE_SET_METHOD(target, "getTransactionWithBlockInfo", GetTransactionWithBlockInfo);

View File

@ -20,8 +20,6 @@
NAN_METHOD(StartBitcoind); NAN_METHOD(StartBitcoind);
NAN_METHOD(OnBlocksReady); NAN_METHOD(OnBlocksReady);
NAN_METHOD(OnTipUpdate); NAN_METHOD(OnTipUpdate);
NAN_METHOD(IsStopping);
NAN_METHOD(IsStopped);
NAN_METHOD(StopBitcoind); NAN_METHOD(StopBitcoind);
NAN_METHOD(GetBlock); NAN_METHOD(GetBlock);
NAN_METHOD(GetTransaction); NAN_METHOD(GetTransaction);

View File

@ -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() { describe('#_writeBlock', function() {
it('should update hashes and call putBlock', function(done) { it('should update hashes and call putBlock', function(done) {
var chain = new Chain(); var chain = new Chain();

View File

@ -16,7 +16,7 @@ var Transaction = bitcore.Transaction;
describe('Bitcoin DB', function() { describe('Bitcoin DB', function() {
var coinbaseAmount = 50 * 1e8; var coinbaseAmount = 50 * 1e8;
describe('#initialize', function() { describe('#start', function() {
it('should emit ready', function(done) { it('should emit ready', function(done) {
var db = new DB({store: memdown}); var db = new DB({store: memdown});
db._modules = ['mod1', 'mod2']; db._modules = ['mod1', 'mod2'];
@ -24,8 +24,25 @@ describe('Bitcoin DB', function() {
on: sinon.spy() on: sinon.spy()
}; };
db.addModule = sinon.spy(); db.addModule = sinon.spy();
db.on('ready', done); var readyFired = false;
db.initialize(); db.on('ready', function() {
readyFired = true;
});
db.start(function() {
readyFired.should.equal(true);
done();
});
});
});
describe('#stop', function() {
it('should immediately call the callback', function(done) {
var db = new DB({store: memdown});
db.stop(function(err) {
should.not.exist(err);
done();
});
}); });
}); });

View File

@ -273,7 +273,50 @@ describe('Bitcoind Node', function() {
}); });
node._syncBitcoind(); 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() { describe('#_loadNetwork', function() {
it('should use the testnet network if testnet is specified', function() { it('should use the testnet network if testnet is specified', function() {
var config = { 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() { 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) { it('should initialize', function(done) {
var node = new Node({}); node.once('ready', function() {
node.chain = {}; done();
node.Block = 'Block'; });
node.bitcoind = 'bitcoind';
node.db = {}; node.start = sinon.stub().callsArg(0);
node._initializeBitcoind = sinon.spy();
node._initializeDatabase = sinon.spy();
node._initializeChain = sinon.spy();
node._initialize(); node._initialize();
// references // references
@ -518,19 +486,38 @@ describe('Bitcoind Node', function() {
node.chain.db.should.equal(node.db); node.chain.db.should.equal(node.db);
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 // start syncing
node.setSyncStrategy = sinon.spy(); node.setSyncStrategy = sinon.spy();
node.on('ready', function() {
done();
});
node.emit('ready');
}); });
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.start = sinon.stub().callsArgWith(0, new Error('error'));
node._initialize();
});
});
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']);
});
}); });
}); });