From 7f6425c74a2aa8a0678692e80e457d8f9ab8249a Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 3 Sep 2015 16:40:23 -0400 Subject: [PATCH 1/5] Start of BWS as a Bitcore Node Service. --- bcmonitor/bcmonitor.js | 4 + bitcorenode/index.js | 133 ++++++++++++++++++ bws.js | 3 + emailservice/emailservice.js | 3 + lib/blockchainexplorer.js | 3 +- lib/blockchainexplorers/insight.js | 11 +- package.json | 24 +++- test/bitcorenode.js | 217 +++++++++++++++++++++++++++++ 8 files changed, 385 insertions(+), 13 deletions(-) create mode 100644 bitcorenode/index.js create mode 100644 test/bitcorenode.js diff --git a/bcmonitor/bcmonitor.js b/bcmonitor/bcmonitor.js index 3a6e6e7..8509bff 100644 --- a/bcmonitor/bcmonitor.js +++ b/bcmonitor/bcmonitor.js @@ -7,6 +7,10 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); +if (process.argv[2]) { + config = JSON.parse(process.argv[2]); +} + var BlockchainMonitor = require('../lib/blockchainmonitor'); var bcm = new BlockchainMonitor(); diff --git a/bitcorenode/index.js b/bitcorenode/index.js new file mode 100644 index 0000000..66b3920 --- /dev/null +++ b/bitcorenode/index.js @@ -0,0 +1,133 @@ +'use strict'; + +var util = require('util'); +var fs = require('fs'); +var async = require('async'); +var path = require('path'); +var bitcore = require('bitcore'); +var Networks = bitcore.Networks; +var mkdirp = require('mkdirp'); +var child_process = require('child_process'); +var spawn = child_process.spawn; +var EventEmitter = require('events').EventEmitter; +var baseConfig = require('../config'); + +var Service = function(options) { + EventEmitter.call(this); + + this.node = options.node; + this.children = []; +}; + +util.inherits(Service, EventEmitter); + +Service.dependencies = ['bitcoind', 'db', 'address', 'insight-api']; + +Service.prototype.blockHandler = function(block, add, callback) { + setImmediate(function() { + callback(null, []); + }); +}; + +Service.prototype.start = function(done) { + + var self = this; + var providerOptions = { + provider: 'insight', + url: 'http://localhost:' + self.node.port, + apiPrefix: '/insight-api' + }; + + // A bitcore-node is either livenet or testnet, so we'll pass + // the configuration options to communicate via the local running + // instance of the insight-api service. + if (self.node.network === Networks.livenet) { + baseConfig.blockchainExplorerOpts = { + livenet: providerOptions + }; + } else if (self.node.network === Networks.testnet) { + baseConfig.blockchainExplorerOpts = { + testnet: providerOptions + }; + } else { + return done(new Error('Unknown network')); + } + + var services = [ + ['locker.log', 'locker/locker.js'], + ['messagebroker.log', 'messagebroker/messagebroker.js'], + ['bcmonitor.log', 'bcmonitor/bcmonitor.js', JSON.stringify(baseConfig)], + ['emailservice.log', 'emailservice/emailservice.js', JSON.stringify(baseConfig)], + ['bws.log', 'bws.js', JSON.stringify(baseConfig)], + ]; + + var basePath = path.resolve(__dirname, '..'); + var logBasePath = path.resolve(self.node.datadir, './bws-logs/'); + + // Make sure that the logs directory exists + if (!fs.existsSync(logBasePath)) { + mkdirp.sync(logBasePath); + } + + async.eachSeries( + services, + function(service, next) { + + var logPath = path.resolve(logBasePath, service[0]); + var servicePath = path.resolve(basePath, service[1]); + var config = service[2]; + + var stderr = fs.openSync(logPath, 'a+'); + var stdout = stderr; + + var options = { + stdio: ['ignore', stdout, stderr], + cwd: basePath, + env: process.env + }; + + var child = spawn('node', [servicePath, config], options); + self.children.push(child); + next(); + }, + function(err) { + if (err) { + return done(err); + } + done(); + } + ); + +}; + +Service.prototype.stop = function(done) { + var self = this; + async.eachSeries( + self.children, + function(child, next) { + child.kill(); + next(); + }, + function(err) { + if (err) { + return done(err); + } + done(); + } + ); +}; + +Service.prototype.getAPIMethods = function() { + return []; +}; + +Service.prototype.getPublishEvents = function() { + return []; +}; + + +Service.prototype.setupRoutes = function(app) { + // TODO: Run bws express/websocket app (setup routes and events on the web service) +}; + +module.exports = Service; diff --git a/bws.js b/bws.js index f1cbb21..fec0c0b 100755 --- a/bws.js +++ b/bws.js @@ -6,6 +6,9 @@ var fs = require('fs'); var ExpressApp = require('./lib/expressapp'); var WsApp = require('./lib/wsapp'); var config = require('./config'); +if (process.argv[2]) { + config = JSON.parse(process.argv[2]); +} var sticky = require('sticky-session'); var log = require('npmlog'); log.debug = log.verbose; diff --git a/emailservice/emailservice.js b/emailservice/emailservice.js index fbb7e38..87213ac 100644 --- a/emailservice/emailservice.js +++ b/emailservice/emailservice.js @@ -7,6 +7,9 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); +if (process.argv[2]) { + config = JSON.parse(process.argv[2]); +} var EmailService = require('../lib/emailservice'); var emailService = new EmailService(); diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index 227dedb..a0f4072 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -29,7 +29,8 @@ function BlockChainExplorer(opts) { case 'insight': return new Insight({ network: network, - url: url + url: url, + apiPrefix: opts.apiPrefix }); default: throw new Error('Provider ' + provider + ' not supported.'); diff --git a/lib/blockchainexplorers/insight.js b/lib/blockchainexplorers/insight.js index 9711055..3961bc2 100644 --- a/lib/blockchainexplorers/insight.js +++ b/lib/blockchainexplorers/insight.js @@ -12,6 +12,7 @@ function Insight(opts) { $.checkArgument(_.contains(['livenet', 'testnet'], opts.network)); $.checkArgument(opts.url); + this.apiPrefix = opts.apiPrefix || '/api'; this.network = opts.network || 'livenet'; this.url = opts.url; }; @@ -34,7 +35,7 @@ Insight.prototype.getConnectionInfo = function() { * Retrieve a list of unspent outputs associated with an address or set of addresses */ Insight.prototype.getUnspentUtxos = function(addresses, cb) { - var url = this.url + '/api/addrs/utxo'; + var url = this.url + this.apiPrefix + '/addrs/utxo'; var args = { method: 'POST', url: url, @@ -53,7 +54,7 @@ Insight.prototype.getUnspentUtxos = function(addresses, cb) { * Broadcast a transaction to the bitcoin network */ Insight.prototype.broadcast = function(rawTx, cb) { - var url = this.url + '/api/tx/send'; + var url = this.url + this.apiPrefix + '/tx/send'; var args = { method: 'POST', url: url, @@ -69,7 +70,7 @@ Insight.prototype.broadcast = function(rawTx, cb) { }; Insight.prototype.getTransaction = function(txid, cb) { - var url = this.url + '/api/tx/' + txid; + var url = this.url + this.apiPrefix + '/tx/' + txid; var args = { method: 'GET', url: url, @@ -89,7 +90,7 @@ Insight.prototype.getTransactions = function(addresses, from, to, cb) { if (_.isNumber(from)) qs.push('from=' + from); if (_.isNumber(to)) qs.push('to=' + to); - var url = this.url + '/api/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''); + var url = this.url + this.apiPrefix + '/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''); var args = { method: 'POST', url: url, @@ -119,7 +120,7 @@ Insight.prototype.getAddressActivity = function(addresses, cb) { }; Insight.prototype.estimateFee = function(nbBlocks, cb) { - var url = this.url + '/api/utils/estimatefee'; + var url = this.url + this.apiPrefix + '/utils/estimatefee'; if (nbBlocks) { url += '?nbBlocks=' + [].concat(nbBlocks).join(','); } diff --git a/package.json b/package.json index e5e6a7a..5fe62ba 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "locker": "^0.1.0", "locker-server": "^0.1.3", "lodash": "^3.10.1", + "mkdirp": "^0.5.1", "mocha-lcov-reporter": "0.0.1", "moment": "^2.10.3", "mongodb": "^2.0.27", @@ -51,6 +52,7 @@ "jsdoc": "^3.3.0-beta1", "memdown": "^1.0.0", "mocha": "^1.18.2", + "proxyquire": "^1.7.2", "sinon": "1.10.3", "supertest": "*", "tingodb": "^0.3.4" @@ -62,11 +64,19 @@ "test": "./node_modules/.bin/mocha", "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" }, - "contributors": [{ - "name": "Ivan Socolsky", - "email": "ivan@bitpay.com" - }, { - "name": "Matias Alejo Garcia", - "email": "ematiu@gmail.com" - }] + "bitcoreNode": "./bitcorenode", + "contributors": [ + { + "name": "Braydon Fuller", + "email": "braydon@bitpay.com" + }, + { + "name": "Ivan Socolsky", + "email": "ivan@bitpay.com" + }, + { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + } + ] } diff --git a/test/bitcorenode.js b/test/bitcorenode.js new file mode 100644 index 0000000..ec66ee1 --- /dev/null +++ b/test/bitcorenode.js @@ -0,0 +1,217 @@ +'use strict'; + +var should = require('chai').should(); +var sinon = require('sinon'); +var bitcore = require('bitcore'); +var path = require('path'); +var proxyquire = require('proxyquire'); + +describe('Bitcore Node Service', function() { + + describe('#start', function() { + + it('will create a log directory if it doesn\'t exist', function(done) { + var node = { + network: bitcore.Networks.testnet, + datadir: './testdir', + port: 3001 + }; + var mkdirpSync = sinon.stub(); + var Service = proxyquire('../bitcorenode', { + fs: { + existsSync: sinon.stub().returns(false), + openSync: sinon.stub() + }, + child_process: { + spawn: sinon.stub() + }, + mkdirp: { + sync: mkdirpSync + } + }); + var node = { + network: bitcore.Networks.testnet, + datadir: './testdir', + port: 3001 + }; + var service = new Service({node: node}); + + service.start(function() { + mkdirpSync.callCount.should.equal(1); + mkdirpSync.args[0][0].should.equal(path.resolve(node.datadir, './bws-logs')); + done(); + }); + }); + + it('will call spawn with the correct arguments', function(done) { + var spawnCallCount = 0; + var basePath = path.resolve(__dirname, '../'); + + var expectedScripts = [ + path.resolve(basePath, 'locker/locker.js'), + path.resolve(basePath, 'messagebroker/messagebroker.js'), + path.resolve(basePath, 'bcmonitor/bcmonitor.js'), + path.resolve(basePath, 'emailservice/emailservice.js'), + path.resolve(basePath, 'bws.js'), + ]; + + var baseConfig = require('../config'); + baseConfig.blockchainExplorerOpts = { + testnet: { + provider: 'insight', + url: 'http://localhost:3001', + apiPrefix: '/insight-api' + } + }; + + var expectedArgs = [ + undefined, + undefined, + JSON.stringify(baseConfig), + JSON.stringify(baseConfig), + JSON.stringify(baseConfig) + ]; + + var spawn = function(program, args, options) { + program.should.equal('node'); + args.length.should.equal(2); + args[0].should.equal(expectedScripts[spawnCallCount]); + should.equal(args[1], expectedArgs[spawnCallCount]); + options.stdio.length.should.equal(3); + options.stdio[0].should.equal('ignore'); + options.stdio[1].should.equal(fileStream); + options.stdio[2].should.equal(fileStream); + options.cwd.should.equal(path.resolve(__dirname, '..')); + should.equal(process.env, options.env); + spawnCallCount++; + }; + var mkdirpSync = sinon.stub().returns(true); + var existsSync = sinon.stub(); + var fileStream = {}; + var openSync = sinon.stub().returns(fileStream); + var Service = proxyquire('../bitcorenode', { + fs: { + existsSync: existsSync, + openSync: openSync + }, + child_process: { + spawn: spawn + }, + mkdirp: { + sync: mkdirpSync + } + }); + + var node = { + network: bitcore.Networks.testnet, + datadir: './testdir', + port: 3001 + }; + var service = new Service({node: node}); + + service.start(function() { + service.children.length.should.equal(5); + done(); + }); + + }); + + it('will give an error with unknown network', function(done) { + var Service = proxyquire('../bitcorenode', { + fs: { + existsSync: sinon.stub(), + openSync: sinon.stub() + }, + child_process: { + spawn: sinon.stub() + }, + mkdirp: { + sync: sinon.stub() + } + }); + + var node = { + network: 'unknown', + datadir: './testdir', + port: 3001 + }; + var service = new Service({node: node}); + + service.start(function(err) { + err.message.should.equal('Unknown network'); + done(); + }); + + }); + + it('will use livenet', function(done) { + var baseConfig = require('../config'); + baseConfig.blockchainExplorerOpts = { + livenet: { + provider: 'insight', + url: 'http://localhost:3001', + apiPrefix: '/insight-api' + } + }; + + var spawnCallCount = 0; + var expectedArgs = [ + undefined, + undefined, + JSON.stringify(baseConfig), + JSON.stringify(baseConfig), + JSON.stringify(baseConfig) + ]; + var spawn = function(program, args, options) { + should.equal(args[1], expectedArgs[spawnCallCount]); + spawnCallCount++; + }; + var Service = proxyquire('../bitcorenode', { + fs: { + existsSync: sinon.stub(), + openSync: sinon.stub() + }, + child_process: { + spawn: spawn + }, + mkdirp: { + sync: sinon.stub() + } + }); + + var node = { + network: bitcore.Networks.livenet, + datadir: './testdir', + port: 3001 + }; + var service = new Service({node: node}); + + service.start(function() { + spawnCallCount.should.equal(5); + done(); + }); + + }); + + }); + + describe('#stop', function() { + it('will call kill an each process', function() { + var Service = proxyquire('../bitcorenode', {}); + var node = { + network: bitcore.Networks.testnet, + datadir: './testdir', + port: 3001 + }; + var service = new Service({node: node}); + var childProcess = { + kill: sinon.stub() + }; + service.children = [childProcess]; + service.stop(function() { + childProcess.kill.callCount.should.equal(1); + }); + }); + }); + +}); From 8ea3e6c278a94a75b0d92256caa629bbc317ec87 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Thu, 24 Sep 2015 18:31:39 -0400 Subject: [PATCH 2/5] Single process when running as a Bitcore Node service. --- bcmonitor/bcmonitor.js | 4 - bitcorenode/index.js | 162 +++++++++++++++----------- bws.js | 3 - emailservice/emailservice.js | 3 - test/bitcorenode.js | 217 ----------------------------------- 5 files changed, 98 insertions(+), 291 deletions(-) delete mode 100644 test/bitcorenode.js diff --git a/bcmonitor/bcmonitor.js b/bcmonitor/bcmonitor.js index 8509bff..3a6e6e7 100644 --- a/bcmonitor/bcmonitor.js +++ b/bcmonitor/bcmonitor.js @@ -7,10 +7,6 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); -if (process.argv[2]) { - config = JSON.parse(process.argv[2]); -} - var BlockchainMonitor = require('../lib/blockchainmonitor'); var bcm = new BlockchainMonitor(); diff --git a/bitcorenode/index.js b/bitcorenode/index.js index 66b3920..6073022 100644 --- a/bitcorenode/index.js +++ b/bitcorenode/index.js @@ -2,11 +2,19 @@ var util = require('util'); var fs = require('fs'); +var io = require('socket.io'); +var https = require('https'); +var http = require('http'); var async = require('async'); var path = require('path'); var bitcore = require('bitcore'); var Networks = bitcore.Networks; var mkdirp = require('mkdirp'); +var Locker = require('locker-server'); +var BlockchainMonitor = require('../lib/blockchainmonitor'); +var EmailService = require('../lib/emailservice'); +var ExpressApp = require('../lib/expressapp'); +var WsApp = require('../lib/wsapp'); var child_process = require('child_process'); var spawn = child_process.spawn; var EventEmitter = require('events').EventEmitter; @@ -16,19 +24,48 @@ var Service = function(options) { EventEmitter.call(this); this.node = options.node; - this.children = []; + this.https = options.https || this.node.https; + this.httpsOptions = options.httpsOptions || this.node.httpsOptions; + this.bwsPort = options.bwsPort || Service.BWS_PORT; + this.messageBrokerPort = options.messageBrokerPort || Service.MESSAGE_BROKER_PORT; + this.lockerPort = options.lockerPort || Service.LOCKER_PORT; }; util.inherits(Service, EventEmitter); -Service.dependencies = ['bitcoind', 'db', 'address', 'insight-api']; +Service.BWS_PORT = 3232; +Service.MESSAGE_BROKER_PORT = 3380; +Service.LOCKER_PORT = 3231; -Service.prototype.blockHandler = function(block, add, callback) { - setImmediate(function() { - callback(null, []); - }); +Service.dependencies = ['insight-api']; + +/** + * This method will read `key` and `cert` files from disk based on `httpsOptions` and + * return `serverOpts` with the read files. + */ +Service.prototype.readHttpsOptions = function() { + if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) { + throw new Error('Missing https options'); + } + + var serverOpts = {}; + serverOpts.key = fs.readFileSync(this.httpOptions.key); + serverOpts.cert = fs.readFileSync(this.httpsOptions.cert); + + // This sets the intermediate CA certs only if they have all been designated in the config.js + if (this.httpsOptions.CAinter1 && this.httpsOptions.CAinter2 && this.httpsOptions.CAroot) { + serverOpts.ca = [ + fs.readFileSync(this.httpsOptions.CAinter1), + fs.readFileSync(this.httpsOptions.CAinter2), + fs.readFileSync(this.httpsOptions.CAroot) + ]; + } + return serverOpts; }; +/** + * Called by the node to start the service + */ Service.prototype.start = function(done) { var self = this; @@ -53,68 +90,70 @@ Service.prototype.start = function(done) { return done(new Error('Unknown network')); } - var services = [ - ['locker.log', 'locker/locker.js'], - ['messagebroker.log', 'messagebroker/messagebroker.js'], - ['bcmonitor.log', 'bcmonitor/bcmonitor.js', JSON.stringify(baseConfig)], - ['emailservice.log', 'emailservice/emailservice.js', JSON.stringify(baseConfig)], - ['bws.log', 'bws.js', JSON.stringify(baseConfig)], - ]; - - var basePath = path.resolve(__dirname, '..'); - var logBasePath = path.resolve(self.node.datadir, './bws-logs/'); - - // Make sure that the logs directory exists - if (!fs.existsSync(logBasePath)) { - mkdirp.sync(logBasePath); - } - - async.eachSeries( - services, - function(service, next) { + async.series([ + function(next) { + // Locker Server + var locker = new Locker(); + locker.listen(self.lockerPort); + + // Message Broker + var messageServer = io(self.messageBrokerPort); + messageServer.on('connection', function(s) { + s.on('msg', function(d) { + messageServer.emit('msg', d); + }); + }); + + // Blockchain Monitor + var blockChainMonitor = new BlockchainMonitor(); + blockChainMonitor.start(baseConfig, next); + }, + function(next) { + if (baseConfig.emailOpts) { + var emailService = new EmailService(); + emailService.start(baseConfig, next); + } else { + setImmediate(next); + } + }, + function(next) { - var logPath = path.resolve(logBasePath, service[0]); - var servicePath = path.resolve(basePath, service[1]); - var config = service[2]; + var expressApp = new ExpressApp(); + var wsApp = new WsApp(); - var stderr = fs.openSync(logPath, 'a+'); - var stdout = stderr; + if (self.https) { + var serverOpts = self.readHttpsOptions(); + self.server = https.createServer(serverOpts, expressApp.app); + } else { + self.server = http.Server(expressApp.app); + } - var options = { - stdio: ['ignore', stdout, stderr], - cwd: basePath, - env: process.env - }; + async.parallel([ + function(done) { + expressApp.start(baseConfig, done); + }, + function(done) { + wsApp.start(self.server, baseConfig, done); + }, + ], function(err) { + if (err) { + return next(err); + } + self.server.listen(self.bwsPort, next); + }); - var child = spawn('node', [servicePath, config], options); - self.children.push(child); - next(); - }, - function(err) { - if (err) { - return done(err); - } - done(); } - ); + ], done); }; +/** + * Called by node to stop the service + */ Service.prototype.stop = function(done) { - var self = this; - async.eachSeries( - self.children, - function(child, next) { - child.kill(); - next(); - }, - function(err) { - if (err) { - return done(err); - } - done(); - } - ); + setImmediate(function() { + done(); + }); }; Service.prototype.getAPIMethods = function() { @@ -125,9 +164,4 @@ Service.prototype.getPublishEvents = function() { return []; }; - -Service.prototype.setupRoutes = function(app) { - // TODO: Run bws express/websocket app (setup routes and events on the web service) -}; - module.exports = Service; diff --git a/bws.js b/bws.js index fec0c0b..f1cbb21 100755 --- a/bws.js +++ b/bws.js @@ -6,9 +6,6 @@ var fs = require('fs'); var ExpressApp = require('./lib/expressapp'); var WsApp = require('./lib/wsapp'); var config = require('./config'); -if (process.argv[2]) { - config = JSON.parse(process.argv[2]); -} var sticky = require('sticky-session'); var log = require('npmlog'); log.debug = log.verbose; diff --git a/emailservice/emailservice.js b/emailservice/emailservice.js index 87213ac..fbb7e38 100644 --- a/emailservice/emailservice.js +++ b/emailservice/emailservice.js @@ -7,9 +7,6 @@ var log = require('npmlog'); log.debug = log.verbose; var config = require('../config'); -if (process.argv[2]) { - config = JSON.parse(process.argv[2]); -} var EmailService = require('../lib/emailservice'); var emailService = new EmailService(); diff --git a/test/bitcorenode.js b/test/bitcorenode.js deleted file mode 100644 index ec66ee1..0000000 --- a/test/bitcorenode.js +++ /dev/null @@ -1,217 +0,0 @@ -'use strict'; - -var should = require('chai').should(); -var sinon = require('sinon'); -var bitcore = require('bitcore'); -var path = require('path'); -var proxyquire = require('proxyquire'); - -describe('Bitcore Node Service', function() { - - describe('#start', function() { - - it('will create a log directory if it doesn\'t exist', function(done) { - var node = { - network: bitcore.Networks.testnet, - datadir: './testdir', - port: 3001 - }; - var mkdirpSync = sinon.stub(); - var Service = proxyquire('../bitcorenode', { - fs: { - existsSync: sinon.stub().returns(false), - openSync: sinon.stub() - }, - child_process: { - spawn: sinon.stub() - }, - mkdirp: { - sync: mkdirpSync - } - }); - var node = { - network: bitcore.Networks.testnet, - datadir: './testdir', - port: 3001 - }; - var service = new Service({node: node}); - - service.start(function() { - mkdirpSync.callCount.should.equal(1); - mkdirpSync.args[0][0].should.equal(path.resolve(node.datadir, './bws-logs')); - done(); - }); - }); - - it('will call spawn with the correct arguments', function(done) { - var spawnCallCount = 0; - var basePath = path.resolve(__dirname, '../'); - - var expectedScripts = [ - path.resolve(basePath, 'locker/locker.js'), - path.resolve(basePath, 'messagebroker/messagebroker.js'), - path.resolve(basePath, 'bcmonitor/bcmonitor.js'), - path.resolve(basePath, 'emailservice/emailservice.js'), - path.resolve(basePath, 'bws.js'), - ]; - - var baseConfig = require('../config'); - baseConfig.blockchainExplorerOpts = { - testnet: { - provider: 'insight', - url: 'http://localhost:3001', - apiPrefix: '/insight-api' - } - }; - - var expectedArgs = [ - undefined, - undefined, - JSON.stringify(baseConfig), - JSON.stringify(baseConfig), - JSON.stringify(baseConfig) - ]; - - var spawn = function(program, args, options) { - program.should.equal('node'); - args.length.should.equal(2); - args[0].should.equal(expectedScripts[spawnCallCount]); - should.equal(args[1], expectedArgs[spawnCallCount]); - options.stdio.length.should.equal(3); - options.stdio[0].should.equal('ignore'); - options.stdio[1].should.equal(fileStream); - options.stdio[2].should.equal(fileStream); - options.cwd.should.equal(path.resolve(__dirname, '..')); - should.equal(process.env, options.env); - spawnCallCount++; - }; - var mkdirpSync = sinon.stub().returns(true); - var existsSync = sinon.stub(); - var fileStream = {}; - var openSync = sinon.stub().returns(fileStream); - var Service = proxyquire('../bitcorenode', { - fs: { - existsSync: existsSync, - openSync: openSync - }, - child_process: { - spawn: spawn - }, - mkdirp: { - sync: mkdirpSync - } - }); - - var node = { - network: bitcore.Networks.testnet, - datadir: './testdir', - port: 3001 - }; - var service = new Service({node: node}); - - service.start(function() { - service.children.length.should.equal(5); - done(); - }); - - }); - - it('will give an error with unknown network', function(done) { - var Service = proxyquire('../bitcorenode', { - fs: { - existsSync: sinon.stub(), - openSync: sinon.stub() - }, - child_process: { - spawn: sinon.stub() - }, - mkdirp: { - sync: sinon.stub() - } - }); - - var node = { - network: 'unknown', - datadir: './testdir', - port: 3001 - }; - var service = new Service({node: node}); - - service.start(function(err) { - err.message.should.equal('Unknown network'); - done(); - }); - - }); - - it('will use livenet', function(done) { - var baseConfig = require('../config'); - baseConfig.blockchainExplorerOpts = { - livenet: { - provider: 'insight', - url: 'http://localhost:3001', - apiPrefix: '/insight-api' - } - }; - - var spawnCallCount = 0; - var expectedArgs = [ - undefined, - undefined, - JSON.stringify(baseConfig), - JSON.stringify(baseConfig), - JSON.stringify(baseConfig) - ]; - var spawn = function(program, args, options) { - should.equal(args[1], expectedArgs[spawnCallCount]); - spawnCallCount++; - }; - var Service = proxyquire('../bitcorenode', { - fs: { - existsSync: sinon.stub(), - openSync: sinon.stub() - }, - child_process: { - spawn: spawn - }, - mkdirp: { - sync: sinon.stub() - } - }); - - var node = { - network: bitcore.Networks.livenet, - datadir: './testdir', - port: 3001 - }; - var service = new Service({node: node}); - - service.start(function() { - spawnCallCount.should.equal(5); - done(); - }); - - }); - - }); - - describe('#stop', function() { - it('will call kill an each process', function() { - var Service = proxyquire('../bitcorenode', {}); - var node = { - network: bitcore.Networks.testnet, - datadir: './testdir', - port: 3001 - }; - var service = new Service({node: node}); - var childProcess = { - kill: sinon.stub() - }; - service.children = [childProcess]; - service.stop(function() { - childProcess.kill.callCount.should.equal(1); - }); - }); - }); - -}); From 1696d38d1b8c26005463430dcaed96b886e73120 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Fri, 25 Sep 2015 00:38:16 -0400 Subject: [PATCH 3/5] Add tests for bitcore node wallet service --- bitcorenode/index.js | 129 +++++++++------ test/bitcorenode.js | 380 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 462 insertions(+), 47 deletions(-) create mode 100644 test/bitcorenode.js diff --git a/bitcorenode/index.js b/bitcorenode/index.js index 6073022..5747c6f 100644 --- a/bitcorenode/index.js +++ b/bitcorenode/index.js @@ -9,7 +9,6 @@ var async = require('async'); var path = require('path'); var bitcore = require('bitcore'); var Networks = bitcore.Networks; -var mkdirp = require('mkdirp'); var Locker = require('locker-server'); var BlockchainMonitor = require('../lib/blockchainmonitor'); var EmailService = require('../lib/emailservice'); @@ -20,6 +19,15 @@ var spawn = child_process.spawn; var EventEmitter = require('events').EventEmitter; var baseConfig = require('../config'); +/** + * A Bitcore Node Service module + * @param {Object} options + * @param {Node} options.node - A reference to the Bitcore Node instance +-* @param {Boolean} options.https - Enable https for this module, defaults to node settings. + * @param {Number} options.bwsPort - Port for Bitcore Wallet Service API + * @param {Number} options.messageBrokerPort - Port for BWS message broker + * @param {Number} options.lockerPort - Port for BWS locker port + */ var Service = function(options) { EventEmitter.call(this); @@ -42,14 +50,15 @@ Service.dependencies = ['insight-api']; /** * This method will read `key` and `cert` files from disk based on `httpsOptions` and * return `serverOpts` with the read files. + * @returns {Object} */ -Service.prototype.readHttpsOptions = function() { +Service.prototype._readHttpsOptions = function() { if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) { throw new Error('Missing https options'); } var serverOpts = {}; - serverOpts.key = fs.readFileSync(this.httpOptions.key); + serverOpts.key = fs.readFileSync(this.httpsOptions.key); serverOpts.cert = fs.readFileSync(this.httpsOptions.cert); // This sets the intermediate CA certs only if they have all been designated in the config.js @@ -64,11 +73,13 @@ Service.prototype.readHttpsOptions = function() { }; /** - * Called by the node to start the service + * Will get the configuration with settings for the locally + * running Insight API. + * @returns {Object} */ -Service.prototype.start = function(done) { - +Service.prototype._getConfiguration = function() { var self = this; + var providerOptions = { provider: 'insight', url: 'http://localhost:' + self.node.port, @@ -87,61 +98,85 @@ Service.prototype.start = function(done) { testnet: providerOptions }; } else { - return done(new Error('Unknown network')); + throw new Error('Unknown network'); + } + + return baseConfig; + +}; + +/** + * Will start the HTTP web server and socket.io for the wallet service. + */ +Service.prototype._startWalletService = function(config, next) { + var self = this; + var expressApp = new ExpressApp(); + var wsApp = new WsApp(); + + if (self.https) { + var serverOpts = self._readHttpsOptions(); + self.server = https.createServer(serverOpts, expressApp.app); + } else { + self.server = http.Server(expressApp.app); + } + + async.parallel([ + function(done) { + expressApp.start(config, done); + }, + function(done) { + wsApp.start(self.server, config, done); + }, + ], function(err) { + if (err) { + return next(err); + } + self.server.listen(self.bwsPort, next); + }); +}; + +/** + * Called by the node to start the service + */ +Service.prototype.start = function(done) { + + var self = this; + var config; + try { + config = self._getConfiguration(); + } catch(err) { + return done(err); } + // Locker Server + var locker = new Locker(); + locker.listen(self.lockerPort); + + // Message Broker + var messageServer = io(self.messageBrokerPort); + messageServer.on('connection', function(s) { + s.on('msg', function(d) { + messageServer.emit('msg', d); + }); + }); + async.series([ function(next) { - // Locker Server - var locker = new Locker(); - locker.listen(self.lockerPort); - - // Message Broker - var messageServer = io(self.messageBrokerPort); - messageServer.on('connection', function(s) { - s.on('msg', function(d) { - messageServer.emit('msg', d); - }); - }); - // Blockchain Monitor var blockChainMonitor = new BlockchainMonitor(); - blockChainMonitor.start(baseConfig, next); + blockChainMonitor.start(config, next); }, function(next) { - if (baseConfig.emailOpts) { + // Email Service + if (config.emailOpts) { var emailService = new EmailService(); - emailService.start(baseConfig, next); + emailService.start(config, next); } else { setImmediate(next); } }, function(next) { - - var expressApp = new ExpressApp(); - var wsApp = new WsApp(); - - if (self.https) { - var serverOpts = self.readHttpsOptions(); - self.server = https.createServer(serverOpts, expressApp.app); - } else { - self.server = http.Server(expressApp.app); - } - - async.parallel([ - function(done) { - expressApp.start(baseConfig, done); - }, - function(done) { - wsApp.start(self.server, baseConfig, done); - }, - ], function(err) { - if (err) { - return next(err); - } - self.server.listen(self.bwsPort, next); - }); - + self._startWalletService(config, next); } ], done); diff --git a/test/bitcorenode.js b/test/bitcorenode.js new file mode 100644 index 0000000..4bff6cb --- /dev/null +++ b/test/bitcorenode.js @@ -0,0 +1,380 @@ +'use strict'; + +var should = require('chai').should(); +var proxyquire = require('proxyquire'); +var bitcore = require('bitcore'); +var sinon = require('sinon'); +var Service = require('../bitcorenode'); + +describe('Bitcore Node Service', function() { + describe('#constructor', function() { + it('https settings from node', function() { + var node = { + https: true, + httpsOptions: { + key: 'key', + cert: 'cert' + } + }; + var options = { + node: node + }; + var service = new Service(options); + service.node.should.equal(node); + service.https.should.equal(true); + service.httpsOptions.should.deep.equal({ + key: 'key', + cert: 'cert' + }); + service.bwsPort.should.equal(Service.BWS_PORT); + service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); + service.lockerPort.should.equal(Service.LOCKER_PORT); + }); + it('direct https options', function() { + var node = {}; + var options = { + node: node, + https: true, + httpsOptions: { + key: 'key', + cert: 'cert' + } + }; + var service = new Service(options); + service.https.should.equal(true); + service.httpsOptions.should.deep.equal({ + key: 'key', + cert: 'cert' + }); + service.bwsPort.should.equal(Service.BWS_PORT); + service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); + service.lockerPort.should.equal(Service.LOCKER_PORT); + }); + it('can set custom ports', function() { + var node = {}; + var options = { + node: node, + bwsPort: 1000, + messageBrokerPort: 1001, + lockerPort: 1002 + }; + var service = new Service(options); + service.bwsPort.should.equal(1000); + service.messageBrokerPort.should.equal(1001); + service.lockerPort.should.equal(1002); + }); + }); + describe('#readHttpsOptions', function() { + var TestService = proxyquire('../bitcorenode', { + fs: { + readFileSync: function(arg) { + return arg; + } + } + }); + it('will create server options from httpsOptions', function() { + var options = { + node: { + https: true, + httpsOptions: { + key: 'key', + cert: 'cert', + CAinter1: 'CAinter1', + CAinter2: 'CAinter2', + CAroot: 'CAroot' + } + } + }; + var service = new TestService(options); + var serverOptions = service._readHttpsOptions(); + serverOptions.key.should.equal('key'); + serverOptions.cert.should.equal('cert'); + serverOptions.ca[0].should.equal('CAinter1'); + serverOptions.ca[1].should.equal('CAinter2'); + serverOptions.ca[2].should.equal('CAroot'); + }); + }); + describe('#_getConfiguration', function() { + it('will throw with an unknown network', function() { + var options = { + node: { + network: 'unknown' + } + }; + var service = new Service(options); + (function() { + service._getConfiguration(); + }).should.throw('Unknown network'); + }); + it('livenet local insight', function() { + var options = { + node: { + network: bitcore.Networks.livenet, + port: 3001 + } + }; + var service = new Service(options); + var config = service._getConfiguration(); + config.blockchainExplorerOpts.livenet.should.deep.equal({ + 'apiPrefix': '/insight-api', + 'provider': 'insight', + 'url': 'http://localhost:3001' + }); + }); + it('testnet local insight', function() { + var options = { + node: { + network: bitcore.Networks.testnet, + port: 3001 + } + }; + var service = new Service(options); + var config = service._getConfiguration(); + config.blockchainExplorerOpts.testnet.should.deep.equal({ + 'apiPrefix': '/insight-api', + 'provider': 'insight', + 'url': 'http://localhost:3001' + }); + }); + }); + describe('#_startWalletService', function() { + it('will start express and web socket servers', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + if (err) { + throw err; + } + TestExpressApp.prototype.start.callCount.should.equal(1); + TestExpressApp.prototype.start.args[0][0].should.equal(config); + TestExpressApp.prototype.start.args[0][1].should.be.a('function'); + TestWSApp.prototype.start.callCount.should.equal(1); + TestWSApp.prototype.start.args[0][0].should.equal(service.server); + TestWSApp.prototype.start.args[0][1].should.equal(config); + TestWSApp.prototype.start.args[0][2].should.be.a('function'); + listen.callCount.should.equal(1); + listen.args[0][0].should.equal(3232); + listen.args[0][1].should.be.a('function'); + done(); + }); + }); + it('error from express', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from web socket', function(done) { + function TestExpressApp() {} + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArgWith(2, new Error('test')); + var listen = sinon.stub().callsArg(1); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: sinon.stub().returns({ + listen: listen + }) + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from server.listen', function(done) { + var app = {}; + function TestExpressApp() { + this.app = app; + } + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArgWith(1, new Error('test')); + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'http': { + Server: function() { + arguments[0].should.equal(app); + return { + listen: listen + }; + } + } + }); + var options = { + node: { + bwsPort: 3232 + } + }; + var service = new TestService(options); + var config = {}; + service._startWalletService(config, function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('will enable https', function(done) { + var app = {}; + function TestExpressApp() { + this.app = app; + } + TestExpressApp.prototype.start = sinon.stub().callsArg(1); + function TestWSApp() {} + TestWSApp.prototype.start = sinon.stub().callsArg(2); + var listen = sinon.stub().callsArg(1); + var httpsOptions = {}; + var createServer = function() { + arguments[0].should.equal(httpsOptions); + arguments[1].should.equal(app); + return { + listen: listen + }; + }; + var TestService = proxyquire('../bitcorenode', { + '../lib/expressapp': TestExpressApp, + '../lib/wsapp': TestWSApp, + 'https': { + createServer: createServer + } + }); + var options = { + node: { + https: true, + bwsPort: 3232 + } + }; + var service = new TestService(options); + service._readHttpsOptions = sinon.stub().returns(httpsOptions); + var config = {}; + service._startWalletService(config, function(err) { + service._readHttpsOptions.callCount.should.equal(1); + listen.callCount.should.equal(1); + done(); + }); + }); + }); + describe('#start', function(done) { + it('error from configuration', function(done) { + var options = { + node: {} + }; + var service = new Service(options); + service._getConfiguration = function() { + throw new Error('test'); + }; + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from blockchain monitor', function(done) { + var app = {}; + function TestBlockchainMonitor() {} + TestBlockchainMonitor.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + function TestLocker() {} + TestLocker.prototype.listen = sinon.stub(); + function TestEmailService() {} + TestEmailService.prototype.start = sinon.stub(); + var TestService = proxyquire('../bitcorenode', { + '../lib/blockchainmonitor': TestBlockchainMonitor, + '../lib/emailservice': TestEmailService, + 'socket.io': sinon.stub().returns({ + on: sinon.stub() + }), + 'locker-server': TestLocker, + }); + var options = { + node: {} + }; + var service = new TestService(options); + var config = {}; + service._getConfiguration = sinon.stub().returns(config); + service._startWalletService = sinon.stub().callsArg(1); + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + it('error from email service', function(done) { + var app = {}; + function TestBlockchainMonitor() {} + TestBlockchainMonitor.prototype.start = sinon.stub().callsArg(1); + function TestLocker() {} + TestLocker.prototype.listen = sinon.stub(); + function TestEmailService() {} + TestEmailService.prototype.start = sinon.stub().callsArgWith(1, new Error('test')); + var TestService = proxyquire('../bitcorenode', { + '../lib/blockchainmonitor': TestBlockchainMonitor, + '../lib/emailservice': TestEmailService, + 'socket.io': sinon.stub().returns({ + on: sinon.stub() + }), + 'locker-server': TestLocker, + }); + var options = { + node: {} + }; + var service = new TestService(options); + service._getConfiguration = sinon.stub().returns({ + emailOpts: {} + }); + var config = {}; + service._startWalletService = sinon.stub().callsArg(1); + service.start(function(err) { + err.message.should.equal('test'); + done(); + }); + }); + }); +}); From 0b6ceeaceb83c9760e373c158debd46b5a1c727e Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 6 Oct 2015 11:23:46 -0400 Subject: [PATCH 4/5] Update package.json versions. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 5fe62ba..e221c28 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,6 @@ "locker": "^0.1.0", "locker-server": "^0.1.3", "lodash": "^3.10.1", - "mkdirp": "^0.5.1", "mocha-lcov-reporter": "0.0.1", "moment": "^2.10.3", "mongodb": "^2.0.27", From e476e86b7d37187534b68e28ee002feac4ea14e2 Mon Sep 17 00:00:00 2001 From: Braydon Fuller Date: Tue, 6 Oct 2015 12:55:02 -0400 Subject: [PATCH 5/5] Use defaults from config.js --- bitcorenode/index.js | 13 ++++++------- test/bitcorenode.js | 12 ++++++------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/bitcorenode/index.js b/bitcorenode/index.js index 5747c6f..ee7c85d 100644 --- a/bitcorenode/index.js +++ b/bitcorenode/index.js @@ -34,17 +34,16 @@ var Service = function(options) { this.node = options.node; this.https = options.https || this.node.https; this.httpsOptions = options.httpsOptions || this.node.httpsOptions; - this.bwsPort = options.bwsPort || Service.BWS_PORT; - this.messageBrokerPort = options.messageBrokerPort || Service.MESSAGE_BROKER_PORT; - this.lockerPort = options.lockerPort || Service.LOCKER_PORT; + this.bwsPort = options.bwsPort || baseConfig.port; + this.messageBrokerPort = options.messageBrokerPort || 3380; + if (baseConfig.lockOpts) { + this.lockerPort = baseConfig.lockOpts.lockerServer.port; + } + this.lockerPort = options.lockerPort || this.lockerPort; }; util.inherits(Service, EventEmitter); -Service.BWS_PORT = 3232; -Service.MESSAGE_BROKER_PORT = 3380; -Service.LOCKER_PORT = 3231; - Service.dependencies = ['insight-api']; /** diff --git a/test/bitcorenode.js b/test/bitcorenode.js index 4bff6cb..987809c 100644 --- a/test/bitcorenode.js +++ b/test/bitcorenode.js @@ -26,9 +26,9 @@ describe('Bitcore Node Service', function() { key: 'key', cert: 'cert' }); - service.bwsPort.should.equal(Service.BWS_PORT); - service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); - service.lockerPort.should.equal(Service.LOCKER_PORT); + service.bwsPort.should.equal(3232); + service.messageBrokerPort.should.equal(3380); + service.lockerPort.should.equal(3231); }); it('direct https options', function() { var node = {}; @@ -46,9 +46,9 @@ describe('Bitcore Node Service', function() { key: 'key', cert: 'cert' }); - service.bwsPort.should.equal(Service.BWS_PORT); - service.messageBrokerPort.should.equal(Service.MESSAGE_BROKER_PORT); - service.lockerPort.should.equal(Service.LOCKER_PORT); + service.bwsPort.should.equal(3232); + service.messageBrokerPort.should.equal(3380); + service.lockerPort.should.equal(3231); }); it('can set custom ports', function() { var node = {};