From 470fd84ab7dc101eda2ecd31d5b61eb1bf9e1f6b Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 10 Jun 2015 11:49:52 -0300 Subject: [PATCH 1/4] refactor bcmonitor initialization --- lib/blockchainexplorer.js | 41 +++++++++++++++++++----------- lib/blockchainmonitor.js | 51 +++++++++++++++++++++----------------- lib/emailservice.js | 6 +---- test/blockchainexplorer.js | 6 ++--- 4 files changed, 58 insertions(+), 46 deletions(-) diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index f2d4482..1846218 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -9,29 +9,40 @@ var Explorers = require('bitcore-explorers'); var request = require('request'); var io = require('socket.io-client'); +var PROVIDERS = { + 'insight': { + 'livenet': 'https://insight.bitpay.com:443', + 'testnet': 'https://test-insight.bitpay.com:443', + }, +}; function BlockChainExplorer(opts) { $.checkArgument(opts); - var provider = opts.provider || 'insight'; - var network = opts.network || 'livenet'; - var dfltUrl = network == 'livenet' ? 'https://insight.bitpay.com:443' : - 'https://test-insight.bitpay.com:443'; - var url = opts.url || dfltUrl; - - var url; - switch (provider) { + + this.provider = opts.provider || 'insight'; + this.network = opts.network || 'livenet'; + + $.checkState(PROVIDERS[this.provider], 'Provider ' + this.provider + ' not supported'); + $.checkState(PROVIDERS[this.provider][this.network], 'Network ' + this.network + ' not supported'); + + this.url = opts.url || PROVIDERS[this.provider][this.network]; + + switch (this.provider) { + default: case 'insight': - var explorer = new Explorers.Insight(url, network); - explorer.getTransaction = _.bind(getTransactionInsight, explorer, url); - explorer.getTransactions = _.bind(getTransactionsInsight, explorer, url); - explorer.getAddressActivity = _.bind(getAddressActivityInsight, explorer, url); - explorer.initSocket = _.bind(initSocketInsight, explorer, url); + var explorer = new Explorers.Insight(this.url, this.network); + explorer.getTransaction = _.bind(getTransactionInsight, explorer, this.url); + explorer.getTransactions = _.bind(getTransactionsInsight, explorer, this.url); + explorer.getAddressActivity = _.bind(getAddressActivityInsight, explorer, this.url); + explorer.initSocket = _.bind(initSocketInsight, explorer, this.url); return explorer; - default: - throw new Error('Provider ' + provider + ' not supported'); }; }; +BlockChainExplorer.prototype.getConnectionInfo = function() { + return this.provider + ' (' + this.network + ') @ ' + this.url; +}; + function getTransactionInsight(url, txid, cb) { var url = url + '/api/tx/' + txid; var args = { diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js index 812f386..09d7399 100644 --- a/lib/blockchainmonitor.js +++ b/lib/blockchainmonitor.js @@ -17,10 +17,6 @@ function BlockchainMonitor() {}; BlockchainMonitor.prototype.start = function(opts, cb) { opts = opts || {}; - $.checkArgument(opts.blockchainExplorerOpts); - $.checkArgument(opts.storageOpts); - $.checkArgument(opts.messageBrokerOpts); - $.checkArgument(opts.lockOpts); var self = this; @@ -28,21 +24,39 @@ BlockchainMonitor.prototype.start = function(opts, cb) { function(done) { self.explorers = _.map(['livenet', 'testnet'], function(network) { - var config = opts.blockchainExplorerOpts[network] || {}; - return self._initExplorer(config.provider, network, config.url); + var explorer; + if (opts.blockchainExplorers) { + explorer = opts.blockchainExplorers[network]; + } else { + var config = opts.blockchainExplorerOpts[network] || {}; + var explorer = new BlockchainExplorer({ + provider: config.provider, + network: network, + url: config.url, + }); + } + $.checkState(explorer); + self._initExplorer(explorer); + return explorer; }); done(); }, function(done) { - self.storage = new Storage(); - self.storage.connect(opts.storageOpts, done); + if (opts.storage) { + self.storage = opts.storage; + done(); + } else { + self.storage = new Storage(); + self.storage.connect(opts.storageOpts, done); + } }, function(done) { - self.messageBroker = new MessageBroker(opts.messageBrokerOpts); + self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts); + self.messageBroker.onMessage(_.bind(self.sendEmail, self)); done(); }, function(done) { - self.lock = new Lock(opts.lockOpts); + self.lock = opts.lock || new Lock(opts.lockOpts); done(); }, ], function(err) { @@ -53,30 +67,21 @@ BlockchainMonitor.prototype.start = function(opts, cb) { }); }; -BlockchainMonitor.prototype._initExplorer = function(provider, network, url) { - $.checkArgument(provider == 'insight', 'Blockchain monitor ' + provider + ' not supported'); +BlockchainMonitor.prototype._initExplorer = function(explorer) { + $.checkArgument(explorer.provider == 'insight', 'Blockchain monitor ' + provider + ' not supported'); var self = this; - var explorer = new BlockchainExplorer({ - provider: provider, - network: network, - url: url, - }); - var socket = explorer.initSocket(); - var connectionInfo = provider + ' (' + network + ') @ ' + url; socket.on('connect', function() { - log.info('Connected to ' + connectionInfo); + log.info('Connected to ' + explorer.getConnectionInfo()); socket.emit('subscribe', 'inv'); }); socket.on('connect_error', function() { - log.error('Error connecting to ' + connectionInfo); + log.error('Error connecting to ' + explorer.getConnectionInfo()); }); socket.on('tx', _.bind(self._handleIncommingTx, self)); - - return explorer; }; BlockchainMonitor.prototype._handleIncommingTx = function(data) { diff --git a/lib/emailservice.js b/lib/emailservice.js index aa6e994..50ebf6d 100644 --- a/lib/emailservice.js +++ b/lib/emailservice.js @@ -63,11 +63,7 @@ EmailService.prototype.start = function(opts, cb) { } }, function(done) { - if (opts.messageBroker) { - self.messageBroker = opts.messageBroker; - } else { - self.messageBroker = new MessageBroker(opts.messageBrokerOpts); - } + self.messageBroker = opts.messageBroker || new MessageBroker(opts.messageBrokerOpts); self.messageBroker.onMessage(_.bind(self.sendEmail, self)); done(); }, diff --git a/test/blockchainexplorer.js b/test/blockchainexplorer.js index 2e49139..14b4d32 100644 --- a/test/blockchainexplorer.js +++ b/test/blockchainexplorer.js @@ -9,7 +9,7 @@ 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({ + var exp = new BlockchainExplorer({ provider: 'insight', network: 'testnet', }); @@ -19,7 +19,7 @@ describe('Blockchain explorer', function() { exp.should.respondTo('getAddressActivity'); exp.should.respondTo('getUnspentUtxos'); exp.should.respondTo('initSocket'); - var exp = BlockchainExplorer({ + var exp = new BlockchainExplorer({ provider: 'insight', network: 'livenet', }); @@ -27,7 +27,7 @@ describe('Blockchain explorer', function() { }); it('should fail on unsupported provider', function() { (function() { - var exp = BlockchainExplorer({ + var exp = new BlockchainExplorer({ provider: 'dummy', }); }).should.throw('not supported'); From 4f8faa826b33be4fa740f1f0bd32dc4dcd891b7d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 11 Jun 2015 18:24:58 -0300 Subject: [PATCH 2/4] implement Insight as a separate blockchain explorer --- lib/blockchainexplorer.js | 83 ++++----------------- lib/blockchainexplorers/insight.js | 111 +++++++++++++++++++++++++++++ package.json | 17 ++--- test/blockchainexplorer.js | 1 + 4 files changed, 132 insertions(+), 80 deletions(-) create mode 100644 lib/blockchainexplorers/insight.js diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index 1846218..bfffe0e 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -5,9 +5,7 @@ var $ = require('preconditions').singleton(); var log = require('npmlog'); log.debug = log.verbose; -var Explorers = require('bitcore-explorers'); -var request = require('request'); -var io = require('socket.io-client'); +var Insight = require('./blockchainexplorers/insight'); var PROVIDERS = { 'insight': { @@ -19,78 +17,23 @@ var PROVIDERS = { function BlockChainExplorer(opts) { $.checkArgument(opts); - this.provider = opts.provider || 'insight'; - this.network = opts.network || 'livenet'; + var provider = opts.provider || 'insight'; + var network = opts.network || 'livenet'; - $.checkState(PROVIDERS[this.provider], 'Provider ' + this.provider + ' not supported'); - $.checkState(PROVIDERS[this.provider][this.network], 'Network ' + this.network + ' not supported'); + $.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported'); + $.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider'); - this.url = opts.url || PROVIDERS[this.provider][this.network]; + var url = opts.url || PROVIDERS[provider][network]; - switch (this.provider) { - default: + switch (provider) { case 'insight': - var explorer = new Explorers.Insight(this.url, this.network); - explorer.getTransaction = _.bind(getTransactionInsight, explorer, this.url); - explorer.getTransactions = _.bind(getTransactionsInsight, explorer, this.url); - explorer.getAddressActivity = _.bind(getAddressActivityInsight, explorer, this.url); - explorer.initSocket = _.bind(initSocketInsight, explorer, this.url); - return explorer; - }; -}; - -BlockChainExplorer.prototype.getConnectionInfo = function() { - return this.provider + ' (' + this.network + ') @ ' + this.url; -}; - -function getTransactionInsight(url, txid, cb) { - var url = url + '/api/tx/' + txid; - var args = { - method: "GET", - url: url, - }; - - request(args, function(err, res, tx) { - if (err || res.statusCode != 200) return cb(err || res); - return cb(null, tx); - }); -}; - -function getTransactionsInsight(url, addresses, from, to, cb) { - var qs = []; - if (_.isNumber(from)) qs.push('from=' + from); - if (_.isNumber(to)) qs.push('to=' + to); - - var url = url + '/api/addrs/txs' + (qs.length > 0 ? '?' + qs.join('&') : ''); - var args = { - method: "POST", - url: url, - json: { - addrs: [].concat(addresses).join(',') - }, + return new Insight({ + network: network, + url: url + }); + default: + throw new Error('Provider ' + provider + ' not supperted.'); }; - - request(args, function(err, res, txs) { - if (err || res.statusCode != 200) return cb(err || res); - // NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code. - if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args))); - - return cb(null, txs); - }); -}; - -function getAddressActivityInsight(url, addresses, cb) { - getTransactionsInsight(url, addresses, null, null, function(err, result) { - if (err) return cb(err); - return cb(null, result && result.length > 0); - }); -}; - -function initSocketInsight(url) { - var socket = io.connect(url, { - 'reconnection': true, - }); - return socket; }; module.exports = BlockChainExplorer; diff --git a/lib/blockchainexplorers/insight.js b/lib/blockchainexplorers/insight.js new file mode 100644 index 0000000..6c1113c --- /dev/null +++ b/lib/blockchainexplorers/insight.js @@ -0,0 +1,111 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('preconditions').singleton(); +var log = require('npmlog'); +log.debug = log.verbose; +var request = require('request'); +var io = require('socket.io-client'); + +function Insight(opts) { + $.checkArgument(opts); + $.checkArgument(_.contains(['livenet', 'testnet'], opts.network)); + $.checkArgument(opts.url); + + this.network = opts.network || 'livenet'; + this.url = opts.url; +}; + +Insight.prototype.getConnectionInfo = function() { + return 'Insight (' + this.network + ') @ ' + this.url; +}; + +/** + * 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 args = { + method: 'POST', + url: url, + json: { + addrs: [].concat(addresses).join(',') + }, + }; + + request(args, function(err, res, unspent) { + if (err || res.statusCode !== 200) return cb(err || res); + return cb(null, unspent); + }); +}; + +/** + * Broadcast a transaction to the bitcoin network + */ +Insight.prototype.broadcast = function(rawTx, cb) { + var url = this.url + '/api/tx/send'; + var args = { + method: 'POST', + url: url, + json: { + rawtx: rawTx + }, + }; + + request(args, function(err, res, unspent) { + if (err || res.statusCode !== 200) return cb(err || res); + return cb(null, body ? body.txid : null); + }); +}; + +Insight.prototype.getTransaction = function(txid, cb) { + var url = this.url + '/api/tx/' + txid; + var args = { + method: 'GET', + url: url, + }; + + request(args, function(err, res, tx) { + if (err || res.statusCode != 200) return cb(err || res); + return cb(null, tx); + }); +}; + +Insight.prototype.getTransactions = function(addresses, from, to, cb) { + var qs = []; + 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 args = { + method: 'POST', + url: url, + json: { + addrs: [].concat(addresses).join(',') + }, + }; + + request(args, function(err, res, txs) { + if (err || res.statusCode != 200) return cb(err || res); + // NOTE: Whenever Insight breaks communication with bitcoind, it returns invalid data but no error code. + if (!_.isArray(txs) || (txs.length != _.compact(txs).length)) return cb(new Error('Could not retrieve transactions from blockchain. Request was:' + JSON.stringify(args))); + + return cb(null, txs); + }); +}; + +Insight.prototype.getAddressActivity = function(addresses, cb) { + this.getTransactions(addresses, null, null, function(err, result) { + if (err) return cb(err); + return cb(null, result && result.length > 0); + }); +}; + +Insight.prototype.initSocket = function() { + var socket = io.connect(this.url, { + 'reconnection': true, + }); + return socket; +}; + +module.exports = Insight; diff --git a/package.json b/package.json index 160d4d1..d543af8 100644 --- a/package.json +++ b/package.json @@ -62,14 +62,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" + }] } diff --git a/test/blockchainexplorer.js b/test/blockchainexplorer.js index 14b4d32..097dfa0 100644 --- a/test/blockchainexplorer.js +++ b/test/blockchainexplorer.js @@ -15,6 +15,7 @@ describe('Blockchain explorer', function() { }); should.exist(exp); exp.should.respondTo('broadcast'); + exp.should.respondTo('getUnspentUtxos'); exp.should.respondTo('getTransactions'); exp.should.respondTo('getAddressActivity'); exp.should.respondTo('getUnspentUtxos'); From b45acded823609fe14a397f4a984e98001d75b63 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 11 Jun 2015 18:50:50 -0300 Subject: [PATCH 3/4] fix data parsing --- lib/blockchainexplorers/insight.js | 2 +- lib/server.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/blockchainexplorers/insight.js b/lib/blockchainexplorers/insight.js index 6c1113c..f11e901 100644 --- a/lib/blockchainexplorers/insight.js +++ b/lib/blockchainexplorers/insight.js @@ -52,7 +52,7 @@ Insight.prototype.broadcast = function(rawTx, cb) { }, }; - request(args, function(err, res, unspent) { + request(args, function(err, res, body) { if (err || res.statusCode !== 200) return cb(err || res); return cb(null, body ? body.txid : null); }); diff --git a/lib/server.js b/lib/server.js index a0a3a0d..fe4a220 100644 --- a/lib/server.js +++ b/lib/server.js @@ -584,8 +584,8 @@ WalletService.prototype._getUtxos = function(cb) { log.error('Could not fetch unspent outputs', err); return cb(new ClientError('BLOCKCHAINERROR', 'Could not fetch unspent outputs')); } - var utxos = _.map(inutxos, function(i) { - return _.pick(i.toObject(), ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']); + var utxos = _.map(inutxos, function(utxo) { + return _.pick(utxo, ['txid', 'vout', 'address', 'scriptPubKey', 'amount', 'satoshis']); }); self.getPendingTxs({}, function(err, txps) { if (err) return cb(err); From a0b45b78f5dfd7c13cb4e62d888dec76b30016e8 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 11 Jun 2015 18:51:02 -0300 Subject: [PATCH 4/4] remove bitcore-explorers ref --- package.json | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index d543af8..ebb42bd 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "dependencies": { "async": "^0.9.0", "bitcore": "^0.11.6", - "bitcore-explorers": "^0.10.3", "bitcore-wallet-utils": "0.0.13", "body-parser": "^1.11.0", "coveralls": "^2.11.2", @@ -62,11 +61,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" + } + ] }