mirror of https://github.com/BTCPrivate/copay.git
all test passing!
This commit is contained in:
parent
753b890658
commit
f5f9848ff1
|
@ -17,8 +17,6 @@ var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version'];
|
|||
function TxProposal(opts) {
|
||||
preconditions.checkArgument(opts);
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
preconditions.checkArgument(opts.creator, 'no creator');
|
||||
preconditions.checkArgument(opts.createdTs, 'no createdTs');
|
||||
preconditions.checkArgument(opts.builder, 'no builder');
|
||||
preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths');
|
||||
|
||||
|
@ -106,8 +104,7 @@ TxProposal.prototype.toObj = function() {
|
|||
};
|
||||
|
||||
|
||||
TxProposal.trim = function() {
|
||||
var o = this.toObj();
|
||||
TxProposal._trim = function(o) {
|
||||
var ret = {};
|
||||
CORE_FIELDS.forEach(function(k) {
|
||||
ret[k] = o[k];
|
||||
|
@ -115,7 +112,6 @@ TxProposal.trim = function() {
|
|||
return ret;
|
||||
};
|
||||
|
||||
// fromObj => from a trusted source
|
||||
TxProposal.fromObj = function(o, forceOpts) {
|
||||
preconditions.checkArgument(o.builderObj);
|
||||
delete o['builder'];
|
||||
|
@ -137,6 +133,9 @@ TxProposal.fromObj = function(o, forceOpts) {
|
|||
return new TxProposal(o);
|
||||
};
|
||||
|
||||
TxProposal.fromUntrustedObj = function(o, forceOpts) {
|
||||
return TxProposal.fromObj(TxProposal._trim(o),forceOpts);
|
||||
};
|
||||
|
||||
|
||||
TxProposal._formatKeys = function(keys) {
|
||||
|
@ -208,7 +207,11 @@ TxProposal.prototype.setSeen = function(copayerId) {
|
|||
};
|
||||
|
||||
TxProposal.prototype.setRejected = function(copayerId) {
|
||||
if (!this.rejectedBy[copayerId] && !this.signedBy)
|
||||
|
||||
if (this.signedBy[copayerId])
|
||||
throw new Error('Can not reject a signed TX');
|
||||
|
||||
if (!this.rejectedBy[copayerId])
|
||||
this.rejectedBy[copayerId] = Date.now();
|
||||
};
|
||||
|
||||
|
@ -230,7 +233,7 @@ TxProposal.prototype._allSignatures = function() {
|
|||
|
||||
|
||||
TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
||||
var newCopayers = {},
|
||||
var newCopayer = {},
|
||||
oldCopayers = {}, newSignedBy = {}, readOnlyPeers = {}, isNew = 1;
|
||||
|
||||
for(var k in this.signedBy) {
|
||||
|
@ -253,35 +256,36 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) {
|
|||
if (oldCopayers[copayerId]) {
|
||||
//Already have it. Do nothing
|
||||
} else {
|
||||
newCopayers[copayerId] = Date.now();
|
||||
newCopayer[copayerId] = Date.now();
|
||||
delete oldCopayers[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!newCopayers[senderId] && !readOnlyPeers[senderId])
|
||||
if (!newCopayer[senderId] && !readOnlyPeers[senderId])
|
||||
throw new Error('TX must have a (new) senders signature')
|
||||
|
||||
if (isNew && Object.keys(newCopayers).length>1)
|
||||
throw new Error('New TX must have only 1 signature');
|
||||
if (Object.keys(newCopayer).length>1)
|
||||
throw new Error('New TX must have only 1 new signature');
|
||||
|
||||
// Handler creator / createdTs.
|
||||
// from senderId, and must be signed by senderId
|
||||
if (isNew) {
|
||||
this.creator = Object.keys(newCopayers)[0];
|
||||
this.creator = Object.keys(newCopayer)[0];
|
||||
this.createdTs = Date.now();
|
||||
}
|
||||
|
||||
//Ended. Update this.
|
||||
for(var i in newCopayers) {
|
||||
this.signedBy[i] = newCopayers[i];
|
||||
for(var i in newCopayer) {
|
||||
this.signedBy[i] = newCopayer[i];
|
||||
}
|
||||
|
||||
// signedBy has preference over rejectedBy
|
||||
for(var i in this.signedBy) {
|
||||
delete this.rejectedBy[i];
|
||||
}
|
||||
console.log('[TxProposal.js.287:newCopayer:]',newCopayer); //TODO
|
||||
|
||||
return Object.keys(newCopayers);
|
||||
return Object.keys(newCopayer);
|
||||
};
|
||||
|
||||
// merge will not merge any metadata.
|
||||
|
|
|
@ -56,13 +56,12 @@ TxProposals.prototype.toObj = function() {
|
|||
};
|
||||
|
||||
|
||||
TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, builderOpts) {
|
||||
var safeObj = inObj.trimUntrustedObj();
|
||||
var incomingTx = TxProposal.fromObj(safeObj, builderOpts);
|
||||
TxProposals.prototype.merge = function(inObj, builderOpts) {
|
||||
var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts);
|
||||
incomingTx._sync();
|
||||
|
||||
var myTxps = this.txps;
|
||||
var ntxid = inTxp.getId();
|
||||
var ntxid = incomingTx.getId();
|
||||
var ret = {
|
||||
ntxid: ntxid
|
||||
};
|
||||
|
@ -70,37 +69,39 @@ TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, buil
|
|||
if (myTxps[ntxid]) {
|
||||
|
||||
// Merge an existing txProposal
|
||||
ret.hasChanged = myTxps[ntxid].merge(inTxp, allowedPubKeys);
|
||||
ret.hasChanged = myTxps[ntxid].merge(incomingTx, allowedPubKeys);
|
||||
|
||||
|
||||
} else {
|
||||
// Create a new one
|
||||
ret.new = 1;
|
||||
this.txps[ntxid] = inTxp;
|
||||
this.txps[ntxid] = incomingTx;
|
||||
}
|
||||
|
||||
ret.txp = this.txps[ntxid];
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) {
|
||||
var inTxp = TxProposal.fromObj(txProposalObj, opts);
|
||||
var mergeInfo = this.merge(inTxp, allowedPubKeys);
|
||||
mergeInfo.inTxp = inTxp;
|
||||
return mergeInfo;
|
||||
};
|
||||
|
||||
|
||||
// Add a LOCALLY CREATED (trusted) tx proposal
|
||||
TxProposals.prototype.add = function(txp) {
|
||||
txp.sync();
|
||||
txp._sync();
|
||||
var ntxid = txp.getId();
|
||||
this.txps[ntxid] = txp;
|
||||
return ntxid;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype._getTxp = function(ntxid) {
|
||||
var ret = this.txps[ntxid];
|
||||
if (!ret)
|
||||
throw new Error('Could not find txp: '+ntxid);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
||||
var txp = this.txps[ntxid];
|
||||
var txp = this._getTxp(ntxid);
|
||||
|
||||
var i = JSON.parse(JSON.stringify(txp));
|
||||
i.builder = txp.builder;
|
||||
i.ntxid = ntxid;
|
||||
|
@ -136,6 +137,17 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) {
|
|||
return i;
|
||||
};
|
||||
|
||||
|
||||
TxProposals.prototype.reject = function(ntxid, copayerId) {
|
||||
var txp = this._getTxp(ntxid);
|
||||
txp.setRejected(copayerId);
|
||||
};
|
||||
|
||||
TxProposals.prototype.seen = function(ntxid, copayerId) {
|
||||
var txp = this._getTxp(ntxid);
|
||||
txp.setSeen(copayerId);
|
||||
};
|
||||
|
||||
//returns the unspent txid-vout used in PENDING Txs
|
||||
TxProposals.prototype.getUsedUnspent = function(maxRejectCount) {
|
||||
var ret = {};
|
||||
|
|
|
@ -130,30 +130,27 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) {
|
|||
};
|
||||
|
||||
|
||||
Wallet.prototype._processProposalEvents = function(mergeInfo) {
|
||||
var ev = [];
|
||||
if (mergeInfo) {
|
||||
if (mergeInfo.new) {
|
||||
Wallet.prototype._processProposalEvents = function(senderId, m) {
|
||||
var ev;
|
||||
if (m) {
|
||||
if (m.new) {
|
||||
ev = {
|
||||
type: 'new',
|
||||
cid: senderId
|
||||
}
|
||||
} else {
|
||||
for (var i in mergeInfo.newCopayers) {
|
||||
var copayerId = mergeInfo.newCopayers[i];
|
||||
ev.push({
|
||||
} else if(m.newCopayer){
|
||||
ev={
|
||||
type: 'signed',
|
||||
cid: copayerId
|
||||
});
|
||||
}
|
||||
cid: m.newCopayer
|
||||
};
|
||||
}
|
||||
} else {
|
||||
ev = {
|
||||
type: 'corrupt',
|
||||
cId: senderId,
|
||||
error: e,
|
||||
};
|
||||
}
|
||||
|
||||
if (ev)
|
||||
this.emit('txProposalEvent', ev);
|
||||
};
|
||||
|
@ -189,13 +186,12 @@ Wallet.prototype._handleTxProposal = function(senderId, data) {
|
|||
var m;
|
||||
|
||||
try {
|
||||
m = this.txProposals.mergeObj(senderId, data.txProposal, Wallet.builderOpts);
|
||||
|
||||
m = this.txProposals.merge(data.txProposal, Wallet.builderOpts);
|
||||
var keyMap = this._getKeyMap(m.tpx,senderId);
|
||||
ret.newCopayers = m.txp.setCopayers(senderId, keyMap);
|
||||
ret.newCopayer = m.txp.setCopayers(senderId, keyMap);
|
||||
|
||||
} catch (e) {
|
||||
this.log('Corrupt TX proposal received', senderId, e); //TODO
|
||||
this.log('Corrupt TX proposal received', senderId, e);
|
||||
}
|
||||
|
||||
if (m) {
|
||||
|
@ -632,20 +628,12 @@ Wallet.prototype.getTxProposals = function() {
|
|||
|
||||
|
||||
Wallet.prototype.reject = function(ntxid) {
|
||||
var myId = this.getMyCopayerId();
|
||||
var txp = this.txProposals.txps[ntxid];
|
||||
if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) {
|
||||
throw new Error('Invalid transaction to reject: ' + ntxid);
|
||||
}
|
||||
|
||||
txp.rejectedBy[myId] = Date.now();
|
||||
var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()) ;
|
||||
this.sendReject(ntxid);
|
||||
this.store();
|
||||
this.emit('txProposalsUpdated');
|
||||
};
|
||||
|
||||
|
||||
|
||||
Wallet.prototype.sign = function(ntxid, cb) {
|
||||
preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined');
|
||||
var self = this;
|
||||
|
@ -834,9 +822,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
|||
var priv = this.privateKey;
|
||||
opts = opts || {};
|
||||
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName());
|
||||
preconditions.checkState(pkr.isComplete());
|
||||
preconditions.checkState(priv);
|
||||
preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch');
|
||||
preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete');
|
||||
preconditions.checkState(priv,'no private key');
|
||||
if (comment) preconditions.checkArgument(comment.length <= 100);
|
||||
|
||||
if (!opts.remainderOut) {
|
||||
|
|
|
@ -668,18 +668,28 @@ describe('Wallet model', function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
it('should create & reject transaction', function(done) {
|
||||
it('should fail to reject a signed transaction', function() {
|
||||
var w = cachedCreateW2();
|
||||
w.privateKey = null;
|
||||
var utxo = createUTXO(w);
|
||||
w.blockchain.fixUnspent(utxo);
|
||||
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||
w.on('txProposalsUpdated', function() {
|
||||
w.getTxProposals()[0].signedByUs.should.equal(false);
|
||||
w.getTxProposals()[0].rejectedByUs.should.equal(true);
|
||||
done();
|
||||
(function() {w.reject(ntxid);}).should.throw('reject a signed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create & reject transaction', function(done) {
|
||||
var w = cachedCreateW2();
|
||||
var oldK = w.privateKey;
|
||||
var utxo = createUTXO(w);
|
||||
w.blockchain.fixUnspent(utxo);
|
||||
w.createTx(toAddress, amountSatStr, null, function(ntxid) {
|
||||
var s = sinon.stub(w, 'getMyCopayerId').returns('213');
|
||||
Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(0);
|
||||
w.reject(ntxid);
|
||||
Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(1);
|
||||
w.txProposals._getTxp(ntxid).rejectedBy['213'].should.gt(1);
|
||||
s.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should create & sign & send a transaction', function(done) {
|
||||
|
@ -1030,8 +1040,9 @@ describe('Wallet model', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('validate txProposals', function() {
|
||||
var testValidate = function(shouldThrow, result, done) {
|
||||
describe('_handleTxProposal', function() {
|
||||
var testValidate = function(response, result, done) {
|
||||
|
||||
var w = cachedCreateW();
|
||||
var spy = sinon.spy();
|
||||
w.on('txProposalEvent', spy);
|
||||
|
@ -1039,26 +1050,31 @@ describe('Wallet model', function() {
|
|||
e.type.should.equal(result);
|
||||
done();
|
||||
});
|
||||
var txp = {dummy:1};
|
||||
// txp.prototype.getId = function() {return 'aa'};
|
||||
var txp = {dummy:1};
|
||||
var txp = { 'txProposal': txp };
|
||||
var merge = sinon.stub(w.txProposals, 'mergeFromObj', function() {
|
||||
if (shouldThrow) throw new Error();
|
||||
return {events: [{type:'new'}]};
|
||||
var merge = sinon.stub(w.txProposals, 'merge', function() {
|
||||
if (response==0) throw new Error();
|
||||
return {newCopayer: ['juan'], ntxid:1, new:response==1};
|
||||
});
|
||||
|
||||
w._handleTxProposal('senderID', txp, true);
|
||||
w._handleTxProposal('senderID', txp);
|
||||
spy.callCount.should.equal(1);
|
||||
merge.restore();
|
||||
};
|
||||
|
||||
it('should validate for undefined', function(done) {
|
||||
it('should handle corrupt', function(done) {
|
||||
var result = 'corrupt';
|
||||
testValidate(1, result, done);
|
||||
});
|
||||
it('should validate for SIGHASH_ALL', function(done) {
|
||||
var result = 'new';
|
||||
testValidate(0, result, done);
|
||||
});
|
||||
it('should handle new', function(done) {
|
||||
var result = 'new';
|
||||
testValidate(1, result, done);
|
||||
});
|
||||
it('should handle signed', function(done) {
|
||||
var result = 'signed';
|
||||
testValidate(2, result, done);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,15 @@ var should = chai.should();
|
|||
var PrivateKey = require('../js/models/core/PrivateKey');
|
||||
var PublicKeyRing = require('../js/models/core/PublicKeyRing');
|
||||
|
||||
var getNewEpk = function() {
|
||||
return new PrivateKey({
|
||||
networkName: 'livenet',
|
||||
})
|
||||
.deriveBIP45Branch()
|
||||
.extendedPublicKeyString();
|
||||
}
|
||||
|
||||
|
||||
describe('Performance tests', function() {
|
||||
describe('PrivateKey', function() {
|
||||
it('should optimize BIP32 private key gen time with cache', function() {
|
||||
|
@ -43,7 +52,7 @@ describe('Performance tests', function() {
|
|||
requiredCopayers: M
|
||||
});
|
||||
for (var i = 0; i < N; i++) {
|
||||
pkr1.addCopayer(); // add new random ext public key
|
||||
pkr1.addCopayer(getNewEpk()); // add new random ext public key
|
||||
}
|
||||
var generateN = 5;
|
||||
var generated = [];
|
||||
|
|
Loading…
Reference in New Issue