From 6dfaefdcc57427bc64979e7cf47e3669003b4989 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 17 Nov 2014 16:24:48 -0300 Subject: [PATCH 1/3] added pagination to addrs/txs --- app/controllers/addresses.js | 35 +++++++++++++++++++++++++++++++---- app/models/Address.js | 27 +++++++++++++++++---------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/app/controllers/addresses.js b/app/controllers/addresses.js index 0686422a..9e6c55c3 100644 --- a/app/controllers/addresses.js +++ b/app/controllers/addresses.js @@ -94,8 +94,23 @@ exports.multiutxo = function(req, res, next) { exports.multitxs = function(req, res, next) { - function processTxs(txs, cb) { - txs = _.uniq(_.flatten(txs)); + function processTxs(txs, from, to, cb) { + txs = _.uniq(_.flatten(txs), 'txid'); + + var nbTxs = txs.length; + var paginated = !_.isUndefined(from) || !_.isUndefined(to); + + if (paginated) { + txs.sort(function(a, b) { + return (b.ts || b.ts) - (a.ts || a.ts); + }); + var start = Math.max(from || 0, 0); + var end = Math.min(to || txs.length, txs.length); + txs = txs.slice(start, end); + } + + txs = _.pluck(txs, 'txid'); + var transactions = []; async.each(txs, function (tx, callback) { tDb.fromIdWithInfo(tx, function(err, tx) { @@ -107,10 +122,22 @@ exports.multitxs = function(req, res, next) { }); }, function (err) { if (err) return cb(err); + + if (paginated) { + transactions = { + nbItems: nbTxs, + from: +from, + to: +to, + data: transactions, + }; + } return cb(null, transactions); }); }; + var from = req.query.from; + var to = req.query.to; + var as = getAddrs(req, res, next); if (as) { var txs = []; @@ -119,10 +146,10 @@ exports.multitxs = function(req, res, next) { if (err) callback(err); txs = txs.concat(a.transactions); callback(); - }, {ignoreCache: req.param('noCache')}); + }, {ignoreCache: req.param('noCache'), includeTxInfo: true}); }, function(err) { // finished callback if (err) return common.handleErrors(err, res); - processTxs(txs, function (err, transactions) { + processTxs(txs, from, to, function (err, transactions) { if (err) return common.handleErrors(err, res); res.jsonp(transactions); }); diff --git a/app/models/Address.js b/app/models/Address.js index 2f8e8ec1..1cfe7e76 100644 --- a/app/models/Address.js +++ b/app/models/Address.js @@ -93,25 +93,31 @@ Address.prototype.getObj = function() { }; }; -Address.prototype._addTxItem = function(txItem, txList) { +Address.prototype._addTxItem = function(txItem, txList, includeInfo) { + function addTx(data) { + if (!txList) return; + if (includeInfo) { + txList.push(data); + } else { + txList.push(data.txid); + } + }; + var add=0, addSpend=0; var v = txItem.value_sat; var seen = this.seen; // Founding tx - if ( !seen[txItem.txid] ) { - seen[txItem.txid]=1; - add=1; + if (!seen[txItem.txid]) { + seen[txItem.txid] = 1; + add = 1; - if (txList) - txList.push(txItem.txid); + addTx({ txid: txItem.txid, ts: txItem.ts }); } // Spent tx if (txItem.spentTxId && !seen[txItem.spentTxId] ) { - if (txList) { - txList.push(txItem.spentTxId); - } + addTx({ txid: txItem.spentTxId, ts: txItem.spentTs }); seen[txItem.spentTxId]=1; addSpend=1; } @@ -143,6 +149,7 @@ Address.prototype._addTxItem = function(txItem, txList) { // opts are // .onlyUnspent // .txLimit (=0 -> no txs, => -1 no limit) +// .includeTxInfo // Address.prototype.update = function(next, opts) { var self = this; @@ -188,7 +195,7 @@ Address.prototype.update = function(next, opts) { } else { txOut.forEach(function(txItem){ - self._addTxItem(txItem, txList); + self._addTxItem(txItem, txList, opts.includeTxInfo); }); if (txList) self.transactions = txList; From 51b6416f1adeee96cf8e363b3a0b551cbf5b9896 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Mon, 17 Nov 2014 16:27:46 -0300 Subject: [PATCH 2/3] renamed fields in resultset --- app/controllers/addresses.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/addresses.js b/app/controllers/addresses.js index 9e6c55c3..cd8c05f7 100644 --- a/app/controllers/addresses.js +++ b/app/controllers/addresses.js @@ -125,10 +125,10 @@ exports.multitxs = function(req, res, next) { if (paginated) { transactions = { - nbItems: nbTxs, + totalItems: nbTxs, from: +from, to: +to, - data: transactions, + items: transactions, }; } return cb(null, transactions); From c07b770685b758141509d80490780c4018418865 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 20 Nov 2014 12:20:47 -0300 Subject: [PATCH 3/3] integration tests --- app/controllers/addresses.js | 14 +++---- test/integration/txs.js | 71 ++++++++++++++++++++++++++++++++++++ test/integration/txs.json | 32 ++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 test/integration/txs.js create mode 100644 test/integration/txs.json diff --git a/app/controllers/addresses.js b/app/controllers/addresses.js index cd8c05f7..e9929a9b 100644 --- a/app/controllers/addresses.js +++ b/app/controllers/addresses.js @@ -96,7 +96,6 @@ exports.multitxs = function(req, res, next) { function processTxs(txs, from, to, cb) { txs = _.uniq(_.flatten(txs), 'txid'); - var nbTxs = txs.length; var paginated = !_.isUndefined(from) || !_.isUndefined(to); @@ -109,20 +108,18 @@ exports.multitxs = function(req, res, next) { txs = txs.slice(start, end); } - txs = _.pluck(txs, 'txid'); - - var transactions = []; async.each(txs, function (tx, callback) { - tDb.fromIdWithInfo(tx, function(err, tx) { + tDb.fromIdWithInfo(tx.txid, function(err, tx) { if (err) console.log(err); if (tx && tx.info) { - transactions.push(tx.info); + _.find(txs, { txid: tx.txid }).info = tx.info; } callback(); }); }, function (err) { if (err) return cb(err); - + + var transactions = _.pluck(txs, 'info'); if (paginated) { transactions = { totalItems: nbTxs, @@ -144,7 +141,7 @@ exports.multitxs = function(req, res, next) { async.each(as, function(a, callback) { a.update(function(err) { if (err) callback(err); - txs = txs.concat(a.transactions); + txs.push(a.transactions); callback(); }, {ignoreCache: req.param('noCache'), includeTxInfo: true}); }, function(err) { // finished callback @@ -152,6 +149,7 @@ exports.multitxs = function(req, res, next) { processTxs(txs, from, to, function (err, transactions) { if (err) return common.handleErrors(err, res); res.jsonp(transactions); + return next(); }); }); } diff --git a/test/integration/txs.js b/test/integration/txs.js new file mode 100644 index 00000000..33f54bea --- /dev/null +++ b/test/integration/txs.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +'use strict'; + +process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + +var _ = require('lodash'); +var async = require('async'); +var assert = require('assert'); +var fs = require('fs'); +var Address = require('../../app/models/Address'); +var addresses = require('../../app/controllers/addresses'); +var TransactionDb = require('../../lib/TransactionDb').default(); +var fixture = JSON.parse(fs.readFileSync('test/integration/txs.json')); +var should = require('chai'); +var sinon = require('sinon'); + +var txDb; +describe('Transactions for multiple addresses', function() { + this.timeout(5000); + + var req, res; + before(function(c) { + txDb = TransactionDb; + + var i = 0; + _.each(_.flatten(_.pluck(fixture, 'addrs')), function(addr) { + TransactionDb.deleteCacheForAddress(addr, function() { + if (++i === fixture.length) return c(); + }); + }); + }); + + beforeEach(function(c) { + req = {}; + res = {}; + req.query = {}; + res.jsonp = sinon.spy(); + return c(); + }); + + describe('All', function () { + _.each(fixture, function (f) { + it(f.test, function (done) { + req.param = sinon.stub().withArgs('addrs').returns(f.addrs.join(',')); + var paginated = !_.isUndefined(f.from) || !_.isUndefined(f.to); + if (paginated) { + req.query = { + from: f.from, + to: f.to + }; + } + addresses.multitxs(req, res, function() { + var txs = res.jsonp.getCall(0).args[0]; + txs.should.exist; + if (paginated) { + txs.totalItems.should.equal(f.totalTransactions); + txs.items.length.should.equal(f.returnedTransactions); + if (f.transactions) { + JSON.stringify(_.pluck(txs.items, 'txid')).should.equal(JSON.stringify(f.transactions)); + } + } else { + txs.should.be.instanceof(Array); + txs.length.should.equal(f.returnedTransactions); + } + done(); + }); + }); + }); + }); +}); diff --git a/test/integration/txs.json b/test/integration/txs.json new file mode 100644 index 00000000..88abb2b1 --- /dev/null +++ b/test/integration/txs.json @@ -0,0 +1,32 @@ +[{ + "test": "should return non-paginated txs for single address", + "addrs": ["mtA6woo1wjCeu1dLkWgpSD3tRnRfrHt3FL"], + "totalTransactions": 13, + "returnedTransactions": 13 +},{ + "test": "should return paginated txs for single address", + "addrs": ["mtA6woo1wjCeu1dLkWgpSD3tRnRfrHt3FL"], + "totalTransactions": 13, + "from": 8, + "to": 12, + "returnedTransactions": 4 +},{ + "test": "should return non-paginated txs for multiple addresses", + "addrs": ["mqtYvjsUg59XpngBAYjFF51Xauc28a6Ckd","myDZmGN9euuRhM1mniSaXhQjNdqFmUzUkj"], + "totalTransactions": 4, + "returnedTransactions": 4 +},{ + "test": "should return paginated txs for multiple addresses", + "addrs": ["mqtYvjsUg59XpngBAYjFF51Xauc28a6Ckd","myDZmGN9euuRhM1mniSaXhQjNdqFmUzUkj", "n2NbTWCuUyTvpA2YKTpVD4SmCouFLcm8rS"], + "totalTransactions": 6, + "from": 2, + "to": 6, + "returnedTransactions": 4, + "transactions": [ + "3e81723d069b12983b2ef694c9782d32fca26cc978de744acbc32c3d3496e915", + "b701b0680bc2b6f8b58f13d035a754486ecefd2438e3495cd8f395ab7f5e7ba9", + "80fb4fd2a6e5e795a4f30aebeda49424e7ed4d3630b128efb946aa964e0bc9c0", + "855e881892d7cd4a8fa81dbd8f6e9d142d02bffc10ffbe5428036ee55c3e3e0f" + ] +} +]