From 781f2c44b7cfaadc9bc81a36e5a6e61e10588d0f Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 12:32:29 -0300 Subject: [PATCH 1/8] rearrange static methods --- lib/server.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/server.js b/lib/server.js index 8a88323..0839dd4 100644 --- a/lib/server.js +++ b/lib/server.js @@ -52,12 +52,6 @@ function WalletService() { this.notifyTicker = 0; }; -WalletService.getServiceVersion = function() { - if (!serviceVersion) - serviceVersion = 'bws-' + require('../package').version; - return serviceVersion; -}; - // Time after which a Tx proposal can be erased by any copayer. in seconds WalletService.DELETE_LOCKTIME = 24 * 3600; @@ -89,6 +83,15 @@ WalletService.FEE_LEVELS = [{ }]; +/** + * Gets the current version of BWS + */ +WalletService.getServiceVersion = function() { + if (!serviceVersion) + serviceVersion = 'bws-' + require('../package').version; + return serviceVersion; +}; + /** * Initializes global settings for all instances. * @param {Object} opts From bee66435e90358e98e61b2083f69f6cdc9159044 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 13:09:33 -0300 Subject: [PATCH 2/8] store new block notifications with walletId=network --- lib/blockchainmonitor.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js index 8e8a551..c0d6336 100644 --- a/lib/blockchainmonitor.js +++ b/lib/blockchainmonitor.js @@ -83,7 +83,7 @@ BlockchainMonitor.prototype._initExplorer = function(explorer) { log.error('Error connecting to ' + explorer.getConnectionInfo()); }); socket.on('tx', _.bind(self._handleIncommingTx, self)); - socket.on('block', _.bind(self._handleNewBlock, self)); + socket.on('block', _.bind(self._handleNewBlock, self, explorer.network)); }; BlockchainMonitor.prototype._handleTxId = function(data, processIt) { @@ -177,12 +177,13 @@ BlockchainMonitor.prototype._handleIncommingTx = function(data) { this._handleTxOuts(data); }; -BlockchainMonitor.prototype._handleNewBlock = function(hash) { +BlockchainMonitor.prototype._handleNewBlock = function(network, hash) { var self = this; - log.info('New block: ', hash); + log.info('New ' + network + ' block: ', hash); var notification = Notification.create({ type: 'NewBlock', + walletId: network, // use network name as wallet id for global notifications data: { hash: hash, }, From 92944d1d408db7fe023da52e7d3bbc25ffaf1b2f Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 14:27:29 -0300 Subject: [PATCH 3/8] remove old fetchNotifications method --- lib/storage.js | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/lib/storage.js b/lib/storage.js index 73e4941..983737f 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -277,44 +277,6 @@ Storage.prototype.fetchTxs = function(walletId, opts, cb) { }; -/** - * fetchNotifications - * - * @param walletId - * @param opts.minTs - * @param opts.maxTs - * @param opts.limit - * @param opts.reverse - */ -Storage.prototype.fetchNotifications = function(walletId, opts, cb) { - var self = this; - - opts = opts || {}; - - var tsFilter = {}; - if (_.isNumber(opts.minTs)) tsFilter.$gte = opts.minTs; - if (_.isNumber(opts.maxTs)) tsFilter.$lte = opts.maxTs; - - var filter = { - walletId: walletId - }; - if (!_.isEmpty(tsFilter)) filter.createdOn = tsFilter; - - var mods = {}; - if (_.isNumber(opts.limit)) mods.limit = opts.limit; - - this.db.collection(collections.NOTIFICATIONS).find(filter, mods).sort({ - id: opts.reverse ? -1 : 1, - }).toArray(function(err, result) { - if (err) return cb(err); - if (!result) return cb(); - var notifications = _.map(result, function(notification) { - return Model.Notification.fromObj(notification); - }); - return cb(null, notifications); - }); -}; - /** * Retrieves notifications after a specific id or from a given ts (whichever is more recent). * From 5c048e390c90bfc8b2b12c3cda2e422478c18035 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 14:28:43 -0300 Subject: [PATCH 4/8] fetch new block notifications along with regular wallet notifications --- lib/server.js | 16 ++++++++++++++-- test/integration/server.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index 0839dd4..e5007e3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1660,9 +1660,21 @@ WalletService.prototype.getNotifications = function(opts, cb) { var self = this; opts = opts || {}; - self.storage.fetchNotifications(self.walletId, opts.notificationId, opts.minTs || 0, function(err, notifications) { + self.getWallet({}, function(err, wallet) { if (err) return cb(err); - return cb(null, notifications); + + async.map([wallet.network, self.walletId], function(walletId, next) { + self.storage.fetchNotifications(walletId, opts.notificationId, opts.minTs || 0, next); + }, function(err, res) { + if (err) return cb(err); + + var notifications = _.sortBy(_.map(_.flatten(res), function(n) { + n.walletId = self.walletId; + return n; + }), 'id'); + + return cb(null, notifications); + }); }); }; diff --git a/test/integration/server.js b/test/integration/server.js index de82df0..2d48500 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -3923,6 +3923,38 @@ describe('Wallet service', function() { }); }); + it('should pull new block notifications along with wallet notifications in the last 60 seconds', function(done) { + // Simulate new block notification + server.walletId = 'livenet'; + server._notify('NewBlock', { + hash: 'dummy hash', + }, { + isGlobal: true + }, function(err) { + should.not.exist(err); + server.walletId = 'testnet'; + server._notify('NewBlock', { + hash: 'dummy hash', + }, { + isGlobal: true + }, function(err) { + should.not.exist(err); + server.walletId = wallet.id; + server.getNotifications({ + minTs: +Date.now() - (60 * 1000), + }, function(err, notifications) { + should.not.exist(err); + var types = _.pluck(notifications, 'type'); + types.should.deep.equal(['NewTxProposal', 'NewTxProposal', 'NewBlock']); + var walletIds = _.uniq(_.pluck(notifications, 'walletId')); + walletIds.length.should.equal(1); + walletIds[0].should.equal(wallet.id); + done(); + }); + }); + }); + }); + it('should pull notifications in the last 60 seconds', function(done) { server.getNotifications({ minTs: +Date.now() - (60 * 1000), @@ -4134,7 +4166,7 @@ describe('Wallet service', function() { }); }, function(next) { - server.getNotifications({}, function(err, items) { + server.storage.fetchNotifications(wallet.id, null, 0, function(err, items) { items.length.should.equal(0); next(); }); From 09c5af073dc30dfccdda352525fcea83b3d5fcdb Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 14:58:38 -0300 Subject: [PATCH 5/8] accept minTs arg on v1/notifications but limit it to now - 60s --- lib/expressapp.js | 2 +- test/expressapp.js | 84 ++++++++++++++++++++++++++++++---------------- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index df8268d..f67109e 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -440,7 +440,7 @@ ExpressApp.prototype.start = function(opts, cb) { router.get('/v1/notifications/', function(req, res) { getServerWithAuth(req, res, function(server) { var opts = { - minTs: +Date.now() - (60 * 1000), + minTs: Math.max(+req.query.minTs, +Date.now() - (60 * 1000)), notificationId: req.query.notificationId, }; server.getNotifications(opts, function(err, notifications) { diff --git a/test/expressapp.js b/test/expressapp.js index cbf8671..02cb1ae 100644 --- a/test/expressapp.js +++ b/test/expressapp.js @@ -84,37 +84,65 @@ describe('ExpressApp', function() { }); }); - it('/v1/notifications', function(done) { - var clock = sinon.useFakeTimers(1234000, 'Date'); + describe('/v1/notifications', function(done) { + var server, TestExpressApp, clock; + beforeEach(function() { + clock = sinon.useFakeTimers(1234000, 'Date'); - var server = { - getNotifications: sinon.stub().callsArgWith(1, null, {}) - }; - var TestExpressApp = proxyquire('../lib/expressapp', { - './server': { - initialize: sinon.stub().callsArg(1), - getInstanceWithAuth: sinon.stub().callsArgWith(1, null, server), - } - }); - start(TestExpressApp, function() { - var requestOptions = { - url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123&minTs=0', - headers: { - 'x-identity': 'identity', - 'x-signature': 'signature' - } + server = { + getNotifications: sinon.stub().callsArgWith(1, null, {}) }; - request(requestOptions, function(err, res, body) { - should.not.exist(err); - res.statusCode.should.equal(200); - body.should.equal('{}'); - server.getNotifications.calledWith({ - notificationId: '123', - minTs: 1234000 - 60000, // override minTs argument with a hardcoded 60 seconds span - }).should.be.true; + TestExpressApp = proxyquire('../lib/expressapp', { + './server': { + initialize: sinon.stub().callsArg(1), + getInstanceWithAuth: sinon.stub().callsArgWith(1, null, server), + } + }); + }); + afterEach(function() { + clock.restore(); + }); - clock.restore(); - done(); + it('should fetch notifications from a specific id', function(done) { + start(TestExpressApp, function() { + var requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123&minTs=' + (Date.now() - 30000), + headers: { + 'x-identity': 'identity', + 'x-signature': 'signature' + } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.should.equal('{}'); + server.getNotifications.calledWith({ + notificationId: '123', + minTs: +Date.now() - 30000, + }).should.be.true; + done(); + }); + }); + }); + it('should limit minTs to 60 seconds', function(done) { + start(TestExpressApp, function() { + var requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=1', + headers: { + 'x-identity': 'identity', + 'x-signature': 'signature' + } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + body.should.equal('{}'); + server.getNotifications.calledWith({ + notificationId: undefined, + minTs: Date.now() - 60000, // override minTs argument with a hardcoded 60 seconds span + }).should.be.true; + done(); + }); }); }); }); From d6f9633a733ce31fa548f82c1e40fff7aa3acbfe Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 15:03:49 -0300 Subject: [PATCH 6/8] improve tests --- lib/expressapp.js | 2 +- test/expressapp.js | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index f67109e..142a23f 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -440,7 +440,7 @@ ExpressApp.prototype.start = function(opts, cb) { router.get('/v1/notifications/', function(req, res) { getServerWithAuth(req, res, function(server) { var opts = { - minTs: Math.max(+req.query.minTs, +Date.now() - (60 * 1000)), + minTs: Math.max(+req.query.minTs || 0, +Date.now() - (60 * 1000)), notificationId: req.query.notificationId, }; server.getNotifications(opts, function(err, notifications) { diff --git a/test/expressapp.js b/test/expressapp.js index 02cb1ae..faedbb9 100644 --- a/test/expressapp.js +++ b/test/expressapp.js @@ -103,10 +103,10 @@ describe('ExpressApp', function() { clock.restore(); }); - it('should fetch notifications from a specific id', function(done) { + it('should fetch notifications from a specified id', function(done) { start(TestExpressApp, function() { var requestOptions = { - url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123&minTs=' + (Date.now() - 30000), + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?notificationId=123', headers: { 'x-identity': 'identity', 'x-signature': 'signature' @@ -118,6 +118,26 @@ describe('ExpressApp', function() { body.should.equal('{}'); server.getNotifications.calledWith({ notificationId: '123', + minTs: +Date.now() - 60000, + }).should.be.true; + done(); + }); + }); + }); + it('should allow custom minTs within limits', function(done) { + start(TestExpressApp, function() { + var requestOptions = { + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=' + (Date.now() - 30000), + headers: { + 'x-identity': 'identity', + 'x-signature': 'signature' + } + }; + request(requestOptions, function(err, res, body) { + should.not.exist(err); + res.statusCode.should.equal(200); + server.getNotifications.calledWith({ + notificationId: undefined, minTs: +Date.now() - 30000, }).should.be.true; done(); @@ -127,7 +147,7 @@ describe('ExpressApp', function() { it('should limit minTs to 60 seconds', function(done) { start(TestExpressApp, function() { var requestOptions = { - url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=1', + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=' + (Date.now() - 90000), headers: { 'x-identity': 'identity', 'x-signature': 'signature' From e7855e1e8944d4034d1ad34556a1fc0bec9a0628 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 17:23:50 -0300 Subject: [PATCH 7/8] replace minTs with timeSpan (in secs) in /v1/notifications --- lib/expressapp.js | 3 ++- test/expressapp.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/expressapp.js b/lib/expressapp.js index 142a23f..aafceda 100644 --- a/lib/expressapp.js +++ b/lib/expressapp.js @@ -439,8 +439,9 @@ ExpressApp.prototype.start = function(opts, cb) { router.get('/v1/notifications/', function(req, res) { getServerWithAuth(req, res, function(server) { + var timeSpan = req.query.timeSpan ? Math.min(+req.query.timeSpan || 0, 60) : 60; var opts = { - minTs: Math.max(+req.query.minTs || 0, +Date.now() - (60 * 1000)), + minTs: +Date.now() - (timeSpan * 1000), notificationId: req.query.notificationId, }; server.getNotifications(opts, function(err, notifications) { diff --git a/test/expressapp.js b/test/expressapp.js index faedbb9..7b73d2d 100644 --- a/test/expressapp.js +++ b/test/expressapp.js @@ -127,7 +127,7 @@ describe('ExpressApp', function() { it('should allow custom minTs within limits', function(done) { start(TestExpressApp, function() { var requestOptions = { - url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=' + (Date.now() - 30000), + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?timeSpan=30', headers: { 'x-identity': 'identity', 'x-signature': 'signature' @@ -147,7 +147,7 @@ describe('ExpressApp', function() { it('should limit minTs to 60 seconds', function(done) { start(TestExpressApp, function() { var requestOptions = { - url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?minTs=' + (Date.now() - 90000), + url: testHost + ':' + testPort + config.basePath + '/v1/notifications' + '?timeSpan=90', headers: { 'x-identity': 'identity', 'x-signature': 'signature' From e6207c2616b72429be77257e55735aefa5bbeed6 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 19 Oct 2015 17:24:25 -0300 Subject: [PATCH 8/8] v1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31b963b..d20dea0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bitcore-wallet-service", "description": "A service for Mutisig HD Bitcoin Wallets", "author": "BitPay Inc", - "version": "1.0.0", + "version": "1.1.0", "keywords": [ "bitcoin", "copay",