From 8b55f69d404e9f707240dcbccf1d5a9e22888f8a Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 27 Aug 2014 16:42:07 -0300 Subject: [PATCH 01/17] Add method removeTxWithSpentInputs to wallet --- js/models/core/TxProposals.js | 7 +++++++ js/models/core/Wallet.js | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 11d9ad3b8..2481b3a72 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -44,6 +44,13 @@ TxProposals.prototype.getNtxids = function() { return Object.keys(this.txps); }; +TxProposals.prototype.deleteOne = function(ntxid) { + var txp = this.txps[ntxid]; + if (!txp) + throw new Error('Unknown TXP: ' + ntxid); + delete this.txps[ntxid]; +}; + TxProposals.prototype.deleteAll = function() { this.txps = {}; }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 0a9785516..8de654705 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1525,6 +1525,42 @@ Wallet.prototype.getUnspent = function(cb) { }); }; +Wallet.prototype.removeTxWithSpentInputs = function(cb) { + var self = this; + + var txps = this.getTxProposals(); + + var inputs = []; + txps.forEach(function (txp) { + txp.builder.utxos.forEach(function (utxo) { + inputs.push({ ntxid: txp.ntxid, txid: utxo.txid, vout: utxo.vout }); + }); + }); + if (inputs.length === 0) + return; + + this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { + if (err) { + return cb(err); + } + + unspentList.forEach(function (unspent) { + inputs.forEach(function (input) { + input.unspent = input.unspent || (input.txid === unspent.txid && input.vout === unspent.vout); + }); + }); + + inputs.forEach(function (input) { + if (!input.unspent) { + self.txProposals.deleteOne(input.ntxid); + } + }); + + self.emit('txProposalsUpdated'); + self.store(); + }); + +}; Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) { var self = this; From 25064829e06d45bcdacf14f105d080a739a8b0ec Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 27 Aug 2014 16:42:52 -0300 Subject: [PATCH 02/17] Tests --- test/test.Wallet.js | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 09dc4afe5..cf8a0e26c 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -802,6 +802,55 @@ describe('Wallet model', function() { }); }); + describe('removeTxWithSpentInputs', function () { + it('should remove TxProposal with spent inputs', function(done) { + var w = cachedCreateW2(); + var utxo = createUTXO(w); + chai.expect(w.getTxProposals().length).to.equal(0); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, amountSatStr, null, function(ntxid) { + w.sendTxProposal(ntxid); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Inputs are still available, txp still valid + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Simulate input spent. txp should be removed from txps list + w.blockchain.fixUnspent([]); + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(0); + + done(); + }); + }); + + it('should remove TxProposal with at least 1 spent input', function(done) { + var w = cachedCreateW2(); + var utxo = [createUTXO(w)[0], createUTXO(w)[0]]; + utxo[0].amount = 80000; + utxo[1].amount = 80000; + utxo[1].vout = 1; + chai.expect(w.getTxProposals().length).to.equal(0); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, '100000', null, function(ntxid) { + w.sendTxProposal(ntxid); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Inputs are still available, txp still valid + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Simulate 1 input spent. txp should be removed from txps list + w.blockchain.fixUnspent([utxo[0]]); + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(0); + + done(); + }); + }); + }); + describe('#send', function() { it('should call this.network.send', function() { var w = cachedCreateW2(); From a07ffee0689947f6b4c262662ce8e5a0c5752f4d Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 28 Aug 2014 11:03:47 -0300 Subject: [PATCH 03/17] Precondition check for TxProposals#deleteOne --- js/models/core/TxProposals.js | 4 +--- test/test.TxProposals.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 2481b3a72..9e33d6eab 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -45,9 +45,7 @@ TxProposals.prototype.getNtxids = function() { }; TxProposals.prototype.deleteOne = function(ntxid) { - var txp = this.txps[ntxid]; - if (!txp) - throw new Error('Unknown TXP: ' + ntxid); + preconditions.checkState(this.txps[ntxid], 'Unknown TXP: ' + ntxid); delete this.txps[ntxid]; }; diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index a7a25e727..f94435f48 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -65,6 +65,21 @@ describe('TxProposals', function() { txps.getNtxids().should.deep.equal(['a','b']); }); }); + describe.only('#deleteOne', function() { + it('should delete specified ntxid', function() { + var txps = new TxProposals(); + txps.txps = {a:1, b:2}; + txps.deleteOne('a'); + txps.getNtxids().should.deep.equal(['b']); + }); + it('should fail on non-existent ntxid', function() { + var txps = new TxProposals(); + txps.txps = {a:1, b:2}; + (function () { + txps.deleteOne('c'); + }).should.throw('Unknown TXP: c'); + }); + }); describe('#toObj', function() { it('should an object', function() { var txps = TxProposals.fromObj({ From ea394de56a68980d885adc9739bf4bd7ceaacf70 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 28 Aug 2014 11:04:45 -0300 Subject: [PATCH 04/17] Tests --- test/test.TxProposals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index f94435f48..2cc332e69 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -65,7 +65,7 @@ describe('TxProposals', function() { txps.getNtxids().should.deep.equal(['a','b']); }); }); - describe.only('#deleteOne', function() { + describe('#deleteOne', function() { it('should delete specified ntxid', function() { var txps = new TxProposals(); txps.txps = {a:1, b:2}; From 55aa4e8f68a7362323a1a657ba85fe33911db0ed Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 3 Sep 2014 11:27:58 -0300 Subject: [PATCH 05/17] Added call from UX --- js/models/core/Wallet.js | 7 ++++--- js/services/controllerUtils.js | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8de654705..a4d5e3380 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1528,6 +1528,7 @@ Wallet.prototype.getUnspent = function(cb) { Wallet.prototype.removeTxWithSpentInputs = function(cb) { var self = this; + cb = cb || function () {}; var txps = this.getTxProposals(); var inputs = []; @@ -1540,9 +1541,7 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { return; this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { - if (err) { - return cb(err); - } + if (err) return cb(err); unspentList.forEach(function (unspent) { inputs.forEach(function (input) { @@ -1558,6 +1557,8 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { self.emit('txProposalsUpdated'); self.store(); + + cb(null); }); }; diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index ba0c9d2d6..d307d2b63 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -166,6 +166,8 @@ angular.module('copayApp.services') if (!w) return root.onErrorDigest(); if (!w.isReady()) return; + w.removeTxWithSpentInputs(); + $rootScope.balanceByAddr = {}; $rootScope.updatingBalance = true; From e9e36020fe0b4ac32bd4701e56629febfb0897b6 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 3 Sep 2014 18:06:58 -0300 Subject: [PATCH 06/17] Add method to FakeWallet --- test/mocks/FakeWallet.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/mocks/FakeWallet.js b/test/mocks/FakeWallet.js index 2270c4d64..77b2d04a5 100644 --- a/test/mocks/FakeWallet.js +++ b/test/mocks/FakeWallet.js @@ -98,6 +98,9 @@ FakeWallet.prototype.getBalance = function(cb) { return cb(null, this.balance, this.balanceByAddr, this.safeBalance); }; +FakeWallet.prototype.removeTxWithSpentInputs = function (cb) { +}; + FakeWallet.prototype.setEnc = function(enc) { this.enc = enc; }; From b76a18c295d758991f2359fbe5b88caebdb6ecdc Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Thu, 4 Sep 2014 10:34:22 -0300 Subject: [PATCH 07/17] Fixes reading from local storage configuration --- js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index fcc03a23a..726a00922 100644 --- a/js/app.js +++ b/js/app.js @@ -13,7 +13,7 @@ if (localConfig) { if (key === 'networkName' && config['forceNetwork']) { return; } - config[name] = value; + config[key] = value; }); } } From 111dac1a0eeafca40fd29a05536b609b69a3e6be Mon Sep 17 00:00:00 2001 From: bechi Date: Thu, 4 Sep 2014 11:34:24 -0300 Subject: [PATCH 08/17] fix send section --- views/send.html | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/views/send.html b/views/send.html index cae16e3b9..6a351bf23 100644 --- a/views/send.html +++ b/views/send.html @@ -8,9 +8,10 @@

{{title}}

+
-
+
-
-
+
+
-
+
-
-
+
+
@@ -125,6 +126,7 @@
+
From d92f0ed0eba6ad029f91d558ee71c4b3fe67d9a5 Mon Sep 17 00:00:00 2001 From: Esteban Ordano Date: Wed, 3 Sep 2014 09:58:24 -0300 Subject: [PATCH 09/17] JSDoc and beautified code for PrivateKey --- js/models/core/PrivateKey.js | 165 +++++++++++++++++++++++++++++++---- 1 file changed, 147 insertions(+), 18 deletions(-) diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js index 7c220dadd..8df3325c7 100644 --- a/js/models/core/PrivateKey.js +++ b/js/models/core/PrivateKey.js @@ -1,23 +1,45 @@ 'use strict'; +// 62.9% typed (by google's closure-compiler account) var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; var WalletKey = bitcore.WalletKey; var networks = bitcore.networks; var util = bitcore.util; +var _ = require('underscore'); +var preconditions = require('preconditions').instance(); var HDPath = require('./HDPath'); +/** + * @desc + * Wrapper for bitcore.HierarchicalKey to be used inside of Copay. + * + * @param {Object} opts + * @param {string} opts.networkName if set to 'testnet', use the test3 bitcoin + * network constants (livenet otherwise) + * @param {string} opts.extendedPrivateKeyString if set, use this private key + * string, othewise create a new + * private key + */ function PrivateKey(opts) { opts = opts || {}; - this.network = opts.networkName === 'testnet' ? - networks.testnet : networks.livenet; + this.network = opts.networkName === 'testnet' ? networks.testnet : networks.livenet; var init = opts.extendedPrivateKeyString || this.network.name; this.bip = new HK(init); this.privateKeyCache = {}; this.publicHex = this.deriveBIP45Branch().eckey.public.toString('hex'); }; +/** + * @desc Retrieve this derivated private key's public key in hexa format + * + * The value returned is calculated using the path from PrivateKey's + * HDParams.IdFullBranch. This key is used to identify the copayer + * (signing messages mostly). + * + * @returns {string} the public key in a hexadecimal string + */ PrivateKey.prototype.getId = function() { if (!this.id) { this.cacheId(); @@ -25,6 +47,15 @@ PrivateKey.prototype.getId = function() { return this.id; }; +/** + * @desc Retrieve this private key's private key in hex format + * + * The value returned is calculated using the path from PrivateKey's + * HDParams.IdFullBranch. This key is used to identify the copayer + * (signing messages mostly). + * + * @returns {string} the private key in a hexadecimal string + */ PrivateKey.prototype.getIdPriv = function() { if (!this.idpriv) { this.cacheId(); @@ -32,6 +63,15 @@ PrivateKey.prototype.getIdPriv = function() { return this.idpriv; }; +/** + * @desc Retrieve this private key's private key + * + * The value returned is calculated using the path from PrivateKey's + * HDParams.IdFullBranch. This key is used to identify the copayer + * (signing messages mostly). + * + * @returns {bitcore.PrivateKey} the private key + */ PrivateKey.prototype.getIdKey = function() { if (!this.idkey) { this.cacheId(); @@ -39,6 +79,11 @@ PrivateKey.prototype.getIdKey = function() { return this.idkey; }; +/** + * @desc Caches the result of deriving IdFullBranch + * + * @private + */ PrivateKey.prototype.cacheId = function() { var path = HDPath.IdFullBranch; var idhk = this.bip.derive(path); @@ -47,54 +92,116 @@ PrivateKey.prototype.cacheId = function() { this.idpriv = idhk.eckey.private.toString('hex'); }; +/** + * @desc Derive the master branch for Copay. + */ PrivateKey.prototype.deriveBIP45Branch = function() { if (!this.bip45Branch) { this.bip45Branch = this.bip.derive(HDPath.BIP45_PUBLIC_PREFIX); } return this.bip45Branch; -} - - -PrivateKey.trim = function(data) { - var opts = {}; - ['networkName', 'extendedPrivateKeyString'].forEach(function(k){ - opts[k] = data[k]; - }); - - return opts; }; +/** + * @desc Returns an object with information needed to rebuild a PrivateKey + * (as most of its properties are derived from the extended private key). + * + * @TODO: Figure out if this is the correct pattern + * This is a static method and is probably used for serialization. + * + * @static + * @param {Object} data + * @param {*} data.networkName - a name for a bitcoin network + * @param {*} data.extendedPrivateKeyString - a bip32 extended private key + * @returns {Object} an object with two properties: networkName and + * extendedPrivateKeyString, taken from the data + * parameter. + */ +PrivateKey.trim = function(data) { + var opts = {}; + ['networkName', 'extendedPrivateKeyString'].forEach(function(k){ + opts[k] = data[k]; + }); + return opts +}; + +/** + * @desc Generate a private Key from a serialized object + * + * @TODO: This method uses PrivateKey.trim but it's actually not needed... + * + * @param {Object} data + * @param {*} data.networkName - a name for a bitcoin network + * @param {*} data.extendedPrivateKeyString - a bip32 extended private key + * @returns {PrivateKey} + */ PrivateKey.fromObj = function(obj) { return new PrivateKey(PrivateKey.trim(obj)); }; +/** + * @desc Serialize a private key, keeping only the data necessary to rebuild it + * + * @returns {Object} + */ PrivateKey.prototype.toObj = function() { return { extendedPrivateKeyString: this.getExtendedPrivateKeyString(), - networkName: this.network.name, + networkName: this.network.name }; }; +/** + * @desc Retrieve a BIP32 extended public key as generated by bitcore + * + * @returns {string} + */ PrivateKey.prototype.getExtendedPublicKeyString = function() { return this.bip.extendedPublicKeyString(); }; +/** + * @desc Retrieve a BIP32 extended private key as generated by bitcore + * + * @returns {string} + */ PrivateKey.prototype.getExtendedPrivateKeyString = function() { return this.bip.extendedPrivateKeyString(); }; +/** + * @desc + * Retrieve a HierarchicalKey derived from the given path as generated by + * bitcore + * @param {string} path - a string for derivation (something like "m/234'/1/2") + * @returns {bitcore.HierarchicalKey} + */ PrivateKey.prototype._getHK = function(path) { - if (typeof path === 'undefined') { + if (_.isUndefined(path)) { return this.bip; } var ret = this.bip.derive(path); return ret; }; +/** + * @desc + * Retrieve an array of WalletKey derived from given paths. {@see PrivateKey#getForPath} + * + * @param {string[]} paths - the paths to derive + * @returns {bitcore.WalletKey[]} - the derived keys + */ PrivateKey.prototype.getForPaths = function(paths) { return paths.map(this.getForPath.bind(this)); }; +/** + * @desc + * Retrieve a WalletKey derived from a path. + * + * @param {string} paths - the path to derive + * @returns {bitcore.WalletKey} - the derived key + */ PrivateKey.prototype.getForPath = function(path) { var pk = this.privateKeyCache[path]; if (!pk) { @@ -110,14 +217,38 @@ PrivateKey.prototype.getForPath = function(path) { return wk; }; +/** + * @desc + * Retrieve a Branch for Copay using the given path + * + * @TODO: Investigate when is this called and if this is really needed + * + * @param {number} index - the index of the key to generate + * @param {boolean} isChange - whether this is a change adderess or a receive + * @param {number} cosigner - the cosigner index + * @return {bitcore.HierarchicalKey} + */ PrivateKey.prototype.get = function(index, isChange, cosigner) { + + // TODO: Add parameter validation? + var path = HDPath.FullBranch(index, isChange, cosigner); return this.getForPath(path); }; +/** + * @desc + * Retrieve multiple branches for Copay up to the received indexes + * + * @TODO: Investigate when is this called and if this is really needed + * + * @param {number} receiveIndex - the number of receive addresses to generate + * @param {number} changeIndex - the number of change addresses to generate + * @param {number} cosigner - the cosigner index + * @return {bitcore.HierarchicalKey} + */ PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) { - if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined') - throw new Error('Invalid parameters'); + preconditions.checkArgument(!_.isUndefined(receiveIndex) && !_.isUndefined(changeIndex)); var ret = []; for (var i = 0; i < receiveIndex; i++) { @@ -129,6 +260,4 @@ PrivateKey.prototype.getAll = function(receiveIndex, changeIndex, cosigner) { return ret; }; - - module.exports = PrivateKey; From b9702dfa3f60609cc227535033da268059932ba6 Mon Sep 17 00:00:00 2001 From: Gustavo Maximiliano Cortez Date: Thu, 4 Sep 2014 11:42:21 -0300 Subject: [PATCH 10/17] Fixes parseFloat for compatibility with all browsers --- js/controllers/send.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/controllers/send.js b/js/controllers/send.js index 3dc673183..a3112a093 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -34,7 +34,7 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._alternative = newValue; if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - this._amount = Number.parseFloat( + this._amount = parseFloat( (rateService.fromFiat(newValue, config.alternativeIsoCode) * satToUnit ).toFixed(config.unitDecimals), 10); } else { @@ -52,7 +52,7 @@ angular.module('copayApp.controllers').controller('SendController', set: function (newValue) { this._amount = newValue; if (typeof(newValue) === 'number' && $scope.isRateAvailable) { - this._alternative = Number.parseFloat( + this._alternative = parseFloat( (rateService.toFiat(newValue * config.unitToSatoshi, config.alternativeIsoCode) ).toFixed(2), 10); } else { From a9bf9a25ab99c9ddc6aba4c9f5ce924e20fe2162 Mon Sep 17 00:00:00 2001 From: bechi Date: Thu, 4 Sep 2014 12:50:27 -0300 Subject: [PATCH 11/17] fix transactions list --- views/transactions.html | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/views/transactions.html b/views/transactions.html index f91565fbe..cb0315da5 100644 --- a/views/transactions.html +++ b/views/transactions.html @@ -30,14 +30,14 @@ No transactions yet.
-
-
+
+ -
+
first seen at @@ -49,7 +49,7 @@
-
+
{{vin.value| noFractionNumber}} {{$root.unitName}}

@@ -57,10 +57,13 @@

-
+
-
+
+ +
+
{{vout.value| noFractionNumber}} {{$root.unitName}}

@@ -70,9 +73,13 @@

From ac6688a1824be8a7a2668f88fcd94834117d7b77 Mon Sep 17 00:00:00 2001 From: bechi Date: Thu, 4 Sep 2014 17:03:54 -0300 Subject: [PATCH 12/17] sidebar-mobile + locked amount + signout in header mobile --- css/src/main.css | 1 + css/src/mobile.css | 8 +++++ index.html | 3 ++ views/includes/sidebar-mobile.html | 55 ++++++++++++++---------------- views/includes/sidebar.html | 3 +- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/css/src/main.css b/css/src/main.css index 352e30ad2..82566a843 100644 --- a/css/src/main.css +++ b/css/src/main.css @@ -368,6 +368,7 @@ a:hover { .pr {position: relative;} .pa {position: absolute;} .m0 {margin: 0;} +.p0 {padding: 0 !important;} .db {display: block;} .size-12 { font-size: 12px; } .size-14 { font-size: 14px; } diff --git a/css/src/mobile.css b/css/src/mobile.css index 05e37c635..5b8f44afa 100644 --- a/css/src/mobile.css +++ b/css/src/mobile.css @@ -102,6 +102,14 @@ border-right: 1px solid #425568; } + .right-small { + border-left: 1px solid #425568; + } + + .right-small a { + color: white; + } + .panel .secret { padding-top: 0.5rem; display: block; diff --git a/index.html b/index.html index d3241cec2..570c9f7d6 100644 --- a/index.html +++ b/index.html @@ -19,6 +19,9 @@
+
+ +

diff --git a/views/includes/sidebar-mobile.html b/views/includes/sidebar-mobile.html index f71e5cf7a..2ce45fa58 100644 --- a/views/includes/sidebar-mobile.html +++ b/views/includes/sidebar-mobile.html @@ -6,36 +6,33 @@

-
-
-
- Balance - - - - {{totalBalance || 0 - |noFractionNumber}} {{$root.unitName}} - -
-
- Locked - - - - {{lockedBalance || 0|noFractionNumber}} {{$root.unitName}} -   -
-
-
+
+
+

+ {{$root.wallet.getName()}} + {{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}} +

+
+ Balance + + + + + {{totalBalance || 0 + |noFractionNumber}} {{$root.unitName}} + +
+
+ Locked + + + + {{lockedBalance || 0|noFractionNumber}} {{$root.unitName}} +   +
+
+
From ace301d1f82e2e900dacb7f91acddbf3b9b00879 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 4 Sep 2014 17:37:38 -0300 Subject: [PATCH 13/17] Only remove pending txps --- js/models/core/Wallet.js | 11 ++++++++++- test/test.Wallet.js | 30 +++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index a4d5e3380..91123d20a 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1529,7 +1529,16 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { var self = this; cb = cb || function () {}; - var txps = this.getTxProposals(); + + var txps = []; + var maxRejectCount = this.maxRejectCount(); + for (var ntxid in this.txProposals.txps) { + var txp = this.txProposals.txps[ntxid]; + txp.ntxid = ntxid; + if (txp.isPending(maxRejectCount)) { + txps.push(txp); + } + } var inputs = []; txps.forEach(function (txp) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index cf8a0e26c..222296a76 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -802,8 +802,8 @@ describe('Wallet model', function() { }); }); - describe('removeTxWithSpentInputs', function () { - it('should remove TxProposal with spent inputs', function(done) { + describe.only('removeTxWithSpentInputs', function () { + it('should remove pending TxProposal with spent inputs', function(done) { var w = cachedCreateW2(); var utxo = createUTXO(w); chai.expect(w.getTxProposals().length).to.equal(0); @@ -825,7 +825,7 @@ describe('Wallet model', function() { }); }); - it('should remove TxProposal with at least 1 spent input', function(done) { + it('should remove pending TxProposal with at least 1 spent input', function(done) { var w = cachedCreateW2(); var utxo = [createUTXO(w)[0], createUTXO(w)[0]]; utxo[0].amount = 80000; @@ -849,6 +849,30 @@ describe('Wallet model', function() { done(); }); }); + + it('should not remove complete TxProposal', function(done) { + var w = cachedCreateW2(); + var utxo = createUTXO(w); + chai.expect(w.getTxProposals().length).to.equal(0); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, amountSatStr, null, function(ntxid) { + w.sendTxProposal(ntxid); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Inputs are still available, txp still valid + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(1); + + // Simulate input spent. txp should be removed from txps list + w.blockchain.fixUnspent([]); + var txp = w.txProposals.get(ntxid); + sinon.stub(txp, 'isPending', function () { return false; }) + w.removeTxWithSpentInputs(); + chai.expect(w.getTxProposals().length).to.equal(1); + + done(); + }); + }); }); describe('#send', function() { From 48a4e8b555f3aebef961d50cf0280e1fae3ebbc7 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Thu, 4 Sep 2014 17:38:09 -0300 Subject: [PATCH 14/17] Test --- test/test.Wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 222296a76..05c9cc658 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -802,7 +802,7 @@ describe('Wallet model', function() { }); }); - describe.only('removeTxWithSpentInputs', function () { + describe('removeTxWithSpentInputs', function () { it('should remove pending TxProposal with spent inputs', function(done) { var w = cachedCreateW2(); var utxo = createUTXO(w); From 8fd6aa1eb03765c41cd70cc2d039b6da005cf3c9 Mon Sep 17 00:00:00 2001 From: bechi Date: Thu, 4 Sep 2014 17:40:16 -0300 Subject: [PATCH 15/17] fix addres section --- css/src/main.css | 9 +++++++-- css/src/mobile.css | 4 ++++ views/addresses.html | 41 ++++++++++++++++++++--------------------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/css/src/main.css b/css/src/main.css index 82566a843..7831e8d16 100644 --- a/css/src/main.css +++ b/css/src/main.css @@ -924,8 +924,8 @@ button, .button, p { .addresses .list-addr i { position: absolute; - top: -25px; - left: 6px; + top: -15px; + left: 0; cursor: pointer; } @@ -1195,5 +1195,10 @@ a.text-warning:hover {color: #FD7262;} padding: 50px; } +.collapse { + margin: auto; + max-width: 100%; +} + /*-----------------------------------------------------------------*/ diff --git a/css/src/mobile.css b/css/src/mobile.css index 5b8f44afa..9deb53219 100644 --- a/css/src/mobile.css +++ b/css/src/mobile.css @@ -131,6 +131,10 @@ height: 200px; } + .addresses .panel { + padding: 1rem 0.8rem; + } + .btn-copy { display: none; } diff --git a/views/addresses.html b/views/addresses.html index 6fbbf245e..5845c8b5b 100644 --- a/views/addresses.html +++ b/views/addresses.html @@ -7,30 +7,29 @@
-
    -
  • - -
    -
    -   - - +
    +
    +
    +
    +   + + + + + change +
    +
    + +
    + + + + + {{addr.balance || 0|noFractionNumber}} {{$root.unitName}} - - change
    - -
    - - - - - {{addr.balance || 0|noFractionNumber}} {{$root.unitName}} - -
    -
  • -
+
Show all From 6fc683dcaabac09857d613c716b4358492dde0aa Mon Sep 17 00:00:00 2001 From: Matias Pando Date: Thu, 4 Sep 2014 17:47:24 -0300 Subject: [PATCH 16/17] Bug found that stops sending message --- js/models/network/Async.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/models/network/Async.js b/js/models/network/Async.js index b15ce9087..19cf047aa 100644 --- a/js/models/network/Async.js +++ b/js/models/network/Async.js @@ -340,6 +340,7 @@ Network.prototype.getCopayerIds = function() { Network.prototype.send = function(dest, payload, cb) { + preconditions.checkState(this.socket); preconditions.checkArgument(payload); var self = this; @@ -357,7 +358,8 @@ Network.prototype.send = function(dest, payload, cb) { var to = dest[ii]; if (to == this.copayerId) continue; - log.debug('SEND to: ' + to, this.copayerId, payload); + + log.debug('SEND to: ' + to, this.copayerId, JSON.stringify(payload)); var message = this.encode(to, payload); this.socket.emit('message', message); From 48cbd016861159dc52a0cd67ca91ba303cbf5789 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 5 Sep 2014 12:03:50 -0300 Subject: [PATCH 17/17] Fixed reentrant event --- js/models/core/Wallet.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 91123d20a..c327d5da0 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -1549,6 +1549,8 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { if (inputs.length === 0) return; + + var proposalsChanged = false; this.blockchain.getUnspent(this.getAddressesStr(), function(err, unspentList) { if (err) return cb(err); @@ -1560,12 +1562,15 @@ Wallet.prototype.removeTxWithSpentInputs = function(cb) { inputs.forEach(function (input) { if (!input.unspent) { + proposalsChanged = true; self.txProposals.deleteOne(input.ntxid); } }); - self.emit('txProposalsUpdated'); - self.store(); + if (proposalsChanged) { + self.emit('txProposalsUpdated'); + self.store(); + } cb(null); });