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); + }); + }); + }); + +});