Merge pull request #765 from matiu/feat/interrupted-scan-cache
Feat/interrupted scan cache
This commit is contained in:
commit
1ab4e27bde
|
@ -36,6 +36,7 @@ var errors = {
|
|||
WALLET_BUSY: 'Wallet is busy, try later',
|
||||
WALLET_NOT_COMPLETE: 'Wallet is not complete',
|
||||
WALLET_NOT_FOUND: 'Wallet not found',
|
||||
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
|
||||
};
|
||||
|
||||
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {
|
||||
|
|
|
@ -55,6 +55,18 @@ AddressManager.prototype.rewindIndex = function(isChange, n) {
|
|||
}
|
||||
};
|
||||
|
||||
AddressManager.prototype.getCurrentIndex = function(isChange) {
|
||||
return isChange ? this.changeAddressIndex : this.receiveAddressIndex;
|
||||
};
|
||||
|
||||
|
||||
AddressManager.prototype.getBaseAddressPath = function(isChange) {
|
||||
return 'm/' +
|
||||
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
||||
(isChange ? 1 : 0) + '/' +
|
||||
0;
|
||||
};
|
||||
|
||||
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
|
||||
return 'm/' +
|
||||
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
||||
|
|
|
@ -1037,6 +1037,9 @@ WalletService.prototype.createAddress = function(opts, cb) {
|
|||
self.getWallet({}, function(err, wallet) {
|
||||
if (err) return cb(err);
|
||||
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE);
|
||||
if (wallet.scanStatus == 'error')
|
||||
return cb(Errors.WALLET_NEED_SCAN);
|
||||
|
||||
|
||||
var createFn = wallet.singleAddress ? getFirstAddress : createNewAddress;
|
||||
return createFn(wallet, cb);
|
||||
|
@ -1161,6 +1164,10 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) {
|
|||
self.getWallet({}, function(err, wallet) {
|
||||
if (err) return next(err);
|
||||
|
||||
if (wallet.scanStatus == 'error')
|
||||
return cb(Errors.WALLET_NEED_SCAN);
|
||||
|
||||
|
||||
coin = wallet.coin;
|
||||
return next();
|
||||
});
|
||||
|
@ -2265,6 +2272,10 @@ WalletService.prototype.createTx = function(opts, cb) {
|
|||
if (err) return cb(err);
|
||||
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE);
|
||||
|
||||
if (wallet.scanStatus == 'error')
|
||||
return cb(Errors.WALLET_NEED_SCAN);
|
||||
|
||||
|
||||
checkTxpAlreadyExists(opts.txProposalId, function(err, txp) {
|
||||
if (err) return cb(err);
|
||||
if (txp) return cb(null, txp);
|
||||
|
@ -3330,23 +3341,31 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
var allAddresses = [];
|
||||
var gap = Defaults.SCAN_ADDRESS_GAP;
|
||||
|
||||
|
||||
async.whilst(function() {
|
||||
self.logi('Scanning addr gap:'+ inactiveCounter);
|
||||
self.logi('Scanning addr branch: %s index: %d gap %d ', derivator.id, derivator.index(), inactiveCounter);
|
||||
return inactiveCounter < gap;
|
||||
}, function(next) {
|
||||
var address = derivator.derive();
|
||||
|
||||
checkActivity(wallet, address.address, function(err, activity) {
|
||||
if (err) return next(err);
|
||||
|
||||
allAddresses.push(address);
|
||||
inactiveCounter = activity ? 0 : inactiveCounter + 1;
|
||||
next();
|
||||
|
||||
if (!activity)
|
||||
return next();
|
||||
|
||||
// Store address manager cache. This is usefull for interrupted scans
|
||||
// in which the addresses are not inserted.
|
||||
self.storage.storeAddressIndexCache(wallet.id, derivator.id, derivator.index(),next);
|
||||
});
|
||||
}, function(err) {
|
||||
derivator.rewind(gap);
|
||||
return cb(err, _.dropRight(allAddresses, gap));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
self._runLocked(cb, function(cb) {
|
||||
|
@ -3363,14 +3382,18 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
var derivators = [];
|
||||
_.each([false, true], function(isChange) {
|
||||
derivators.push({
|
||||
id: wallet.addressManager.getBaseAddressPath(isChange),
|
||||
derive: _.bind(wallet.createAddress, wallet, isChange),
|
||||
index: _.bind(wallet.addressManager.getCurrentIndex, wallet.addressManager, isChange),
|
||||
rewind: _.bind(wallet.addressManager.rewindIndex, wallet.addressManager, isChange),
|
||||
});
|
||||
if (opts.includeCopayerBranches) {
|
||||
_.each(wallet.copayers, function(copayer) {
|
||||
if (copayer.addressManager) {
|
||||
derivators.push({
|
||||
id: copayer.addressManager.getBaseAddressPath(isChange),
|
||||
derive: _.bind(copayer.createAddress, copayer, wallet, isChange),
|
||||
index: _.bind(copayer.addressManager.getCurrentIndex, copayer.addressManager, isChange),
|
||||
rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange),
|
||||
});
|
||||
}
|
||||
|
@ -3379,10 +3402,26 @@ WalletService.prototype.scan = function(opts, cb) {
|
|||
});
|
||||
|
||||
async.eachSeries(derivators, function(derivator, next) {
|
||||
scanBranch(wallet, derivator, function(err, addresses) {
|
||||
|
||||
self.storage.fetchAddressIndexCache(wallet.id, derivator.id, function(err, index){
|
||||
var addresses = [];
|
||||
|
||||
// prederive known addresses
|
||||
var diff = index - derivator.index();
|
||||
if (diff>0) {
|
||||
self.logi('Prederiving '+ diff +' addresses for ' + derivator.id);
|
||||
for(var i =0; i<diff; i++) {
|
||||
var address = derivator.derive();
|
||||
addresses.push(address);
|
||||
}
|
||||
}
|
||||
|
||||
scanBranch(wallet, derivator, function(err, scannedAddresses) {
|
||||
if (err) return next(err);
|
||||
addresses = addresses.concat(scannedAddresses);
|
||||
self.storage.storeAddressAndWallet(wallet, addresses, next);
|
||||
});
|
||||
});
|
||||
}, function(error) {
|
||||
self.storage.fetchWallet(wallet.id, function(err, wallet) {
|
||||
if (err) return cb(err);
|
||||
|
|
|
@ -1070,6 +1070,36 @@ Storage.prototype._dump = function(cb, fn) {
|
|||
});
|
||||
};
|
||||
|
||||
Storage.prototype.fetchAddressIndexCache = function (walletId, key, cb) {
|
||||
this.db.collection(collections.CACHE).findOne({
|
||||
walletId: walletId,
|
||||
type: 'addressIndexCache',
|
||||
key: key,
|
||||
}, function(err, ret) {
|
||||
if (err) return cb(err);
|
||||
if (!ret) return cb();
|
||||
cb(null, ret.index);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Storage.prototype.storeAddressIndexCache = function (walletId, key, index, cb) {
|
||||
this.db.collection(collections.CACHE).update({
|
||||
walletId: walletId,
|
||||
type: 'addressIndexCache',
|
||||
key: key,
|
||||
}, {
|
||||
"$set":
|
||||
{
|
||||
index: index,
|
||||
}
|
||||
}, {
|
||||
w: 1,
|
||||
upsert: true,
|
||||
}, cb);
|
||||
};
|
||||
|
||||
|
||||
Storage.prototype._addressHash = function(addresses) {
|
||||
var all = addresses.join();
|
||||
|
@ -1110,6 +1140,8 @@ Storage.prototype.checkAndUseBalanceCache = function(walletId, addresses, durati
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
|
||||
var key = this._addressHash(addresses);
|
||||
var now = Date.now();
|
||||
|
|
|
@ -380,8 +380,22 @@ helpers.stubFeeLevels = function(levels) {
|
|||
};
|
||||
};
|
||||
|
||||
helpers.stubAddressActivity = function(activeAddresses) {
|
||||
|
||||
var stubAddressActivityFailsOn = null;
|
||||
var stubAddressActivityFailsOnCount=1;
|
||||
helpers.stubAddressActivity = function(activeAddresses, failsOn) {
|
||||
|
||||
stubAddressActivityFailsOnCount=1;
|
||||
|
||||
// could be null
|
||||
stubAddressActivityFailsOn = failsOn;
|
||||
|
||||
blockchainExplorer.getAddressActivity = function(address, cb) {
|
||||
if (stubAddressActivityFailsOnCount === stubAddressActivityFailsOn)
|
||||
return cb('failed on request');
|
||||
|
||||
stubAddressActivityFailsOnCount++;
|
||||
|
||||
return cb(null, _.contains(activeAddresses, address));
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7647,6 +7647,7 @@ describe('Wallet service', function() {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not go beyond max gap', function(done) {
|
||||
helpers.stubAddressActivity(
|
||||
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
||||
|
@ -7720,8 +7721,8 @@ describe('Wallet service', function() {
|
|||
wallet.addressManager.receiveAddressIndex.should.equal(1);
|
||||
wallet.addressManager.changeAddressIndex.should.equal(0);
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
address.path.should.equal('m/0/1');
|
||||
should.exist(err);
|
||||
err.code.should.equal('WALLET_NEED_SCAN');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -7784,6 +7785,9 @@ describe('Wallet service', function() {
|
|||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||
should.not.exist(err);
|
||||
addresses.should.be.empty;
|
||||
server.getStatus({}, function(err, status) {
|
||||
should.exist(err);
|
||||
err.code.should.equal('WALLET_NEED_SCAN');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -7791,6 +7795,96 @@ describe('Wallet service', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('index cache: should use cache, if previous scan failed', function(done) {
|
||||
helpers.stubAddressActivity(
|
||||
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
||||
'1GdXraZ1gtoVAvBh49D4hK9xLm6SKgesoE', // m/0/2
|
||||
'1FUzgKcyPJsYwDLUEVJYeE2N3KVaoxTjGS', // m/1/0
|
||||
], 4);
|
||||
|
||||
// First without activity
|
||||
var addr = '1KbTiFvjbN6B5reCVS4tTT49vPQkvsqnE2'; // m/0/3
|
||||
|
||||
server.scan({}, function(err) {
|
||||
should.exist('failed on request');
|
||||
|
||||
server.getWallet({}, function(err, wallet) {
|
||||
should.not.exist(err);
|
||||
|
||||
// Because it failed
|
||||
wallet.addressManager.receiveAddressIndex.should.equal(0);
|
||||
wallet.addressManager.changeAddressIndex.should.equal(0);
|
||||
|
||||
helpers.stubAddressActivity(
|
||||
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
||||
'1GdXraZ1gtoVAvBh49D4hK9xLm6SKgesoE', // m/0/2
|
||||
'1FUzgKcyPJsYwDLUEVJYeE2N3KVaoxTjGS', // m/1/0
|
||||
], -1);
|
||||
var getAddressActivitySpy = sinon.spy(blockchainExplorer, 'getAddressActivity');
|
||||
|
||||
server.scan({}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
// should prederive 3 address, so
|
||||
// First call should be m/0/3
|
||||
var calls = getAddressActivitySpy.getCalls();
|
||||
calls[0].args[0].should.equal(addr);
|
||||
|
||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||
should.exist(addresses);
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
address.path.should.equal('m/0/3');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('index cache: should not use cache, if scan worked ok', function(done) {
|
||||
helpers.stubAddressActivity(
|
||||
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
||||
'1GdXraZ1gtoVAvBh49D4hK9xLm6SKgesoE', // m/0/2
|
||||
'1FUzgKcyPJsYwDLUEVJYeE2N3KVaoxTjGS', // m/1/0
|
||||
]);
|
||||
|
||||
// First without activity
|
||||
var addr = '1KbTiFvjbN6B5reCVS4tTT49vPQkvsqnE2'; // m/0/3
|
||||
|
||||
server.scan({}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
server.getWallet({}, function(err, wallet) {
|
||||
should.not.exist(err);
|
||||
wallet.addressManager.receiveAddressIndex.should.equal(3);
|
||||
wallet.addressManager.changeAddressIndex.should.equal(1);
|
||||
|
||||
var getAddressActivitySpy = sinon.spy(blockchainExplorer, 'getAddressActivity');
|
||||
|
||||
server.scan({}, function(err) {
|
||||
should.not.exist(err);
|
||||
|
||||
var calls = getAddressActivitySpy.getCalls();
|
||||
calls[0].args[0].should.equal(addr);
|
||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||
should.exist(addresses);
|
||||
server.createAddress({}, function(err, address) {
|
||||
should.not.exist(err);
|
||||
address.path.should.equal('m/0/3');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('shared wallet (BIP45)', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
|
Loading…
Reference in New Issue