fast balance cache

This commit is contained in:
Matias Alejo Garcia 2017-11-02 13:03:52 -03:00
parent bf2e281787
commit 1db21481d6
No known key found for this signature in database
GPG Key ID: 02470DB551277AB3
5 changed files with 160 additions and 60 deletions

View File

@ -16,6 +16,7 @@
"maxlen": 120,
"maxparams": 4,
"maxstatements": 15,
"no-extra-semi": 0,
"mocha": true,
"newcap": true,
"noarg": true,

View File

@ -99,9 +99,6 @@ Defaults.HISTORY_CACHE_ADDRESS_THRESOLD = 100;
// Number of addresses from which balance in cache for a few seconds
Defaults.BALANCE_CACHE_ADDRESS_THRESOLD = Defaults.HISTORY_CACHE_ADDRESS_THRESOLD;
// Duration in seconds of the balance cache
Defaults.BALANCE_CACHE_DURATION = 5
// Cache time for blockchain height (in seconds)
Defaults.BLOCKHEIGHT_CACHE_TIME = 10 * 60;

View File

@ -1244,46 +1244,64 @@ WalletService.prototype._totalizeUtxos = function(utxos) {
WalletService.prototype._getBalanceFromAddresses = function(opts, cb, i) {
var self = this;
var opts = opts || {};
var isBig = opts.addresses.length > Default.BALANCE_CACHE_ADDRESS_THRESOLD;
opts.addresses = opts.addresses || [];
function checkBalanceCache(cb) {
if (opts.addresses.length < Defaults.BALANCE_CACHE_ADDRESS_THRESOLD)
return cb();
self.storage.checkAndUseBalanceCache(self.walletId, opts.addresses, cb);
};
function storeBalanceCache(balance, cb) {
if (opts.addresses.length < Defaults.BALANCE_CACHE_ADDRESS_THRESOLD)
return cb(null, balance);
self.storage.storeBalanceCache(self.walletId, opts.addresses, balance, function(err) {
if (err)
log.warn('Could not save cache:',err);
return cb(null, balance);
});
};
// This lock is to prevent server starvation on big wallets
self._runLocked(cb, function(cb) {
if (isBig && self.storage.checkAndUseBalanceCache(addresses,cb))
log.info('Using UTXO Cache');
return;
}
self._getUtxosForCurrentWallet({
coin: opts.coin,
addresses: opts.addresses
}, function(err, utxos) {
checkBalanceCache(function(err, cache) {
if (err) return cb(err);
var balance = self._totalizeUtxos(utxos);
// Compute balance by address
var byAddress = {};
_.each(_.indexBy(_.sortBy(utxos, 'address'), 'address'), function(value, key) {
byAddress[key] = {
address: key,
path: value.path,
amount: 0,
};
});
_.each(utxos, function(utxo) {
byAddress[utxo.address].amount += utxo.satoshis;
});
balance.byAddress = _.values(byAddress);
if (isBig) {
return self.storage.storeBalanceCache(addresses, balance, cb);
} else {
return cb(null, balance);
if (cache) {
log.info('Using UTXO Cache');
return cb(null, cache, true);
}
self._getUtxosForCurrentWallet({
coin: opts.coin,
addresses: opts.addresses
}, function(err, utxos) {
if (err) return cb(err);
var balance = self._totalizeUtxos(utxos);
// Compute balance by address
var byAddress = {};
_.each(_.indexBy(_.sortBy(utxos, 'address'), 'address'), function(value, key) {
byAddress[key] = {
address: key,
path: value.path,
amount: 0,
};
});
_.each(utxos, function(utxo) {
byAddress[utxo.address].amount += utxo.satoshis;
});
balance.byAddress = _.values(byAddress);
storeBalanceCache(balance, cb);
});
});
});
};
@ -1296,7 +1314,7 @@ WalletService.prototype._getBalanceOneStep = function(opts, cb) {
self._getBalanceFromAddresses({
coin: opts.coin,
addresses: addresses
}, function(err, balance) {
}, function(err, balance, cacheUsed) {
if (err) return cb(err);
// Update cache
@ -1305,7 +1323,7 @@ WalletService.prototype._getBalanceOneStep = function(opts, cb) {
if (err) {
log.warn('Could not update wallet cache', err);
}
return cb(null, balance);
return cb(null, balance, cacheUsed);
});
});
});
@ -1382,7 +1400,6 @@ WalletService.prototype.getBalance = function(opts, cb, i) {
return self._getBalanceOneStep(opts, cb);
}
self.storage.getTwoStepCache(self.walletId, function(err, twoStepCache) {
if (err) return cb(err);
twoStepCache = twoStepCache || {};
@ -1404,9 +1421,9 @@ WalletService.prototype.getBalance = function(opts, cb, i) {
self._getBalanceFromAddresses({
coin: opts.coin,
addresses: activeAddresses
}, function(err, partialBalance) {
}, function(err, partialBalance, cacheUsed) {
if (err) return cb(err);
cb(null, partialBalance);
cb(null, partialBalance, cacheUsed);
var now = Math.floor(Date.now() / 1000);

View File

@ -8,7 +8,6 @@ log.debug = log.verbose;
log.disableColor();
var util = require('util');
var Bitcore = require('bitcore-lib');
var mongodb = require('mongodb');
var Model = require('./model');
@ -551,7 +550,7 @@ Storage.prototype.fetchAddressByCoin = function(coin, address, cb) {
if (!result || _.isEmpty(result)) return cb();
if (result.length > 1) {
result = _.find(result, function(address) {
return coin == (address.coin || Defaults.COIN);
return coin == (address.coin || 'btc');
});
} else {
result = _.first(result);
@ -1072,45 +1071,47 @@ Storage.prototype._addressHash = function(addresses) {
return Bitcore.crypto.Hash.ripemd160(new Buffer(all)).toString('hex');
};
Storage.prototype.checkAndUseBalanceCache = function(addresses, cb) {
var key = ths._addressHash(addresses);
Storage.prototype.checkAndUseBalanceCache = function(walletId, addresses, cb) {
var self = this;
var key = self._addressHash(addresses);
var now = Date.now();
var BALANCE_CACHE_DURATION = 10;
self.db.collection(collections.CACHE).findOne({
walletId: walletId,
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, function(err, ret) {
if (err) return cb(err);
if (!ret) return cb();
var validFor = ret.ts + Defauls.BALANCE_CACHE_DURATION * 1000 - now;
var validFor = ret.ts + 10 * 1000 - now;
if (validFor > 0) {
log.debug('','Using Balance Cache valid for %d ms more', validFor);
return cb(null, ret.result);
cb(null, ret.result);
return true;
}
return cb();
cb();
log.debug('','Balance cache expired, deleting');
self.db.collection(collections.CACHE).remove({
walletId: walletId,
type: 'balanceCache',
key: key,
}, {}, function() {
});
}, {}, function() {});
return false;
});
});
};
Storage.prototype.storeBalanceCache = function (addresses, balance, cb) {
var key = ths._addressHash(addresses);
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
var key = this._addressHash(addresses);
var now = Date.now();
self.db.collection(collections.CACHE).update( {
walletId: walletId,
this.db.collection(collections.CACHE).update({
walletId: walletId || key,
type: 'balanceCache',
key: key,
}, {

View File

@ -2617,6 +2617,90 @@ describe('Wallet service', function() {
});
describe('#getBalance fast cache', function() {
var server, wallet, clock;
var _old = Defaults.BALANCE_CACHE_ADDRESS_THRESOLD;
beforeEach(function(done) {
clock = sinon.useFakeTimers(Date.now(), 'Date');
Defaults.BALANCE_CACHE_ADDRESS_THRESOLD = 0;
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
done();
});
});
afterEach(function() {
clock.restore();
Defaults.BALANCE_CACHE_ADDRESS_THRESOLD = _old;
});
function checkBalance(balance) {
should.exist(balance);
balance.totalAmount.should.equal(helpers.toSatoshi(6));
should.exist(balance.byAddress);
balance.byAddress.length.should.equal(2);
balance.byAddress[1].amount.should.equal(helpers.toSatoshi(2));
};
it('should get balance from insight and store cache', function(done) {
helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() {
server.getBalance({
twoStep: false
}, function(err, balance, cacheUsed) {
should.not.exist(err);
should.not.exist(cacheUsed);
checkBalance(balance);
done();
});
});
});
it('should get balance from cache', function(done) {
helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() {
server.getBalance({
twoStep: false
}, function(err, balance, cacheUsed) {
should.not.exist(err);
should.not.exist(cacheUsed);
server.getBalance({
twoStep: false
}, function(err, balance, cacheUsed) {
should.not.exist(err);
cacheUsed.should.equal(true);
checkBalance(balance);
done();
});
});
});
});
it('should not get balance from cache, after 11secs', function(done) {
helpers.stubUtxos(server, wallet, [1, 'u2', 3], function() {
server.getBalance({
twoStep: false
}, function(err, balance, cacheUsed) {
should.not.exist(err);
should.not.exist(cacheUsed);
clock.tick((10+1) * 1000);
server.getBalance({
twoStep: false
}, function(err, balance, cacheUsed) {
should.not.exist(err);
should.not.exist(cacheUsed);
checkBalance(balance);
done();
});
});
});
});
});
describe('#getFeeLevels', function() {
var server, wallet, levels;
before(function() {