From 6c276ada497c10f54fd941247e55d3650250744c Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Wed, 2 Sep 2015 11:49:32 -0400 Subject: [PATCH 1/4] update path --- lib/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/status.js b/lib/status.js index ef62339..6c3ad94 100644 --- a/lib/status.js +++ b/lib/status.js @@ -66,7 +66,7 @@ StatusController.prototype.peer = function(req, res) { }; StatusController.prototype.version = function(req, res) { - var pjson = require('../../../package.json'); + var pjson = require('../package.json'); res.json({ version: pjson.version }); From d67950145c9cb8294827d958120615cea799c993 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 10:45:18 -0400 Subject: [PATCH 2/4] add status tests --- lib/status.js | 7 ++- lib/transactions.js | 2 + test/status.js | 150 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 test/status.js diff --git a/lib/status.js b/lib/status.js index 6c3ad94..0209e85 100644 --- a/lib/status.js +++ b/lib/status.js @@ -28,7 +28,10 @@ StatusController.prototype.show = function(req, res) { }; StatusController.prototype.getInfo = function() { - return this.node.services.bitcoind.getInfo(); + var info = this.node.services.bitcoind.getInfo(); + return { + info: info + }; }; StatusController.prototype.getDifficulty = function() { @@ -67,7 +70,7 @@ StatusController.prototype.peer = function(req, res) { StatusController.prototype.version = function(req, res) { var pjson = require('../package.json'); - res.json({ + res.jsonp({ version: pjson.version }); } diff --git a/lib/transactions.js b/lib/transactions.js index 495528f..c08e731 100644 --- a/lib/transactions.js +++ b/lib/transactions.js @@ -136,6 +136,8 @@ TxController.prototype.transformOutput = function(output, index) { }; TxController.prototype.rawTransaction = function(req, res, next, txid) { + var self = this; + this.node.getTransaction(txid, true, function(err, transaction) { if (err && err instanceof self.node.errors.Transaction.NotFound) { return common.handleErrors(null, res); diff --git a/test/status.js b/test/status.js new file mode 100644 index 0000000..0d7e573 --- /dev/null +++ b/test/status.js @@ -0,0 +1,150 @@ +'use strict'; + +var sinon = require('sinon'); +var should = require('should'); +var StatusController = require('../lib/status'); + +describe('Status', function() { + describe('/status', function() { + var info = { + version: 110000, + protocolversion: 70002, + blocks: 548645, + timeoffset: 0, + connections: 8, + difficulty: 21546.906405522557, + testnet: true, + relayfee: 1000, + errors: '' + }; + + var node = { + services: { + bitcoind: { + getInfo: sinon.stub().returns(info) + } + } + }; + + var status = new StatusController(node); + + it('getInfo', function(done) { + var req = { + query: {} + }; + var res = { + jsonp: function(data) { + should.exist(data.info.version); + should.exist(data.info.protocolversion); + should.exist(data.info.blocks); + should.exist(data.info.timeoffset); + should.exist(data.info.connections); + should.exist(data.info.difficulty); + should.exist(data.info.testnet); + should.exist(data.info.relayfee); + done(); + } + }; + + status.show(req, res); + }); + + it('getDifficulty', function(done) { + var req = { + query: { + q: 'getDifficulty' + } + }; + var res = { + jsonp: function(data) { + data.difficulty.should.equal(info.difficulty); + done(); + } + }; + + status.show(req, res); + }); + }); + + describe('/sync', function() { + it('should have correct data', function(done) { + var node = { + services: { + db: { + tip: { + __height: 500000 + } + }, + bitcoind: { + height: 500000, + isSynced: sinon.stub().returns(true) + } + } + }; + + var expected = { + status: 'finished', + blockChainHeight: 500000, + syncPercentage: 100, + height: 500000, + error: null, + type: 'bitcore node' + }; + + var status = new StatusController(node); + + var req = {}; + var res = { + jsonp: function(data) { + should(data).eql(expected); + done(); + } + }; + status.sync(req, res); + }); + }); + + describe('/peer', function() { + it('should have correct data', function(done) { + var node = {}; + + var expected = { + connected: true, + host: '127.0.0.1', + port: null + }; + + var req = {}; + var res = { + jsonp: function(data) { + should(data).eql(expected); + done(); + } + }; + + var status = new StatusController(node); + + status.peer(req, res); + }); + }); + + describe('/version', function() { + it('should have correct data', function(done) { + var node = {}; + var expected = { + version: '0.3.0' + }; + + var req = {}; + var res = { + jsonp: function(data) { + should(data).eql(expected); + done(); + } + }; + + var status = new StatusController(node); + status.version(req, res); + }); + }); +}); \ No newline at end of file From 69c536be7dc9103d625cb73658d06e4923096237 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 16:40:23 -0400 Subject: [PATCH 3/4] fix date bug, add inv subscription --- lib/index.js | 72 +++++++++++++++++++++++++++++++++++++++++++- lib/transactions.js | 4 +-- test/addresses.js | 32 +------------------- test/transactions.js | 50 ++---------------------------- 4 files changed, 77 insertions(+), 81 deletions(-) diff --git a/lib/index.js b/lib/index.js index 1f7da8b..94d6a86 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,17 +6,33 @@ var BlockController = require('./blocks'); var TxController = require('./transactions'); var AddressController = require('./addresses'); var StatusController = require('./status'); +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var Transaction = bitcore.Transaction; +var EventEmitter = require('events').EventEmitter; var InsightAPI = function(options) { BaseService.call(this, options); + + this.subscriptions = { + inv: [] + }; + + this.txController = new TxController(this.node); }; InsightAPI.dependencies = ['address', 'web']; inherits(InsightAPI, BaseService); +InsightAPI.prototype.start = function(callback) { + this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this)); + + setImmediate(callback); +}; + InsightAPI.prototype.setupRoutes = function(app) { - var apiPrefix = '/insight-api'; + var apiPrefix = ''; //Block routes var blocks = new BlockController(this.node); @@ -94,4 +110,58 @@ InsightAPI.prototype.setupRoutes = function(app) { app.get('*', index.render);*/ }; +InsightAPI.prototype.getPublishEvents = function() { + return [ + { + name: 'inv', + scope: this, + subscribe: this.subscribe.bind(this), + unsubscribe: this.unsubscribe.bind(this), + extraEvents: ['tx', 'block'] + } + ]; +}; + +InsightAPI.prototype.blockHandler = function(block, add, callback) { + // Notify inv subscribers + for (var i = 0; i < this.subscriptions.inv.length; i++) { + this.subscriptions.inv[i].emit('block', block.hash); + } + + setImmediate(function() { + callback(null, []); + }); +}; + +InsightAPI.prototype.transactionHandler = function(txInfo) { + if(txInfo.mempool) { + var tx = Transaction().fromBuffer(txInfo.buffer); + tx = this.txController.transformTransaction(tx); + + for (var i = 0; i < this.subscriptions.inv.length; i++) { + this.subscriptions.inv[i].emit('tx', tx); + } + } +}; + +InsightAPI.prototype.subscribe = function(emitter) { + $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); + + var emitters = this.subscriptions.inv; + var index = emitters.indexOf(emitter); + if(index === -1) { + emitters.push(emitter); + } +}; + +InsightAPI.prototype.unsubscribe = function(emitter) { + $.checkArgument(emitter instanceof EventEmitter, 'First argument is expected to be an EventEmitter'); + + var emitters = this.subscriptions.inv; + var index = emitters.indexOf(emitter); + if(index > -1) { + emitters.splice(index, 1); + } +}; + module.exports = InsightAPI; \ No newline at end of file diff --git a/lib/transactions.js b/lib/transactions.js index c08e731..876d57b 100644 --- a/lib/transactions.js +++ b/lib/transactions.js @@ -70,7 +70,7 @@ TxController.prototype.transformTransaction = function(transaction) { transformed.blockhash = transaction.__blockHash; transformed.confirmations = confirmations; - transformed.time = transaction.__timestamp ? Math.round(transaction.__timestamp / 1000) : Math.round(Date.now() / 1000); // can we get this from bitcoind? + transformed.time = transaction.__timestamp ? transaction.__timestamp : Math.round(Date.now() / 1000); // can we get this from bitcoind? transformed.blocktime = transformed.time; if(transaction.isCoinbase()) { @@ -188,7 +188,7 @@ TxController.prototype.list = function(req, res) { async.mapSeries(txs, function(tx, next) { tx.__blockHash = block.hash; tx.__height = blockInfo.height; - tx.__timestamp = block.header.time * 1000; + tx.__timestamp = block.header.time; tx.populateInputs(self.node.services.db, [], function(err) { if(err) { diff --git a/test/addresses.js b/test/addresses.js index 83ffd4b..79ac3d6 100644 --- a/test/addresses.js +++ b/test/addresses.js @@ -5,36 +5,6 @@ var AddressController = require('../lib/addresses'); var _ = require('lodash'); var bitcore = require('bitcore'); -var diff = function(a, b) { - if(Array.isArray(a)) { - var r = []; - for(var i = 0; i < a.length; i++) { - if(b[i] === a[i]) { - break; - } else { - if(_.isObject(a[i])) { - r.push(diff(a[i], b[i])); - } else { - r.push(a[i]); - } - } - } - - return r; - } else { - var r = {}; - _.each(a, function(v,k) { - if(b[k] === v) return; - // but what if it returns an empty object? still attach? - r[k] = _.isObject(v) - ? diff(v, b[k]) - : v - ; - }); - return r; - } -} - var txinfos = [ { "address": "mkPvAKZ2rar6qeG3KjBtJHHMSP1wFZH7Er", @@ -163,7 +133,7 @@ var tx = bitcore.Transaction().fromObject({ }); tx.__height = 534181; -tx.__timestamp = 1441116143000; +tx.__timestamp = 1441116143; tx.__blockHash = '0000000000000041ddc94ecf4f86a456a83b2e320c36c6f0c13ff92c7e75f013'; var txinfos2 = [ { diff --git a/test/transactions.js b/test/transactions.js index a7b4331..def82bd 100644 --- a/test/transactions.js +++ b/test/transactions.js @@ -5,49 +5,6 @@ var bitcore = require('bitcore'); var TxController = require('../lib/transactions'); var _ = require('lodash'); -/*var diff = function(a, b) { - var r = {}; - _.each(a, function(v,k) { - if(b[k] === v) return; - // but what if it returns an empty object? still attach? - r[k] = _.isObject(v) - ? diff(v, b[k]) - : v - ; - }); - return r; -};*/ - -var diff = function(a, b) { - if(Array.isArray(a)) { - var r = []; - for(var i = 0; i < a.length; i++) { - if(b[i] === a[i]) { - break; - } else { - if(_.isObject(a[i])) { - r.push(diff(a[i], b[i])); - } else { - r.push(a[i]); - } - } - } - - return r; - } else { - var r = {}; - _.each(a, function(v,k) { - if(b[k] === v) return; - // but what if it returns an empty object? still attach? - r[k] = _.isObject(v) - ? diff(v, b[k]) - : v - ; - }); - return r; - } -} - describe('Transactions', function() { describe('/tx/:txid', function() { it('should have correct data', function(done) { @@ -215,7 +172,7 @@ describe('Transactions', function() { var bitcoreTx = bitcore.Transaction(bitcoreTxObj); bitcoreTx.__blockHash = '0000000000000afa0c3c0afd450c793a1e300ec84cbe9555166e06132f19a8f7'; bitcoreTx.__height = 533974; - bitcoreTx.__timestamp = 1440987503000; + bitcoreTx.__timestamp = 1440987503; bitcoreTx.populateInputs = sinon.stub().callsArg(2); bitcoreTx.toObject = sinon.stub().returns(bitcoreTxObj); @@ -657,11 +614,11 @@ describe('Transactions', function() { ]; txinfos[0].tx.__blockHash = '00000000000001001aba15de213648f370607fb048288dd27b96f7e833a73520'; - txinfos[0].tx.__timestamp = 1441068774000; + txinfos[0].tx.__timestamp = 1441068774; txinfos[0].tx.__height = 534105; txinfos[1].tx.__blockHash = '0000000000000a3acc1f7fe72917eb48bb319ed96c125a6dfcc0ba6acab3c4d0'; - txinfos[1].tx.__timestamp = 1441072817000; + txinfos[1].tx.__timestamp = 1441072817; txinfos[1].tx.__height = 534110; var node = { @@ -877,7 +834,6 @@ describe('Transactions', function() { var res = { jsonp: function(data) { - var d = diff(insight, data); var merged = _.merge(data, todos); should(merged).eql(insight); done(); From 546c4a3345edd123e9aa59b225e2f9b617d1b954 Mon Sep 17 00:00:00 2001 From: Patrick Nagurny Date: Thu, 3 Sep 2015 17:44:19 -0400 Subject: [PATCH 4/4] remove apiPrefix --- lib/index.js | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/index.js b/lib/index.js index 94d6a86..f570883 100644 --- a/lib/index.js +++ b/lib/index.js @@ -32,52 +32,50 @@ InsightAPI.prototype.start = function(callback) { }; InsightAPI.prototype.setupRoutes = function(app) { - var apiPrefix = ''; - //Block routes var blocks = new BlockController(this.node); - app.get(apiPrefix + '/blocks', blocks.list.bind(blocks)); + app.get('/blocks', blocks.list.bind(blocks)); - app.get(apiPrefix + '/block/:blockHash', blocks.show.bind(blocks)); + app.get('/block/:blockHash', blocks.show.bind(blocks)); app.param('blockHash', blocks.block.bind(blocks)); - app.get(apiPrefix + '/block-index/:height', blocks.blockIndex.bind(blocks)); + app.get('/block-index/:height', blocks.blockIndex.bind(blocks)); app.param('height', blocks.blockIndex.bind(blocks)); // Transaction routes var transactions = new TxController(this.node); - app.get(apiPrefix + '/tx/:txid', transactions.show.bind(transactions)); + app.get('/tx/:txid', transactions.show.bind(transactions)); app.param('txid', transactions.transaction.bind(transactions)); - app.get(apiPrefix + '/txs', transactions.list.bind(transactions)); - app.post(apiPrefix + '/tx/send', transactions.send.bind(transactions)); + app.get('/txs', transactions.list.bind(transactions)); + app.post('/tx/send', transactions.send.bind(transactions)); // Raw Routes - app.get(apiPrefix + '/rawtx/:txid', transactions.showRaw.bind(transactions)); + app.get('/rawtx/:txid', transactions.showRaw.bind(transactions)); app.param('txid', transactions.rawTransaction.bind(transactions)); // Address routes var addresses = new AddressController(this.node); - app.get(apiPrefix + '/addr/:addr', addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); - app.get(apiPrefix + '/addr/:addr/utxo', addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); - app.get(apiPrefix + '/addrs/:addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); - app.post(apiPrefix + '/addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); - app.get(apiPrefix + '/addrs/:addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); - app.post(apiPrefix + '/addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); + app.get('/addr/:addr', addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); + app.get('/addr/:addr/utxo', addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); + app.get('/addrs/:addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); + app.post('/addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); + app.get('/addrs/:addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); + app.post('/addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); // Address property routes - app.get(apiPrefix + '/addr/:addr/balance', addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); - app.get(apiPrefix + '/addr/:addr/totalReceived', addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); - app.get(apiPrefix + '/addr/:addr/totalSent', addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); - app.get(apiPrefix + '/addr/:addr/unconfirmedBalance', addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); + app.get('/addr/:addr/balance', addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); + app.get('/addr/:addr/totalReceived', addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); + app.get('/addr/:addr/totalSent', addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); + app.get('/addr/:addr/unconfirmedBalance', addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); // Status route var status = new StatusController(this.node); - app.get(apiPrefix + '/status', status.show.bind(status)); - app.get(apiPrefix + '/sync', status.sync.bind(status)); - app.get(apiPrefix + '/peer', status.peer.bind(status)); - app.get(apiPrefix + '/version', status.version.bind(status)); + app.get('/status', status.show.bind(status)); + app.get('/sync', status.sync.bind(status)); + app.get('/peer', status.peer.bind(status)); + app.get('/version', status.version.bind(status)); // Utils route