From 7690b594a44d466a54394b8ad53e0ddbb9848d0f Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 10:43:15 -0300 Subject: [PATCH 01/14] add dep socket.io-client --- package.json | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6313f7d..99e10c1 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "async": "^0.9.0", - "bitcore-wallet-utils": "^0.0.7", "bitcore-explorers": "^0.9.1", + "bitcore-wallet-utils": "^0.0.7", "body-parser": "^1.11.0", "coveralls": "^2.11.2", "express": "^4.10.0", @@ -36,6 +36,7 @@ "request": "^2.53.0", "sjcl": "^1.0.2", "socket.io": "^1.3.5", + "socket.io-client": "^1.3.5", "uuid": "*" }, "devDependencies": { @@ -53,11 +54,14 @@ "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" - }] + "contributors": [ + { + "name": "Ivan Socolsky", + "email": "ivan@bitpay.com" + }, + { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + } + ] } From 3fe2aaa8282b56e188710cb17345d5aff4d1b5ae Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 12:40:58 -0300 Subject: [PATCH 02/14] subscribe addresses --- lib/wsapp.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/wsapp.js b/lib/wsapp.js index 8196e03..597fb8d 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -23,16 +23,38 @@ WsApp._unauthorized = function() { socket.disconnect(); }; +WsApp.subscribeAddresses = function(walletId, addresses) { + console.log('*** [wsapp.js ln27] subscribing:', addresses, walletId); // TODO + + _.each([].concat(addresses), function(address) { + subscriptions[address] = walletId; + }); +}; + +WsApp.subscribeWallet = function(serviceInstance) { + // TODO: optimize! + serviceInstance.getMainAddresses({}, function(err, addresses) { + if (err) { + log.warn('Could not subscribe to addresses for wallet ' + serviceInstance.walletId); + return; + } + WsApp.subscribeAddress(_.pluck(addresses, 'address'), serviceInstance.walletId); + }); +}; + WsApp.start = function(server) { var self = this; var io = require('socket.io')(server); WalletService.onNotification(function(serviceInstance, args) { - var room = serviceInstance.walletId || args.walletId; - if (room) { - io.to(room).emit('notification', args); + var walletId = serviceInstance.walletId || args.walletId; + if (!walletId) return; + + if (args.type == 'NewAddress') { + WsApp.subscribeAddress(walletId, args.address); } + io.to(walletId).emit('notification', args); }); io.on('connection', function(socket) { @@ -47,6 +69,8 @@ WsApp.start = function(server) { socket.join(res.walletId); socket.emit('authorized'); + + WsApp.subscribeWallet(res); }); }); }); From afb96bf01e35afb53b3e62b657b468c69973c06a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 12:43:43 -0300 Subject: [PATCH 03/14] test new address notification --- test/integration/server.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/integration/server.js b/test/integration/server.js index b3d1c30..6708669 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -601,14 +601,23 @@ describe('Copay server', function() { }); }); - it('should create address', function(done) { + it.only('should create address', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; address.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); address.isChange.should.be.false; address.path.should.equal('m/2147483647/0/0'); - done(); + server.getNotifications({}, function(err, notifications) { + should.not.exist(err); + var notif = _.find(notifications, { + type: 'NewAddress' + }); + should.exist(notif); + notif.length.should.equal(1); + notif[0].data.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); + done(); + }); }); }); From af98e5af9686627c75d2c31faf67c4b631d69436 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 12:45:43 -0300 Subject: [PATCH 04/14] add address data to notification --- lib/server.js | 4 +++- test/integration/server.js | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/server.js b/lib/server.js index 4fcc19a..5be41c2 100644 --- a/lib/server.js +++ b/lib/server.js @@ -284,7 +284,9 @@ WalletService.prototype.createAddress = function(opts, cb) { self.storage.storeAddressAndWallet(wallet, address, function(err) { if (err) return cb(err); - self._notify('NewAddress'); + self._notify('NewAddress', { + address: address.address, + }); return cb(null, address); }); }); diff --git a/test/integration/server.js b/test/integration/server.js index 6708669..b06df71 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -601,7 +601,7 @@ describe('Copay server', function() { }); }); - it.only('should create address', function(done) { + it('should create address', function(done) { server.createAddress({}, function(err, address) { should.not.exist(err); address.should.exist; @@ -614,8 +614,7 @@ describe('Copay server', function() { type: 'NewAddress' }); should.exist(notif); - notif.length.should.equal(1); - notif[0].data.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); + notif.data.address.should.equal('3KxttbKQQPWmpsnXZ3rB4mgJTuLnVR7frg'); done(); }); }); From 2075357d16867ca79dc56c605eb8534665470e78 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 15:34:05 -0300 Subject: [PATCH 05/14] extract block explorer --- lib/blockexplorer.js | 54 ++++++++++++++++++++++++++++++++++++++++++++ lib/server.js | 42 ++++++---------------------------- 2 files changed, 61 insertions(+), 35 deletions(-) create mode 100644 lib/blockexplorer.js diff --git a/lib/blockexplorer.js b/lib/blockexplorer.js new file mode 100644 index 0000000..e163632 --- /dev/null +++ b/lib/blockexplorer.js @@ -0,0 +1,54 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('preconditions').singleton(); +var log = require('npmlog'); +log.debug = log.verbose; + +var Explorers = require('bitcore-explorers'); +var request = require('request'); + + +function BlockExplorer(opts) { + $.checkArgument(opts); + var provider = opts.provider || 'insight'; + var network = opts.network || 'livenet'; + + var url; + + switch (provider) { + case 'insight': + switch (network) { + default: + case 'livenet': + url = 'https://insight.bitpay.com:443'; + break; + case 'testnet': + url = 'https://test-insight.bitpay.com:443' + break; + } + var bc = new Explorers.Insight(url, network); + bc.getTransactions = _.bind(getTransactionsInsight, bc, url); + this.blockExplorer = bc; + break; + default: + throw new Error('Provider ' + provider + ' not supported'); + break; + }; +}; + + +function getTransactionsInsight(url, addresses, cb) { + request({ + method: "POST", + url: url + '/api/addrs/txs', + json: { + addrs: [].concat(addresses).join(',') + } + }, function(err, res, body) { + if (err || res.statusCode != 200) return cb(err || res); + return cb(null, body); + }); +}; + +module.exports = BlockExplorer; diff --git a/lib/server.js b/lib/server.js index 5be41c2..2c307b8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -10,12 +10,12 @@ var Bitcore = WalletUtils.Bitcore; var PublicKey = Bitcore.PublicKey; var HDPublicKey = Bitcore.HDPublicKey; var Address = Bitcore.Address; -var Explorers = require('bitcore-explorers'); var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); var EventBroadcaster = require('./eventbroadcaster'); +var BlockExplorer = require('./blockexplorer'); var Wallet = require('./model/wallet'); var Copayer = require('./model/copayer'); @@ -336,42 +336,14 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { WalletService.prototype._getBlockExplorer = function(provider, network) { - var url; - - function getTransactionsInsight(url, addresses, cb) { - var request = require('request'); - request({ - method: "POST", - url: url + '/api/addrs/txs', - json: { - addrs: [].concat(addresses).join(',') - } - }, function(err, res, body) { - if (err || res.statusCode != 200) return cb(err || res); - return cb(null, body); + if (!this.blockExplorer) { + this.blockexplorer = new BlockExplorer({ + provider: provider, + network: network, }); - }; - - if (this.blockExplorer) - return this.blockExplorer; - - switch (provider) { - default: ; - case 'insight': - switch (network) { - default: - case 'livenet': - url = 'https://insight.bitpay.com:443'; - break; - case 'testnet': - url = 'https://test-insight.bitpay.com:443' - break; - } - var bc = new Explorers.Insight(url, network); - bc.getTransactions = _.bind(getTransactionsInsight, bc, url); - return bc; - break; } + + return this.blockExplorer; }; /** From ff29691cd28a420484fabd26df31f9622c214a52 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 16:57:55 -0300 Subject: [PATCH 06/14] add socket connection to block explorer --- lib/blockexplorer.js | 10 ++++- lib/model/notification.js | 4 +- lib/server.js | 2 +- lib/wsapp.js | 78 ++++++++++++++++++++++++++++++--------- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/lib/blockexplorer.js b/lib/blockexplorer.js index e163632..5aafea0 100644 --- a/lib/blockexplorer.js +++ b/lib/blockexplorer.js @@ -7,6 +7,7 @@ log.debug = log.verbose; var Explorers = require('bitcore-explorers'); var request = require('request'); +var io = require('socket.io-client'); function BlockExplorer(opts) { @@ -15,7 +16,6 @@ function BlockExplorer(opts) { var network = opts.network || 'livenet'; var url; - switch (provider) { case 'insight': switch (network) { @@ -29,7 +29,8 @@ function BlockExplorer(opts) { } var bc = new Explorers.Insight(url, network); bc.getTransactions = _.bind(getTransactionsInsight, bc, url); - this.blockExplorer = bc; + bc.initSocket = _.bind(initSocketInsight, bc, url); + return bc; break; default: throw new Error('Provider ' + provider + ' not supported'); @@ -51,4 +52,9 @@ function getTransactionsInsight(url, addresses, cb) { }); }; +function initSocketInsight(url) { + var socket = io.connect(url, {}); + return socket; +}; + module.exports = BlockExplorer; diff --git a/lib/model/notification.js b/lib/model/notification.js index 4e45c0f..3ea768b 100644 --- a/lib/model/notification.js +++ b/lib/model/notification.js @@ -12,8 +12,8 @@ var Uuid = require('uuid'); * txProposalFinallyRejected - txProposalId * txProposalFinallyAccepted - txProposalId * - * newIncommingTx (amount) - * newOutgoingTx - (txProposalId, txid) + * NewIncommingTx (address, txid) + * NewOutgoingTx - (txProposalId, txid) * * data Examples: * { amount: 'xxx', address: 'xxx'} diff --git a/lib/server.js b/lib/server.js index 2c307b8..30fd40c 100644 --- a/lib/server.js +++ b/lib/server.js @@ -337,7 +337,7 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { WalletService.prototype._getBlockExplorer = function(provider, network) { if (!this.blockExplorer) { - this.blockexplorer = new BlockExplorer({ + this.blockExplorer = new BlockExplorer({ provider: provider, network: network, }); diff --git a/lib/wsapp.js b/lib/wsapp.js index 597fb8d..77d6d11 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -9,12 +9,22 @@ var querystring = require('querystring'); var bodyParser = require('body-parser') var Uuid = require('uuid'); +var WalletUtils = require('bitcore-wallet-utils'); +var Bitcore = WalletUtils.Bitcore; var WalletService = require('./server'); +var BlockExplorer = require('./blockexplorer'); + +var Notification = require('./model/notification'); log.debug = log.verbose; log.level = 'debug'; -var subscriptions = {}; +var io; +var blockExplorerSockets = {}; +var subscriptions = { + wallets: {}, + addresses: {}, +}; var WsApp = function() {}; @@ -24,37 +34,71 @@ WsApp._unauthorized = function() { }; WsApp.subscribeAddresses = function(walletId, addresses) { - console.log('*** [wsapp.js ln27] subscribing:', addresses, walletId); // TODO + if (!addresses || addresses.length == 0) return; - _.each([].concat(addresses), function(address) { + function handlerFor(walletId, address, txid) { + var notification = Notification.create({ + walletId: walletId, + type: 'NewIncommingTx', + data: { + address: address, + txid: txid, + }, + }); + WsApp._sendNotification(notification); + }; + + var addresses = [].concat(addresses); + var network = Bitcore.Address.fromString(addresses[0]).network; + var socket = blockExplorerSockets[network]; + _.each(addresses, function(address) { subscriptions[address] = walletId; + socket.emit('subscribe', address); + socket.on(address, _.bind(handlerFor, null, walletId, address)); }); }; WsApp.subscribeWallet = function(serviceInstance) { - // TODO: optimize! + var walletId = serviceInstance.walletId; + if (subscriptions.wallets[walletId]) return; + + subscriptions.wallets[walletId] = true; serviceInstance.getMainAddresses({}, function(err, addresses) { if (err) { + delete subscriptions.wallets[walletId]; log.warn('Could not subscribe to addresses for wallet ' + serviceInstance.walletId); return; } - WsApp.subscribeAddress(_.pluck(addresses, 'address'), serviceInstance.walletId); + WsApp.subscribeAddresses(serviceInstance.walletId, _.pluck(addresses, 'address')); }); }; +WsApp._sendNotification = function(notification) { + if (notification.type == 'NewAddress') { + WsApp.subscribeAddresses(notification.walletId, notification.data.address); + } + io.to(notification.walletId).emit('notification', notification); +}; + +WsApp._initBlockExplorerSocket = function(provider, network) { + var explorer = new BlockExplorer({ + provider: provider, + network: network, + }); + + blockExplorerSockets[network] = explorer.initSocket(); +}; + WsApp.start = function(server) { - var self = this; + io = require('socket.io')(server); - var io = require('socket.io')(server); + WsApp._initBlockExplorerSocket('insight', 'testnet'); + WsApp._initBlockExplorerSocket('insight', 'livenet'); - WalletService.onNotification(function(serviceInstance, args) { - var walletId = serviceInstance.walletId || args.walletId; - if (!walletId) return; + WalletService.onNotification(function(serviceInstance, notification) { + if (!notification.walletId) return; - if (args.type == 'NewAddress') { - WsApp.subscribeAddress(walletId, args.address); - } - io.to(walletId).emit('notification', args); + WsApp._sendNotification(notification); }); io.on('connection', function(socket) { @@ -64,13 +108,13 @@ WsApp.start = function(server) { socket.on('authorize', function(data) { if (data.message != socket.nonce) return WsApp.unauthorized(); - WalletService.getInstanceWithAuth(data, function(err, res) { + WalletService.getInstanceWithAuth(data, function(err, service) { if (err) return WsApp.unauthorized(); - socket.join(res.walletId); + socket.join(service.walletId); socket.emit('authorized'); - WsApp.subscribeWallet(res); + WsApp.subscribeWallet(service); }); }); }); From bd4a018b8fe4fbcb1bec07edc5b594787e88f589 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 16:59:20 -0300 Subject: [PATCH 07/14] v0.0.17 --- package.json | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 99e10c1..93d5b1d 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": "0.0.16", + "version": "0.0.17", "keywords": [ "bitcoin", "copay", @@ -54,14 +54,11 @@ "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" - } - ] + "contributors": [{ + "name": "Ivan Socolsky", + "email": "ivan@bitpay.com" + }, { + "name": "Matias Alejo Garcia", + "email": "ematiu@gmail.com" + }] } From 26d4d5c434de804610d20aec7926c569d72d1f43 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 17:22:45 -0300 Subject: [PATCH 08/14] fix typo --- README.md | 4 ++-- lib/model/notification.js | 2 +- lib/wsapp.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 219048b..8dfb666 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Optional Arguments: * limit: Total number of records to return (return all available records if not specified). Returns: - * History of incomming and outgoing transactions of the wallet. The list is paginated using the `skip` & `limit` params. Each item has the following fields: + * History of incoming and outgoing transactions of the wallet. The list is paginated using the `skip` & `limit` params. Each item has the following fields: * action ('sent', 'received', 'moved') * amount * fees @@ -72,7 +72,7 @@ Returns: `/v1/addresses/`: Get Wallet's main addresses (does not include change addresses) Returns: - * List of Addresses object: (https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/adddress.js)). This call is mainly provided so the client check this addresses for incomming transactions (using a service like [Insight](https://insight.is) + * List of Addresses object: (https://github.com/bitpay/bitcore-wallet-service/blob/master/lib/model/adddress.js)). This call is mainly provided so the client check this addresses for incoming transactions (using a service like [Insight](https://insight.is) `/v1/balance/`: Get Wallet's balance diff --git a/lib/model/notification.js b/lib/model/notification.js index 3ea768b..143aced 100644 --- a/lib/model/notification.js +++ b/lib/model/notification.js @@ -12,7 +12,7 @@ var Uuid = require('uuid'); * txProposalFinallyRejected - txProposalId * txProposalFinallyAccepted - txProposalId * - * NewIncommingTx (address, txid) + * NewIncomingTx (address, txid) * NewOutgoingTx - (txProposalId, txid) * * data Examples: diff --git a/lib/wsapp.js b/lib/wsapp.js index 77d6d11..a4d69e3 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -39,7 +39,7 @@ WsApp.subscribeAddresses = function(walletId, addresses) { function handlerFor(walletId, address, txid) { var notification = Notification.create({ walletId: walletId, - type: 'NewIncommingTx', + type: 'NewIncomingTx', data: { address: address, txid: txid, From b408f8108abc5b92afd8caa543a7ebc63b51cf32 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 18:37:34 -0300 Subject: [PATCH 09/14] refactor subscription data structure --- lib/wsapp.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/wsapp.js b/lib/wsapp.js index a4d69e3..a2544d0 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -21,10 +21,7 @@ log.level = 'debug'; var io; var blockExplorerSockets = {}; -var subscriptions = { - wallets: {}, - addresses: {}, -}; +var subscriptions = {}; var WsApp = function() {}; @@ -34,11 +31,13 @@ WsApp._unauthorized = function() { }; WsApp.subscribeAddresses = function(walletId, addresses) { + $.checkArgument(walletId); + if (!addresses || addresses.length == 0) return; - function handlerFor(walletId, address, txid) { + function handlerFor(address, txid) { var notification = Notification.create({ - walletId: walletId, + walletId: this, type: 'NewIncomingTx', data: { address: address, @@ -48,24 +47,28 @@ WsApp.subscribeAddresses = function(walletId, addresses) { WsApp._sendNotification(notification); }; + if (!subscriptions[walletId]) { + subscriptions[walletId] = { + addresses: [], + }; + }; var addresses = [].concat(addresses); var network = Bitcore.Address.fromString(addresses[0]).network; var socket = blockExplorerSockets[network]; _.each(addresses, function(address) { - subscriptions[address] = walletId; + subscriptions[walletId].addresses.push(address); socket.emit('subscribe', address); - socket.on(address, _.bind(handlerFor, null, walletId, address)); + socket.on(address, _.bind(handlerFor, walletId, address)); }); }; WsApp.subscribeWallet = function(serviceInstance) { var walletId = serviceInstance.walletId; - if (subscriptions.wallets[walletId]) return; + if (subscriptions[walletId]) return; - subscriptions.wallets[walletId] = true; serviceInstance.getMainAddresses({}, function(err, addresses) { if (err) { - delete subscriptions.wallets[walletId]; + delete subscriptions[walletId]; log.warn('Could not subscribe to addresses for wallet ' + serviceInstance.walletId); return; } From f4fb471ace47720f5cbcaa63a7d9d83f9a51ea13 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 19:44:16 -0300 Subject: [PATCH 10/14] refactor code to make it testable --- lib/blockchainmonitor.js | 90 ++++++++++++++++++++++++++++++++++ lib/eventbroadcaster.js | 25 ---------- lib/notificationbroadcaster.js | 25 ++++++++++ lib/server.js | 6 +-- lib/wsapp.js | 87 ++++++-------------------------- 5 files changed, 133 insertions(+), 100 deletions(-) create mode 100644 lib/blockchainmonitor.js delete mode 100644 lib/eventbroadcaster.js create mode 100644 lib/notificationbroadcaster.js diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js new file mode 100644 index 0000000..2b7bc1d --- /dev/null +++ b/lib/blockchainmonitor.js @@ -0,0 +1,90 @@ +'use strict'; + +var $ = require('preconditions').singleton(); +var _ = require('lodash'); +var async = require('async'); +var log = require('npmlog'); +log.debug = log.verbose; +var Uuid = require('uuid'); +var inherits = require('inherits'); +var events = require('events'); +var nodeutil = require('util'); + +var WalletUtils = require('bitcore-wallet-utils'); +var Bitcore = WalletUtils.Bitcore; +var WalletService = require('./server'); +var BlockExplorer = require('./blockexplorer'); + +var Notification = require('./model/notification'); + +function BlockchainMonitor() { + this.subscriptions = {}; + this.sockets = {}; + + this._initBlockExplorerSocket('insight', 'livenet'); + this._initBlockExplorerSocket('insight', 'testnet'); +}; + +nodeutil.inherits(BlockchainMonitor, events.EventEmitter); + +BlockchainMonitor.prototype._initBlockExplorerSocket = function(provider, network) { + var explorer = new BlockExplorer({ + provider: provider, + network: network, + }); + + this.sockets[network] = explorer.initSocket(); +}; + +BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { + $.checkArgument(walletId); + + var self = this; + + if (!addresses || addresses.length == 0) return; + + function handlerFor(address, txid) { + var notification = Notification.create({ + walletId: this, + type: 'NewIncomingTx', + data: { + address: address, + txid: txid, + }, + }); + self.emit('notification', notification); + }; + + if (!self.subscriptions[walletId]) { + self.subscriptions[walletId] = { + addresses: [], + }; + }; + var addresses = [].concat(addresses); + var network = Bitcore.Address.fromString(addresses[0]).network.name; + var socket = self.sockets[network]; + _.each(addresses, function(address) { + self.subscriptions[walletId].addresses.push(address); + socket.emit('subscribe', address); + socket.on(address, _.bind(handlerFor, walletId, address)); + }); +}; + +BlockchainMonitor.prototype.subscribeWallet = function(walletService) { + var self = this; + + var walletId = walletService.walletId; + if (self.subscriptions[walletId]) return; + + walletService.getMainAddresses({}, function(err, addresses) { + if (err) { + delete self.subscriptions[walletId]; + log.warn('Could not subscribe to addresses for wallet ' + walletId); + return; + } + self.subscribeAddresses(walletService.walletId, _.pluck(addresses, 'address')); + }); +}; + + +module.exports = BlockchainMonitor; diff --git a/lib/eventbroadcaster.js b/lib/eventbroadcaster.js deleted file mode 100644 index e8ab506..0000000 --- a/lib/eventbroadcaster.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -var log = require('npmlog'); -log.debug = log.verbose; -var inherits = require('inherits'); -var events = require('events'); -var nodeutil = require('util'); - -function EventBroadcaster() {}; - -nodeutil.inherits(EventBroadcaster, events.EventEmitter); - -EventBroadcaster.prototype.broadcast = function(eventName, serviceInstance, args) { - this.emit(eventName, serviceInstance, args); -}; - -var _eventBroadcasterInstance; -EventBroadcaster.singleton = function() { - if (!_eventBroadcasterInstance) { - _eventBroadcasterInstance = new EventBroadcaster(); - } - return _eventBroadcasterInstance; -}; - -module.exports = EventBroadcaster.singleton(); diff --git a/lib/notificationbroadcaster.js b/lib/notificationbroadcaster.js new file mode 100644 index 0000000..692c43e --- /dev/null +++ b/lib/notificationbroadcaster.js @@ -0,0 +1,25 @@ +'use strict'; + +var log = require('npmlog'); +log.debug = log.verbose; +var inherits = require('inherits'); +var events = require('events'); +var nodeutil = require('util'); + +function NotificationBroadcaster() {}; + +nodeutil.inherits(NotificationBroadcaster, events.EventEmitter); + +NotificationBroadcaster.prototype.broadcast = function(eventName, notification) { + this.emit(eventName, notification); +}; + +var _instance; +NotificationBroadcaster.singleton = function() { + if (!_instance) { + _instance = new NotificationBroadcaster(); + } + return _instance; +}; + +module.exports = NotificationBroadcaster.singleton(); diff --git a/lib/server.js b/lib/server.js index 30fd40c..b5bb64e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,7 +14,7 @@ var Address = Bitcore.Address; var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); -var EventBroadcaster = require('./eventbroadcaster'); +var NotificationBroadcaster = require('./notificationbroadcaster'); var BlockExplorer = require('./blockexplorer'); var Wallet = require('./model/wallet'); @@ -41,7 +41,7 @@ function WalletService() { }; WalletService.onNotification = function(func) { - EventBroadcaster.on('notification', func); + NotificationBroadcaster.on('notification', func); }; /** @@ -167,7 +167,7 @@ WalletService.prototype._verifySignature = function(text, signature, pubKey) { * @param {Object} args */ WalletService.prototype._emit = function(eventName, args) { - EventBroadcaster.broadcast(eventName, this, args); + NotificationBroadcaster.broadcast(eventName, args); }; /** diff --git a/lib/wsapp.js b/lib/wsapp.js index a2544d0..19b3a78 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -4,24 +4,19 @@ var $ = require('preconditions').singleton(); var _ = require('lodash'); var async = require('async'); var log = require('npmlog'); -var express = require('express'); -var querystring = require('querystring'); -var bodyParser = require('body-parser') +log.debug = log.verbose; var Uuid = require('uuid'); var WalletUtils = require('bitcore-wallet-utils'); var Bitcore = WalletUtils.Bitcore; var WalletService = require('./server'); -var BlockExplorer = require('./blockexplorer'); +var BlockchainMonitor = require('./blockchainmonitor') var Notification = require('./model/notification'); -log.debug = log.verbose; log.level = 'debug'; -var io; -var blockExplorerSockets = {}; -var subscriptions = {}; +var io, bcMonitor; var WsApp = function() {}; @@ -30,79 +25,27 @@ WsApp._unauthorized = function() { socket.disconnect(); }; -WsApp.subscribeAddresses = function(walletId, addresses) { - $.checkArgument(walletId); - - if (!addresses || addresses.length == 0) return; - - function handlerFor(address, txid) { - var notification = Notification.create({ - walletId: this, - type: 'NewIncomingTx', - data: { - address: address, - txid: txid, - }, - }); - WsApp._sendNotification(notification); - }; - - if (!subscriptions[walletId]) { - subscriptions[walletId] = { - addresses: [], - }; - }; - var addresses = [].concat(addresses); - var network = Bitcore.Address.fromString(addresses[0]).network; - var socket = blockExplorerSockets[network]; - _.each(addresses, function(address) { - subscriptions[walletId].addresses.push(address); - socket.emit('subscribe', address); - socket.on(address, _.bind(handlerFor, walletId, address)); - }); -}; - -WsApp.subscribeWallet = function(serviceInstance) { - var walletId = serviceInstance.walletId; - if (subscriptions[walletId]) return; - - serviceInstance.getMainAddresses({}, function(err, addresses) { - if (err) { - delete subscriptions[walletId]; - log.warn('Could not subscribe to addresses for wallet ' + serviceInstance.walletId); - return; - } - WsApp.subscribeAddresses(serviceInstance.walletId, _.pluck(addresses, 'address')); - }); -}; - -WsApp._sendNotification = function(notification) { +WsApp.handleNotification = function(service, notification) { if (notification.type == 'NewAddress') { - WsApp.subscribeAddresses(notification.walletId, notification.data.address); + self.subscribeAddresses(notification.walletId, notification.data.address); } io.to(notification.walletId).emit('notification', notification); }; -WsApp._initBlockExplorerSocket = function(provider, network) { - var explorer = new BlockExplorer({ - provider: provider, - network: network, - }); - - blockExplorerSockets[network] = explorer.initSocket(); -}; - WsApp.start = function(server) { io = require('socket.io')(server); - WsApp._initBlockExplorerSocket('insight', 'testnet'); - WsApp._initBlockExplorerSocket('insight', 'livenet'); + bcMonitor = new BlockchainMonitor(); - WalletService.onNotification(function(serviceInstance, notification) { - if (!notification.walletId) return; + function handleNotification(notification) { + if (notification.type == 'NewAddress') { + bcMonitor.subscribeAddresses(notification.walletId, notification.data.address); + } + io.to(notification.walletId).emit('notification', notification); + }; - WsApp._sendNotification(notification); - }); + bcMonitor.on('notification', handleNotification); + WalletService.onNotification(handleNotification); io.on('connection', function(socket) { socket.nonce = Uuid.v4(); @@ -117,7 +60,7 @@ WsApp.start = function(server) { socket.join(service.walletId); socket.emit('authorized'); - WsApp.subscribeWallet(service); + bcMonitor.subscribeWallet(service); }); }); }); From cf73449e7c6743578bf439bac3bd9122cfd2b11f Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 30 Mar 2015 20:16:51 -0300 Subject: [PATCH 11/14] mv blockExplorer -> blockchainExplorer --- ...blockexplorer.js => blockchainexplorer.js} | 4 ++-- lib/server.js | 24 +++++++++---------- test/integration/server.js | 16 ++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) rename lib/{blockexplorer.js => blockchainexplorer.js} (94%) diff --git a/lib/blockexplorer.js b/lib/blockchainexplorer.js similarity index 94% rename from lib/blockexplorer.js rename to lib/blockchainexplorer.js index 5aafea0..70fd076 100644 --- a/lib/blockexplorer.js +++ b/lib/blockchainexplorer.js @@ -10,7 +10,7 @@ var request = require('request'); var io = require('socket.io-client'); -function BlockExplorer(opts) { +function BlockChainExplorer(opts) { $.checkArgument(opts); var provider = opts.provider || 'insight'; var network = opts.network || 'livenet'; @@ -57,4 +57,4 @@ function initSocketInsight(url) { return socket; }; -module.exports = BlockExplorer; +module.exports = BlockChainExplorer; diff --git a/lib/server.js b/lib/server.js index b5bb64e..33ec1e3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -15,7 +15,7 @@ var ClientError = require('./clienterror'); var Utils = require('./utils'); var Storage = require('./storage'); var NotificationBroadcaster = require('./notificationbroadcaster'); -var BlockExplorer = require('./blockexplorer'); +var BlockchainExplorer = require('./blockchainexplorer'); var Wallet = require('./model/wallet'); var Copayer = require('./model/copayer'); @@ -24,7 +24,7 @@ var TxProposal = require('./model/txproposal'); var Notification = require('./model/notification'); var initialized = false; -var storage, blockExplorer; +var storage, blockchainExplorer; /** @@ -36,7 +36,7 @@ function WalletService() { throw new Error('Server not initialized'); this.storage = storage; - this.blockExplorer = blockExplorer; + this.blockchainExplorer = blockchainExplorer; this.notifyTicker = 0; }; @@ -48,12 +48,12 @@ WalletService.onNotification = function(func) { * Initializes global settings for all instances. * @param {Object} opts * @param {Storage} [opts.storage] - The storage provider. - * @param {Storage} [opts.blockExplorer] - The blockExporer provider. + * @param {Storage} [opts.blockchainExplorer] - The blockchainExporer provider. */ WalletService.initialize = function(opts) { opts = opts || {}; storage = opts.storage ||  new Storage(); - blockExplorer = opts.blockExplorer; + blockchainExplorer = opts.blockchainExplorer; initialized = true; }; @@ -335,15 +335,15 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { }; -WalletService.prototype._getBlockExplorer = function(provider, network) { - if (!this.blockExplorer) { - this.blockExplorer = new BlockExplorer({ +WalletService.prototype._getBlockchainExplorer = function(provider, network) { + if (!this.blockchainExplorer) { + this.blockchainExplorer = new BlockchainExplorer({ provider: provider, network: network, }); } - return this.blockExplorer; + return this.blockchainExplorer; }; /** @@ -363,7 +363,7 @@ WalletService.prototype._getUtxos = function(cb) { var addressToPath = _.indexBy(addresses, 'address'); // TODO : check performance var networkName = Bitcore.Address(addressStrs[0]).toObject().network; - var bc = self._getBlockExplorer('insight', networkName); + var bc = self._getBlockchainExplorer('insight', networkName); bc.getUnspentUtxos(addressStrs, function(err, inutxos) { if (err) return cb(err); var utxos = _.map(inutxos, function(i) { @@ -669,7 +669,7 @@ WalletService.prototype._broadcastTx = function(txp, cb) { } catch (ex) { return cb(ex); } - var bc = this._getBlockExplorer('insight', txp.getNetworkName()); + var bc = this._getBlockchainExplorer('insight', txp.getNetworkName()); bc.broadcast(raw, function(err, txid) { return cb(err, txid); }) @@ -1014,7 +1014,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { var addressStrs = _.pluck(addresses, 'address'); var networkName = Bitcore.Address(addressStrs[0]).toObject().network; - var bc = self._getBlockExplorer('insight', networkName); + var bc = self._getBlockchainExplorer('insight', networkName); async.parallel([ function(next) { diff --git a/test/integration/server.js b/test/integration/server.js index b06df71..132ce88 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -150,22 +150,22 @@ helpers.stubUtxos = function(server, wallet, amounts, cb) { }; return obj; }); - blockExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); + blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, utxos); return cb(utxos); }); }; helpers.stubBroadcast = function(txid) { - blockExplorer.broadcast = sinon.stub().callsArgWith(1, null, txid); + blockchainExplorer.broadcast = sinon.stub().callsArgWith(1, null, txid); }; helpers.stubBroadcastFail = function() { - blockExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error'); + blockchainExplorer.broadcast = sinon.stub().callsArgWith(1, 'broadcast error'); }; helpers.stubHistory = function(txs) { - blockExplorer.getTransactions = sinon.stub().callsArgWith(1, null, txs); + blockchainExplorer.getTransactions = sinon.stub().callsArgWith(1, null, txs); }; helpers.clientSign = WalletUtils.signTxp; @@ -198,7 +198,7 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { }); }; -var db, storage, blockExplorer; +var db, storage, blockchainExplorer; describe('Copay server', function() { @@ -209,11 +209,11 @@ describe('Copay server', function() { storage = new Storage({ db: db }); - blockExplorer = sinon.stub(); + blockchainExplorer = sinon.stub(); WalletService.initialize({ storage: storage, - blockExplorer: blockExplorer, + blockchainExplorer: blockchainExplorer, }); helpers.offset = 0; }); @@ -699,7 +699,7 @@ describe('Copay server', function() { }); }); it('should get balance when there are no funds', function(done) { - blockExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, []); + blockchainExplorer.getUnspentUtxos = sinon.stub().callsArgWith(1, null, []); server.createAddress({}, function(err, address) { should.not.exist(err); server.getBalance({}, function(err, balance) { From 6209a2ac970f83b4cda2f2bd65e3ac35d250223a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 31 Mar 2015 12:04:02 -0300 Subject: [PATCH 12/14] test blockchain monitor using stubs --- lib/blockchainexplorer.js | 9 ++-- lib/blockchainmonitor.js | 21 +++++---- lib/wsapp.js | 4 +- test/integration/server.js | 97 +++++++++++++++++++++++++++++++++++++- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index 70fd076..bee70b9 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -27,10 +27,10 @@ function BlockChainExplorer(opts) { url = 'https://test-insight.bitpay.com:443' break; } - var bc = new Explorers.Insight(url, network); - bc.getTransactions = _.bind(getTransactionsInsight, bc, url); - bc.initSocket = _.bind(initSocketInsight, bc, url); - return bc; + var explorer = new Explorers.Insight(url, network); + explorer.getTransactions = _.bind(getTransactionsInsight, explorer, url); + explorer.initSocket = _.bind(initSocketInsight, explorer, url); + return explorer; break; default: throw new Error('Provider ' + provider + ' not supported'); @@ -38,7 +38,6 @@ function BlockChainExplorer(opts) { }; }; - function getTransactionsInsight(url, addresses, cb) { request({ method: "POST", diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js index 2b7bc1d..a66fc8c 100644 --- a/lib/blockchainmonitor.js +++ b/lib/blockchainmonitor.js @@ -13,27 +13,27 @@ var nodeutil = require('util'); var WalletUtils = require('bitcore-wallet-utils'); var Bitcore = WalletUtils.Bitcore; var WalletService = require('./server'); -var BlockExplorer = require('./blockexplorer'); +var BlockchainExplorer = require('./blockchainexplorer'); var Notification = require('./model/notification'); function BlockchainMonitor() { + var self = this; this.subscriptions = {}; this.sockets = {}; - - this._initBlockExplorerSocket('insight', 'livenet'); - this._initBlockExplorerSocket('insight', 'testnet'); + this.sockets['livenet'] = self._getBlockchainExplorerSocket('insight', 'livenet'); + this.sockets['testnet'] = self._getBlockchainExplorerSocket('insight', 'testnet'); }; nodeutil.inherits(BlockchainMonitor, events.EventEmitter); -BlockchainMonitor.prototype._initBlockExplorerSocket = function(provider, network) { - var explorer = new BlockExplorer({ +BlockchainMonitor.prototype._getBlockchainExplorerSocket = function(provider, network) { + var explorer = new BlockchainExplorer({ provider: provider, network: network, }); - this.sockets[network] = explorer.initSocket(); + return explorer.initSocket(); }; BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { @@ -60,6 +60,7 @@ BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { addresses: [], }; }; + var addresses = [].concat(addresses); var network = Bitcore.Address.fromString(addresses[0]).network.name; var socket = self.sockets[network]; @@ -70,7 +71,7 @@ BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { }); }; -BlockchainMonitor.prototype.subscribeWallet = function(walletService) { +BlockchainMonitor.prototype.subscribeWallet = function(walletService, cb) { var self = this; var walletId = walletService.walletId; @@ -79,10 +80,10 @@ BlockchainMonitor.prototype.subscribeWallet = function(walletService) { walletService.getMainAddresses({}, function(err, addresses) { if (err) { delete self.subscriptions[walletId]; - log.warn('Could not subscribe to addresses for wallet ' + walletId); - return; + return cb(new Error('Could not subscribe to addresses for wallet ' + walletId)); } self.subscribeAddresses(walletService.walletId, _.pluck(addresses, 'address')); + return cb(); }); }; diff --git a/lib/wsapp.js b/lib/wsapp.js index 19b3a78..d307be2 100644 --- a/lib/wsapp.js +++ b/lib/wsapp.js @@ -60,7 +60,9 @@ WsApp.start = function(server) { socket.join(service.walletId); socket.emit('authorized'); - bcMonitor.subscribeWallet(service); + bcMonitor.subscribeWallet(service, function(err) { + if (err) log.warn(err.message); + }); }); }); }); diff --git a/test/integration/server.js b/test/integration/server.js index 132ce88..4486473 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -16,6 +16,7 @@ var Utils = require('../../lib/utils'); var WalletUtils = require('bitcore-wallet-utils'); var Bitcore = WalletUtils.Bitcore; var Storage = require('../../lib/storage'); +var BlockchainMonitor = require('../../lib/blockchainmonitor'); var Wallet = require('../../lib/model/wallet'); var TxProposal = require('../../lib/model/txproposal'); @@ -201,7 +202,101 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var db, storage, blockchainExplorer; -describe('Copay server', function() { +describe('Blockchain monitor', function() { + var bcSocket, monitor; + + beforeEach(function() { + db = levelup(memdown, { + valueEncoding: 'json' + }); + storage = new Storage({ + db: db + }); + blockchainExplorer = sinon.stub(); + + WalletService.initialize({ + storage: storage, + blockchainExplorer: blockchainExplorer, + }); + helpers.offset = 0; + + bcSocket = sinon.stub(); + bcSocket.emit = sinon.stub(); + bcSocket.on = sinon.stub(); + sinon.stub(BlockchainMonitor.prototype, '_getBlockchainExplorerSocket').onFirstCall().returns(bcSocket); + monitor = new BlockchainMonitor(); + }); + + afterEach(function() { + BlockchainMonitor.prototype._getBlockchainExplorerSocket.restore(); + }); + + it('should subscribe wallet', function(done) { + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + server.createAddress({}, function(err, address2) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + bcSocket.emit.calledTwice.should.be.true; + bcSocket.emit.calledWith('subscribe', address1.address).should.be.true; + bcSocket.emit.calledWith('subscribe', address2.address).should.be.true; + done(); + }); + }); + }); + }); + }); + + it('should be able to subscribe new address', function(done) { + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + bcSocket.emit.calledOnce.should.be.true; + bcSocket.emit.calledWith('subscribe', address1.address).should.be.true; + server.createAddress({}, function(err, address2) { + should.not.exist(err); + monitor.subscribeAddresses(wallet.id, address2.address); + bcSocket.emit.calledTwice.should.be.true; + bcSocket.emit.calledWith('subscribe', address2.address).should.be.true; + done(); + }); + }); + }); + }); + }); + + it('should create NewIncomingTx notification when a new tx arrives on registered address', function(done) { + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + bcSocket.on.calledOnce.should.be.true; + bcSocket.on.getCall(0).args[0].should.equal(address1.address); + var handler = bcSocket.on.getCall(0).args[1]; + _.isFunction(handler).should.be.true; + + var emitSpy = sinon.spy(monitor, 'emit'); + handler('txid'); + emitSpy.calledOnce.should.be.true; + emitSpy.getCall(0).args[0].should.equal('notification'); + var notification = emitSpy.getCall(0).args[1]; + notification.type.should.equal('NewIncomingTx'); + notification.data.address.should.equal(address1.address); + notification.data.txid.should.equal('txid'); + done(); + }); + }); + }); + }); +}); + + +describe('Wallet service', function() { beforeEach(function() { db = levelup(memdown, { valueEncoding: 'json' From ff8c3604359f4df5e5ffe7a12863077f15d88cd7 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 31 Mar 2015 12:29:28 -0300 Subject: [PATCH 13/14] cleanup test code --- lib/blockchainmonitor.js | 25 +++-- test/integration/server.js | 188 ++++++++++++++++++------------------- 2 files changed, 111 insertions(+), 102 deletions(-) diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js index a66fc8c..2947e72 100644 --- a/lib/blockchainmonitor.js +++ b/lib/blockchainmonitor.js @@ -20,20 +20,30 @@ var Notification = require('./model/notification'); function BlockchainMonitor() { var self = this; this.subscriptions = {}; - this.sockets = {}; - this.sockets['livenet'] = self._getBlockchainExplorerSocket('insight', 'livenet'); - this.sockets['testnet'] = self._getBlockchainExplorerSocket('insight', 'testnet'); + this.subscriber = {}; + this.subscriber['livenet'] = self._getAddressSubscriber('insight', 'livenet'); + this.subscriber['testnet'] = self._getAddressSubscriber('insight', 'testnet'); }; nodeutil.inherits(BlockchainMonitor, events.EventEmitter); -BlockchainMonitor.prototype._getBlockchainExplorerSocket = function(provider, network) { +BlockchainMonitor.prototype._getAddressSubscriber = function(provider, network) { + $.checkArgument(provider == 'insight', 'Blockchain monitor ' + provider + ' not supported'); + var explorer = new BlockchainExplorer({ provider: provider, network: network, }); - return explorer.initSocket(); + var socket = explorer.initSocket(); + + // TODO: Extract on its own class once more providers are implemented + return { + subscribe: function(address, handler) { + socket.emit('subscribe', address); + socket.on(address, handler); + }, + }; }; BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { @@ -63,11 +73,10 @@ BlockchainMonitor.prototype.subscribeAddresses = function(walletId, addresses) { var addresses = [].concat(addresses); var network = Bitcore.Address.fromString(addresses[0]).network.name; - var socket = self.sockets[network]; + var subscriber = self.subscriber[network]; _.each(addresses, function(address) { self.subscriptions[walletId].addresses.push(address); - socket.emit('subscribe', address); - socket.on(address, _.bind(handlerFor, walletId, address)); + subscriber.subscribe(address, _.bind(handlerFor, walletId, address)); }); }; diff --git a/test/integration/server.js b/test/integration/server.js index 4486473..bd700e2 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -202,100 +202,6 @@ helpers.createAddresses = function(server, wallet, main, change, cb) { var db, storage, blockchainExplorer; -describe('Blockchain monitor', function() { - var bcSocket, monitor; - - beforeEach(function() { - db = levelup(memdown, { - valueEncoding: 'json' - }); - storage = new Storage({ - db: db - }); - blockchainExplorer = sinon.stub(); - - WalletService.initialize({ - storage: storage, - blockchainExplorer: blockchainExplorer, - }); - helpers.offset = 0; - - bcSocket = sinon.stub(); - bcSocket.emit = sinon.stub(); - bcSocket.on = sinon.stub(); - sinon.stub(BlockchainMonitor.prototype, '_getBlockchainExplorerSocket').onFirstCall().returns(bcSocket); - monitor = new BlockchainMonitor(); - }); - - afterEach(function() { - BlockchainMonitor.prototype._getBlockchainExplorerSocket.restore(); - }); - - it('should subscribe wallet', function(done) { - helpers.createAndJoinWallet(2, 2, function(server, wallet) { - server.createAddress({}, function(err, address1) { - should.not.exist(err); - server.createAddress({}, function(err, address2) { - should.not.exist(err); - monitor.subscribeWallet(server, function(err) { - should.not.exist(err); - bcSocket.emit.calledTwice.should.be.true; - bcSocket.emit.calledWith('subscribe', address1.address).should.be.true; - bcSocket.emit.calledWith('subscribe', address2.address).should.be.true; - done(); - }); - }); - }); - }); - }); - - it('should be able to subscribe new address', function(done) { - helpers.createAndJoinWallet(2, 2, function(server, wallet) { - server.createAddress({}, function(err, address1) { - should.not.exist(err); - monitor.subscribeWallet(server, function(err) { - should.not.exist(err); - bcSocket.emit.calledOnce.should.be.true; - bcSocket.emit.calledWith('subscribe', address1.address).should.be.true; - server.createAddress({}, function(err, address2) { - should.not.exist(err); - monitor.subscribeAddresses(wallet.id, address2.address); - bcSocket.emit.calledTwice.should.be.true; - bcSocket.emit.calledWith('subscribe', address2.address).should.be.true; - done(); - }); - }); - }); - }); - }); - - it('should create NewIncomingTx notification when a new tx arrives on registered address', function(done) { - helpers.createAndJoinWallet(2, 2, function(server, wallet) { - server.createAddress({}, function(err, address1) { - should.not.exist(err); - monitor.subscribeWallet(server, function(err) { - should.not.exist(err); - bcSocket.on.calledOnce.should.be.true; - bcSocket.on.getCall(0).args[0].should.equal(address1.address); - var handler = bcSocket.on.getCall(0).args[1]; - _.isFunction(handler).should.be.true; - - var emitSpy = sinon.spy(monitor, 'emit'); - handler('txid'); - emitSpy.calledOnce.should.be.true; - emitSpy.getCall(0).args[0].should.equal('notification'); - var notification = emitSpy.getCall(0).args[1]; - notification.type.should.equal('NewIncomingTx'); - notification.data.address.should.equal(address1.address); - notification.data.txid.should.equal('txid'); - done(); - }); - }); - }); - }); -}); - - describe('Wallet service', function() { beforeEach(function() { db = levelup(memdown, { @@ -2568,3 +2474,97 @@ describe('Wallet service', function() { }); }); }); + + +describe('Blockchain monitor', function() { + var addressSubscriber; + + beforeEach(function() { + db = levelup(memdown, { + valueEncoding: 'json' + }); + storage = new Storage({ + db: db + }); + blockchainExplorer = sinon.stub(); + + WalletService.initialize({ + storage: storage, + blockchainExplorer: blockchainExplorer, + }); + helpers.offset = 0; + + addressSubscriber = sinon.stub(); + addressSubscriber.subscribe = sinon.stub(); + sinon.stub(BlockchainMonitor.prototype, '_getAddressSubscriber').onFirstCall().returns(addressSubscriber); + }); + + afterEach(function() { + BlockchainMonitor.prototype._getAddressSubscriber.restore(); + }); + + it('should subscribe wallet', function(done) { + var monitor = new BlockchainMonitor(); + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + server.createAddress({}, function(err, address2) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + addressSubscriber.subscribe.calledTwice.should.be.true; + addressSubscriber.subscribe.calledWith(address1.address).should.be.true; + addressSubscriber.subscribe.calledWith(address2.address).should.be.true; + done(); + }); + }); + }); + }); + }); + + it('should be able to subscribe new address', function(done) { + var monitor = new BlockchainMonitor(); + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + addressSubscriber.subscribe.calledOnce.should.be.true; + addressSubscriber.subscribe.calledWith(address1.address).should.be.true; + server.createAddress({}, function(err, address2) { + should.not.exist(err); + monitor.subscribeAddresses(wallet.id, address2.address); + addressSubscriber.subscribe.calledTwice.should.be.true; + addressSubscriber.subscribe.calledWith(address2.address).should.be.true; + done(); + }); + }); + }); + }); + }); + + it('should create NewIncomingTx notification when a new tx arrives on registered address', function(done) { + var monitor = new BlockchainMonitor(); + helpers.createAndJoinWallet(2, 2, function(server, wallet) { + server.createAddress({}, function(err, address1) { + should.not.exist(err); + monitor.subscribeWallet(server, function(err) { + should.not.exist(err); + addressSubscriber.subscribe.calledOnce.should.be.true; + addressSubscriber.subscribe.getCall(0).args[0].should.equal(address1.address); + var handler = addressSubscriber.subscribe.getCall(0).args[1]; + _.isFunction(handler).should.be.true; + + monitor.on('notification', function(notification) { + notification.type.should.equal('NewIncomingTx'); + notification.data.address.should.equal(address1.address); + notification.data.txid.should.equal('txid'); + done(); + }); + + handler('txid'); + }); + }); + }); + }); +}); From 6b5254e6ad9d6c09e7be78f533a42b9b4549e2c2 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Tue, 31 Mar 2015 12:58:51 -0300 Subject: [PATCH 14/14] test blockchain explorer --- lib/blockchainexplorer.js | 2 -- test/blockchainexplorer.js | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 test/blockchainexplorer.js diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index bee70b9..c11a82c 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -31,10 +31,8 @@ function BlockChainExplorer(opts) { explorer.getTransactions = _.bind(getTransactionsInsight, explorer, url); explorer.initSocket = _.bind(initSocketInsight, explorer, url); return explorer; - break; default: throw new Error('Provider ' + provider + ' not supported'); - break; }; }; diff --git a/test/blockchainexplorer.js b/test/blockchainexplorer.js new file mode 100644 index 0000000..b1fbd3d --- /dev/null +++ b/test/blockchainexplorer.js @@ -0,0 +1,35 @@ +'use strict'; + +var _ = require('lodash'); +var chai = require('chai'); +var sinon = require('sinon'); +var should = chai.should(); +var BlockchainExplorer = require('../lib/blockchainexplorer'); + +describe('Blockchain explorer', function() { + describe('#constructor', function() { + it('should return a blockchain explorer with basic methods', function() { + var exp = BlockchainExplorer({ + provider: 'insight', + network: 'testnet', + }); + should.exist(exp); + exp.should.respondTo('broadcast'); + exp.should.respondTo('getTransactions'); + exp.should.respondTo('getUnspentUtxos'); + exp.should.respondTo('initSocket'); + var exp = BlockchainExplorer({ + provider: 'insight', + network: 'livenet', + }); + should.exist(exp); + }); + it('should fail on unsupported provider', function() { + (function() { + var exp = BlockchainExplorer({ + provider: 'dummy', + }); + }).should.throw('not supported'); + }); + }); +});