diff --git a/.gitignore b/.gitignore index 1bafe798..48655ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ libbitcoind libbitcoind* libbitcoind.includes *.log +.DS_Store diff --git a/lib/node.js b/lib/node.js index 16828a19..a6d2c152 100644 --- a/lib/node.js +++ b/lib/node.js @@ -145,6 +145,7 @@ Node.prototype._instantiateService = function(service) { var config = service.config; config.node = this; + config.name = service.name; var mod = new service.module(config); // include in loaded services diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index 951c026b..b698e6a9 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -9,16 +9,7 @@ var path = require('path'); var packageFile = require('../../package.json'); var mkdirp = require('mkdirp'); var fs = require('fs'); - -var BASE_CONFIG = { - name: 'My Node', - services: [ - 'address' - ], - datadir: './data', - network: 'livenet', - port: 3001 -}; +var defaultConfig = require('./default-config'); var version; if (packageFile.version.match('-dev')) { @@ -59,19 +50,19 @@ function createBitcoinDirectory(datadir, done) { /** * Will create a base Bitcore Node configuration directory and files. * @param {String} configDir - The absolute path - * @param {String} name - The name of the node * @param {String} datadir - The bitcoin database directory * @param {Boolean} isGlobal - If the configuration depends on globally installed node services. * @param {Function} done - The callback function called when finished */ -function createConfigDirectory(configDir, name, datadir, isGlobal, done) { +function createConfigDirectory(configDir, datadir, isGlobal, done) { mkdirp(configDir, function(err) { if (err) { throw err; } - var config = BASE_CONFIG; - config.name = name || 'Bitcore Node'; + var configInfo = defaultConfig(); + var config = configInfo.config; + config.datadir = datadir; var configJSON = JSON.stringify(config, null, 2); var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); @@ -95,7 +86,6 @@ function createConfigDirectory(configDir, name, datadir, isGlobal, done) { * @param {Object} options * @param {String} options.cwd - The current working directory * @param {String} options.dirname - The name of the bitcore node configuration directory - * @param {String} options.name - The name of the bitcore node * @param {String} options.datadir - The path to the bitcoin datadir * @param {Function} done - A callback function called when finished */ @@ -106,13 +96,11 @@ function create(options, done) { $.checkArgument(_.isFunction(done)); $.checkArgument(_.isString(options.cwd)); $.checkArgument(_.isString(options.dirname)); - $.checkArgument(_.isString(options.name) || _.isUndefined(options.name)); $.checkArgument(_.isBoolean(options.isGlobal)); $.checkArgument(_.isString(options.datadir)); var cwd = options.cwd; var dirname = options.dirname; - var name = options.name; var datadir = options.datadir; var isGlobal = options.isGlobal; @@ -123,7 +111,7 @@ function create(options, done) { function(next) { // Setup the the bitcore-node directory and configuration if (!fs.existsSync(absConfigDir)) { - createConfigDirectory(absConfigDir, name, datadir, isGlobal, next); + createConfigDirectory(absConfigDir, datadir, isGlobal, next); } else { next(new Error('Directory "' + absConfigDir+ '" already exists.')); } diff --git a/lib/service.js b/lib/service.js index 97fed380..cc47ce1a 100644 --- a/lib/service.js +++ b/lib/service.js @@ -7,6 +7,7 @@ var Service = function(options) { EventEmitter.call(this); this.node = options.node; + this.name = options.name; }; util.inherits(Service, EventEmitter); @@ -81,4 +82,10 @@ Service.prototype.setupRoutes = function(app) { // Setup express routes here }; +Service.prototype.getRoutePrefix = function() { + return this.name; +}; + + + module.exports = Service; diff --git a/lib/services/db.js b/lib/services/db.js index 3527416d..9a0e117a 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -247,13 +247,13 @@ DB.prototype.estimateFee = function(blocks, callback) { DB.prototype.getPublishEvents = function() { return [ { - name: 'transaction', + name: 'db/transaction', scope: this, subscribe: this.subscribe.bind(this, 'transaction'), unsubscribe: this.unsubscribe.bind(this, 'transaction') }, { - name: 'block', + name: 'db/block', scope: this, subscribe: this.subscribe.bind(this, 'block'), unsubscribe: this.unsubscribe.bind(this, 'block') @@ -435,12 +435,15 @@ DB.prototype.getHashes = function getHashes(tipHash, callback) { if (hash === self.genesis.hash) { // Stop at the genesis block self.cache.chainHashes[tipHash] = hashes; + callback(null, hashes); } else if(self.cache.chainHashes[hash]) { hashes.shift(); hashes = self.cache.chainHashes[hash].concat(hashes); - delete self.cache.chainHashes[hash]; self.cache.chainHashes[tipHash] = hashes; + if(hash !== tipHash) { + delete self.cache.chainHashes[hash]; + } callback(null, hashes); } else { // Continue with the previous hash @@ -643,8 +646,15 @@ DB.prototype.sync = function() { // 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. - self.syncRewind(block, done); + log.warn('Beginning reorg! Current tip: ' + self.tip.hash + '; New tip: ' + block.hash); + self.syncRewind(block, function(err) { + if(err) { + return done(err); + } + log.warn('Reorg complete. New tip is ' + self.tip.hash); + done(); + }); } }); }, function(err) { diff --git a/lib/services/web.js b/lib/services/web.js index 63fac376..0684b387 100644 --- a/lib/services/web.js +++ b/lib/services/web.js @@ -6,6 +6,8 @@ var bodyParser = require('body-parser'); var socketio = require('socket.io'); var BaseService = require('../service'); var inherits = require('util').inherits; +var index = require('../'); +var log = index.log; var WebService = function(options) { var self = this; @@ -13,6 +15,7 @@ var WebService = function(options) { this.port = options.port || this.node.port || 3456; this.node.on('ready', function() { + self.eventNames = self.getEventNames(); self.setupAllRoutes(); self.server.listen(self.port); self.createMethodsMap(); @@ -50,7 +53,15 @@ WebService.prototype.stop = function(callback) { WebService.prototype.setupAllRoutes = function() { for(var key in this.node.services) { - this.node.services[key].setupRoutes(this.app); + var subApp = new express.Router(); + var service = this.node.services[key]; + + if(service.getRoutePrefix && service.setupRoutes) { + this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp); + this.node.services[key].setupRoutes(subApp, express); + } else { + log.info('Not setting up routes for ' + service.name); + } } }; @@ -71,7 +82,32 @@ WebService.prototype.createMethodsMap = function() { args: args }; }); -} +}; + +WebService.prototype.getEventNames = function() { + var events = this.node.getAllPublishEvents(); + var eventNames = []; + + function addEventName(name) { + if(eventNames.indexOf(name) > -1) { + throw new Error('Duplicate event ' + name); + } + + eventNames.push(name); + }; + + events.forEach(function(event) { + addEventName(event.name); + + if(event.extraEvents) { + event.extraEvents.forEach(function(name) { + addEventName(name); + }); + } + }); + + return eventNames; +}; WebService.prototype.socketHandler = function(socket) { var self = this; @@ -88,10 +124,8 @@ WebService.prototype.socketHandler = function(socket) { bus.unsubscribe(name, params); }); - var events = self.node.getAllPublishEvents(); - - events.forEach(function(event) { - bus.on(event.name, function() { + this.eventNames.forEach(function(eventName) { + bus.on(eventName, function() { if(socket.connected) { var results = []; @@ -99,7 +133,7 @@ WebService.prototype.socketHandler = function(socket) { results.push(arguments[i]); } - var params = [event.name].concat(results); + var params = [eventName].concat(results); socket.emit.apply(socket, params); } }); diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index 08cc3d26..313a3908 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -74,8 +74,7 @@ describe('#create', function() { should.equal(fs.existsSync(bitcoinConfig), true); var config = JSON.parse(fs.readFileSync(configPath)); - config.name.should.equal('My Node 1'); - config.services.should.deep.equal(['address']); + config.services.should.deep.equal(['bitcoind', 'db', 'address', 'web']); config.datadir.should.equal('./data'); config.network.should.equal('livenet'); diff --git a/test/services/web.unit.js b/test/services/web.unit.js index 2db7aad6..b0c34eaf 100644 --- a/test/services/web.unit.js +++ b/test/services/web.unit.js @@ -39,15 +39,20 @@ describe('WebService', function() { on: sinon.spy(), services: { one: { - setupRoutes: sinon.spy() + setupRoutes: sinon.spy(), + getRoutePrefix: sinon.stub().returns('one') }, two: { - setupRoutes: sinon.spy() + setupRoutes: sinon.spy(), + getRoutePrefix: sinon.stub().returns('two') } } }; var web = new WebService({node: node}); + web.app = { + use: sinon.spy() + }; web.setupAllRoutes(); node.services.one.setupRoutes.callCount.should.equal(1); @@ -97,6 +102,54 @@ describe('WebService', function() { }); }); + describe('#getEventNames', function() { + it('should get event names', function() { + var Module1 = function() {}; + Module1.prototype.getPublishEvents = function() { + return [ + { + name: 'event1', + extraEvents: ['event2'] + } + ]; + }; + + var module1 = new Module1(); + var node = { + on: sinon.spy(), + getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents()) + }; + + var web = new WebService({node: node}); + var events = web.getEventNames(); + + events.should.deep.equal(['event1', 'event2']); + }); + + it('should throw an error if there is a duplicate event', function() { + var Module1 = function() {}; + Module1.prototype.getPublishEvents = function() { + return [ + { + name: 'event1', + extraEvents: ['event1'] + } + ]; + }; + + var module1 = new Module1(); + var node = { + on: sinon.spy(), + getAllPublishEvents: sinon.stub().returns(module1.getPublishEvents()) + }; + + var web = new WebService({node: node}); + (function() { + var events = web.getEventNames(); + }).should.throw('Duplicate event event1'); + }); + }); + describe('#socketHandler', function() { var bus = new EventEmitter(); @@ -104,7 +157,8 @@ describe('WebService', function() { Module1.prototype.getPublishEvents = function() { return [ { - name: 'event1' + name: 'event1', + extraEvents: ['event2'] } ]; }; @@ -121,6 +175,7 @@ describe('WebService', function() { it('on message should call socketMessageHandler', function(done) { web = new WebService({node: node}); + web.eventNames = web.getEventNames(); web.socketMessageHandler = function(param1) { param1.should.equal('data'); done(); @@ -149,13 +204,13 @@ describe('WebService', function() { }); it('publish events from bus should be emitted from socket', function(done) { - socket.once('event1', function(param1, param2) { + socket.once('event2', function(param1, param2) { param1.should.equal('param1'); param2.should.equal('param2'); done(); }); socket.connected = true; - bus.emit('event1', 'param1', 'param2'); + bus.emit('event2', 'param1', 'param2'); }); it('on disconnect should close bus', function(done) {