Browse Source

.

activeAddress
Ivan Socolsky 10 years ago
parent
commit
8c8c7e51fb
  1. 65
      lib/model/txproposal.js
  2. 61
      lib/server.js
  3. 3
      test/integration.js

65
lib/model/txproposal.js

@ -1,5 +1,7 @@
'use strict';
var TxProposalAction = require('./txproposalaction');
function TxProposal(opts) {
opts = opts || {};
@ -8,8 +10,12 @@ function TxProposal(opts) {
this.creatorId = opts.creatorId;
this.toAddress = opts.toAddress;
this.amount = opts.amount;
this.message = opts.message;
this.changeAddress = opts.changeAddress;
this.inputs = opts.inputs;
this.requiredSignatures = opts.requiredSignatures;
this.maxRejections = opts.maxRejections;
this.status = 'pending';
this.actions = [];
};
@ -21,19 +27,62 @@ TxProposal.fromObj = function (obj) {
x.creatorId = obj.creatorId;
x.toAddress = obj.toAddress;
x.amount = obj.amount;
x.message = obj.message;
x.changeAddress = obj.changeAddress;
x.inputs = obj.inputs;
x.raw = obj.raw;
x.actions = _.map(obj.actions, function(a) {
return {
createdOn: a.createdOn,
copayerId: a.copayerId,
type: a.type,
signature: a.signature,
};
x.rawTx = obj.rawTx;
x.requiredSignatures = obj.requiredSignatures;
x.maxRejections = obj.maxRejections;
x.status = obj.status;
x.txid = obj.txid;
x.actions = _.map(obj.actions, function(action) {
return new TxProposalAction(action);
});
return x;
};
TxProposal.prototype._updateStatus = function () {
if (this.status != 'pending') return;
if (this.isRejected()) {
this.status = 'rejected';
} else if (this.isAccepted()) {
this.status = 'accepted';
}
};
TxProposal.prototype.addAction = function (copayerId, type, signature) {
var action = new TxProposalAction({
copayerId: copayerId,
type: type,
signature: signature,
});
this.actions.push(action);
this._updateStatus();
};
TxProposal.prototype.sign = function (copayerId, signature) {
this.addAction(copayerId, 'accept', signature);
};
TxProposal.prototype.reject = function (copayerId) {
this.addAction(copayerId, 'reject');
};
TxProposal.prototype.isAccepted = function () {
var votes = _.countBy(this.actions, 'type');
return votes['accept'] >= this.requiredSignatures;
};
TxProposal.prototype.isRejected = function () {
var votes = _.countBy(this.actions, 'type');
return votes['reject'] > this.maxRejections;
};
TxProposal.prototype.setBroadcasted = function (txid) {
this.txid = txid;
this.status = 'broadcasted';
};
module.exports = TxProposal;

61
lib/server.js

@ -227,21 +227,13 @@ CopayServer.prototype._getUtxos = function (opts, cb) {
});
};
CopayServer.prototype._doCreateTx = function (copayerId, toAddress, amount, changeAddress, utxos, cb) {
var tx = new TxProposal({
creatorId: copayerId,
toAddress: toAddress,
amount: amount,
changeAddress: changeAddress,
inputs: utxos,
});
tx.raw = new Bitcore.Transaction()
CopayServer.prototype._createRawTx = function (tx) {
var rawTx = new Bitcore.Transaction()
.from(tx.inputs)
.to(tx.toAddress, tx.amount)
.change(tx.changeAddress);
return tx;
return rawTx;
};
@ -264,18 +256,28 @@ CopayServer.prototype.createTx = function (opts, cb) {
self._getUtxos({ walletId: wallet.id }, function (err, utxos) {
if (err) return cb(err);
var tx = self._doCreateTx(opts.copayerId, opts.toAddress, opts.amount, opts.changeAddress, utxos);
var txp = new TxProposal({
creatorId: opts.copayerId,
toAddress: opts.toAddress,
amount: opts.amount,
changeAddress: opts.changeAddress,
inputs: utxos,
requiredSignatures: wallet.m,
maxRejections: wallet.n - wallet.m,
});
txp.rawTx = self._createRawTx(txp);
self.storage.storeTx(tx, function (err) {
self.storage.storeTx(txp, function (err) {
if (err) return cb(err);
return cb(null, tx);
return cb(null, txp);
});
});
});
};
CopayServer.prototype._broadcastTx = function (tx, cb) {
CopayServer.prototype._broadcastTx = function (rawTx, cb) {
// TODO: this should attempt to broadcast _all_ accepted txps?
cb = cb || function () {};
throw 'not implemented';
@ -295,29 +297,32 @@ CopayServer.prototype.signTx = function (opts, cb) {
self.getWallet({ id: opts.walletId }, function (err, wallet) {
if (err || !wallet) return cb(err);
self.fetchTx(wallet.id, opts.txProposalId, function (err, tx) {
self.fetchTx(wallet.id, opts.txProposalId, function (err, txp) {
if (err) return cb(err);
if (!tx) return cb('Transaction proposal not found');
var action = _.find(tx.actions, { copayerId: opts.copayerId });
if (!txp) return cb('Transaction proposal not found');
var action = _.find(txp.actions, { copayerId: opts.copayerId });
if (action) return cb('Copayer already acted upon this transaction proposal');
if (tx.status != 'pending') return cb('The transaction proposal is not pending');
if (txp.status != 'pending') return cb('The transaction proposal is not pending');
tx.sign(opts.copayerId, opts.signature);
if (tx.isRejected()) {
tx.status = 'rejected';
} else if (tx.isAccepted()) {
tx.status = 'accepted';
}
self.storage.storeTx(wallet.id, tx, function (err) {
txp.sign(opts.copayerId, opts.signature);
self.storage.storeTx(wallet.id, txp, function (err) {
if (err) return cb(err);
if (txp.status == 'accepted');
self._broadcastTx(txp.rawTx, function (err, txid) {
if (err) return cb(err);
if (tx.status == 'accepted');
self._broadcastTx(tx);
tx.setBroadcasted(txid);
self.storage.storeTx(wallet.id, txp, function (err) {
if (err) return cb(err);
return cb();
});
});
});
});
});
};

3
test/integration.js

@ -399,11 +399,10 @@ describe('Copay server', function() {
});
it.skip('should create tx', function (done) {
server._verifyMessageSignature = sinon.stub().returns(true);
var bc = sinon.stub();
bc.getUnspentUtxos = sinon.stub().yields(null, ['utxo1', 'utxo2']);
server._getBlockExplorer = sinon.stub().returns(bc);
var txOpts = {
copayerId: '1',
walletId: '123',

Loading…
Cancel
Save