Add wallet addresses index discovery on importing backup

This commit is contained in:
Yemel Jardi 2014-06-18 10:58:34 -03:00
parent 107414ec7e
commit 29920abdb3
7 changed files with 167 additions and 3 deletions

View File

@ -159,6 +159,27 @@ Insight.prototype.sendRawTransaction = function(rawtx, cb) {
});
};
Insight.prototype.checkActivity = function(addresses, cb) {
if (!addresses) throw new Error('address must be set');
this.getTransactions(addresses, function onResult(txs) {
var flatArray = function (xss) { return xss.reduce(function(r, xs) { return r.concat(xs); }, []); };
var getInputs = function (t) { return t.vin.map(function (vin) { return vin.addr }); };
var getOutputs = function (t) { return flatArray(
t.vout.map(function (vout) { return vout.scriptPubKey.addresses; })
);};
var activityMap = new Array(addresses.length);
var activeAddress = flatArray(txs.map(function(t) { return getInputs(t).concat(getOutputs(t)); }));
activeAddress.forEach(function (addr) {
var index = addresses.indexOf(addr);
if (index != -1) activityMap[index] = true;
});
cb(null, activityMap);
});
};
Insight.prototype._request = function(options, callback) {

View File

@ -30,7 +30,7 @@ AddressIndex.prototype.toObj = function() {
AddressIndex.prototype.checkRange = function(index, isChange) {
if ((isChange && index > this.changeIndex) ||
(!isChange && index > this.receiveIndex)) {
throw new Error('Out of bounds at index %d isChange: %d', index, isChange);
throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange);
}
};

View File

@ -157,8 +157,6 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) {
// TODO this could be cached
PublicKeyRing.prototype.getRedeemScript = function (index, isChange) {
this.indexes.checkRange(index, isChange);
var pubKeys = this.getPubKeys(index, isChange);
var script = Script.createMultisig(this.requiredCopayers, pubKeys);
return script;

View File

@ -8,6 +8,7 @@ var coinUtil = bitcore.util;
var buffertools = bitcore.buffertools;
var Builder = bitcore.TransactionBuilder;
var http = require('http');
var async = require('async');
var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
var copay = copay || require('../../../copay');
var SecureRandom = bitcore.SecureRandom;
@ -711,6 +712,71 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
return ntxid;
};
Wallet.prototype.updateIndexes = function(callback) {
var self = this;
var start = self.publicKeyRing.indexes.changeIndex;
self.indexDiscovery(start, true, 20, function(err, changeIndex) {
if (err) return callback(err);
if (changeIndex != -1)
self.publicKeyRing.indexes.changeIndex = changeIndex + 1;
start = self.publicKeyRing.indexes.receiveIndex;
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
if (err) return callback(err);
if (receiveIndex != -1)
self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1;
self.emit('publicKeyRingUpdated');
self.store();
callback();
});
});
}
Wallet.prototype.deriveAddresses = function(index, amout, isChange) {
var ret = new Array(amout);
for(var i = 0; i < amout; i++) {
ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString();
}
return ret;
}
// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity,
// using a scan window of @gap. The argument @change defines the branch to scan: internal or external.
// Returns -1 if no activity is found in range.
Wallet.prototype.indexDiscovery = function(start, change, gap, cb) {
var scanIndex = start;
var lastActive = -1;
var hasActivity = false;
var self = this;
async.doWhilst(
function _do(next) {
// Optimize window to minimize the derivations.
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
var addresses = self.deriveAddresses(scanIndex, scanWindow, change);
self.blockchain.checkActivity(addresses, function(err, actives){
if (err) throw err;
// Check for new activities in the newlly scanned addresses
var recentActive = actives.reduce(function(r, e, i) {
return e ? scanIndex + i : r;
}, lastActive);
hasActivity = lastActive != recentActive;
lastActive = recentActive;
scanIndex += scanWindow;
next();
});
},
function _while() { return hasActivity; },
function _finnaly(err) {
if (err) return cb(err);
cb(null, lastActive);
}
);
}
Wallet.prototype.disconnect = function() {
this.log('## DISCONNECTING');
this.network.disconnect();

View File

@ -71,6 +71,11 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) {
var walletObj = this.storage.import(base64);
if (!walletObj) return false;
var w = this.fromObj(walletObj);
var self = this;
w.updateIndexes(function(err) {
if (err) throw err;
self.log('Indexes updated');
});
return w;
};

View File

@ -593,4 +593,48 @@ describe('Wallet model', function() {
w.getNetworkName().should.equal('testnet');
});
var mockFakeActivity = function(w, isChange, f) {
var ADDRESSES = w.deriveAddresses(0, 20, isChange);
w.blockchain.checkActivity = function(addresses, cb) {
var activity = new Array(addresses.length);
for(var i=0; i<addresses.length; i++) activity[i] = f(ADDRESSES.indexOf(addresses[i]));
cb(null, activity);
}
}
it('#indexDiscovery should work without found activities', function(done) {
var w = createW2();
mockFakeActivity(w, false, function(index) { return false });
w.indexDiscovery(0, false, 5, function(e, lastActive){
lastActive.should.equal(-1);
done();
});
});
it('#indexDiscovery should continue scanning', function(done) {
var w = createW2();
mockFakeActivity(w, false, function(index) { return index <= 7 });
w.indexDiscovery(0, false, 5, function(e, lastActive){
lastActive.should.equal(7);
done();
});
});
it('#indexDiscovery should not found beyond the scannWindow', function(done) {
var w = createW2();
mockFakeActivity(w, false, function(index) { return index <= 10 || index == 17 });
w.indexDiscovery(0, false, 5, function(e, lastActive){
lastActive.should.equal(10);
done();
});
});
it('#indexDiscovery should look for activity along the scannWindow', function(done) {
var w = createW2();
mockFakeActivity(w, false, function(index) { return index <= 14 && index % 2 == 0 });
w.indexDiscovery(0, false, 5, function(e, lastActive){
lastActive.should.equal(14);
done();
});
});
});

View File

@ -72,5 +72,35 @@ describe('Insight model', function() {
done();
});
});
it('#checkActivity for innactive addreses', function(done) {
var w = new Insight();
w.getTransactions = function(addresses, cb) {
cb([]);
};
w.checkActivity(addresses, function(err, actives){
console.log(err);
actives.length.should.equal(addresses.length);
actives.filter(function(i) { return i }).length.should.equal(0);
done();
});
});
it('#checkActivity for active addreses', function(done) {
var w = new Insight();
w.getTransactions = function(addresses, cb) {
cb([
{vin: [{ addr: '2NATQJnaQe2CUKLyhL1zdNkttJM1dUH9HaM'}], vout: []},
{vin: [{ addr: '2NATQJnaQe2CUKLyhL1zdNkttJM1dUH9HaM'}], vout: []},
{vin: [{ addr: '2N9D5bcCQ2bPWUDByQ6Qb5bMgMtgsk1rw3x'}], vout: []},
{vin: [], vout: [{scriptPubKey: {addresses: ['2NFjCBFZSsxiwWAD7CKQ3hzWFtf9DcqTucY']}}]}
]);
};
w.checkActivity(addresses, function(err, actives){
actives.length.should.equal(addresses.length);
actives.filter(function(i) { return i }).length.should.equal(3);
done();
});
});
});