From 4f8faa826b33be4fa740f1f0bd32dc4dcd891b7d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 11 Jun 2015 18:24:58 -0300 Subject: [PATCH] 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');