bitcoind: paginate txids in address summary

so that one request doesn't yield a 80MB response
This commit is contained in:
Braydon Fuller 2016-05-10 13:48:56 -04:00
parent 0387c1a6e4
commit 75c43559d4
2 changed files with 83 additions and 20 deletions

View File

@ -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();
});
}

View File

@ -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({