Merge pull request #140 from pnagurny/enhance/ctrl-c
Better Ctrl-C Behavior
This commit is contained in:
commit
4a50df9d4c
|
@ -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}));
|
32
bin/start.js
32
bin/start.js
|
@ -141,3 +141,35 @@ node.chain.on('addblock', function(block) {
|
|||
}, 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}));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
193
lib/daemon.js
193
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;
|
||||
|
||||
|
@ -67,94 +48,21 @@ 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;
|
||||
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 (options[key] == null) {
|
||||
options[key] = self.options[key];
|
||||
bitcoind.start(this.options, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
|
||||
bitcoind.start(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) {
|
||||
|
@ -168,69 +76,18 @@ Daemon.prototype.start = function(options, 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]);
|
||||
}
|
||||
});
|
||||
|
||||
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() {
|
||||
|
@ -282,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;
|
||||
|
|
10
lib/db.js
10
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,13 @@ DB.prototype.initialize = function() {
|
|||
}
|
||||
this.bitcoind.on('tx', this.transactionHandler.bind(this));
|
||||
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) {
|
||||
var self = this;
|
||||
|
|
|
@ -45,4 +45,12 @@ Module.prototype.getAPIMethods = function() {
|
|||
//
|
||||
// };
|
||||
|
||||
Module.prototype.start = function() {
|
||||
|
||||
};
|
||||
|
||||
Module.prototype.stop = function() {
|
||||
|
||||
};
|
||||
|
||||
module.exports = Module;
|
||||
|
|
196
lib/node.js
196
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});
|
||||
};
|
||||
|
@ -230,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) {
|
||||
|
||||
|
@ -253,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.
|
||||
|
@ -272,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');
|
||||
|
@ -372,69 +383,8 @@ Node.prototype._loadConsensus = function(config) {
|
|||
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() {
|
||||
var self = this;
|
||||
|
||||
// DB References
|
||||
this.db.chain = this.chain;
|
||||
|
@ -444,11 +394,113 @@ Node.prototype._initialize = function() {
|
|||
// Chain References
|
||||
this.chain.db = this.db;
|
||||
|
||||
// Setup Chain of Events
|
||||
this._initializeBitcoind();
|
||||
this._initializeDatabase();
|
||||
this._initializeChain();
|
||||
// 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;
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -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.
|
||||
|
@ -674,18 +676,11 @@ 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;
|
||||
}
|
||||
|
||||
static void
|
||||
start_node_thread(void) {
|
||||
boost::thread_group threadGroup;
|
||||
CScheduler scheduler;
|
||||
|
||||
// Workaround for AppInit2() arg parsing. Not ideal, but it works.
|
||||
|
@ -798,7 +793,6 @@ start_node_thread(void) {
|
|||
*/
|
||||
|
||||
NAN_METHOD(StopBitcoind) {
|
||||
fprintf(stderr, "Stopping Bitcoind please wait!\n");
|
||||
Isolate* isolate = Isolate::GetCurrent();
|
||||
HandleScope scope(isolate);
|
||||
|
||||
|
@ -840,7 +834,9 @@ NAN_METHOD(StopBitcoind) {
|
|||
static void
|
||||
async_stop_node(uv_work_t *req) {
|
||||
async_node_data *data = static_cast<async_node_data*>(req->data);
|
||||
|
||||
StartShutdown();
|
||||
|
||||
while(!shutdown_complete) {
|
||||
usleep(1E6);
|
||||
}
|
||||
|
@ -885,30 +881,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<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()
|
||||
* bitcoind.getBlock([blockhash,blockheight], callback)
|
||||
|
@ -1650,8 +1622,6 @@ init(Handle<Object> 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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,25 @@ 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 immediately call the callback', function(done) {
|
||||
var db = new DB({store: memdown});
|
||||
|
||||
db.stop(function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,19 +486,38 @@ 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() {
|
||||
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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue