2015-07-15 15:13:41 -07:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
var util = require('util');
|
2015-08-26 12:18:58 -07:00
|
|
|
var EventEmitter = require('events').EventEmitter;
|
|
|
|
var async = require('async');
|
2015-10-16 21:56:29 -07:00
|
|
|
var bitcore = require('bitcore-lib');
|
2015-07-15 15:13:41 -07:00
|
|
|
var Networks = bitcore.Networks;
|
2015-07-21 06:09:59 -07:00
|
|
|
var $ = bitcore.util.preconditions;
|
2015-08-31 10:38:21 -07:00
|
|
|
var _ = bitcore.deps._;
|
2015-08-26 12:18:58 -07:00
|
|
|
var index = require('./');
|
|
|
|
var log = index.log;
|
2015-07-29 10:36:23 -07:00
|
|
|
var Bus = require('./bus');
|
2015-08-28 10:54:29 -07:00
|
|
|
var errors = require('./errors');
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* A node is a hub of services, and will manage starting and stopping the services in
|
|
|
|
* the correct order based the the dependency chain. The node also holds common configuration
|
|
|
|
* properties that can be shared across services, such as network settings.
|
|
|
|
*
|
|
|
|
* The array of services should have the format:
|
2015-09-29 11:15:49 -07:00
|
|
|
* ```js
|
2015-09-22 08:38:14 -07:00
|
|
|
* {
|
|
|
|
* name: 'bitcoind',
|
|
|
|
* config: {}, // options to pass into constructor
|
2015-09-29 11:15:49 -07:00
|
|
|
* module: ServiceConstructor
|
2015-09-22 08:38:14 -07:00
|
|
|
* }
|
2015-09-29 11:15:49 -07:00
|
|
|
* ```
|
2015-09-22 08:38:14 -07:00
|
|
|
*
|
|
|
|
* @param {Object} config - The configuration of the node
|
|
|
|
* @param {Array} config.services - The array of services
|
|
|
|
* @param {Number} config.port - The HTTP port for services
|
|
|
|
* @param {Boolean} config.https - Enable https
|
|
|
|
* @param {Object} config.httpsOptions - Options for https
|
|
|
|
* @param {String} config.httpsOptions.key - Path to key file
|
|
|
|
* @param {String} config.httpsOptions.cert - Path to cert file
|
|
|
|
* @param {}
|
|
|
|
*/
|
2015-07-15 15:13:41 -07:00
|
|
|
function Node(config) {
|
2015-09-22 08:38:14 -07:00
|
|
|
/* jshint maxstatements: 20 */
|
2015-08-26 12:18:58 -07:00
|
|
|
if(!(this instanceof Node)) {
|
|
|
|
return new Node(config);
|
|
|
|
}
|
2016-04-07 07:14:34 -07:00
|
|
|
this.configPath = config.path;
|
2015-09-22 08:38:14 -07:00
|
|
|
this.errors = errors;
|
2015-08-31 10:38:21 -07:00
|
|
|
this.log = log;
|
2015-08-31 06:00:00 -07:00
|
|
|
this.network = null;
|
2015-08-31 06:00:00 -07:00
|
|
|
this.services = {};
|
|
|
|
this._unloadedServices = [];
|
2015-08-27 13:09:27 -07:00
|
|
|
|
2015-08-31 06:00:00 -07:00
|
|
|
// TODO type check the arguments of config.services
|
|
|
|
if (config.services) {
|
|
|
|
$.checkArgument(Array.isArray(config.services));
|
|
|
|
this._unloadedServices = config.services;
|
2015-08-27 13:09:27 -07:00
|
|
|
}
|
2015-08-31 10:05:46 -07:00
|
|
|
this.port = config.port;
|
2015-09-10 08:06:37 -07:00
|
|
|
this.https = config.https;
|
|
|
|
this.httpsOptions = config.httpsOptions;
|
2015-08-31 06:00:00 -07:00
|
|
|
this._setNetwork(config);
|
|
|
|
}
|
2015-08-11 14:16:04 -07:00
|
|
|
|
2015-08-31 06:00:00 -07:00
|
|
|
util.inherits(Node, EventEmitter);
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will set the this.network based on a network string.
|
|
|
|
* @param {Object} config
|
|
|
|
* @param {String} config.network - Possible options "testnet", "regtest" or "livenet"
|
|
|
|
*/
|
2015-08-31 06:00:00 -07:00
|
|
|
Node.prototype._setNetwork = function(config) {
|
2015-07-21 10:47:25 -07:00
|
|
|
if (config.network === 'testnet') {
|
2015-07-15 15:13:41 -07:00
|
|
|
this.network = Networks.get('testnet');
|
2015-07-21 10:47:25 -07:00
|
|
|
} else if (config.network === 'regtest') {
|
2016-02-03 15:29:33 -08:00
|
|
|
Networks.enableRegtest();
|
2015-07-21 10:47:25 -07:00
|
|
|
this.network = Networks.get('regtest');
|
2015-07-15 15:13:41 -07:00
|
|
|
} else {
|
2015-08-31 06:00:00 -07:00
|
|
|
this.network = Networks.defaultNetwork;
|
2015-07-15 15:13:41 -07:00
|
|
|
}
|
2015-07-21 06:09:59 -07:00
|
|
|
$.checkState(this.network, 'Unrecognized network');
|
2015-07-15 15:13:41 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will instantiate a new Bus for this node.
|
|
|
|
* @returns {Bus}
|
|
|
|
*/
|
2015-08-31 06:00:00 -07:00
|
|
|
Node.prototype.openBus = function() {
|
2015-08-31 06:00:00 -07:00
|
|
|
return new Bus({node: this});
|
2015-08-24 11:46:48 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will get an array of API method descriptions from all of the available services.
|
|
|
|
* @returns {Array}
|
|
|
|
*/
|
2015-08-31 06:00:00 -07:00
|
|
|
Node.prototype.getAllAPIMethods = function() {
|
|
|
|
var methods = [];
|
2015-08-31 06:00:00 -07:00
|
|
|
for(var i in this.services) {
|
|
|
|
var mod = this.services[i];
|
2015-08-31 06:00:00 -07:00
|
|
|
methods = methods.concat(mod.getAPIMethods());
|
|
|
|
}
|
|
|
|
return methods;
|
2015-07-15 15:13:41 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will get an array of events from all of the available services.
|
|
|
|
* @returns {Array}
|
|
|
|
*/
|
2015-08-31 06:00:00 -07:00
|
|
|
Node.prototype.getAllPublishEvents = function() {
|
|
|
|
var events = [];
|
2015-08-31 06:00:00 -07:00
|
|
|
for (var i in this.services) {
|
|
|
|
var mod = this.services[i];
|
2015-08-31 06:00:00 -07:00
|
|
|
events = events.concat(mod.getPublishEvents());
|
|
|
|
}
|
|
|
|
return events;
|
2015-08-24 13:33:44 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will organize services into the order that they should be started
|
|
|
|
* based on the service's dependencies.
|
|
|
|
* @returns {Array}
|
|
|
|
*/
|
2015-08-27 13:09:27 -07:00
|
|
|
Node.prototype.getServiceOrder = function() {
|
2015-08-24 13:33:44 -07:00
|
|
|
|
2015-08-31 06:00:00 -07:00
|
|
|
var services = this._unloadedServices;
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-08-27 13:09:27 -07:00
|
|
|
// organize data for sorting
|
|
|
|
var names = [];
|
|
|
|
var servicesByName = {};
|
|
|
|
for (var i = 0; i < services.length; i++) {
|
|
|
|
var service = services[i];
|
|
|
|
names.push(service.name);
|
|
|
|
servicesByName[service.name] = service;
|
2015-08-20 14:50:14 -07:00
|
|
|
}
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-08-27 13:09:27 -07:00
|
|
|
var stackNames = {};
|
|
|
|
var stack = [];
|
|
|
|
|
|
|
|
function addToStack(names) {
|
|
|
|
for(var i = 0; i < names.length; i++) {
|
|
|
|
|
|
|
|
var name = names[i];
|
|
|
|
var service = servicesByName[name];
|
2015-08-31 06:00:00 -07:00
|
|
|
$.checkState(service, 'Required dependency "' + name + '" not available.');
|
2015-08-27 13:09:27 -07:00
|
|
|
|
|
|
|
// first add the dependencies
|
2015-08-31 10:38:21 -07:00
|
|
|
addToStack(service.module.dependencies);
|
2015-08-27 13:09:27 -07:00
|
|
|
|
|
|
|
// add to the stack if it hasn't been added
|
|
|
|
if(!stackNames[name]) {
|
|
|
|
stack.push(service);
|
|
|
|
stackNames[name] = true;
|
|
|
|
}
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-08-20 14:50:14 -07:00
|
|
|
}
|
|
|
|
}
|
2015-08-27 13:09:27 -07:00
|
|
|
|
|
|
|
addToStack(names);
|
|
|
|
|
2015-08-20 14:50:14 -07:00
|
|
|
return stack;
|
|
|
|
};
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will instantiate an instance of the service module, add it to the node
|
|
|
|
* services, start the service and add available API methods to the node and
|
|
|
|
* checking for any conflicts.
|
|
|
|
* @param {Object} serviceInfo
|
|
|
|
* @param {String} serviceInfo.name - The name of the service
|
|
|
|
* @param {Object} serviceInfo.module - The service module constructor
|
|
|
|
* @param {Object} serviceInfo.config - Options to pass into the constructor
|
|
|
|
* @param {Function} callback - Called when the service is started
|
|
|
|
* @private
|
|
|
|
*/
|
2015-09-04 12:05:32 -07:00
|
|
|
Node.prototype._startService = function(serviceInfo, callback) {
|
2015-08-31 06:00:00 -07:00
|
|
|
var self = this;
|
2015-08-31 10:37:11 -07:00
|
|
|
|
2015-09-04 12:05:32 -07:00
|
|
|
$.checkState(_.isObject(serviceInfo.config));
|
|
|
|
$.checkState(!serviceInfo.config.node);
|
|
|
|
$.checkState(!serviceInfo.config.name);
|
2015-08-31 10:38:21 -07:00
|
|
|
|
2015-09-04 12:05:32 -07:00
|
|
|
log.info('Starting ' + serviceInfo.name);
|
|
|
|
|
|
|
|
var config = serviceInfo.config;
|
2015-08-31 10:37:11 -07:00
|
|
|
config.node = this;
|
2015-09-04 12:05:32 -07:00
|
|
|
config.name = serviceInfo.name;
|
|
|
|
var service = new serviceInfo.module(config);
|
2015-09-15 13:38:41 -07:00
|
|
|
|
|
|
|
// include in loaded services
|
|
|
|
self.services[serviceInfo.name] = service;
|
2015-09-04 12:05:32 -07:00
|
|
|
|
|
|
|
service.start(function(err) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// add API methods
|
|
|
|
var methodData = service.getAPIMethods();
|
|
|
|
var methodNameConflicts = [];
|
|
|
|
methodData.forEach(function(data) {
|
|
|
|
var name = data[0];
|
|
|
|
var instance = data[1];
|
|
|
|
var method = data[2];
|
|
|
|
|
|
|
|
if (self[name]) {
|
|
|
|
methodNameConflicts.push(name);
|
|
|
|
} else {
|
|
|
|
self[name] = function() {
|
|
|
|
return method.apply(instance, arguments);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (methodNameConflicts.length > 0) {
|
|
|
|
return callback(new Error('Existing API method(s) exists: ' + methodNameConflicts.join(', ')));
|
2015-08-31 06:00:00 -07:00
|
|
|
}
|
2015-09-04 12:05:32 -07:00
|
|
|
|
|
|
|
callback();
|
|
|
|
|
2015-08-31 06:00:00 -07:00
|
|
|
});
|
2015-09-04 12:05:32 -07:00
|
|
|
|
2015-08-31 06:00:00 -07:00
|
|
|
};
|
|
|
|
|
2016-04-07 07:14:34 -07:00
|
|
|
Node.prototype._logTitle = function() {
|
2016-04-08 11:44:24 -07:00
|
|
|
if (this.configPath) {
|
|
|
|
log.info('Using config:', this.configPath);
|
|
|
|
}
|
2016-04-07 07:14:34 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will start all running services in the order based on the dependency chain.
|
|
|
|
* @param {Function} callback - Called when all services are started
|
|
|
|
*/
|
2015-08-20 14:50:14 -07:00
|
|
|
Node.prototype.start = function(callback) {
|
|
|
|
var self = this;
|
2015-08-24 13:33:44 -07:00
|
|
|
var servicesOrder = this.getServiceOrder();
|
2015-08-20 14:50:14 -07:00
|
|
|
|
2016-04-07 07:14:34 -07:00
|
|
|
self._logTitle();
|
|
|
|
|
2015-08-20 14:50:14 -07:00
|
|
|
async.eachSeries(
|
2015-08-24 13:33:44 -07:00
|
|
|
servicesOrder,
|
2015-08-20 14:50:14 -07:00
|
|
|
function(service, next) {
|
2015-09-04 12:05:32 -07:00
|
|
|
self._startService(service, next);
|
|
|
|
},
|
|
|
|
function(err) {
|
2015-09-16 07:22:24 -07:00
|
|
|
if (err) {
|
2015-08-31 06:00:00 -07:00
|
|
|
return callback(err);
|
2015-08-27 13:09:27 -07:00
|
|
|
}
|
2015-09-04 12:05:32 -07:00
|
|
|
self.emit('ready');
|
|
|
|
callback();
|
|
|
|
}
|
2015-08-20 14:50:14 -07:00
|
|
|
);
|
|
|
|
};
|
2015-07-15 15:13:41 -07:00
|
|
|
|
2016-04-08 13:00:33 -07:00
|
|
|
Node.prototype.getNetworkName = function() {
|
|
|
|
var network = this.network.name;
|
|
|
|
if (this.network.regtestEnabled) {
|
|
|
|
network = 'regtest';
|
|
|
|
}
|
|
|
|
return network;
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Will stop all running services in the reverse order that they
|
|
|
|
* were initially started.
|
|
|
|
* @param {Function} callback - Called when all services are stopped
|
|
|
|
*/
|
2015-08-20 14:50:14 -07:00
|
|
|
Node.prototype.stop = function(callback) {
|
2015-08-21 12:39:46 -07:00
|
|
|
log.info('Beginning shutdown');
|
2015-08-20 14:50:14 -07:00
|
|
|
var self = this;
|
|
|
|
var services = this.getServiceOrder().reverse();
|
|
|
|
|
2015-08-21 08:53:20 -07:00
|
|
|
this.stopping = true;
|
|
|
|
this.emit('stopping');
|
|
|
|
|
2015-08-20 14:50:14 -07:00
|
|
|
async.eachSeries(
|
|
|
|
services,
|
|
|
|
function(service, next) {
|
2015-09-15 13:38:41 -07:00
|
|
|
if (self.services[service.name]) {
|
2015-09-04 12:05:32 -07:00
|
|
|
log.info('Stopping ' + service.name);
|
2015-09-15 13:38:41 -07:00
|
|
|
self.services[service.name].stop(next);
|
2015-09-04 12:05:32 -07:00
|
|
|
} else {
|
|
|
|
log.info('Stopping ' + service.name + ' (not started)');
|
|
|
|
setImmediate(next);
|
|
|
|
}
|
2015-08-20 14:50:14 -07:00
|
|
|
},
|
|
|
|
callback
|
|
|
|
);
|
2015-07-15 15:13:41 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Node;
|