Merge pull request #611 from isocolsky/has-balance

WIP Active addresses for balance
This commit is contained in:
Matias Alejo Garcia 2016-12-30 12:26:12 -03:00 committed by GitHub
commit 5620b79ea6
5 changed files with 177 additions and 4 deletions

View File

@ -112,4 +112,6 @@ Defaults.RateLimit = {
Defaults.MAX_REORG_DEPTH = 32;
Defaults.RECENT_ADDRESS_TIMESPAN = 7 * 24 * 3600; // TXs within the last 7 days are considered recent
module.exports = Defaults;

View File

@ -25,6 +25,7 @@ Address.create = function(opts) {
x.type = opts.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = undefined;
x.lastUsedOn = now;
x.hasBalance = false;
return x;
};
@ -42,6 +43,7 @@ Address.fromObj = function(obj) {
x.type = obj.type || Constants.SCRIPT_TYPES.P2SH;
x.hasActivity = obj.hasActivity;
x.lastUsedOn = obj.lastUsedOn;
x.hasBalance = obj.hasBalance;
return x;
};

View File

@ -1139,11 +1139,49 @@ WalletService.prototype._getBalanceFromAddresses = function(addresses, cb) {
WalletService.prototype.getBalance = function(opts, cb) {
var self = this;
self.storage.fetchAddresses(self.walletId, function(err, addresses) {
function getActiveAddresses(cb) {
async.parallel([
function(done) {
self.storage.fetchRecentAddresses(self.walletId, (Date.now() / 1000) - Defaults.RECENT_ADDRESS_TIMESPAN, done);
},
function(done) {
self.storage.fetchAddressesWithBalance(self.walletId, done);
}
], function(err, res) {
return cb(err, res[0].concat(res[1]));
})
};
getActiveAddresses(function(err, addresses) {
if (err) return cb(err);
self._getBalanceFromAddresses(addresses, function(err, balance) {
if (err) return cb(err);
return cb(null, balance);
// TODO: return as soon as the balance is known and udpate addresses in the background
// Update active addresses
async.waterfall([
function(next) {
self.storage.fetchAddressesWithBalance(self.walletId, function(err, res) {
if (err) return next(err);
return next(null, _.pluck(res, 'address'));
});
},
function(previous, next) {
var current = _.pluck(balance.byAddress, 'address');
var difference = _.difference(previous, current);
self.storage.updateHasBalance(current, true, function(err) {
if (err) return next(err);
self.storage.updateHasBalance(difference, false, next);
});
},
], function(err) {
if (err) return cb(err);
return cb(null, balance);
});
});
});
};

View File

@ -639,7 +639,7 @@ Storage.prototype.updateBlockchainTip = function(network, hashes, cb) {
};
Storage.prototype.getBlockchainTip = function(network,cb) {
Storage.prototype.getBlockchainTip = function(network, cb) {
this.db.collection(collections.CACHE).findOne({
type: 'tip',
key: network,
@ -908,6 +908,56 @@ Storage.prototype.fetchRecentAddresses = function(walletId, minUsedOn, cb) {
});
};
Storage.prototype.updateHasBalance = function(addresses, hasBalance, cb) {
var self = this;
if (!_.isArray(addresses))
addresses = [addresses];
async.eachLimit(addresses, 10, function(address, next) {
// Do not use `upsert` here.
self.db.collection(collections.ADDRESSES).update({
address: address,
}, {
$set: {
hasBalance: hasBalance,
},
}, {
w: 1,
}, next);
}, cb);
};
Storage.prototype.fetchAddressesWithBalance = function(walletId, cb) {
var self = this;
function getResult(cb) {
self.db.collection(collections.ADDRESSES).find({
walletId: walletId,
hasBalance: true,
}).toArray(function(err, result) {
if (err) return cb(err);
if (!_.isEmpty(result)) return cb(null, result);
self.db.collection(collections.ADDRESSES).find({
walletId: walletId,
hasBalance: {
$exists: false
},
}).toArray(cb);
});
};
getResult(function(err, result) {
if (err) return cb(err);
if (!result) return cb();
var addresses = _.map(result, function(address) {
return Model.Address.fromObj(address);
});
return cb(null, addresses);
});
};

View File

@ -1112,6 +1112,7 @@ describe('Wallet service', function() {
address.isChange.should.be.false;
address.path.should.equal('m/2147483647/0/0');
address.type.should.equal('P2SH');
address.hasBalance.should.be.false;
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
@ -1175,6 +1176,7 @@ describe('Wallet service', function() {
address.isChange.should.be.false;
address.path.should.equal('m/0/0');
address.type.should.equal('P2SH');
address.hasBalance.should.be.false;
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
@ -1242,6 +1244,7 @@ describe('Wallet service', function() {
address.isChange.should.be.false;
address.path.should.equal('m/0/0');
address.type.should.equal('P2PKH');
address.hasBalance.should.be.false;
server.getNotifications({}, function(err, notifications) {
should.not.exist(err);
var notif = _.find(notifications, {
@ -1805,7 +1808,7 @@ describe('Wallet service', function() {
});
});
});
it('should only include addresses with balance', function(done) {
it('should only return addresses with balance', function(done) {
helpers.stubUtxos(server, wallet, 1, function(utxos) {
server.createAddress({}, function(err, address) {
should.not.exist(err);
@ -1830,6 +1833,84 @@ describe('Wallet service', function() {
});
});
});
it('should tag addresses with balance', function(done) {
helpers.stubUtxos(server, wallet, 1, function(utxos) {
server.createAddress({}, function(err, newAddress) {
should.not.exist(err);
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.byAddress.length.should.equal(1);
balance.byAddress[0].address.should.equal(utxos[0].address);
server.getMainAddresses({}, function(err, addresses) {
should.not.exist(err);
addresses.length.should.equal(2);
_.find(addresses, {
address: utxos[0].address
}).hasBalance.should.be.true;
_.find(addresses, {
address: newAddress.address
}).hasBalance.should.be.false;
done();
});
});
});
});
});
it('should only request balance for relevant addresses', function(done) {
var clock = sinon.useFakeTimers(Date.now(), 'Date');
helpers.stubUtxos(server, wallet, 1, function(utxos) {
server.storage.updateHasBalance(utxos[0].address, true, function() {
server.createAddress({}, function(err, oldAddress) {
should.not.exist(err);
clock.tick(365 * 24 * 3600 * 1000); // One year
server.createAddress({}, function(err, newAddress) {
should.not.exist(err);
var getUtxosSpy = sinon.spy(blockchainExplorer, 'getUtxos');
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.byAddress.length.should.equal(1);
balance.byAddress[0].address.should.equal(utxos[0].address);
getUtxosSpy.callCount.should.equal(1);
var addressesInCall = getUtxosSpy.getCalls()[0].args[0];
addressesInCall.length.should.equal(2);
addressesInCall.should.not.contain(oldAddress.address);
clock.restore();
done();
});
});
});
});
});
});
it('should request balance for previously stored addresses', function(done) {
var clock = sinon.useFakeTimers(Date.now(), 'Date');
helpers.stubUtxos(server, wallet, 1, function(utxos) {
server.getMainAddresses({}, function(err, addr) {
delete addr[0].hasBalance;
var Collections = require('../../lib/storage').collections;
server.storage.db.collection(Collections.ADDRESSES).update({
address: addr[0].address
}, addr[0], function(err) {
should.not.exist(err);
clock.tick(365 * 24 * 3600 * 1000); // One year
server.getBalance({}, function(err, balance) {
should.not.exist(err);
balance.byAddress.length.should.equal(1);
balance.byAddress[0].address.should.equal(utxos[0].address);
server.getMainAddresses({}, function(err, addresses) {
_.find(addresses, {
address: utxos[0].address
}).hasBalance.should.be.true;
clock.restore();
done();
});
});
});
});
});
});
});
describe('#getFeeLevels', function() {