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 @@

Master Private Key

Your master private key contains the information to sign any transaction on this wallet. Handle with care.

- + Show Hide @@ -39,13 +39,31 @@

Scan Wallet Addresses

-

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.

- + Scan
+
+

Purge Pending Transaction Proposals

+

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. +

+ + Purge + +
+
+
+

Purge ALL Transaction Proposals

+

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. +

+