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

View File

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

View File

@ -686,7 +686,7 @@ CopayServer.prototype.rejectTx = function(opts, cb) {
if (txp.status != '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) {
if (err) return cb(err);

View File

@ -877,6 +877,7 @@ describe('Copay server', function() {
server.rejectTx({
txProposalId: txid,
reason: 'some reason',
}, function(err) {
should.not.exist(err);
server.getPendingTxs({}, function(err, txs) {
@ -887,8 +888,9 @@ describe('Copay server', function() {
var actors = tx.getActors();
actors.length.should.equal(1);
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();
});
});
@ -1126,8 +1128,9 @@ describe('Copay server', function() {
server = s;
wallet = w;
server.createAddress({}, function(err, address) {
helpers.createUtxos(server, wallet, _.range(1, 9), function(inutxos) {
utxos = inutxos;
helpers.createUtxos(server, wallet, _.range(1, 9), function(inUtxos) {
utxos = inUtxos;
helpers.stubBlockExplorer(server, utxos, '999');
done();
});
});
@ -1135,7 +1138,6 @@ describe('Copay server', function() {
});
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);
server.createTx(txOpts, function(err, txp) {
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() {
var server, wallet, clock;
@ -1256,8 +1414,6 @@ describe('Copay server', function() {
var server, wallet;
beforeEach(function(done) {
if (server) return done();
console.log('\tCreating TXS...');
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
wallet = w;
@ -1304,9 +1460,6 @@ describe('Copay server', function() {
});
});
it('should pull the first 5 notifications after wallet creation', function(done) {
server.getNotifications({
minTs: 0,