mirror of https://github.com/BTCPrivate/copay.git
Add wallet addresses index discovery on importing backup
This commit is contained in:
parent
107414ec7e
commit
29920abdb3
|
@ -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) {
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue