diff --git a/js/controllers/more.js b/js/controllers/more.js index 96e3e8632..a6ece95e2 100644 --- a/js/controllers/more.js +++ b/js/controllers/more.js @@ -24,6 +24,12 @@ angular.module('copayApp.controllers').controller('MoreController', }); }; + $scope.purge = function(deleteAll) { + var w = $rootScope.wallet; + var removed = w.purgeTxProposals(deleteAll); + notification.info('Tx Proposals Purged', removed + ' transactions proposals were purged'); + }; + $scope.updateIndexes = function() { var w = $rootScope.wallet; diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index fb258af51..0349a654a 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -65,6 +65,17 @@ TxProposal.prototype._check = function() { } }; +TxProposal.prototype.rejectCount = function() { + return Object.keys(this.rejectedBy); +}; + +TxProposal.prototype.isPending = function(maxRejectCount) { + if (this.rejectCount() < maxRejectCount || p.sentTxid) + return false; + + return true; +}; + TxProposal.prototype._updateSignedBy = function() { this._inputSignatures = []; diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 558d9c501..e5b48cf5b 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -36,10 +36,25 @@ TxProposals.fromObj = function(o, forceOpts) { return ret; }; +TxProposals.prototype.length = function() { + return Object.keys(this.txps).length; +}; + TxProposals.prototype.getNtxids = function() { return Object.keys(this.txps); }; +TxProposals.prototype.deleteAll = function() { + this.txps = {}; +}; + +TxProposals.prototype.deletePending = function(maxRejectCount) { + for (var ntxid in this.txps) { + if (this.txps[ntxid].isPending(maxRejectCount)) + delete this.txps[ntxid]; + }; +}; + TxProposals.prototype.toObj = function() { var ret = []; for (var id in this.txps) { @@ -153,7 +168,8 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { for (var i in this.txps) { var u = this.txps[i].builder.getSelectedUnspent(); var p = this.getTxProposal(i); - if (p.rejectCount > maxRejectCount || p.sentTxid) + + if (!p.isPending(maxRejectCount)) continue; for (var j in u) { diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2d1786d92..d53334927 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -706,6 +706,18 @@ Wallet.prototype.getTxProposals = function() { return ret; }; +Wallet.prototype.purgeTxProposals = function(deleteAll) { + var m = this.txProposals.length(); + + if (deleteAll) { + this.txProposals.deleteAll(); + } else { + this.txProposals.deletePending(this.maxRejectCount()); + } + + var n = this.txProposals.length(); + return m-n; +}; Wallet.prototype.reject = function(ntxid) { var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()); @@ -1484,6 +1496,17 @@ Wallet.prototype.getBalance = function(cb) { }); }; + +// See +// https://github.com/bitpay/copay/issues/1056 +// +// maxRejectCount should equal requiredCopayers +// strictly. +// +Wallet.prototype.maxRejectCount = function(cb) { + return this.totalCopayers - this.requiredCopayers; +}; + Wallet.prototype.getUnspent = function(cb) { var self = this; this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { @@ -1493,8 +1516,7 @@ Wallet.prototype.getUnspent = function(cb) { } var safeUnspendList = []; - var maxRejectCount = self.totalCopayers - self.requiredCopayers; - var uu = self.txProposals.getUsedUnspent(maxRejectCount); + var uu = self.txProposals.getUsedUnspent(self.maxRejectCount()); for (var i in unspentList) { var u = unspentList[i]; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 59ac675ca..320c90284 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -357,6 +357,39 @@ describe('Wallet model', function() { throw(); }); + + it.only('#maxRejectCount', function() { + var w = cachedCreateW(); + w.maxRejectCount().should.equal(2); + }); + + + describe('#purgeTxProposals', function() { + it('should delete all', function() { + var w = cachedCreateW(); + var spy1 = sinon.spy(w.txProposals, 'deleteAll'); + var spy2 = sinon.spy(w.txProposals, 'deletePending'); + w.purgeTxProposals(1); + spy1.callCount.should.equal(1); + spy2.callCount.should.equal(0); + }); + it('should delete pending', function() { + var w = cachedCreateW(); + var spy1 = sinon.spy(w.txProposals, 'deleteAll'); + var spy2 = sinon.spy(w.txProposals, 'deletePending'); + w.purgeTxProposals(); + spy1.callCount.should.equal(0); + spy2.callCount.should.equal(1); + }); + it('should count deletions', function() { + var w = cachedCreateW(); + var s = sinon.stub(w.txProposals, 'length').returns(10); + var n = w.purgeTxProposals(); + n.should.equal(0); + }); + }); + + //this test fails randomly it.skip('call reconnect after interval', function(done) { this.timeout(10000); @@ -378,6 +411,8 @@ describe('Wallet model', function() { w.isShared().should.equal(false); }); + + it('#isReady', function() { var w = createW(); w.publicKeyRing.isComplete().should.equal(false); diff --git a/views/more.html b/views/more.html index 930232624..f7df99524 100644 --- a/views/more.html +++ b/views/more.html @@ -30,7 +30,7 @@
Your master private key contains the information to sign any transaction on this wallet. Handle with care.
This will scan the blockchain looking for addresses derived from your wallet, in case you have funds in addresses not yet generated (e.g.: you restored an old backup). +
This will scan the blockchain looking for addresses derived from your wallet, in case you have funds in addresses not yet generated (e.g.: you restored an old backup). This will also trigger a syncronization of addresses to other connected peers.
Pending Transactions Proposals will be discarted. This need to be done on ALL peers of a wallet, to prevent the old proposals to be resynced again.
+
ALL Transactions Proposals will be discarted. This need to be done on ALL peers of a wallet, to prevent the old proposals to be resynced again.
+