From 8c8c7e51fbf959719fcd3f059b74d86433593956 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 28 Jan 2015 16:40:07 -0300 Subject: [PATCH] . --- lib/model/txproposal.js | 65 ++++++++++++++++++++++++++++++++++++----- lib/server.js | 63 +++++++++++++++++++++------------------ test/integration.js | 3 +- 3 files changed, 92 insertions(+), 39 deletions(-) diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 0b3286b..758432f 100644 --- a/lib/model/txproposal.js +++ b/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; diff --git a/lib/server.js b/lib/server.js index 244f636..ef70d27 100644 --- a/lib/server.js +++ b/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,26 +297,29 @@ 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 (tx.status == 'accepted'); - self._broadcastTx(tx); + if (txp.status == 'accepted'); + self._broadcastTx(txp.rawTx, function (err, txid) { + if (err) return cb(err); - return cb(); + tx.setBroadcasted(txid); + self.storage.storeTx(wallet.id, txp, function (err) { + if (err) return cb(err); + + return cb(); + }); + }); }); }); }); diff --git a/test/integration.js b/test/integration.js index c888ece..fb36f52 100644 --- a/test/integration.js +++ b/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',