From da6b6e36229a5d719a833876aca59ec2a529ef46 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 16:07:35 -0400 Subject: [PATCH 1/4] more changes for services --- .gitignore | 1 + lib/node.js | 1 + lib/scaffold/create.js | 15 ++++----------- lib/scaffold/default-config.js | 1 + lib/service.js | 7 +++++++ lib/services/db.js | 27 +++++++++++++++++++++++++-- lib/services/web.js | 22 +++++++++++++++++++--- 7 files changed, 58 insertions(+), 16 deletions(-) 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..8be332e9 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')) { @@ -70,7 +61,9 @@ function createConfigDirectory(configDir, name, datadir, isGlobal, done) { throw err; } - var config = BASE_CONFIG; + var configInfo = defaultConfig(); + var config = configInfo.config; + config.name = name || 'Bitcore Node'; config.datadir = datadir; var configJSON = JSON.stringify(config, null, 2); diff --git a/lib/scaffold/default-config.js b/lib/scaffold/default-config.js index 843688f3..cc29dab9 100644 --- a/lib/scaffold/default-config.js +++ b/lib/scaffold/default-config.js @@ -10,6 +10,7 @@ function getDefaultConfig() { return { path: process.cwd(), config: { + name: 'Bitcore Node', datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), network: process.env.BITCORENODE_NETWORK || 'livenet', port: Number(process.env.BITCORENODE_PORT) || 3001, 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..48c15cd6 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -435,12 +435,28 @@ DB.prototype.getHashes = function getHashes(tipHash, callback) { if (hash === self.genesis.hash) { // Stop at the genesis block self.cache.chainHashes[tipHash] = hashes; + + // Only store 2 chains of hashes in memory + var sorted = Object.keys(self.cache.chainHashes).sort(function(a, b) { + return self.cache.chainHashes[a].length < self.cache.chainHashes[b].length; + }); + for(var i = 0; i < sorted.length; i++) { + if(i > 2) { + log.debug('Removing chainHashes ' + sorted[i] + ' ' + (self.cache.chainHashes[sorted[i]].length)); + delete self.cache.chainHashes[sorted[i]]; + } else { + log.debug('Keeping chainHashes ' + sorted[i] + ' ' + (self.cache.chainHashes[sorted[i]].length)); + } + } + 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 +659,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..89e7af07 100644 --- a/lib/services/web.js +++ b/lib/services/web.js @@ -50,7 +50,9 @@ 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(); + this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp); + this.node.services[key].setupRoutes(subApp, express); } }; @@ -89,9 +91,23 @@ WebService.prototype.socketHandler = function(socket) { }); var events = self.node.getAllPublishEvents(); + var eventNames = []; events.forEach(function(event) { - bus.on(event.name, function() { + eventNames.push(event.name); + + if(event.extraEvents) { + eventNames = eventNames.concat(event.extraEvents); + } + }); + + eventNames = eventNames.filter(function(value, index, self) { + // remove any duplicates + return self.indexOf(value) === index; + }); + + eventNames.forEach(function(eventName) { + bus.on(eventName, function() { if(socket.connected) { var results = []; @@ -99,7 +115,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); } }); From 900f715a49e854a19512c747b5f02f96e52a62f1 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 16:38:23 -0400 Subject: [PATCH 2/4] add tests --- lib/services/db.js | 13 ------------- test/scaffold/create.integration.js | 2 +- test/services/web.unit.js | 16 +++++++++++----- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/lib/services/db.js b/lib/services/db.js index 48c15cd6..17fd56f1 100644 --- a/lib/services/db.js +++ b/lib/services/db.js @@ -436,19 +436,6 @@ DB.prototype.getHashes = function getHashes(tipHash, callback) { // Stop at the genesis block self.cache.chainHashes[tipHash] = hashes; - // Only store 2 chains of hashes in memory - var sorted = Object.keys(self.cache.chainHashes).sort(function(a, b) { - return self.cache.chainHashes[a].length < self.cache.chainHashes[b].length; - }); - for(var i = 0; i < sorted.length; i++) { - if(i > 2) { - log.debug('Removing chainHashes ' + sorted[i] + ' ' + (self.cache.chainHashes[sorted[i]].length)); - delete self.cache.chainHashes[sorted[i]]; - } else { - log.debug('Keeping chainHashes ' + sorted[i] + ' ' + (self.cache.chainHashes[sorted[i]].length)); - } - } - callback(null, hashes); } else if(self.cache.chainHashes[hash]) { hashes.shift(); diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index 08cc3d26..3d5d11cc 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -75,7 +75,7 @@ describe('#create', function() { 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..c8c17471 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); @@ -104,7 +109,8 @@ describe('WebService', function() { Module1.prototype.getPublishEvents = function() { return [ { - name: 'event1' + name: 'event1', + extraEvents: ['event2'] } ]; }; @@ -149,13 +155,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) { From 7ac429fbd228c6200ff72d82ba47990025669bb0 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 16:59:43 -0400 Subject: [PATCH 3/4] remove name field from node --- lib/scaffold/create.js | 9 ++------- lib/scaffold/default-config.js | 1 - test/scaffold/create.integration.js | 1 - 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/scaffold/create.js b/lib/scaffold/create.js index 8be332e9..b698e6a9 100644 --- a/lib/scaffold/create.js +++ b/lib/scaffold/create.js @@ -50,12 +50,11 @@ 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; @@ -64,7 +63,6 @@ function createConfigDirectory(configDir, name, datadir, isGlobal, done) { var configInfo = defaultConfig(); var config = configInfo.config; - config.name = name || 'Bitcore Node'; config.datadir = datadir; var configJSON = JSON.stringify(config, null, 2); var packageJSON = JSON.stringify(BASE_PACKAGE, null, 2); @@ -88,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 */ @@ -99,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; @@ -116,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/scaffold/default-config.js b/lib/scaffold/default-config.js index cc29dab9..843688f3 100644 --- a/lib/scaffold/default-config.js +++ b/lib/scaffold/default-config.js @@ -10,7 +10,6 @@ function getDefaultConfig() { return { path: process.cwd(), config: { - name: 'Bitcore Node', datadir: process.env.BITCORENODE_DIR || path.resolve(process.env.HOME, '.bitcoin'), network: process.env.BITCORENODE_NETWORK || 'livenet', port: Number(process.env.BITCORENODE_PORT) || 3001, diff --git a/test/scaffold/create.integration.js b/test/scaffold/create.integration.js index 3d5d11cc..313a3908 100644 --- a/test/scaffold/create.integration.js +++ b/test/scaffold/create.integration.js @@ -74,7 +74,6 @@ 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(['bitcoind', 'db', 'address', 'web']); config.datadir.should.equal('./data'); config.network.should.equal('livenet'); From a0e40ffd157cff5b71dd05e040f3ac1017d2090c Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 17:29:28 -0400 Subject: [PATCH 4/4] check for duplicate events --- lib/services/db.js | 4 +-- lib/services/web.js | 58 +++++++++++++++++++++++++-------------- test/services/web.unit.js | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 22 deletions(-) diff --git a/lib/services/db.js b/lib/services/db.js index 17fd56f1..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') diff --git a/lib/services/web.js b/lib/services/web.js index 89e7af07..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(); @@ -51,8 +54,14 @@ WebService.prototype.stop = function(callback) { WebService.prototype.setupAllRoutes = function() { for(var key in this.node.services) { var subApp = new express.Router(); - this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp); - this.node.services[key].setupRoutes(subApp, express); + 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); + } } }; @@ -73,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; @@ -90,23 +124,7 @@ WebService.prototype.socketHandler = function(socket) { bus.unsubscribe(name, params); }); - var events = self.node.getAllPublishEvents(); - var eventNames = []; - - events.forEach(function(event) { - eventNames.push(event.name); - - if(event.extraEvents) { - eventNames = eventNames.concat(event.extraEvents); - } - }); - - eventNames = eventNames.filter(function(value, index, self) { - // remove any duplicates - return self.indexOf(value) === index; - }); - - eventNames.forEach(function(eventName) { + this.eventNames.forEach(function(eventName) { bus.on(eventName, function() { if(socket.connected) { var results = []; diff --git a/test/services/web.unit.js b/test/services/web.unit.js index c8c17471..b0c34eaf 100644 --- a/test/services/web.unit.js +++ b/test/services/web.unit.js @@ -102,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(); @@ -127,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();