Add interrupted scan cache + status
This commit is contained in:
parent
1c271cfff3
commit
41af549ed2
|
@ -36,6 +36,7 @@ var errors = {
|
||||||
WALLET_BUSY: 'Wallet is busy, try later',
|
WALLET_BUSY: 'Wallet is busy, try later',
|
||||||
WALLET_NOT_COMPLETE: 'Wallet is not complete',
|
WALLET_NOT_COMPLETE: 'Wallet is not complete',
|
||||||
WALLET_NOT_FOUND: 'Wallet not found',
|
WALLET_NOT_FOUND: 'Wallet not found',
|
||||||
|
WALLET_NEED_SCAN: 'Wallet needs addresses scan',
|
||||||
};
|
};
|
||||||
|
|
||||||
var errorObjects = _.zipObject(_.map(errors, function(msg, code) {
|
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) {
|
AddressManager.prototype.getCurrentAddressPath = function(isChange) {
|
||||||
return 'm/' +
|
return 'm/' +
|
||||||
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
(this.derivationStrategy == Constants.DERIVATION_STRATEGIES.BIP45 ? this.copayerIndex + '/' : '') +
|
||||||
|
|
|
@ -1037,6 +1037,9 @@ WalletService.prototype.createAddress = function(opts, cb) {
|
||||||
self.getWallet({}, function(err, wallet) {
|
self.getWallet({}, function(err, wallet) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE);
|
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;
|
var createFn = wallet.singleAddress ? getFirstAddress : createNewAddress;
|
||||||
return createFn(wallet, cb);
|
return createFn(wallet, cb);
|
||||||
|
@ -1161,6 +1164,10 @@ WalletService.prototype._getUtxosForCurrentWallet = function(opts, cb) {
|
||||||
self.getWallet({}, function(err, wallet) {
|
self.getWallet({}, function(err, wallet) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
|
if (wallet.scanStatus == 'error')
|
||||||
|
return cb(Errors.WALLET_NEED_SCAN);
|
||||||
|
|
||||||
|
|
||||||
coin = wallet.coin;
|
coin = wallet.coin;
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
|
@ -2265,6 +2272,10 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (!wallet.isComplete()) return cb(Errors.WALLET_NOT_COMPLETE);
|
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) {
|
checkTxpAlreadyExists(opts.txProposalId, function(err, txp) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
if (txp) return cb(null, txp);
|
if (txp) return cb(null, txp);
|
||||||
|
@ -3330,23 +3341,31 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
var allAddresses = [];
|
var allAddresses = [];
|
||||||
var gap = Defaults.SCAN_ADDRESS_GAP;
|
var gap = Defaults.SCAN_ADDRESS_GAP;
|
||||||
|
|
||||||
|
|
||||||
async.whilst(function() {
|
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;
|
return inactiveCounter < gap;
|
||||||
}, function(next) {
|
}, function(next) {
|
||||||
var address = derivator.derive();
|
var address = derivator.derive();
|
||||||
|
|
||||||
checkActivity(wallet, address.address, function(err, activity) {
|
checkActivity(wallet, address.address, function(err, activity) {
|
||||||
if (err) return next(err);
|
if (err) return next(err);
|
||||||
|
|
||||||
allAddresses.push(address);
|
allAddresses.push(address);
|
||||||
inactiveCounter = activity ? 0 : inactiveCounter + 1;
|
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) {
|
}, function(err) {
|
||||||
derivator.rewind(gap);
|
derivator.rewind(gap);
|
||||||
return cb(err, _.dropRight(allAddresses, gap));
|
return cb(err, _.dropRight(allAddresses, gap));
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
self._runLocked(cb, function(cb) {
|
self._runLocked(cb, function(cb) {
|
||||||
|
@ -3363,14 +3382,18 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
var derivators = [];
|
var derivators = [];
|
||||||
_.each([false, true], function(isChange) {
|
_.each([false, true], function(isChange) {
|
||||||
derivators.push({
|
derivators.push({
|
||||||
|
id: wallet.addressManager.getBaseAddressPath(isChange),
|
||||||
derive: _.bind(wallet.createAddress, wallet, isChange),
|
derive: _.bind(wallet.createAddress, wallet, isChange),
|
||||||
|
index: _.bind(wallet.addressManager.getCurrentIndex, wallet.addressManager, isChange),
|
||||||
rewind: _.bind(wallet.addressManager.rewindIndex, wallet.addressManager, isChange),
|
rewind: _.bind(wallet.addressManager.rewindIndex, wallet.addressManager, isChange),
|
||||||
});
|
});
|
||||||
if (opts.includeCopayerBranches) {
|
if (opts.includeCopayerBranches) {
|
||||||
_.each(wallet.copayers, function(copayer) {
|
_.each(wallet.copayers, function(copayer) {
|
||||||
if (copayer.addressManager) {
|
if (copayer.addressManager) {
|
||||||
derivators.push({
|
derivators.push({
|
||||||
|
id: copayer.addressManager.getBaseAddressPath(isChange),
|
||||||
derive: _.bind(copayer.createAddress, copayer, wallet, isChange),
|
derive: _.bind(copayer.createAddress, copayer, wallet, isChange),
|
||||||
|
index: _.bind(copayer.addressManager.getCurrentIndex, copayer.addressManager, isChange),
|
||||||
rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange),
|
rewind: _.bind(copayer.addressManager.rewindIndex, copayer.addressManager, isChange),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3379,9 +3402,25 @@ WalletService.prototype.scan = function(opts, cb) {
|
||||||
});
|
});
|
||||||
|
|
||||||
async.eachSeries(derivators, function(derivator, next) {
|
async.eachSeries(derivators, function(derivator, next) {
|
||||||
scanBranch(wallet, derivator, function(err, addresses) {
|
|
||||||
if (err) return next(err);
|
self.storage.fetchAddressIndexCache(wallet.id, derivator.id, function(err, index){
|
||||||
self.storage.storeAddressAndWallet(wallet, addresses, next);
|
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) {
|
}, function(error) {
|
||||||
self.storage.fetchWallet(wallet.id, function(err, wallet) {
|
self.storage.fetchWallet(wallet.id, function(err, wallet) {
|
||||||
|
|
|
@ -1068,6 +1068,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) {
|
Storage.prototype._addressHash = function(addresses) {
|
||||||
var all = addresses.join();
|
var all = addresses.join();
|
||||||
|
@ -1108,6 +1138,8 @@ Storage.prototype.checkAndUseBalanceCache = function(walletId, addresses, durati
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
|
Storage.prototype.storeBalanceCache = function (walletId, addresses, balance, cb) {
|
||||||
var key = this._addressHash(addresses);
|
var key = this._addressHash(addresses);
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
|
|
|
@ -380,8 +380,21 @@ helpers.stubFeeLevels = function(levels) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
helpers.stubAddressActivity = function(activeAddresses) {
|
|
||||||
|
var stubAddressActivityFailsOn = null;
|
||||||
|
var stubAddressActivityFailsOnCount=1;
|
||||||
|
helpers.stubAddressActivity = function(activeAddresses, failsOn) {
|
||||||
|
|
||||||
|
if (failsOn) {
|
||||||
|
stubAddressActivityFailsOn = failsOn;
|
||||||
|
}
|
||||||
|
|
||||||
blockchainExplorer.getAddressActivity = function(address, cb) {
|
blockchainExplorer.getAddressActivity = function(address, cb) {
|
||||||
|
if (stubAddressActivityFailsOnCount == stubAddressActivityFailsOn)
|
||||||
|
return cb('failed on request');
|
||||||
|
|
||||||
|
stubAddressActivityFailsOnCount++;
|
||||||
|
|
||||||
return cb(null, _.contains(activeAddresses, address));
|
return cb(null, _.contains(activeAddresses, address));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -7647,6 +7647,7 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should not go beyond max gap', function(done) {
|
it('should not go beyond max gap', function(done) {
|
||||||
helpers.stubAddressActivity(
|
helpers.stubAddressActivity(
|
||||||
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
['1L3z9LPd861FWQhf3vDn89Fnc9dkdBo2CG', // m/0/0
|
||||||
|
@ -7720,8 +7721,8 @@ describe('Wallet service', function() {
|
||||||
wallet.addressManager.receiveAddressIndex.should.equal(1);
|
wallet.addressManager.receiveAddressIndex.should.equal(1);
|
||||||
wallet.addressManager.changeAddressIndex.should.equal(0);
|
wallet.addressManager.changeAddressIndex.should.equal(0);
|
||||||
server.createAddress({}, function(err, address) {
|
server.createAddress({}, function(err, address) {
|
||||||
should.not.exist(err);
|
should.exist(err);
|
||||||
address.path.should.equal('m/0/1');
|
err.code.should.equal('WALLET_NEED_SCAN');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7784,11 +7785,104 @@ describe('Wallet service', function() {
|
||||||
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
server.storage.fetchAddresses(wallet.id, function(err, addresses) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
addresses.should.be.empty;
|
addresses.should.be.empty;
|
||||||
done();
|
server.getStatus({}, function(err, status) {
|
||||||
|
should.exist(err);
|
||||||
|
err.code.should.equal('WALLET_NEED_SCAN');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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() {
|
describe('shared wallet (BIP45)', function() {
|
||||||
|
|
Loading…
Reference in New Issue