From 05325b2d92c90bcdc95c6f8c8f67bc3ed5b14e89 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 26 Nov 2014 08:38:02 -0300 Subject: [PATCH] WIP: sign message --- js/controllers/send.js | 2 +- js/models/BuilderMockV0.js | 26 --- js/models/TxProposal.js | 142 +++++++++------- js/models/TxProposals.js | 89 ++-------- js/models/Wallet.js | 280 ++++++++++++-------------------- test/TxProposal.js | 9 +- test/mocks/FakeBuilder.js | 2 +- views/includes/transaction.html | 2 +- 8 files changed, 203 insertions(+), 349 deletions(-) delete mode 100644 js/models/BuilderMockV0.js diff --git a/js/controllers/send.js b/js/controllers/send.js index a46fec5d1..376a0199f 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -25,7 +25,7 @@ angular.module('copayApp.controllers').controller('SendController', $scope.isRateAvailable = false; $scope.rateService = rateService; $scope.showScanner = false; - + $scope.myId = w.getMyCopayerId(); rateService.whenAvailable(function() { $scope.isRateAvailable = true; diff --git a/js/models/BuilderMockV0.js b/js/models/BuilderMockV0.js deleted file mode 100644 index be3926d10..000000000 --- a/js/models/BuilderMockV0.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - - -var bitcore = require('bitcore'); -var Transaction = bitcore.Transaction; - -function BuilderMockV0 (data) { - this.vanilla = data; - this.tx = new Transaction(); - this.tx.parse(new Buffer(data.tx, 'hex')); -}; - -BuilderMockV0.prototype.build = function() { - return this.tx; -}; - - -BuilderMockV0.prototype.getSelectedUnspent = function() { - return []; -}; - -BuilderMockV0.prototype.toObj = function() { - return this.vanilla; -}; - -module.exports = BuilderMockV0; diff --git a/js/models/TxProposal.js b/js/models/TxProposal.js index 32afe115b..d96861754 100644 --- a/js/models/TxProposal.js +++ b/js/models/TxProposal.js @@ -1,15 +1,16 @@ 'use strict'; -var bitcore = require('bitcore'); var _ = require('lodash'); +var preconditions = require('preconditions').singleton(); + +var bitcore = require('bitcore'); var util = bitcore.util; var Transaction = bitcore.Transaction; -var BuilderMockV0 = require('./BuilderMockV0');; var TransactionBuilder = bitcore.TransactionBuilder; var Script = bitcore.Script; var Key = bitcore.Key; -var buffertools = bitcore.buffertools; -var preconditions = require('preconditions').instance(); + +var log = require('../log'); var TX_MAX_SIZE_KB = 50; var VERSION = 1; @@ -26,13 +27,12 @@ function TxProposal(opts) { this.version = opts.version; this.builder = opts.builder; this.createdTs = opts.createdTs; - this._inputSigners = []; - // CopayerIds - this.creator = opts.creator; + // Copayer Actions ( copayerId: timeStamp ) this.signedBy = opts.signedBy || {}; this.seenBy = opts.seenBy || {}; this.rejectedBy = opts.rejectedBy || {}; + this.sentTs = opts.sentTs || null; this.sentTxid = opts.sentTxid || null; this.comment = opts.comment || null; @@ -41,7 +41,8 @@ function TxProposal(opts) { this.paymentAckMemo = null; this.paymentProtocolURL = opts.paymentProtocolURL || null; - if (opts.creator) { + // New Tx Proposal + if (_.isEmpty(this.seenBy) && opts.creator) { var now = Date.now(); var me = {}; me[opts.creator] = now; @@ -55,8 +56,6 @@ function TxProposal(opts) { throw new Error('Could not sign generated tx'); } } - - this._sync(); } TxProposal.prototype._checkPayPro = function() { @@ -99,7 +98,7 @@ TxProposal.prototype.sign = function(keys, signerId) { this.builder.sign(keys); var signaturesAdded = this.countSignatures() > before; - if (signaturesAdded){ + if (signaturesAdded) { this.signedBy[signerId] = Date.now(); } return signaturesAdded; @@ -117,6 +116,7 @@ TxProposal.prototype._check = function() { if (txSize / 1024 > TX_MAX_SIZE_KB) throw new Error('BIG: Invalid TX proposal. Too big: ' + txSize + ' bytes'); +console.log('[TxProposal.js.118]'); //TODO if (!tx.ins.length) throw new Error('Invalid tx proposal: no ins'); @@ -153,47 +153,80 @@ TxProposal.prototype.addMerchantData = function(merchantData) { this._checkPayPro(); }; +TxProposal.prototype.getScriptSigs = function() { + var tx = this.builder.build(); + var sigs = _.map(tx.ins, function(value) { + value.s.toString('hex'); + }); +console.log('[TxProposal.js.161:sigs:]',sigs); //TODO + + return sigs; +}; + TxProposal.prototype.rejectCount = function() { return _.size(this.rejectedBy); }; -TxProposal.prototype.isPending = function(maxRejectCount) { - preconditions.checkArgument(typeof maxRejectCount != 'undefined'); - if (this.rejectCount() > maxRejectCount || this.sentTxid) +TxProposal.prototype.isFinallyRejected = function(maxRejectCount) { + return this.rejectCount() > maxRejectCount; +}; + +TxProposal.prototype.isPending = function(maxRejectCount) { + preconditions.checkArgument(_.isNumber(maxRejectCount)); + + if (this.isFinallyRejected(maxRejectCount) || this.sentTxid) return false; return true; }; -TxProposal.prototype._updateSignedBy = function() { - this._inputSigners = []; +/** + * getSignersPubKey + * @desc get Pubkeys of signers, for each input + * + * @return {string[][]} array of arrays for pubkeys for each input + */ +TxProposal.prototype.getSignersPubKey = function(forceUpdate) { - var tx = this.builder.build(); - for (var i in tx.ins) { - var scriptSig = new Script(tx.ins[i].s); - var signatureCount = scriptSig.countSignatures(); + var signersPubKey = []; - var info = TxProposal._infoFromRedeemScript(scriptSig); - var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL); - var signersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); - if (signersPubKey.length !== signatureCount) - throw new Error('Invalid signature'); + if (!this._signersPubKey || forceUpdate) { - this._inputSigners[i] = signersPubKey; - }; + log.debug('Verifing signatures...'); + + var tx = this.builder.build(); + _.each(tx.ins, function(input, index) { + + var scriptSig = new Script(input.s); + var signatureCount = scriptSig.countSignatures(); + console.log('[TxProposal.js.191:signatureCount:]', signatureCount); //TODO + + var info = TxProposal._infoFromRedeemScript(scriptSig); + var txSigHash = tx.hashForSignature(info.script, parseInt(index), Transaction.SIGHASH_ALL); + var inputSignersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); + console.log('[TxProposal.js.197:inputSignersPubKey:]', inputSignersPubKey); //TODO + + // Does scriptSig has strings that are not signatures? + if (inputSignersPubKey.length !== signatureCount) + throw new Error('Invalid signature'); + + signersPubKey[index] = inputSignersPubKey; + }); + this._signersPubKey = signersPubKey; + } + + return this._signersPubKey; }; -TxProposal.prototype._sync = function() { - this._check(); - this._updateSignedBy(); - return this; -} - TxProposal.prototype.getId = function() { preconditions.checkState(this.builder); - return this.builder.build().getNormalizedHash().toString('hex'); + + if (!this.ntxid) { + this.ntxid = this.builder.build().getNormalizedHash().toString('hex'); + } + return this.ntxid; }; TxProposal.prototype.toObj = function() { @@ -237,18 +270,19 @@ TxProposal.fromObj = function(o, forceOpts) { try { o.builder = TransactionBuilder.fromObj(o.builderObj); } catch (e) { - - // backwards (V0) compatatibility fix. - if (!o.version) { - o.builder = new BuilderMockV0(o.builderObj); - o.readonly = 1; - }; + log.info('Ignoring TXP:', e); + return null; } return new TxProposal(o); }; TxProposal.fromUntrustedObj = function(o, forceOpts) { - return TxProposal.fromObj(TxProposal._trim(o), forceOpts); + var txp = TxProposal.fromObj(TxProposal._trim(o), forceOpts); + if (!txp) + throw new Error('Invalid Transaction'); + + txp._check(); + return txp; }; TxProposal.prototype.toObjTrim = function() { @@ -340,17 +374,7 @@ TxProposal.prototype.getSent = function() { return this.sentTs; } -TxProposal.prototype._allSignatures = function() { - var ret = {}; - for (var i in this._inputSigners) - for (var j in this._inputSigners[i]) - ret[this._inputSigners[i][j]] = true; - - return ret; -}; - - -TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { +TxProposal.prototype.setCopayers = function(senderId, keyMap) { var newCopayer = {}, oldCopayers = {}, newSignedBy = {}, @@ -375,7 +399,8 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { } - var iSig = this._inputSigners[0]; + var iSig = this.getSignersPubKey(); + console.log('[TxProposal.js.374:iSig:]', iSig, keyMap); //TODO for (var i in iSig) { var copayerId = keyMap[iSig[i]]; @@ -390,10 +415,6 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { } } - // Seems unncessary to check this: - // if (!newCopayer[senderId] && !readOnlyPeers[senderId]) - // throw new Error('TX must have a (new) senders signature') - if (Object.keys(newCopayer).length > 1) throw new Error('New TX must have only 1 new signature'); @@ -414,21 +435,20 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { delete this.rejectedBy[i]; } + console.log('[TxProposal.js.410:newCopayer:]', newCopayer); //TODO return Object.keys(newCopayer); }; // merge will not merge any metadata. TxProposal.prototype.merge = function(incoming) { - preconditions.checkArgument(_.isFunction(incoming._sync)); - incoming._sync(); // Note that all inputs must have the same number of signatures, so checking // one (0) is OK. - var before = this._inputSigners[0].length; + var before = this._inputSigners[0] ? this._inputSigners[0].length : 0; this.builder.merge(incoming.builder); - this._sync(); var after = this._inputSigners[0].length; + console.log('[TxProposal.js.442:after:]', before, after); //TODO return after !== before; }; diff --git a/js/models/TxProposals.js b/js/models/TxProposals.js index a1b890f7d..d8881cf12 100644 --- a/js/models/TxProposals.js +++ b/js/models/TxProposals.js @@ -1,10 +1,8 @@ 'use strict'; -var BuilderMockV0 = require('./BuilderMockV0');; var bitcore = require('bitcore'); var util = bitcore.util; var Transaction = bitcore.Transaction; -var BuilderMockV0 = require('./BuilderMockV0');; var TxProposal = require('./TxProposal');; var Script = bitcore.Script; var Key = bitcore.Key; @@ -51,9 +49,9 @@ TxProposals.prototype.getNtxidsSince = function(sinceTs) { preconditions.checkArgument(sinceTs); var ret = []; - for(var ii in this.txps){ + for (var ii in this.txps) { var txp = this.txps[ii]; - if (txp.createdTs >= sinceTs) + if (txp.createdTs >= sinceTs) ret.push(ii); } return ret; @@ -96,34 +94,9 @@ TxProposals.prototype.toObj = function() { }; -TxProposals.prototype.merge = function(inObj, builderOpts) { - var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts); - - var myTxps = this.txps; - var ntxid = incomingTx.getId(); - var ret = { - ntxid: ntxid - }; - - if (myTxps[ntxid]) { - - // Merge an existing txProposal - ret.hasChanged = myTxps[ntxid].merge(incomingTx); - - - } else { - // Create a new one - ret.new = ret.hasChanged = 1; - this.txps[ntxid] = incomingTx; - } - - ret.txp = this.txps[ntxid]; - return ret; -}; // Add a LOCALLY CREATED (trusted) tx proposal TxProposals.prototype.add = function(txp) { - txp._sync(); var ntxid = txp.getId(); this.txps[ntxid] = txp; return ntxid; @@ -133,64 +106,24 @@ TxProposals.prototype.add = function(txp) { TxProposals.prototype.get = function(ntxid) { var ret = this.txps[ntxid]; if (!ret) - throw new Error('Unknown TXP: '+ntxid); + throw new Error('Unknown TXP: ' + ntxid); return ret; }; -TxProposals.prototype.getTxProposal = function(ntxid, copayers) { - var txp = this.get(ntxid); - - var i = JSON.parse(JSON.stringify(txp)); - i.builder = txp.builder; - i.ntxid = ntxid; - i.peerActions = {}; - - if (copayers) { - for (var j = 0; j < copayers.length; j++) { - var p = copayers[j]; - i.peerActions[p] = {}; - } - } - - for (var p in txp.seenBy) { - i.peerActions[p] = { - seen: txp.seenBy[p] - }; - } - for (var p in txp.signedBy) { - i.peerActions[p] = i.peerActions[p] || {}; - i.peerActions[p].sign = txp.signedBy[p]; - } - var r = 0; - for (var p in txp.rejectedBy) { - i.peerActions[p] = i.peerActions[p] || {}; - i.peerActions[p].rejected = txp.rejectedBy[p]; - r++; - } - i.rejectCount = r; - - var c = txp.creator; - i.peerActions[c] = i.peerActions[c] || {}; - i.peerActions[c].create = txp.createdTs; - return i; -}; - - //returns the unspent txid-vout used in PENDING Txs TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { var ret = {}; - for (var i in this.txps) { - if (!this.txps[i].isPending(maxRejectCount)) - continue; + var self = this; - var u = this.txps[i].builder.getSelectedUnspent(); - var p = this.getTxProposal(i); + _.each(this.txps, function(txp) { + if (!txp.isPending(maxRejectCount)) + return - for (var j in u) { - ret[u[j].txid + ',' + u[j].vout] = 1; - } - } + _.each(txp.builder.getSelectedUnspent(), function(u) { + ret[u.txid + ',' + u.vout] = 1; + }); + }); return ret; }; diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 66ceb9c80..bb1f7dcc9 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -357,8 +357,6 @@ Wallet.prototype._processProposalEvents = function(senderId, m) { type: 'signed', cId: m.newCopayer[0] }; - } else { - log.error('unknown tx proposal event:', m) } } if (ev) @@ -366,48 +364,38 @@ Wallet.prototype._processProposalEvents = function(senderId, m) { }; -/* OTDO - events.push({ -type: 'signed', -cId: k, -txId: ntxid -}); -*/ /** * @desc - * Retrieves a keymap from from a transaction proposal set extracts a maps from + * Retrieves a keymap from a transaction proposal set extracts a maps from * public key to cosignerId for each signed input of the transaction proposal. * * @param {TxProposals} txp - the transaction proposals - * @return {Object} + * @return {Object} [pubkey] -> copayerId */ Wallet.prototype._getKeyMap = function(txp) { preconditions.checkArgument(txp); - var inSig0, keyMapAll = {}; + var inSig0, keyMapAll = {}, + self = this; - for (var i in txp._inputSigners) { - var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSigners[i], txp.inputChainPaths); + _.each(txp.getSignersPubKey(), function(inputSignersPubKey, i) { + var keyMap = self.publicKeyRing.copayersForPubkeys(inputSignersPubKey, txp.inputChainPaths); - if (_.size(keyMap) !== _.size(txp._inputSigners[i])) + if (_.size(keyMap) !== _.size(inputSignersPubKey)) throw new Error('Signature does not match known copayers'); - for (var j in keyMap) { - keyMapAll[j] = keyMap[j]; - } + keyMapAll = _.extend(keyMap, keyMapAll); // From here -> only to check that all inputs have the same sigs - var inSigArr = []; - _.each(keyMap, function(value, key) { - inSigArr.push(value); - }); + var inSigArr = _.values(keyMap); var inSig = JSON.stringify(inSigArr.sort()); - if (i === '0') { + + if (!inSig0) { inSig0 = inSig; - continue; + } else { + if (inSig !== inSig0) + throw new Error('found inputs with different signatures'); } - if (inSig !== inSig0) - throw new Error('found inputs with different signatures'); - } + }); return keyMapAll; }; @@ -442,11 +430,10 @@ Wallet.prototype._checkIfTxIsSent = function(ntxid, cb) { * and send `seen` messages to peers if aplicable. * @param ntxid */ -Wallet.prototype._setTxProposalSeen = function(ntxid) { - var txp = this.txProposals.get(ntxid); +Wallet.prototype._setTxProposalSeen = function(txp) { if (!txp.getSeen(this.getMyCopayerId())) { txp.setSeen(this.getMyCopayerId()); - this.sendSeen(ntxid); + this.sendSeen(txp.getId()); } }; @@ -458,17 +445,13 @@ Wallet.prototype._setTxProposalSeen = function(ntxid) { * @param ntxid * @param {transactionCallback} cb */ -Wallet.prototype._updateTxProposalSent = function(ntxid, cb) { +Wallet.prototype._updateTxProposalSent = function(txp, cb) { var self = this; - var txp = this.txProposals.get(ntxid); - - this._checkIfTxIsSent(ntxid, function(err, txid) { + this._checkIfTxIsSent(txp.getId(), function(err, txid) { if (err) return cb(err); if (txid) { - if (!txp.getSent()) { - txp.setSent(txid); - } + txp.setSent(txid); self.emitAndKeepAlive('txProposalsUpdated'); } if (cb) @@ -486,13 +469,10 @@ Wallet.prototype._updateTxProposalSent = function(ntxid, cb) { * @param mergeInfo Proposals merge information, as returned by TxProposals.merge * @return {fetchPaymentRequestCallback} */ -Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { +Wallet.prototype._processTxProposalPayPro = function(txp, cb) { var self = this; - var txp = mergeInfo.txp; - var isNew = mergeInfo.new; - var ntxid = mergeInfo.ntxid; - if (!isNew || !txp.paymentProtocolURL) + if (!txp.paymentProtocolURL) return cb(); log.info('Received a Payment Protocol TX Proposal'); @@ -515,26 +495,23 @@ Wallet.prototype._processTxProposalPayPro = function(mergeInfo, cb) { /** * _processIncomingTxProposal * - * @desc Process an incoming transaction proposal. Runs safety and sanity checks on it. + * @desc Process an NEW incoming transaction proposal. Runs safety and sanity checks on it. * * @param mergeInfo Proposals merge information, as returned by TxProposals.merge * @return {errCallback} */ -Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) { - if (!mergeInfo) return cb(); +Wallet.prototype._processIncomingNewTxProposal = function(txp, cb) { var self = this; - self._processTxProposalPayPro(mergeInfo, function(err) { + var ntxid = txp.getId(); + self._processTxProposalPayPro(txp, function(err) { if (err) return cb(err); - self._setTxProposalSeen(mergeInfo.ntxid); + self._setTxProposalSeen(txp); - var tx = mergeInfo.txp.builder.build(); - if (tx.isComplete()) - self._updateTxProposalSent(mergeInfo.ntxid); - else { - self.emitAndKeepAlive('txProposalsUpdated'); - } + var tx = txp.builder.build(); + if (tx.isComplete() && !txp.getSent()) + self._updateTxProposalSent(txp); return cb(); }); }; @@ -550,36 +527,34 @@ Wallet.prototype._processIncomingTxProposal = function(mergeInfo, cb) { */ Wallet.prototype._onTxProposal = function(senderId, data) { var self = this; - var m; + var incomingTx = TxProposal.fromUntrustedObj(data.txProposal, Wallet.builderOpts); + var incomingNtxid = incomingTx.getId(); try { - m = self.txProposals.merge(data.txProposal, Wallet.builderOpts); - var keyMap = self._getKeyMap(m.txp); - m.newCopayer = m.txp.setCopayers(senderId, keyMap); - } catch (e) { - log.error('Corrupt TX proposal received from:', senderId, e.toString()); - if (m && m.ntxid) - self.txProposals.deleteOne(m.ntxid); - m = null; + var localTx = this.txProposals.get(incomingNtxid); + } catch (e) {}; + + if (localTx) { + log.debug('Ignoring existing tx Proposal:' + incomingNtxid); + console.log('') + return; } - if (m) { + self._processIncomingNewTxProposal(incomingTx, function(err) { + if (err) { + log.info('Corrupt TX proposal received from:', senderId, err.toString()); + return; + } - self._processIncomingTxProposal(m, function(err) { + var keyMap = self._getKeyMap(incomingTx); + incomingTx.setCopayers(senderId, keyMap); - if (err) { - log.error('Corrupt TX proposal received from:', senderId, err.toString()); - if (m && m.ntxid) - self.txProposals.deleteOne(m.ntxid); - m = null; - } else { - if (m && m.hasChanged) - self.sendTxProposal(m.ntxid); - } - - self._processProposalEvents(senderId, m); + self.txProposals.add(incomingTx); + self.emitAndKeepAlive('txProposalEvent', { + type: 'new', + cId: senderId, }); - } + }); }; /** @@ -1278,6 +1253,24 @@ Wallet.prototype.sendReject = function(ntxid) { }); }; + +/** + * @desc Send a signature for a TX Proposal + * @param {string} ntxid + */ +Wallet.prototype.sendSignature = function(ntxid) { + preconditions.checkArgument(ntxid); + + var txp = this.txProposals.get(ntxid); + + this._sendToPeers(null, { + type: 'sign', + signatures: txp.getScriptSigs(), + walletId: this.id, + }); +}; + + /** * @desc Notify other peers that a wallet has been backed up and it's ready to be used * @param {string[]} [recipients] - the pubkeys of the recipients @@ -1396,45 +1389,28 @@ Wallet.prototype.generateAddress = function(isChange, cb) { }; /** - * @desc Retrieve all the Transaction proposals (see {@link TxProposals}) - * @return {Object[]} each object returned represents a transaction proposal, with two additional - * booleans: signedByUs and rejectedByUs. An optional third boolean signals - * whether the transaction was finally rejected (finallyRejected set to true). - */ -Wallet.prototype.getTxProposals = function() { - var ret = []; - var self = this; - var copayers = self.getRegisteredCopayerIds(); - var myId = self.getMyCopayerId(); - - _.each(self.txProposals.txps, function(txp, ntxid){ - txp.signedByUs = txp.signedBy[myId] ? true : false; - txp.rejectedByUs = txp.rejectedBy[self.getMyCopayerId()] ? true : false; - txp.finallyRejected = self.totalCopayers - txp.rejectCount < self.requiredCopayers; - txp.isPending = !txp.finallyRejected && !txp.sentTxid; - - if (!txp.readonly || txp.finallyRejected || txp.sentTs) { - ret.push(txp); - } - }); - return ret; -}; - -/** + * TODO: get this out of here * @desc get list of actions (see {@link getPendingTxProposals}) */ -Wallet.prototype._getActionList = function(actions) { - if (!actions) return; - var peers = Object.keys(actions).map(function(i) { - return { - cId: i, - actions: actions[i] - } - }); +Wallet.prototype._getActionList = function(txp) { + preconditions.checkArgument(txp); - return peers.sort(function(a, b) { - return !!b.actions.create - !!a.actions.create; + var self = this; + var peers = []; + + _.each(self.getRegisteredCopayerIds(), function(copayerId) { + var actions = { + rejected: txp.rejectedBy[copayerId], + sign: txp.signedBy[copayerId], + seen: txp.seenBy[copayerId], + create: (txp.creator === copayerId) ? txp.createdTs : null, + }; + peers.push({ + cId: copayerId, + actions: actions, + }); }); + return peers; }; /** @@ -1446,15 +1422,22 @@ Wallet.prototype.getPendingTxProposals = function() { var ret = []; ret.txs = []; var pendingForUs = 0; - var txps = this.getTxProposals(); + var txps = this.txProposals.txps; + var maxRejectCount = this.maxRejectCount(); var satToUnit = 1 / this.settings.unitToSatoshi; - _.each(_.where(txps, 'isPending'), function(txp) { + _.each(txps, function(inTxp, ntxid) { + if (!inTxp.isPending(maxRejectCount)) + return; + + var txp = _.clone(inTxp); + txp.ntxid = ntxid; + pendingForUs++; var addresses = {}; var outs = JSON.parse(txp.builder.vanilla.outs); outs.forEach(function(o) { - if (!self.publicKeyRing.addressToPath[o.Straddress]) { + if (!self.addressIsOwn(o.address)) { if (!addresses[o.address]) addresses[o.address] = 0; addresses[o.address] += (o.amountSatStr || Math.round(o.amount * bitcore.util.COIN)); }; @@ -1469,7 +1452,7 @@ Wallet.prototype.getPendingTxProposals = function() { // extra fields txp.fee = txp.builder.feeSat * satToUnit; txp.missingSignatures = txp.builder.build().countInputMissingSignatures(0); - txp.actionList = self._getActionList(txp.peerActions); + txp.actionList = self._getActionList(txp); ret.txs.push(txp); }); @@ -1552,10 +1535,10 @@ Wallet.prototype.sign = function(ntxid) { Wallet.prototype.signAndSend = function(ntxid, cb) { if (this.sign(ntxid)) { var txp = this.txProposals.get(ntxid); + this.sendSignature(ntxid); if (txp.isFullySigned()) { return this.broadcastTx(ntxid, cb); } else { - this.sendTxProposal(ntxid); return cb(null, ntxid, Wallet.TX_SIGNED); } } else { @@ -2133,58 +2116,6 @@ Wallet.prototype.getUnspent = function(cb) { }); }; -// TODO. not used. -Wallet.prototype.removeTxWithSpentInputs = function(cb) { - var self = this; - - cb = cb || function() {}; - - if (!_.some(self.getTxProposals(), { - isPending: true - })) - return cb(); - - var proposalsChanged = false; - this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { - if (err) return cb(err); - - var txps = _.where(self.getTxProposals(), { - isPending: true - }); - if (txps.length === 0) return cb(); - - var inputs = _.flatten(_.map(txps, function(txp) { - return _.map(txp.builder.utxos, function(utxo) { - return { - ntxid: txp.ntxid, - txid: utxo.txid, - vout: utxo.vout, - }; - }); - })); - - _.each(unspentList, function(unspent) { - _.each(inputs, function(input) { - input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout); - }); - }); - - _.each(inputs, function(input) { - if (!input.unspent) { - proposalsChanged = true; - self.txProposals.deleteOne(input.ntxid); - } - }); - - if (proposalsChanged) { - self.emitAndKeepAlive('txProposalsUpdated'); - } - - return cb(); - }); - -}; - /** * spend * @@ -2252,7 +2183,6 @@ Wallet.prototype.spend = function(opts, cb) { log.debug('TXP Added: ', ntxid); - console.log('[Wallet.js.2233]'); //TODO self.sendIndexes(); // Needs only one signature? Broadcast it! if (!self.requiresMultipleSignatures()) { @@ -2361,6 +2291,7 @@ Wallet.prototype._createTxProposal = function(toAddress, amountSat, comment, utx signWith: keys, }); + console.log('[Wallet.js.2303]'); //TODO return txp; }; @@ -2590,7 +2521,7 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { opts = opts || {}; var addresses = self.getAddressesInfo(); - var proposals = self.getTxProposals(); + var proposals = self.txProposals.txps; var satToUnit = 1 / self.settings.unitToSatoshi; var indexedProposals = _.indexBy(proposals, 'sentTxid'); @@ -2689,18 +2620,11 @@ Wallet.prototype.getTransactionHistory = function(opts, cb) { if (proposal) { // TODO refactor tx.comment = proposal.comment; - tx.sentTs = proposal.sentTs; tx.merchant = proposal.merchant; tx.peerActions = proposal.peerActions; - tx.finallyRejected = proposal.finallyRejected; tx.merchant = proposal.merchant; tx.paymentAckMemo = proposal.paymentAckMemo; - tx.peerActions = proposal.peerActions; - tx.finallyRejected = proposal.finallyRejected; - - if (tx.peerActions) { - tx.actionList = self._getActionList(tx.peerActions); - } + tx.actionList = self._getActionList(proposal); } }; diff --git a/test/TxProposal.js b/test/TxProposal.js index c1b69a5ac..bcc9e0d0b 100644 --- a/test/TxProposal.js +++ b/test/TxProposal.js @@ -104,11 +104,11 @@ describe('TxProposal', function() { }); }); - describe('#fromObj', function() { + describe('#fromUntrustedObj', function() { it('should fail to create from wrong object', function() { var b = new FakeBuilder(); (function() { - var txp = TxProposal.fromObj({ + var txp = TxProposal.fromUntrustedObj({ creator: 1, createdTs: 1, builderObj: b.toObj(), @@ -131,7 +131,7 @@ describe('TxProposal', function() { inputChainPaths: ['m/1'], }; (function() { - txp = TxProposal.fromObj(o, { + txp = TxProposal.fromUntrustedObj(o, { pepe: 100 }); }).should.throw('Invalid tx proposal: no ins'); @@ -143,6 +143,9 @@ describe('TxProposal', function() { }); }); }); + describe('#fromObj', function() { + + }); describe('#setSent', function() { diff --git a/test/mocks/FakeBuilder.js b/test/mocks/FakeBuilder.js index 8d81531ff..c0c20b736 100644 --- a/test/mocks/FakeBuilder.js +++ b/test/mocks/FakeBuilder.js @@ -35,6 +35,7 @@ function FakeBuilder() { this.test = 1; this.tx = new Tx(); this.signhash = 1; + this.version =1; this.inputMap = [{ address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6', scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')), @@ -48,7 +49,6 @@ function FakeBuilder() { address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6', amountSatStr: '123', }]), - } } diff --git a/views/includes/transaction.html b/views/includes/transaction.html index dbe6da338..aca03ead0 100644 --- a/views/includes/transaction.html +++ b/views/includes/transaction.html @@ -59,7 +59,7 @@