Address Service: Removed caching and added max query limits

Querying addresses that have millions of transactions is supported however
takes hundreds of seconds to fully calculate the balance. Creating a cache of
previous results wasn't currently working because the `isSpent` query is always
based on the current bitcoind tip. Thus the balance of the outputs would be included
however wouldn't be removed when spent as the output wouldn't be checked again
when querying for blocks past the last checkpoint. Including the satoshis in the
inputs address index would make it possible to subtract the spent amount,
however this degrades optimizations elsewhere. The syncing times or querying
for addresses with 10,000 transactions per address.

It may preferrable to have an additional address service that handles high-volume
addresses be on an opt-in basis so that a custom running client could select
high volume addresses to create optimizations for querying balances and history.
The strategies for creating indexes differs on these use cases.
This commit is contained in:
Braydon Fuller 2016-01-14 17:07:44 -05:00
parent 4fcec8755c
commit ead6c2f45f
2 changed files with 92 additions and 638 deletions

View File

@ -44,12 +44,10 @@ var AddressService = function(options) {
this.node.services.bitcoind.on('tx', this.transactionHandler.bind(this));
this.node.services.bitcoind.on('txleave', this.transactionLeaveHandler.bind(this));
this.summaryCacheThreshold = options.summaryCacheThreshold || constants.SUMMARY_CACHE_THRESHOLD;
this.maxInputsQueryLength = options.maxInputsQueryLength || constants.MAX_INPUTS_QUERY_LENGTH;
this.maxOutputsQueryLength = options.maxOutputsQueryLength || constants.MAX_OUTPUTS_QUERY_LENGTH;
this._setMempoolIndexPath();
this._setSummaryCachePath();
if (options.mempoolMemoryIndex) {
this.levelupStore = memdown;
} else {
@ -98,19 +96,6 @@ AddressService.prototype.start = function(callback) {
},
next
);
},
function(next) {
self.summaryCache = levelup(
self.summaryCachePath,
{
db: self.levelupStore,
keyEncoding: 'binary',
valueEncoding: 'binary',
fillCache: false,
maxOpenFiles: 200
},
next
);
}
], callback);
@ -121,14 +106,6 @@ AddressService.prototype.stop = function(callback) {
this.mempoolIndex.close(callback);
};
/**
* This function will set `this.summaryCachePath` based on `this.node.network`.
* @private
*/
AddressService.prototype._setSummaryCachePath = function() {
this.summaryCachePath = this._getDBPathFor('bitcore-addresssummary.db');
};
/**
* This function will set `this.mempoolIndexPath` based on `this.node.network`.
* @private
@ -1357,21 +1334,20 @@ AddressService.prototype.getAddressSummary = function(addressArg, options, callb
function(next) {
self._getAddressConfirmedSummary(address, options, next);
},
function(cache, next) {
self._getAddressMempoolSummary(address, options, cache, next);
function(result, next) {
self._getAddressMempoolSummary(address, options, result, next);
}
], function(err, cache) {
], function(err, result) {
if (err) {
return callback(err);
}
var summary = self._transformAddressSummaryFromCache(cache, options);
var summary = self._transformAddressSummaryFromCache(result, options);
var timeDelta = new Date() - startTime;
if (timeDelta > 5000) {
var seconds = Math.round(timeDelta / 1000);
log.warn('Slow (' + seconds + 's) getAddressSummary request for address: ' + address.toString());
log.warn('Address Summary:', summary);
}
callback(null, summary);
@ -1382,108 +1358,48 @@ AddressService.prototype.getAddressSummary = function(addressArg, options, callb
AddressService.prototype._getAddressConfirmedSummary = function(address, options, callback) {
var self = this;
var tipHeight = this.node.services.db.tip.__height;
self._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return callback(err);
}
// Immediately give cache is already current, otherwise update
if (cache && cache.height === tipHeight) {
return callback(null, cache);
}
self._updateAddressConfirmedSummaryCache(address, options, cache, tipHeight, callback);
});
};
AddressService.prototype._getAddressConfirmedSummaryCache = function(address, options, callback) {
var self = this;
var baseCache = {
result: {
appearanceIds: {},
totalReceived: 0,
balance: 0,
unconfirmedAppearanceIds: {},
unconfirmedBalance: 0
}
var baseResult = {
appearanceIds: {},
totalReceived: 0,
balance: 0,
unconfirmedAppearanceIds: {},
unconfirmedBalance: 0
};
// Use the base cache if the "start" and "end" options have been used
// We only save and retrieve a cache for the summary of all history
if (options.start >= 0 || options.end >= 0) {
return callback(null, baseCache);
}
var key = encoding.encodeSummaryCacheKey(address);
this.summaryCache.get(key, {
valueEncoding: 'binary',
keyEncoding: 'binary'
}, function(err, buffer) {
if (err instanceof levelup.errors.NotFoundError) {
return callback(null, baseCache);
} else if (err) {
return callback(err);
}
var cache = encoding.decodeSummaryCacheValue(buffer);
// Use base cache if the cached tip/height doesn't match (e.g. there has been a reorg)
var blockIndex = self.node.services.bitcoind.getBlockIndex(cache.height);
if (cache.hash !== blockIndex.hash) {
return callback(null, baseCache);
}
callback(null, cache);
});
};
AddressService.prototype._updateAddressConfirmedSummaryCache = function(address, options, cache, tipHeight, callback) {
var self = this;
var optionsPartial = _.clone(options);
var isHeightQuery = (options.start >= 0 || options.end >= 0);
if (!isHeightQuery) {
// We will pick up from the last point cached and query for all blocks
// proceeding the cache
var cacheHeight = _.isUndefined(cache.height) ? 0 : cache.height + 1;
optionsPartial.start = tipHeight;
optionsPartial.end = cacheHeight;
} else {
$.checkState(_.isUndefined(cache.height));
}
async.waterfall([
function(next) {
self._getAddressConfirmedInputsSummary(address, cache, optionsPartial, next);
self._getAddressConfirmedInputsSummary(address, baseResult, options, next);
},
function(cache, next) {
self._getAddressConfirmedOutputsSummary(address, cache, optionsPartial, next);
function(result, next) {
self._getAddressConfirmedOutputsSummary(address, result, options, next);
},
function(cache, next) {
self._setAndSortTxidsFromAppearanceIds(cache, next);
function(result, next) {
self._setAndSortTxidsFromAppearanceIds(result, next);
}
], function(err, cache) {
], callback);
// Skip saving the cache if the "start" or "end" options have been used, or
// if the transaction length does not exceed the caching threshold.
// We only want to cache full history results for addresses that have a large
// number of transactions.
var exceedsCacheThreshold = (cache.result.txids.length > self.summaryCacheThreshold);
if (exceedsCacheThreshold && !isHeightQuery) {
self._saveAddressConfirmedSummaryCache(address, cache, tipHeight, callback);
} else {
callback(null, cache);
}
});
};
AddressService.prototype._getAddressConfirmedInputsSummary = function(address, cache, options, callback) {
AddressService.prototype._getAddressConfirmedInputsSummary = function(address, result, options, callback) {
$.checkArgument(address instanceof Address);
var self = this;
var error = null;
var count = 0;
var inputsStream = self.createInputsStream(address, options);
inputsStream.on('data', function(input) {
var txid = input.txid;
cache.result.appearanceIds[txid] = input.height;
result.appearanceIds[txid] = input.height;
count++;
if (count > self.maxInputsQueryLength) {
log.warn('Tried to query too many inputs (' + self.maxInputsQueryLength + ') for summary of address ' + address.toString());
error = new Error('Maximum number of inputs (' + self.maxInputsQueryLength + ') per query reached');
inputsStream.pause();
inputsStream.end();
}
});
inputsStream.on('error', function(err) {
@ -1494,17 +1410,18 @@ AddressService.prototype._getAddressConfirmedInputsSummary = function(address, c
if (error) {
return callback(error);
}
callback(null, cache);
callback(null, result);
});
};
AddressService.prototype._getAddressConfirmedOutputsSummary = function(address, cache, options, callback) {
AddressService.prototype._getAddressConfirmedOutputsSummary = function(address, result, options, callback) {
$.checkArgument(address instanceof Address);
$.checkArgument(!_.isUndefined(cache.result) &&
!_.isUndefined(cache.result.appearanceIds) &&
!_.isUndefined(cache.result.unconfirmedAppearanceIds));
$.checkArgument(!_.isUndefined(result) &&
!_.isUndefined(result.appearanceIds) &&
!_.isUndefined(result.unconfirmedAppearanceIds));
var self = this;
var count = 0;
var outputStream = self.createOutputsStream(address, options);
@ -1515,13 +1432,12 @@ AddressService.prototype._getAddressConfirmedOutputsSummary = function(address,
// Bitcoind's isSpent only works for confirmed transactions
var spentDB = self.node.services.bitcoind.isSpent(txid, outputIndex);
cache.result.totalReceived += output.satoshis;
cache.result.appearanceIds[txid] = output.height;
result.totalReceived += output.satoshis;
result.appearanceIds[txid] = output.height;
if (!spentDB) {
cache.result.balance += output.satoshis;
result.balance += output.satoshis;
}
// TODO: subtract if spent (because of cache)?
if (options.queryMempool) {
// Check to see if this output is spent in the mempool and if so
@ -1532,10 +1448,19 @@ AddressService.prototype._getAddressConfirmedOutputsSummary = function(address,
);
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
if (spentMempool) {
cache.result.unconfirmedBalance -= output.satoshis;
result.unconfirmedBalance -= output.satoshis;
}
}
count++;
if (count > self.maxOutputsQueryLength) {
log.warn('Tried to query too many outputs (' + self.maxOutputsQueryLength + ') for summary of address ' + address.toString());
error = new Error('Maximum number of outputs (' + self.maxOutputsQueryLength + ') per query reached');
outputStream.pause();
outputStream.end();
}
});
var error = null;
@ -1548,40 +1473,25 @@ AddressService.prototype._getAddressConfirmedOutputsSummary = function(address,
if (error) {
return callback(error);
}
callback(null, cache);
callback(null, result);
});
};
AddressService.prototype._setAndSortTxidsFromAppearanceIds = function(cache, callback) {
cache.result.txids = Object.keys(cache.result.appearanceIds);
cache.result.txids.sort(function(a, b) {
return cache.result.appearanceIds[a] - cache.result.appearanceIds[b];
AddressService.prototype._setAndSortTxidsFromAppearanceIds = function(result, callback) {
result.txids = Object.keys(result.appearanceIds);
result.txids.sort(function(a, b) {
return result.appearanceIds[a] - result.appearanceIds[b];
});
callback(null, cache);
callback(null, result);
};
AddressService.prototype._saveAddressConfirmedSummaryCache = function(address, cache, tipHeight, callback) {
log.info('Saving address summary cache for: ' + address.toString() + 'at height: ' + tipHeight);
var key = encoding.encodeSummaryCacheKey(address);
var tipBlockIndex = this.node.services.bitcoind.getBlockIndex(tipHeight);
var value = encoding.encodeSummaryCacheValue(cache, tipHeight, tipBlockIndex.hash);
this.summaryCache.put(key, value, function(err) {
if (err) {
return callback(err);
}
callback(null, cache);
});
};
AddressService.prototype._getAddressMempoolSummary = function(address, options, cache, callback) {
AddressService.prototype._getAddressMempoolSummary = function(address, options, result, callback) {
var self = this;
// Skip if the options do not want to include the mempool
if (!options.queryMempool) {
return callback(null, cache);
return callback(null, result);
}
var addressStr = address.toString();
@ -1596,12 +1506,12 @@ AddressService.prototype._getAddressMempoolSummary = function(address, options,
}
for(var i = 0; i < mempoolInputs.length; i++) {
var input = mempoolInputs[i];
cache.result.unconfirmedAppearanceIds[input.txid] = input.timestamp;
result.unconfirmedAppearanceIds[input.txid] = input.timestamp;
}
next(null, cache);
next(null, result);
});
}, function(cache, next) {
}, function(result, next) {
self._getOutputsMempool(addressStr, hashBuffer, hashTypeBuffer, function(err, mempoolOutputs) {
if (err) {
return next(err);
@ -1609,7 +1519,7 @@ AddressService.prototype._getAddressMempoolSummary = function(address, options,
for(var i = 0; i < mempoolOutputs.length; i++) {
var output = mempoolOutputs[i];
cache.result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
result.unconfirmedAppearanceIds[output.txid] = output.timestamp;
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(
new Buffer(output.txid, 'hex'), // TODO: get buffer directly
@ -1618,19 +1528,18 @@ AddressService.prototype._getAddressMempoolSummary = function(address, options,
var spentMempool = self.mempoolSpentIndex[spentIndexSyncKey];
// Only add this to the balance if it's not spent in the mempool already
if (!spentMempool) {
cache.result.unconfirmedBalance += output.satoshis;
result.unconfirmedBalance += output.satoshis;
}
}
next(null, cache);
next(null, result);
});
}
], callback);
};
AddressService.prototype._transformAddressSummaryFromCache = function(cache, options) {
AddressService.prototype._transformAddressSummaryFromCache = function(result, options) {
var result = cache.result;
var confirmedTxids = cache.result.txids;
var confirmedTxids = result.txids;
var unconfirmedTxids = Object.keys(result.unconfirmedAppearanceIds);
var summary = {

View File

@ -100,7 +100,7 @@ describe('Address Service', function() {
done();
});
});
it('start levelup db for mempool and summary index', function(done) {
it('start levelup db for mempool', function(done) {
var levelupStub = sinon.stub().callsArg(2);
var TestAddressService = proxyquire('../../../lib/services/address', {
'fs': {
@ -117,7 +117,7 @@ describe('Address Service', function() {
node: mocknode
});
am.start(function() {
levelupStub.callCount.should.equal(2);
levelupStub.callCount.should.equal(1);
var dbPath1 = levelupStub.args[0][0];
dbPath1.should.equal('testdir/testnet3/bitcore-addressmempool.db');
var options = levelupStub.args[0][1];
@ -125,8 +125,6 @@ describe('Address Service', function() {
options.keyEncoding.should.equal('binary');
options.valueEncoding.should.equal('binary');
options.fillCache.should.equal(false);
var dbPath2 = levelupStub.args[1][0];
dbPath2.should.equal('testdir/testnet3/bitcore-addresssummary.db');
done();
});
});
@ -2115,457 +2113,14 @@ describe('Address Service', function() {
addressService._getAddressMempoolSummary = sinon.stub().callsArgWith(3, null, cache);
addressService._transformAddressSummaryFromCache = sinon.stub().returns(summary);
addressService.getAddressSummary(address, options, function() {
log.warn.callCount.should.equal(2);
log.warn.callCount.should.equal(1);
done();
});
clock.tick(6000);
});
});
describe('#_getAddressConfirmedSummary', function() {
it('handle error from _getAddressConfirmedSummaryCache', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
},
db: {
tip: {
__height: 10
}
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
var options = {};
addressService._getAddressConfirmedSummaryCache = sinon.stub().callsArgWith(2, new Error('test'));
addressService._getAddressConfirmedSummary(address, options, function(err) {
should.exist(err);
err.message.should.equal('test');
done();
});
});
it('will NOT update cache if matches current tip', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
},
db: {
tip: {
__height: 10
}
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
var options = {};
var cache = {
height: 10
};
addressService._updateAddressConfirmedSummaryCache = sinon.stub();
addressService._getAddressConfirmedSummaryCache = sinon.stub().callsArgWith(2, null, cache);
addressService._getAddressConfirmedSummary(address, options, function(err, cache) {
if (err) {
return done(err);
}
should.exist(cache);
addressService._updateAddressConfirmedSummaryCache.callCount.should.equal(0);
done();
});
});
it('will call _updateAddressConfirmedSummaryCache with correct arguments', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
},
db: {
tip: {
__height: 11
}
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = '12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX';
var options = {};
var cache = {
height: 10
};
addressService._updateAddressConfirmedSummaryCache = sinon.stub().callsArgWith(4, null, cache);
addressService._getAddressConfirmedSummaryCache = sinon.stub().callsArgWith(2, null, cache);
addressService._getAddressConfirmedSummary(address, options, function(err, cache) {
if (err) {
return done(err);
}
should.exist(cache);
addressService._updateAddressConfirmedSummaryCache.callCount.should.equal(1);
var args = addressService._updateAddressConfirmedSummaryCache.args[0];
args[0].should.equal(address);
args[1].should.equal(options);
args[2].should.equal(cache);
args[3].should.equal(11);
done();
});
});
});
describe('#_getAddressConfirmedSummaryCache', function() {
function shouldExistBasecache(cache) {
should.exist(cache);
should.not.exist(cache.height);
should.exist(cache.result);
cache.result.appearanceIds.should.deep.equal({});
cache.result.totalReceived.should.equal(0);
cache.result.balance.should.equal(0);
cache.result.unconfirmedAppearanceIds.should.deep.equal({});
cache.result.unconfirmedBalance.should.equal(0);
}
it('give base cache if "start" or "end" options are used (e.g. >= 0)', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {
start: 0,
end: 0
};
addressService._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return done(err);
}
shouldExistBasecache(cache);
done();
});
});
it('give base cache if "start" or "end" options are used (e.g. 10, 9)', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {
start: 10,
end: 9
};
addressService._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return done(err);
}
shouldExistBasecache(cache);
done();
});
});
it('give base cache if cache does NOT exist', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {};
addressService.summaryCache = {};
addressService.summaryCache.get = sinon.stub().callsArgWith(2, new levelup.errors.NotFoundError());
addressService._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return done(err);
}
shouldExistBasecache(cache);
done();
});
});
it('give base cache if cached tip hash differs (e.g. reorg)', function(done) {
var hash = '000000002c05cc2e78923c34df87fd108b22221ac6076c18f3ade378a4d915e9';
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
getBlockIndex: sinon.stub().returns({
hash: '00000000700e92a916b46b8b91a14d1303d5d91ef0b09eecc3151fb958fd9a2e'
})
},
db: {
tip: {
hash: hash
}
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var txid = '5464b1c3f25160f0183fad68a406838d2d1ac0aee05990072ece49326c26c22e';
var options = {};
var cache = {
height: 10,
hash: hash,
result: {
totalReceived: 100,
balance: 10,
txids: [txid],
appearanceIds: {
'5464b1c3f25160f0183fad68a406838d2d1ac0aee05990072ece49326c26c22e': 9
}
}
};
var cacheBuffer = encoding.encodeSummaryCacheValue(cache, 10, hash);
addressService.summaryCache = {};
addressService.summaryCache.get = sinon.stub().callsArgWith(2, null, cacheBuffer);
addressService._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return done(err);
}
shouldExistBasecache(cache);
done();
});
});
it('handle error from levelup', function(done) {
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {};
addressService.summaryCache = {};
addressService.summaryCache.get = sinon.stub().callsArgWith(2, new Error('test'));
addressService._getAddressConfirmedSummaryCache(address, options, function(err) {
should.exist(err);
err.message.should.equal('test');
done();
});
});
it('call encode and decode with args and result', function(done) {
var hash = '000000002c05cc2e78923c34df87fd108b22221ac6076c18f3ade378a4d915e9';
var testnode = {
services: {
bitcoind: {
on: sinon.stub(),
getBlockIndex: sinon.stub().returns({
hash: hash
})
},
db: {
tip: {
hash: hash
}
}
},
datadir: 'testdir'
};
var addressService = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var txid = '5464b1c3f25160f0183fad68a406838d2d1ac0aee05990072ece49326c26c22e';
var options = {};
var cache = {
height: 10,
hash: hash,
result: {
totalReceived: 100,
balance: 10,
txids: [txid],
appearanceIds: {
'5464b1c3f25160f0183fad68a406838d2d1ac0aee05990072ece49326c26c22e': 9
}
}
};
var cacheBuffer = encoding.encodeSummaryCacheValue(cache, 10, hash);
addressService.summaryCache = {};
addressService.summaryCache.get = sinon.stub().callsArgWith(2, null, cacheBuffer);
addressService._getAddressConfirmedSummaryCache(address, options, function(err, cache) {
if (err) {
return done(err);
}
should.exist(cache);
cache.height.should.equal(10);
cache.hash.should.equal(hash);
should.exist(cache.result);
cache.result.totalReceived.should.equal(100);
cache.result.balance.should.equal(10);
cache.result.txids.should.deep.equal([txid]);
cache.result.appearanceIds.should.deep.equal({
'5464b1c3f25160f0183fad68a406838d2d1ac0aee05990072ece49326c26c22e': 9
});
done();
});
});
});
describe('#_updateAddressConfirmedSummaryCache', function() {
it('will pass partial options to input/output summary query', function(done) {
var tipHeight = 12;
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
}
},
datadir: 'testdir'
};
var as = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {};
var cache = {
height: 10,
result: {
txids: []
}
};
as._getAddressConfirmedInputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._getAddressConfirmedOutputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._setAndSortTxidsFromAppearanceIds = sinon.stub().callsArgWith(1, null, cache);
as._saveAddressConfirmedSummaryCache = sinon.stub().callsArg(3, null, cache);
as._updateAddressConfirmedSummaryCache(address, options, cache, tipHeight, function(err, cache) {
if (err) {
return done(err);
}
as._getAddressConfirmedInputsSummary.callCount.should.equal(1);
as._getAddressConfirmedOutputsSummary.callCount.should.equal(1);
as._getAddressConfirmedInputsSummary.args[0][2].start.should.equal(12);
as._getAddressConfirmedInputsSummary.args[0][2].end.should.equal(11);
as._getAddressConfirmedOutputsSummary.args[0][2].start.should.equal(12);
as._getAddressConfirmedOutputsSummary.args[0][2].end.should.equal(11);
done();
});
});
it('will save cache if exceeds threshold and is NOT height query', function(done) {
var tipHeight = 12;
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
}
},
datadir: 'testdir'
};
var as = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {};
var cache = {
height: 10,
result: {
txids: [
'9a816264c50910cbf57aa4637dde5f7fec03df642b822661e8bc9710475986b6',
'05c6b9ccf3fc4026391bf8f8a64b4784a95b930851359b8f85a4be7bb6bf6f1e'
]
}
};
as.summaryCacheThreshold = 1;
as._getAddressConfirmedInputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._getAddressConfirmedOutputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._setAndSortTxidsFromAppearanceIds = sinon.stub().callsArgWith(1, null, cache);
as._saveAddressConfirmedSummaryCache = sinon.stub().callsArg(3, null, cache);
as._updateAddressConfirmedSummaryCache(address, options, cache, tipHeight, function(err) {
if (err) {
return done(err);
}
as._saveAddressConfirmedSummaryCache.callCount.should.equal(1);
done();
});
});
it('will NOT save cache if exceeds threshold and IS height query', function(done) {
var tipHeight = 12;
var testnode = {
services: {
bitcoind: {
on: sinon.stub()
}
},
datadir: 'testdir'
};
var as = new AddressService({
mempoolMemoryIndex: true,
node: testnode
});
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
var options = {};
var cache = {
result: {
txids: []
}
};
as.summaryCacheThreshold = 1;
as._getAddressConfirmedInputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._getAddressConfirmedOutputsSummary = sinon.stub().callsArgWith(3, null, cache);
as._setAndSortTxidsFromAppearanceIds = sinon.stub().callsArgWith(1, null, cache);
as._saveAddressConfirmedSummaryCache = sinon.stub().callsArg(3, null, cache);
as._updateAddressConfirmedSummaryCache(address, options, cache, tipHeight, function(err) {
if (err) {
return done(err);
}
as._saveAddressConfirmedSummaryCache.callCount.should.equal(0);
done();
});
});
describe.skip('#_getAddressConfirmedSummary', function() {
});
describe('#_getAddressConfirmedInputsSummary', function() {
@ -2584,21 +2139,18 @@ describe('Address Service', function() {
mempoolMemoryIndex: true,
node: testnode
});
var cache = {
height: 10,
result: {
appearanceIds: {}
}
var result = {
appearanceIds: {}
};
var options = {};
var txid = 'f2cfc19d13f0c12199f70e420d84e2b3b1d4e499702aa9d737f8c24559c9ec47';
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
as.createInputsStream = sinon.stub().returns(streamStub);
as._getAddressConfirmedInputsSummary(address, cache, options, function(err, cache) {
as._getAddressConfirmedInputsSummary(address, result, options, function(err, result) {
if (err) {
return done(err);
}
cache.result.appearanceIds[txid].should.equal(10);
result.appearanceIds[txid].should.equal(10);
done();
});
@ -2655,16 +2207,14 @@ describe('Address Service', function() {
mempoolMemoryIndex: true,
node: testnode
});
var cache = {
height: 10,
result: {
appearanceIds: {},
unconfirmedAppearanceIds: {},
balance: 0,
totalReceived: 0,
unconfirmedBalance: 0
}
var result = {
appearanceIds: {},
unconfirmedAppearanceIds: {},
balance: 0,
totalReceived: 0,
unconfirmedBalance: 0
};
var options = {
queryMempool: true
};
@ -2676,14 +2226,14 @@ describe('Address Service', function() {
var spentIndexSyncKey = encoding.encodeSpentIndexSyncKey(new Buffer(txid, 'hex'), 2);
as.mempoolSpentIndex[spentIndexSyncKey] = true;
as._getAddressConfirmedOutputsSummary(address, cache, options, function(err, cache) {
as._getAddressConfirmedOutputsSummary(address, result, options, function(err, cache) {
if (err) {
return done(err);
}
cache.result.appearanceIds[txid].should.equal(10);
cache.result.balance.should.equal(1000);
cache.result.totalReceived.should.equal(1000);
cache.result.unconfirmedBalance.should.equal(-1000);
result.appearanceIds[txid].should.equal(10);
result.balance.should.equal(1000);
result.totalReceived.should.equal(1000);
result.unconfirmedBalance.should.equal(-1000);
done();
});
@ -2710,20 +2260,18 @@ describe('Address Service', function() {
mempoolMemoryIndex: true,
node: testnode
});
var cache = {
height: 10,
result: {
appearanceIds: {},
unconfirmedAppearanceIds: {},
balance: 0,
totalReceived: 0,
unconfirmedBalance: 0
}
var result = {
appearanceIds: {},
unconfirmedAppearanceIds: {},
balance: 0,
totalReceived: 0,
unconfirmedBalance: 0
};
var options = {};
var address = new bitcore.Address('12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX');
as.createOutputsStream = sinon.stub().returns(streamStub);
as._getAddressConfirmedOutputsSummary(address, cache, options, function(err, cache) {
as._getAddressConfirmedOutputsSummary(address, result, options, function(err, cache) {
should.exist(err);
err.message.should.equal('test');
done();
@ -2737,9 +2285,6 @@ describe('Address Service', function() {
describe.skip('#_setAndSortTxidsFromAppearanceIds', function() {
});
describe.skip('#_saveAddressConfirmedSummaryCache', function() {
});
describe.skip('#_getAddressMempoolSummary', function() {
});