Merge pull request #19 from isocolsky/complex_flows

Complex flows
This commit is contained in:
Matias Alejo Garcia 2015-02-15 18:15:58 -03:00
commit f359827d97
4 changed files with 181 additions and 27 deletions

View File

@ -132,21 +132,16 @@ TxProposal.prototype.getActors = function() {
* @return {Object} type / createdOn * @return {Object} type / createdOn
*/ */
TxProposal.prototype.getActionBy = function(copayerId) { TxProposal.prototype.getActionBy = function(copayerId) {
var a = this.actions[copayerId]; return this.actions[copayerId];
if (!a) return null;
return {
type: a.type,
createdOn: a.createdOn,
};
}; };
TxProposal.prototype.addAction = function(copayerId, type, signatures, xpub) { TxProposal.prototype.addAction = function(copayerId, type, comment, signatures, xpub) {
var action = new TxProposalAction({ var action = new TxProposalAction({
copayerId: copayerId, copayerId: copayerId,
type: type, type: type,
signatures: signatures, signatures: signatures,
xpub: xpub, xpub: xpub,
comment: comment,
}); });
this.actions[copayerId] = action; this.actions[copayerId] = action;
this._updateStatus(); this._updateStatus();
@ -191,19 +186,19 @@ TxProposal.prototype.sign = function(copayerId, signatures, xpub) {
var t = this._getBitcoreTx(); var t = this._getBitcoreTx();
try { try {
this._addSignaturesToBitcoreTx(t, signatures, xpub); this._addSignaturesToBitcoreTx(t, signatures, xpub);
this.addAction(copayerId, 'accept', signatures, xpub); this.addAction(copayerId, 'accept', null, signatures, xpub);
return true; return true;
} catch (e) { } catch (e) {
return false; return false;
} }
}; };
TxProposal.prototype.reject = function(copayerId) { TxProposal.prototype.reject = function(copayerId, reason) {
this.addAction(copayerId, 'reject'); this.addAction(copayerId, 'reject', reason);
}; };
TxProposal.prototype.isPending = function() { TxProposal.prototype.isPending = function() {
return !_.any(['boradcasted', 'rejected'], this.status); return !_.contains(['broadcasted', 'rejected'], this.status);
}; };
TxProposal.prototype.isAccepted = function() { TxProposal.prototype.isAccepted = function() {
@ -216,6 +211,10 @@ TxProposal.prototype.isRejected = function() {
return votes['reject'] >= this.requiredRejections; return votes['reject'] >= this.requiredRejections;
}; };
TxProposal.prototype.isBroadcasted = function() {
return this.status == 'broadcasted';
};
TxProposal.prototype.setBroadcasted = function(txid) { TxProposal.prototype.setBroadcasted = function(txid) {
this.txid = txid; this.txid = txid;
this.status = 'broadcasted'; this.status = 'broadcasted';

View File

@ -8,9 +8,10 @@ function TxProposalAction(opts) {
this.type = opts.type || (opts.signatures ? 'accept' : 'reject'); this.type = opts.type || (opts.signatures ? 'accept' : 'reject');
this.signatures = opts.signatures; this.signatures = opts.signatures;
this.xpub = opts.xpub; this.xpub = opts.xpub;
this.comment = opts.comment;
}; };
TxProposalAction.fromObj = function (obj) { TxProposalAction.fromObj = function(obj) {
var x = new TxProposalAction(); var x = new TxProposalAction();
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
@ -18,6 +19,7 @@ TxProposalAction.fromObj = function (obj) {
x.type = obj.type; x.type = obj.type;
x.signatures = obj.signatures; x.signatures = obj.signatures;
x.xpub = obj.xpub; x.xpub = obj.xpub;
x.comment = obj.comment;
return x; return x;
}; };

View File

@ -686,7 +686,7 @@ CopayServer.prototype.rejectTx = function(opts, cb) {
if (txp.status != 'pending') if (txp.status != 'pending')
return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending')); return cb(new ClientError('TXNOTPENDING', 'The transaction proposal is not pending'));
txp.reject(self.copayerId); txp.reject(self.copayerId, opts.reason);
self.storage.storeTx(self.walletId, txp, function(err) { self.storage.storeTx(self.walletId, txp, function(err) {
if (err) return cb(err); if (err) return cb(err);

View File

@ -877,6 +877,7 @@ describe('Copay server', function() {
server.rejectTx({ server.rejectTx({
txProposalId: txid, txProposalId: txid,
reason: 'some reason',
}, function(err) { }, function(err) {
should.not.exist(err); should.not.exist(err);
server.getPendingTxs({}, function(err, txs) { server.getPendingTxs({}, function(err, txs) {
@ -887,8 +888,9 @@ describe('Copay server', function() {
var actors = tx.getActors(); var actors = tx.getActors();
actors.length.should.equal(1); actors.length.should.equal(1);
actors[0].should.equal(wallet.copayers[0].id); actors[0].should.equal(wallet.copayers[0].id);
tx.getActionBy(wallet.copayers[0].id).type.should.equal('reject'); var action = tx.getActionBy(wallet.copayers[0].id);
action.type.should.equal('reject');
action.comment.should.equal('some reason');
done(); done();
}); });
}); });
@ -1126,8 +1128,9 @@ describe('Copay server', function() {
server = s; server = s;
wallet = w; wallet = w;
server.createAddress({}, function(err, address) { server.createAddress({}, function(err, address) {
helpers.createUtxos(server, wallet, _.range(1, 9), function(inutxos) { helpers.createUtxos(server, wallet, _.range(1, 9), function(inUtxos) {
utxos = inutxos; utxos = inUtxos;
helpers.stubBlockExplorer(server, utxos, '999');
done(); done();
}); });
}); });
@ -1135,7 +1138,6 @@ describe('Copay server', function() {
}); });
it('other copayers should see pending proposal created by one copayer', function(done) { it('other copayers should see pending proposal created by one copayer', function(done) {
helpers.stubBlockExplorer(server, utxos);
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) { server.createTx(txOpts, function(err, txp) {
should.not.exist(err); should.not.exist(err);
@ -1152,15 +1154,171 @@ describe('Copay server', function() {
}); });
}); });
it.skip('tx proposals should not be broadcast until quorum is reached', function(done) { it('tx proposals should not be broadcast until quorum is reached', function(done) {
var txpId;
async.waterfall([
function(next) {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
should.exist.txp;
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
_.keys(txp.actions).should.be.empty;
next(null, txp);
});
},
function(txp, next) {
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: txpId,
signatures: signatures,
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
txp.isPending().should.be.true;
txp.isRejected().should.be.false;
txp.isAccepted().should.be.false;
_.keys(txp.actions).length.should.equal(1);
var action = txp.actions[wallet.copayers[0].id];
action.type.should.equal('accept');
next(null, txp);
});
},
function(txp, next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server, wallet) {
helpers.stubBlockExplorer(server, utxos, '999');
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
server.signTx({
txProposalId: txpId,
signatures: signatures,
}, function(err) {
should.not.exist(err);
next();
});
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(0);
next();
});
},
function(next) {
server.getTx({
id: txpId
}, function(err, txp) {
should.not.exist(err);
txp.isPending().should.be.false;
txp.isRejected().should.be.false;
txp.isAccepted().should.be.true;
txp.isBroadcasted().should.be.true;
txp.txid.should.equal('999');
_.keys(txp.actions).length.should.equal(2);
done();
});
},
]);
}); });
it.skip('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {}); it('tx proposals should accept as many rejections as possible without finally rejecting', function(done) {
var txpId;
async.waterfall([
it.skip('proposal creator should be able to delete proposal if there are no other signatures', function(done) {}); function(next) {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 10, 'some message', TestData.copayers[0].privKey);
server.createTx(txOpts, function(err, txp) {
txpId = txp.id;
should.not.exist(err);
should.exist.txp;
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
_.keys(txp.actions).should.be.empty;
next();
});
},
function(next) {
server.rejectTx({
txProposalId: txpId,
reason: 'just because'
}, function(err) {
should.not.exist(err);
next();
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(1);
var txp = txps[0];
txp.isPending().should.be.true;
txp.isRejected().should.be.false;
txp.isAccepted().should.be.false;
_.keys(txp.actions).length.should.equal(1);
var action = txp.actions[wallet.copayers[0].id];
action.type.should.equal('reject');
action.comment.should.equal('just because');
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[1].id, function(server, wallet) {
helpers.stubBlockExplorer(server, utxos, '999');
server.rejectTx({
txProposalId: txpId,
reason: 'some other reason'
}, function(err) {
should.not.exist(err);
next();
});
});
},
function(next) {
server.getPendingTxs({}, function(err, txps) {
should.not.exist(err);
txps.length.should.equal(0);
next();
});
},
function(next) {
server.getTx({
id: txpId
}, function(err, txp) {
should.not.exist(err);
txp.isPending().should.be.false;
txp.isRejected().should.be.true;
txp.isAccepted().should.be.false;
_.keys(txp.actions).length.should.equal(2);
done();
});
},
]);
});
}); });
describe('#getTxs', function() { describe('#getTxs', function() {
var server, wallet, clock; var server, wallet, clock;
@ -1256,8 +1414,6 @@ describe('Copay server', function() {
var server, wallet; var server, wallet;
beforeEach(function(done) { beforeEach(function(done) {
if (server) return done();
console.log('\tCreating TXS...');
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
@ -1304,9 +1460,6 @@ describe('Copay server', function() {
}); });
}); });
it('should pull the first 5 notifications after wallet creation', function(done) { it('should pull the first 5 notifications after wallet creation', function(done) {
server.getNotifications({ server.getNotifications({
minTs: 0, minTs: 0,