diff --git a/lib/services/bitcoind.js b/lib/services/bitcoind.js index b86174ef..54d2cee4 100644 --- a/lib/services/bitcoind.js +++ b/lib/services/bitcoind.js @@ -62,6 +62,7 @@ util.inherits(Bitcoin, Service); Bitcoin.dependencies = []; +Bitcoin.DEFAULT_MAX_TXIDS = 1000; Bitcoin.DEFAULT_MAX_HISTORY = 10; Bitcoin.DEFAULT_SHUTDOWN_TIMEOUT = 15000; Bitcoin.DEFAULT_ZMQ_SUBSCRIBE_PROGRESS = 0.9999; @@ -89,6 +90,7 @@ Bitcoin.DEFAULT_CONFIG_SETTINGS = { Bitcoin.prototype._initDefaults = function(options) { // limits + this.maxTxids = options.maxTxids || Bitcoin.DEFAULT_MAX_TXIDS; this.maxTransactionHistory = options.maxTransactionHistory || Bitcoin.DEFAULT_MAX_HISTORY; this.maxAddressesQuery = options.maxAddressesQuery || Bitcoin.DEFAULT_MAX_ADDRESSES_QUERY; this.shutdownTimeout = options.shutdownTimeout || Bitcoin.DEFAULT_SHUTDOWN_TIMEOUT; @@ -1223,10 +1225,6 @@ Bitcoin.prototype._paginateTxids = function(fullTxids, fromArg, toArg) { var to = parseInt(toArg); if (from >= 0 && to >= 0) { $.checkState(from < to, '"from" (' + from + ') is expected to be less than "to" (' + to + ')'); - $.checkState( - (to - from) <= this.maxTransactionHistory, - '"from" (' + from + ') and "to" (' + to + ') range should be less than or equal to ' + this.maxTransactionHistory - ); txids = fullTxids.slice(from, to); } else { txids = fullTxids; @@ -1250,6 +1248,13 @@ Bitcoin.prototype.getAddressHistory = function(addressArg, options, callback) { var queryMempool = _.isUndefined(options.queryMempool) ? true : options.queryMempool; var addressStrings = this._getAddressStrings(addresses); + if ((options.to - options.from) > self.maxTransactionHistory) { + return callback(new Error( + '"from" (' + options.from + ') and "to" (' + options.to + ') range should be less than or equal to ' + + self.maxTransactionHistory + )); + } + self.getAddressTxids(addresses, options, function(err, txids) { if (err) { return callback(err); @@ -1298,6 +1303,33 @@ Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) { var addresses = self._normalizeAddressArg(addressArg); var cacheKey = addresses.join(''); + function finishWithTxids() { + if (!options.noTxList) { + var allTxids = mempoolTxids.reverse().concat(summaryTxids); + var fromArg = parseInt(options.from || 0); + var toArg = parseInt(options.to || self.maxTxids); + + if ((toArg - fromArg) > self.maxTxids) { + return callback(new Error( + '"from" (' + fromArg + ') and "to" (' + toArg + ') range should be less than or equal to ' + + self.maxTxids + )); + } + var paginatedTxids; + try { + paginatedTxids = self._paginateTxids(allTxids, fromArg, toArg); + } catch(e) { + return callback(e); + } + + var allSummary = _.clone(summary); + allSummary.txids = paginatedTxids; + callback(null, allSummary); + } else { + callback(null, summary); + } + } + function querySummary() { async.parallel([ function getTxList(done) { @@ -1340,14 +1372,7 @@ Bitcoin.prototype.getAddressSummary = function(addressArg, options, callback) { return callback(err); } self.summaryCache.set(cacheKey, summary); - if (!options.noTxList) { - var allTxids = mempoolTxids.reverse().concat(summaryTxids); - var allSummary = _.clone(summary); - allSummary.txids = allTxids; - callback(null, allSummary); - } else { - callback(null, summary); - } + finishWithTxids(); }); } diff --git a/test/services/bitcoind.unit.js b/test/services/bitcoind.unit.js index bbc88a8b..2f8875e7 100644 --- a/test/services/bitcoind.unit.js +++ b/test/services/bitcoind.unit.js @@ -2158,13 +2158,6 @@ describe('Bitcoin Service', function() { var paginated = bitcoind._paginateTxids(txids, 3, 13); paginated.should.deep.equal([3, 4, 5, 6, 7, 8, 9, 10]); }); - it('slice txids based on "from" and "to" (3 to 30)', function() { - var bitcoind = new BitcoinService(baseConfig); - var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - (function() { - bitcoind._paginateTxids(txids, 3, 30); - }).should.throw(Error); - }); it('slice txids based on "from" and "to" (0 to 3)', function() { var bitcoind = new BitcoinService(baseConfig); var txids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; @@ -2194,6 +2187,14 @@ describe('Bitcoin Service', function() { describe('#getAddressHistory', function() { var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX'; + it('will give error with "from" and "to" range that exceeds max size', function(done) { + var bitcoind = new BitcoinService(baseConfig); + bitcoind.getAddressHistory(address, {from: 0, to: 30}, function(err) { + should.exist(err); + err.message.match(/^\"from/); + done(); + }); + }); it('will give an error if length of addresses is too long', function(done) { var addresses = []; for (var i = 0; i < 101; i++) { @@ -2241,7 +2242,6 @@ describe('Bitcoin Service', function() { var txid3 = '57b7842afc97a2b46575b490839df46e9273524c6ea59ba62e1e86477cf25247'; var memtxid1 = 'b1bfa8dbbde790cb46b9763ef3407c1a21c8264b67bfe224f462ec0e1f569e92'; var memtxid2 = 'e9dcf22807db77ac0276b03cc2d3a8b03c4837db8ac6650501ef45af1c807cce'; - it('will handle error from getAddressTxids', function(done) { var bitcoind = new BitcoinService(baseConfig); bitcoind.nodes.push({ @@ -2326,6 +2326,7 @@ describe('Bitcoin Service', function() { }) } }); + sinon.spy(bitcoind, '_paginateTxids'); bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]); bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, { received: 30 * 1e8, @@ -2334,6 +2335,9 @@ describe('Bitcoin Service', function() { var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj'; var options = {}; bitcoind.getAddressSummary(address, options, function(err, summary) { + bitcoind._paginateTxids.callCount.should.equal(1); + bitcoind._paginateTxids.args[0][1].should.equal(0); + bitcoind._paginateTxids.args[0][2].should.equal(1000); summary.appearances.should.equal(3); summary.totalReceived.should.equal(3000000000); summary.totalSpent.should.equal(1000000000); @@ -2350,6 +2354,40 @@ describe('Bitcoin Service', function() { done(); }); }); + it('will give error with "from" and "to" range that exceeds max size', function(done) { + var bitcoind = new BitcoinService(baseConfig); + bitcoind.nodes.push({ + client: { + getAddressMempool: sinon.stub().callsArgWith(1, null, { + result: [ + { + txid: memtxid1, + satoshis: -1000000 + }, + { + txid: memtxid2, + satoshis: 99999 + } + ] + }) + } + }); + bitcoind.getAddressTxids = sinon.stub().callsArgWith(2, null, [txid1, txid2, txid3]); + bitcoind.getAddressBalance = sinon.stub().callsArgWith(2, null, { + received: 30 * 1e8, + balance: 20 * 1e8 + }); + var address = '3NbU8XzUgKyuCgYgZEKsBtUvkTm2r7Xgwj'; + var options = { + from: 0, + to: 1001 + }; + bitcoind.getAddressSummary(address, options, function(err) { + should.exist(err); + err.message.match(/^\"from/); + done(); + }); + }); it('will get from cache with noTxList', function(done) { var bitcoind = new BitcoinService(baseConfig); bitcoind.nodes.push({