WIP
This commit is contained in:
parent
6e5f06693d
commit
72e1dfc114
6 changed files with 221 additions and 63 deletions
3
copay.js
3
copay.js
|
|
@ -1,6 +1,7 @@
|
||||||
// core
|
// core
|
||||||
module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing');
|
module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing');
|
||||||
module.exports.TxProposals = require('./js/models/core/TxProposals');
|
module.exports.TxProposal = require('./js/models/core/TxProposal');
|
||||||
|
module.exports.TxProposalsSet = require('./js/models/core/TxProposalsSet');
|
||||||
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
|
module.exports.PrivateKey = require('./js/models/core/PrivateKey');
|
||||||
module.exports.Passphrase = require('./js/models/core/Passphrase');
|
module.exports.Passphrase = require('./js/models/core/Passphrase');
|
||||||
module.exports.HDPath = require('./js/models/core/HDPath');
|
module.exports.HDPath = require('./js/models/core/HDPath');
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) {
|
||||||
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
return BIP45_PUBLIC_PREFIX + '/' + sub;
|
||||||
};
|
};
|
||||||
|
|
||||||
HDPath.indicesForPath = function(path) {
|
HDPath.indexesForPath = function(path) {
|
||||||
preconditions.shouldBeString(path);
|
preconditions.shouldBeString(path);
|
||||||
var s = path.split('/');
|
var s = path.split('/');
|
||||||
return {
|
return {
|
||||||
isChange: s[3] === '1',
|
isChange: s[3] === '1',
|
||||||
index: parseInt(s[4]),
|
addressIndex: parseInt(s[4]),
|
||||||
copayerIndex: parseInt(s[2])
|
copayerIndex: parseInt(s[2])
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
160
js/models/core/TxProposalsSet.js
Normal file
160
js/models/core/TxProposalsSet.js
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
var util = bitcore.util;
|
||||||
|
var Transaction = bitcore.Transaction;
|
||||||
|
var BuilderMockV0 = require('./BuilderMockV0');;
|
||||||
|
var Script = bitcore.Script;
|
||||||
|
var Key = bitcore.Key;
|
||||||
|
var buffertools = bitcore.buffertools;
|
||||||
|
var preconditions = require('preconditions').instance();
|
||||||
|
|
||||||
|
|
||||||
|
function TxProposalsSet(opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
this.walletId = opts.walletId;
|
||||||
|
this.network = opts.networkName === 'livenet' ?
|
||||||
|
bitcore.networks.livenet : bitcore.networks.testnet;
|
||||||
|
this.txps = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TxProposalsSet.fromObj = function(o, forceOpts) {
|
||||||
|
var ret = new TxProposalsSet({
|
||||||
|
networkName: o.networkName,
|
||||||
|
walletId: o.walletId,
|
||||||
|
});
|
||||||
|
|
||||||
|
o.txps.forEach(function(o2) {
|
||||||
|
var t = TxProposal.fromObj(o2, forceOpts);
|
||||||
|
if (t.builder) {
|
||||||
|
var id = t.getID();
|
||||||
|
ret.txps[id] = t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.getNtxids = function() {
|
||||||
|
return Object.keys(this.txps);
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.toObj = function(onlyThisNtxid) {
|
||||||
|
if (onlyThisNtxid) throw new Error();
|
||||||
|
var ret = [];
|
||||||
|
for (var id in this.txps) {
|
||||||
|
|
||||||
|
if (onlyThisNtxid && id != onlyThisNtxid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var t = this.txps[id];
|
||||||
|
if (!t.sent)
|
||||||
|
ret.push(t.toObj());
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
txps: ret,
|
||||||
|
walletId: this.walletId,
|
||||||
|
networkName: this.network.name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) {
|
||||||
|
var inTxp = TxProposal.fromObj(txProposalObj, opts);
|
||||||
|
var mergeInfo = this.txProposals.merge(inTxp, allowedPubKeys);
|
||||||
|
mergeInfo.inTxp = inTxp;
|
||||||
|
return mergeInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) {
|
||||||
|
var myTxps = this.txps;
|
||||||
|
|
||||||
|
var ntxid = inTxp.getID();
|
||||||
|
var ret = {};
|
||||||
|
ret.events = [];
|
||||||
|
ret.events.hasChanged = false;
|
||||||
|
|
||||||
|
if (myTxps[ntxid]) {
|
||||||
|
var v0 = myTxps[ntxid];
|
||||||
|
var v1 = inTxp;
|
||||||
|
ret = v0.merge(v1, allowedPubKeys);
|
||||||
|
} else {
|
||||||
|
this.txps[ntxid] = inTxp;
|
||||||
|
ret.hasChanged = true;
|
||||||
|
ret.events.push({
|
||||||
|
type: 'new',
|
||||||
|
cid: inTxp.creator,
|
||||||
|
tx: ntxid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||||
|
TxProposalsSet.prototype.add = function(data) {
|
||||||
|
var txp = new TxProposal(data);
|
||||||
|
var ntxid = txp.getID();
|
||||||
|
this.txps[ntxid] = txp;
|
||||||
|
return ntxid;
|
||||||
|
};
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.setSent = function(ntxid, txid) {
|
||||||
|
//sent TxProposalsSet are local an not broadcasted.
|
||||||
|
this.txps[ntxid].setSent(txid);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TxProposalsSet.prototype.getTxProposal = function(ntxid, copayers) {
|
||||||
|
var txp = this.txps[ntxid];
|
||||||
|
var i = JSON.parse(JSON.stringify(txp));
|
||||||
|
i.builder = txp.builder;
|
||||||
|
i.ntxid = ntxid;
|
||||||
|
i.peerActions = {};
|
||||||
|
|
||||||
|
if (copayers) {
|
||||||
|
for (var j = 0; j < copayers.length; j++) {
|
||||||
|
var p = copayers[j];
|
||||||
|
i.peerActions[p] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var p in txp.seenBy) {
|
||||||
|
i.peerActions[p] = {
|
||||||
|
seen: txp.seenBy[p]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (var p in txp.signedBy) {
|
||||||
|
i.peerActions[p] = i.peerActions[p] || {};
|
||||||
|
i.peerActions[p].sign = txp.signedBy[p];
|
||||||
|
}
|
||||||
|
var r = 0;
|
||||||
|
for (var p in txp.rejectedBy) {
|
||||||
|
i.peerActions[p] = i.peerActions[p] || {};
|
||||||
|
i.peerActions[p].rejected = txp.rejectedBy[p];
|
||||||
|
r++;
|
||||||
|
}
|
||||||
|
i.rejectCount = r;
|
||||||
|
|
||||||
|
var c = txp.creator;
|
||||||
|
i.peerActions[c] = i.peerActions[c] || {};
|
||||||
|
i.peerActions[c].create = txp.createdTs;
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
//returns the unspent txid-vout used in PENDING Txs
|
||||||
|
TxProposalsSet.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||||
|
var ret = {};
|
||||||
|
for (var i in this.txps) {
|
||||||
|
var u = this.txps[i].builder.getSelectedUnspent();
|
||||||
|
var p = this.getTxProposal(i);
|
||||||
|
if (p.rejectCount > maxRejectCount || p.sentTxid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (var j in u) {
|
||||||
|
ret[u[j].txid + ',' + u[j].vout] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ var Address = bitcore.Address;
|
||||||
|
|
||||||
var HDParams = require('./HDParams');
|
var HDParams = require('./HDParams');
|
||||||
var PublicKeyRing = require('./PublicKeyRing');
|
var PublicKeyRing = require('./PublicKeyRing');
|
||||||
var TxProposals = require('./TxProposals');
|
var TxProposalsSet = require('./TxProposalsSet');
|
||||||
var PrivateKey = require('./PrivateKey');
|
var PrivateKey = require('./PrivateKey');
|
||||||
var copayConfig = require('../../../config');
|
var copayConfig = require('../../../config');
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ function Wallet(opts) {
|
||||||
});
|
});
|
||||||
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
||||||
throw new Error('Network forced to ' + copayConfig.networkName +
|
throw new Error('Network forced to ' + copayConfig.networkName +
|
||||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||||
|
|
||||||
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
||||||
|
|
||||||
|
|
@ -59,10 +59,10 @@ function Wallet(opts) {
|
||||||
|
|
||||||
|
|
||||||
Wallet.builderOpts = {
|
Wallet.builderOpts = {
|
||||||
lockTime: null,
|
lockTime: null,
|
||||||
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
signhash: bitcore.Transaction.SIGNHASH_ALL,
|
||||||
fee: null,
|
fee: null,
|
||||||
feeSat: null,
|
feeSat: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.parent = EventEmitter;
|
Wallet.parent = EventEmitter;
|
||||||
|
|
@ -129,28 +129,25 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
Wallet.prototype._handleTxProposal = function(senderId, data) {
|
||||||
this.log('RECV TXPROPOSAL: ', data);
|
this.log('RECV TXPROPOSAL: ', data);
|
||||||
var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts);
|
var mergeInfo;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts);
|
||||||
var valid = inTxp.isValid();
|
} catch (e) {
|
||||||
if (!valid) {
|
|
||||||
var corruptEvent = {
|
var corruptEvent = {
|
||||||
type: 'corrupt',
|
type: 'corrupt',
|
||||||
cId: inTxp.creator
|
cId: mergeInfo.inTxp.creator
|
||||||
};
|
};
|
||||||
this.emit('txProposalEvent', corruptEvent);
|
this.emit('txProposalEvent', corruptEvent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var mergeInfo = this.txProposals.merge(inTxp, senderId);
|
|
||||||
var added = this.addSeenToTxProposals();
|
|
||||||
|
|
||||||
|
var added = this.addSeenToTxProposals();
|
||||||
if (added) {
|
if (added) {
|
||||||
this.log('### BROADCASTING txProposals with my seenBy updated.');
|
this.log('### BROADCASTING txProposals with my seenBy updated.');
|
||||||
this.sendTxProposal(inTxp.getID());
|
this.sendTxProposal(mergeInfo.inTxp.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('txProposalsUpdated');
|
this.emit('txProposalsUpdated');
|
||||||
|
|
@ -193,24 +190,24 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
||||||
// This handler is repeaded on WalletFactory (#join). TODO
|
// This handler is repeaded on WalletFactory (#join). TODO
|
||||||
case 'walletId':
|
case 'walletId':
|
||||||
this.sendWalletReady(senderId);
|
this.sendWalletReady(senderId);
|
||||||
break;
|
break;
|
||||||
case 'walletReady':
|
case 'walletReady':
|
||||||
this.sendPublicKeyRing(senderId);
|
this.sendPublicKeyRing(senderId);
|
||||||
this.sendAddressBook(senderId);
|
this.sendAddressBook(senderId);
|
||||||
this.sendAllTxProposals(senderId); // send old txps
|
this.sendAllTxProposals(senderId); // send old txps
|
||||||
break;
|
break;
|
||||||
case 'publicKeyRing':
|
case 'publicKeyRing':
|
||||||
this._handlePublicKeyRing(senderId, data, isInbound);
|
this._handlePublicKeyRing(senderId, data, isInbound);
|
||||||
break;
|
break;
|
||||||
case 'txProposal':
|
case 'txProposal':
|
||||||
this._handleTxProposal(senderId, data, isInbound);
|
this._handleTxProposal(senderId, data, isInbound);
|
||||||
break;
|
break;
|
||||||
case 'indexes':
|
case 'indexes':
|
||||||
this._handleIndexes(senderId, data, isInbound);
|
this._handleIndexes(senderId, data, isInbound);
|
||||||
break;
|
break;
|
||||||
case 'addressbook':
|
case 'addressbook':
|
||||||
this._handleAddressBook(senderId, data, isInbound);
|
this._handleAddressBook(senderId, data, isInbound);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -389,7 +386,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
|
||||||
opts.addressBook = o.addressBook;
|
opts.addressBook = o.addressBook;
|
||||||
|
|
||||||
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
||||||
opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts);
|
opts.txProposals = TxProposalsSet.fromObj(o.txProposals, Wallet.builderOpts);
|
||||||
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
||||||
|
|
||||||
opts.storage = storage;
|
opts.storage = storage;
|
||||||
|
|
@ -497,7 +494,7 @@ Wallet.prototype.generateAddress = function(isChange, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Wallet.prototype.getTxProposals = function() {
|
Wallet.prototype.getTxProposalsSet = function() {
|
||||||
var ret = [];
|
var ret = [];
|
||||||
var copayers = this.getRegisteredCopayerIds();
|
var copayers = this.getRegisteredCopayerIds();
|
||||||
for (var ntxid in this.txProposals.txps) {
|
for (var ntxid in this.txProposals.txps) {
|
||||||
|
|
@ -734,11 +731,11 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
||||||
}
|
}
|
||||||
|
|
||||||
var b = new Builder(opts)
|
var b = new Builder(opts)
|
||||||
.setUnspent(utxos)
|
.setUnspent(utxos)
|
||||||
.setOutputs([{
|
.setOutputs([{
|
||||||
address: toAddress,
|
address: toAddress,
|
||||||
amountSatStr: amountSatStr,
|
amountSatStr: amountSatStr,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
var selectedUtxos = b.getSelectedUnspent();
|
var selectedUtxos = b.getSelectedUnspent();
|
||||||
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
var inputChainPaths = selectedUtxos.map(function(utxo) {
|
||||||
|
|
@ -831,29 +828,29 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
async.doWhilst(
|
async.doWhilst(
|
||||||
function _do(next) {
|
function _do(next) {
|
||||||
// Optimize window to minimize the derivations.
|
// Optimize window to minimize the derivations.
|
||||||
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1;
|
||||||
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner);
|
||||||
self.blockchain.checkActivity(addresses, function(err, actives) {
|
self.blockchain.checkActivity(addresses, function(err, actives) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
// Check for new activities in the newlly scanned addresses
|
// Check for new activities in the newlly scanned addresses
|
||||||
var recentActive = actives.reduce(function(r, e, i) {
|
var recentActive = actives.reduce(function(r, e, i) {
|
||||||
return e ? scanIndex + i : r;
|
return e ? scanIndex + i : r;
|
||||||
}, lastActive);
|
}, lastActive);
|
||||||
hasActivity = lastActive != recentActive;
|
hasActivity = lastActive != recentActive;
|
||||||
lastActive = recentActive;
|
lastActive = recentActive;
|
||||||
scanIndex += scanWindow;
|
scanIndex += scanWindow;
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function _while() {
|
function _while() {
|
||||||
return hasActivity;
|
return hasActivity;
|
||||||
},
|
},
|
||||||
function _finnaly(err) {
|
function _finnaly(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
cb(null, lastActive);
|
cb(null, lastActive);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
var imports = require('soop').imports();
|
var imports = require('soop').imports();
|
||||||
|
|
||||||
var TxProposals = require('./TxProposals');
|
var TxProposalsSet = require('./TxProposalsSet');
|
||||||
var PublicKeyRing = require('./PublicKeyRing');
|
var PublicKeyRing = require('./PublicKeyRing');
|
||||||
var PrivateKey = require('./PrivateKey');
|
var PrivateKey = require('./PrivateKey');
|
||||||
var Wallet = require('./Wallet');
|
var Wallet = require('./Wallet');
|
||||||
|
|
@ -124,10 +124,10 @@ WalletFactory.prototype.create = function(opts) {
|
||||||
opts.nickname);
|
opts.nickname);
|
||||||
this.log('\t### PublicKeyRing Initialized');
|
this.log('\t### PublicKeyRing Initialized');
|
||||||
|
|
||||||
opts.txProposals = opts.txProposals || new TxProposals({
|
opts.txProposals = opts.txProposals || new TxProposalsSet({
|
||||||
networkName: this.networkName,
|
networkName: this.networkName,
|
||||||
});
|
});
|
||||||
this.log('\t### TxProposals Initialized');
|
this.log('\t### TxProposalsSet Initialized');
|
||||||
|
|
||||||
this.storage._setPassphrase(opts.passphrase);
|
this.storage._setPassphrase(opts.passphrase);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,9 @@ describe('HDPath model', function() {
|
||||||
].forEach(function(datum) {
|
].forEach(function(datum) {
|
||||||
var path = datum[0];
|
var path = datum[0];
|
||||||
var result = datum[1];
|
var result = datum[1];
|
||||||
it('should get the correct indices for path ' + path, function() {
|
it('should get the correct indexes for path ' + path, function() {
|
||||||
var i = HDPath.indicesForPath(path);
|
var i = HDPath.indexesForPath(path);
|
||||||
i.index.should.equal(result.index);
|
i.addressIndex.should.equal(result.index);
|
||||||
i.isChange.should.equal(result.isChange);
|
i.isChange.should.equal(result.isChange);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue