commit
f359827d97
|
@ -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';
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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) {
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,14 +1154,170 @@ 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,
|
||||||
|
|
Loading…
Reference in New Issue