diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 863863e70..04f007c74 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -2,6 +2,7 @@ var imports = require('soop').imports(); var bitcore = require('bitcore'); +var coinUtil = bitcore.util; var preconditions = require('preconditions').singleton(); var http; @@ -39,9 +40,9 @@ function _asyncForEach(array, fn, callback) { function removeRepeatedElements(ar) { var ya = false, - v = "", - aux = [].concat(ar), - r = Array(); + v = "", + aux = [].concat(ar), + r = Array(); for (var i in aux) { // v = aux[i]; ya = false; @@ -78,6 +79,25 @@ Insight.prototype._getOptions = function(method, path, data) { }; }; + +// This is vulneable to txid maneability +// TODO: if ret = false, +// check output address from similar transactions. +// +Insight.prototype.checkSentTx = function(tx, cb) { + var hash = coinUtil.formatHashFull(tx.getHash()); + var options = this._getOptions('GET', '/api/tx/' + hash); + + this._request(options, function(err, res) { + if (err) return cb(err); + var ret = false; + if (res && res.txid === hash) { + ret = hash; + } + return cb(err, ret); + }); +}; + Insight.prototype.getTransactions = function(addresses, cb) { preconditions.shouldBeArray(addresses); preconditions.shouldBeFunction(cb); @@ -164,8 +184,8 @@ Insight.prototype.checkActivity = function(addresses, cb) { var getOutputs = function(t) { return flatArray( t.vout.map(function(vout) { - return vout.scriptPubKey.addresses; - }) + return vout.scriptPubKey.addresses; + }) ); }; diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 7885509d1..f0933afd6 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -74,7 +74,7 @@ TxProposals.prototype.merge = function(inObj, builderOpts) { } else { // Create a new one - ret.new = 1; + ret.new = ret.hasChanged = 1; this.txps[ntxid] = incomingTx; } @@ -91,16 +91,16 @@ TxProposals.prototype.add = function(txp) { }; -TxProposals.prototype._getTxp = function(ntxid) { +TxProposals.prototype.get = function(ntxid) { var ret = this.txps[ntxid]; if (!ret) - throw new Error('Could not find txp: '+ntxid); + throw new Error('Unknown TXP: '+ntxid); return ret; }; TxProposals.prototype.getTxProposal = function(ntxid, copayers) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); var i = JSON.parse(JSON.stringify(txp)); i.builder = txp.builder; @@ -139,12 +139,12 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) { TxProposals.prototype.reject = function(ntxid, copayerId) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); txp.setRejected(copayerId); }; TxProposals.prototype.seen = function(ntxid, copayerId) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); txp.setSeen(copayerId); }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index bf68497f8..55649c296 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -138,10 +138,10 @@ Wallet.prototype._processProposalEvents = function(senderId, m) { type: 'new', cid: senderId } - } else if(m.newCopayer){ - ev={ + } else if (m.newCopayer) { + ev = { type: 'signed', - cid: m.newCopayer + cid: m.newCopayer }; } } else { @@ -187,29 +187,54 @@ Wallet.prototype._getKeyMap = function(txp) { }; +Wallet.prototype._checkSentTx = function(ntxid, cb) { + var txp = this.txProposals.get(ntxid); + var tx = txp.builder.build(); + + this.blockchain.checkSentTx(tx, function(err, txid) { + var ret = false; + if (txid) { + txp.setSent(txid); + ret = txid; + } + return cb(ret); + }); +}; + + Wallet.prototype._handleTxProposal = function(senderId, data) { + var self = this; this.log('RECV TXPROPOSAL: ', data); var m; try { - m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); - var keyMap = this._getKeyMap(m.txp); - ret.newCopayer = m.txp.setCopayers(senderId, keyMap); + m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); + var keyMap = this._getKeyMap(m.txp); + ret.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { this.log('Corrupt TX proposal received from:', senderId, e); } if (m) { + + if (m.hasChanged) { + this.sendSeen(m.ntxid); + var tx = m.txp.builder.build(); + if (tx.isComplete()) { + this._checkSentTx(m.ntxid, function(ret) { + if (ret) { + self.emit('txProposalsUpdated'); + self.store(); + } + }); + } else { + this.sendTxProposal(m.ntxid); + } + } this.emit('txProposalsUpdated'); this.store(); - - this.sendSeen(m.ntxid); - - if (m.hasChanged) - this.sendTxProposal(m.ntxid); } - this._processProposalEvents(senderId, m); }; @@ -218,7 +243,7 @@ Wallet.prototype._handleReject = function(senderId, data, isInbound) { preconditions.checkState(data.ntxid); this.log('RECV REJECT:', data); - var txp = this.txProposals.txps[data.ntxid]; + var txp = this.txProposals.get(data.ntxid); if (!txp) throw new Error('Received Reject for an unknown TX from:' + senderId); @@ -241,11 +266,7 @@ Wallet.prototype._handleSeen = function(senderId, data, isInbound) { preconditions.checkState(data.ntxid); this.log('RECV SEEN:', data); - var txp = this.txProposals.txps[data.ntxid]; - - if (!txp) - throw new Error('Received Reject for an unknown TX from:' + senderId); - + var txp = this.txProposals.get(data.ntxid); txp.setSeen(senderId); this.store(); this.emit('txProposalsUpdated'); @@ -524,12 +545,11 @@ Wallet.prototype.sendAllTxProposals = function(recipients) { Wallet.prototype.sendTxProposal = function(ntxid, recipients) { preconditions.checkArgument(ntxid); - preconditions.checkState(this.txProposals.txps[ntxid]); this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals); this.send(recipients, { type: 'txProposal', - txProposal: this.txProposals.txps[ntxid].toObjTrim(), + txProposal: this.txProposals.get(ntxid).toObjTrim(), walletId: this.id, }); }; @@ -644,7 +664,7 @@ Wallet.prototype.getTxProposals = function() { Wallet.prototype.reject = function(ntxid) { - var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()) ; + var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()); this.sendReject(ntxid); this.store(); this.emit('txProposalsUpdated'); @@ -655,7 +675,7 @@ Wallet.prototype.sign = function(ntxid, cb) { var self = this; setTimeout(function() { var myId = self.getMyCopayerId(); - var txp = self.txProposals.txps[ntxid]; + var txp = self.txProposals.get(ntxid); // if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { // if (cb) cb(false); // } @@ -678,14 +698,13 @@ Wallet.prototype.sign = function(ntxid, cb) { }, 10); }; + Wallet.prototype.sendTx = function(ntxid, cb) { - var txp = this.txProposals.txps[ntxid]; - if (!txp) return; - + var txp = this.txProposals.get(ntxid); var tx = txp.builder.build(); - if (!tx.isComplete()) return; + if (!tx.isComplete()) + throw new Error('Tx is not complete. Can not broadcast'); this.log('Broadcasting Transaction'); - var scriptSig = tx.ins[0].getScript(); var size = scriptSig.serialize().length; @@ -696,28 +715,23 @@ Wallet.prototype.sendTx = function(ntxid, cb) { this.blockchain.sendRawTransaction(txHex, function(txid) { self.log('BITCOIND txid:', txid); if (txid) { - self.txProposals.txps[ntxid].setSent(txid); + self.txProposals.get(ntxid).setSent(txid); self.sendTxProposal(ntxid); self.store(); + return cb(txid); + } else { + self.log('Sent failed. Checking is the TX was sent already'); + self._checkSentTx(ntxid, function(txid) { + console.log('[Wallet.js.730:txid:]', txid); //TODO + if (txid) + self.store(); + + return cb(txid); + }); } - return cb(txid); }); }; -// Wallet.prototype.addSeenToTxProposals = function() { -// var ret = false; -// var myId = this.getMyCopayerId(); -// -// for (var k in this.txProposals.txps) { -// var txp = this.txProposals.txps[k]; -// if (!txp.seenBy[myId]) { -// -// txp.seenBy[myId] = Date.now(); -// ret = true; -// } -// } -// return ret; -// }; // TODO: remove this method and use getAddressesInfo everywhere Wallet.prototype.getAddresses = function(opts) { @@ -840,7 +854,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch'); preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete'); - preconditions.checkState(priv,'no private key'); + preconditions.checkState(priv, 'no private key'); if (comment) preconditions.checkArgument(comment.length <= 100); if (!opts.remainderOut) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 01b1dbbe7..fad38aff6 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -678,10 +678,10 @@ describe('Wallet model', function() { w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { var s = sinon.stub(w, 'getMyCopayerId').returns('213'); - Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(0); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0); w.reject(ntxid); - Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(1); - w.txProposals._getTxp(ntxid).rejectedBy['213'].should.gt(1); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1); + w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1); s.restore(); done(); }); @@ -1169,7 +1169,7 @@ describe('Wallet model', function() { w._handleReject(1, { ntxid: 1 }, 1); - }).should.throw('unknown TX'); + }).should.throw('Unknown TXP'); }); it('should fail to reject a signed tx', function() { var w = cachedCreateW(); @@ -1223,7 +1223,7 @@ describe('Wallet model', function() { w._handleReject(1, { ntxid: 1 }, 1); - }).should.throw('unknown TX'); + }).should.throw('Unknown TXP'); }); it('should set seen a tx', function() { var w = cachedCreateW(); diff --git a/test/test.blockchain.Insight.js b/test/test.blockchain.Insight.js index 18a35471f..b5451c99a 100644 --- a/test/test.blockchain.Insight.js +++ b/test/test.blockchain.Insight.js @@ -82,9 +82,9 @@ describe('Insight model', function() { sinon - .stub(http, 'request') - .returns(req) - .yields(request); + .stub(http, 'request') + .returns(req) + .yields(request); i.getUnspent(['2MuD5LnZSViZZYwZbpVsagwrH8WWvCztdmV', '2NBSLoMvsHsf2Uv3LA17zV4beH6Gze6RovA'], function(e, ret) { should.not.exist(e); @@ -113,9 +113,9 @@ describe('Insight model', function() { req.end = function() {}; sinon - .stub(http, 'request') - .returns(req) - .yields(request); + .stub(http, 'request') + .returns(req) + .yields(request); i.sendRawTransaction(rawtx, function(a) { should.exist(a); @@ -200,5 +200,36 @@ describe('Insight model', function() { }); }); + describe("#checkSentTx", function() { + it('should return true if Tx is found', function(done) { + var w = new Insight(); + w._request = sinon.stub().yields(null, { + txid: "414142", + }); + var tx = function() {}; + tx.prototype.getHash = function(){return new Buffer('BAA')}; + w.checkSentTx(new tx(), function(err, ret) { + should.not.exist(err); + ret.should.equal('414142'); + done(); + }); + }); + it('should return false if Tx is not found', function(done) { + var w = new Insight(); + w._request = sinon.stub().yields(null, { + txid: "414142", + }); + var tx = function() {}; + tx.prototype.getHash = function(){return new Buffer('ABC')}; + w.checkSentTx(new tx(), function(err, ret) { + should.not.exist(err); + ret.should.equal(false); + done(); + }); + }); + }); + + + });