From 29920abdb3771940d0f5d4eb5f551a3f19d93507 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Wed, 18 Jun 2014 10:58:34 -0300 Subject: [PATCH 01/18] Add wallet addresses index discovery on importing backup --- js/models/blockchain/Insight.js | 21 +++++++++++ js/models/core/AddressIndex.js | 2 +- js/models/core/PublicKeyRing.js | 2 - js/models/core/Wallet.js | 66 +++++++++++++++++++++++++++++++++ js/models/core/WalletFactory.js | 5 +++ test/test.Wallet.js | 44 ++++++++++++++++++++++ test/test.blockchain.Insight.js | 30 +++++++++++++++ 7 files changed, 167 insertions(+), 3 deletions(-) diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 897dd22c8..7803fbd10 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -159,6 +159,27 @@ Insight.prototype.sendRawTransaction = function(rawtx, cb) { }); }; +Insight.prototype.checkActivity = function(addresses, cb) { + if (!addresses) throw new Error('address must be set'); + + this.getTransactions(addresses, function onResult(txs) { + var flatArray = function (xss) { return xss.reduce(function(r, xs) { return r.concat(xs); }, []); }; + var getInputs = function (t) { return t.vin.map(function (vin) { return vin.addr }); }; + var getOutputs = function (t) { return flatArray( + t.vout.map(function (vout) { return vout.scriptPubKey.addresses; }) + );}; + + var activityMap = new Array(addresses.length); + var activeAddress = flatArray(txs.map(function(t) { return getInputs(t).concat(getOutputs(t)); })); + activeAddress.forEach(function (addr) { + var index = addresses.indexOf(addr); + if (index != -1) activityMap[index] = true; + }); + + cb(null, activityMap); + }); +}; + Insight.prototype._request = function(options, callback) { diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js index 8bfe2ffb6..7920b44a2 100644 --- a/js/models/core/AddressIndex.js +++ b/js/models/core/AddressIndex.js @@ -30,7 +30,7 @@ AddressIndex.prototype.toObj = function() { AddressIndex.prototype.checkRange = function(index, isChange) { if ((isChange && index > this.changeIndex) || (!isChange && index > this.receiveIndex)) { - throw new Error('Out of bounds at index %d isChange: %d', index, isChange); + throw new Error('Out of bounds at index ' + index + ' isChange: ' + isChange); } }; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 94cc110f4..fdb0ad8e8 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -157,8 +157,6 @@ PublicKeyRing.prototype.getPubKeys = function(index, isChange) { // TODO this could be cached PublicKeyRing.prototype.getRedeemScript = function (index, isChange) { - this.indexes.checkRange(index, isChange); - var pubKeys = this.getPubKeys(index, isChange); var script = Script.createMultisig(this.requiredCopayers, pubKeys); return script; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2bb53df60..925bdb674 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -8,6 +8,7 @@ var coinUtil = bitcore.util; var buffertools = bitcore.buffertools; var Builder = bitcore.TransactionBuilder; var http = require('http'); +var async = require('async'); var EventEmitter = imports.EventEmitter || require('events').EventEmitter; var copay = copay || require('../../../copay'); var SecureRandom = bitcore.SecureRandom; @@ -711,6 +712,71 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos return ntxid; }; +Wallet.prototype.updateIndexes = function(callback) { + var self = this; + var start = self.publicKeyRing.indexes.changeIndex; + self.indexDiscovery(start, true, 20, function(err, changeIndex) { + if (err) return callback(err); + if (changeIndex != -1) + self.publicKeyRing.indexes.changeIndex = changeIndex + 1; + + start = self.publicKeyRing.indexes.receiveIndex; + self.indexDiscovery(start, false, 20, function(err, receiveIndex) { + if (err) return callback(err); + if (receiveIndex != -1) + self.publicKeyRing.indexes.receiveIndex = receiveIndex + 1; + + self.emit('publicKeyRingUpdated'); + self.store(); + callback(); + }); + }); +} + +Wallet.prototype.deriveAddresses = function(index, amout, isChange) { + var ret = new Array(amout); + for(var i = 0; i < amout; i++) { + ret[i] = this.publicKeyRing.getAddress(index + i, isChange).toString(); + } + return ret; +} + +// This function scans the publicKeyRing branch starting at index @start and reports the index with last activity, +// using a scan window of @gap. The argument @change defines the branch to scan: internal or external. +// Returns -1 if no activity is found in range. +Wallet.prototype.indexDiscovery = function(start, change, gap, cb) { + var scanIndex = start; + var lastActive = -1; + var hasActivity = false; + + var self = this; + async.doWhilst( + function _do(next) { + // Optimize window to minimize the derivations. + var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; + var addresses = self.deriveAddresses(scanIndex, scanWindow, change); + self.blockchain.checkActivity(addresses, function(err, actives){ + if (err) throw err; + + // Check for new activities in the newlly scanned addresses + var recentActive = actives.reduce(function(r, e, i) { + return e ? scanIndex + i : r; + }, lastActive); + hasActivity = lastActive != recentActive; + lastActive = recentActive; + scanIndex += scanWindow; + next(); + }); + }, + function _while() { return hasActivity; }, + function _finnaly(err) { + if (err) return cb(err); + cb(null, lastActive); + } + ); +} + + Wallet.prototype.disconnect = function() { this.log('## DISCONNECTING'); this.network.disconnect(); diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 6ecc70b46..b84233e4e 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -71,6 +71,11 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) { var walletObj = this.storage.import(base64); if (!walletObj) return false; var w = this.fromObj(walletObj); + var self = this; + w.updateIndexes(function(err) { + if (err) throw err; + self.log('Indexes updated'); + }); return w; }; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index c1557b3b6..5b894eba6 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -593,4 +593,48 @@ describe('Wallet model', function() { w.getNetworkName().should.equal('testnet'); }); + var mockFakeActivity = function(w, isChange, f) { + var ADDRESSES = w.deriveAddresses(0, 20, isChange); + w.blockchain.checkActivity = function(addresses, cb) { + var activity = new Array(addresses.length); + for(var i=0; i Date: Wed, 18 Jun 2014 14:24:48 -0300 Subject: [PATCH 02/18] Add more tests --- test/test.Wallet.js | 54 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 5b894eba6..a5d4f06e7 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -593,18 +593,23 @@ describe('Wallet model', function() { w.getNetworkName().should.equal('testnet'); }); - var mockFakeActivity = function(w, isChange, f) { - var ADDRESSES = w.deriveAddresses(0, 20, isChange); + var mockFakeActivity = function(w, f) { + var ADDRESSES_CHANGE = w.deriveAddresses(0, 20, true); + var ADDRESSES_RECEIVE = w.deriveAddresses(0, 20, false); w.blockchain.checkActivity = function(addresses, cb) { var activity = new Array(addresses.length); - for(var i=0; i Date: Thu, 19 Jun 2014 11:35:38 -0300 Subject: [PATCH 03/18] move update indexes to the loading screen --- js/controllers/import.js | 25 ++++++++++--------------- js/models/core/WalletFactory.js | 15 ++++++++++----- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/js/controllers/import.js b/js/controllers/import.js index 639f6a4a1..2157b4f00 100644 --- a/js/controllers/import.js +++ b/js/controllers/import.js @@ -6,21 +6,16 @@ angular.module('copayApp.controllers').controller('ImportController', var reader = new FileReader(); var _importBackup = function(encryptedObj) { Passphrase.getBase64Async($scope.password, function(passphrase){ - var w, errMsg; - try { - w = walletFactory.fromEncryptedObj(encryptedObj, passphrase); - } catch(e) { - errMsg = e.message; - } - if (!w) { - $scope.loading = false; - $rootScope.$flashMessage = { message: errMsg || 'Wrong password', type: 'error'}; - $rootScope.$digest(); - return; - } - $rootScope.wallet = w; - - controllerUtils.startNetwork($rootScope.wallet, $scope); + walletFactory.import(encryptedObj, passphrase, function(err, w) { + if (err) { + $scope.loading = false; + $rootScope.$flashMessage = { message: err.errMsg || 'Wrong password', type: 'error'}; + $rootScope.$digest(); + return; + } + $rootScope.wallet = w; + controllerUtils.startNetwork($rootScope.wallet, $scope); + }); }); }; diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index b84233e4e..8576bb901 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -71,14 +71,19 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) { var walletObj = this.storage.import(base64); if (!walletObj) return false; var w = this.fromObj(walletObj); - var self = this; - w.updateIndexes(function(err) { - if (err) throw err; - self.log('Indexes updated'); - }); return w; }; +WalletFactory.prototype.import = function(base64, password, cb) { + var self = this; + var w = self.fromEncryptedObj(base64, password); + w.updateIndexes(function(err) { + if (err) return cb(err); + self.log('Indexes updated'); + cb(null, w); + }); +} + WalletFactory.prototype.read = function(walletId) { if (!this._checkRead(walletId)) return false; From c9158757fc00cbbe2323a1f2076a0b7bf6c5d4f9 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 19 Jun 2014 12:40:39 -0300 Subject: [PATCH 04/18] Add secure token to travis.yml --- .travis.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index aee558ffc..ee0e79ef3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ language: node_js node_js: - - "0.10" -before_install: "npm install -g grunt-cli" -install: "npm install" -before_script: "grunt shell" +- '0.10' +before_install: npm install -g grunt-cli +install: npm install +before_script: grunt shell +notifications: + hipchat: + rooms: + secure: ibsyaK2/7hdms9pRHCibamfUtA1yb5Ib3tIQOChwnfNSkpOy5ZViuq92duPZpKacgNuRmiwp6wfbDY8duLiadwnCet4V3dHAGqt+t9o+O7A9DzxNeYFFHnnrhqhg95PjWK6UyFqrQajPIr+n11M2itEQLa7kljQxJZ3Z26zJCdI= From 2ea65ec23692f911c14150569116875da1115665 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 19 Jun 2014 13:18:13 -0300 Subject: [PATCH 05/18] less verbose format to hipchat notifications --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index ee0e79ef3..7f5f77cae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,3 +8,6 @@ notifications: hipchat: rooms: secure: ibsyaK2/7hdms9pRHCibamfUtA1yb5Ib3tIQOChwnfNSkpOy5ZViuq92duPZpKacgNuRmiwp6wfbDY8duLiadwnCet4V3dHAGqt+t9o+O7A9DzxNeYFFHnnrhqhg95PjWK6UyFqrQajPIr+n11M2itEQLa7kljQxJZ3Z26zJCdI= + template: + - '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (Details/Change view)' + format: html From 69c871d7a5a3c1bb56a7900b62f627456eb28b69 Mon Sep 17 00:00:00 2001 From: Yemel Jardi Date: Thu, 19 Jun 2014 15:14:32 -0300 Subject: [PATCH 06/18] notify only on failure --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 7f5f77cae..5a0bbe949 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,3 +11,4 @@ notifications: template: - '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (Details/Change view)' format: html + on_success: never From e0db6cac280ab8544d7256e3b7ca9208b4380b1f Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 14:28:26 -0300 Subject: [PATCH 07/18] fix dependency management --- js/models/core/Wallet.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 925bdb674..297d2e022 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -2,18 +2,23 @@ var imports = require('soop').imports(); +var http = require('http'); +var EventEmitter = imports.EventEmitter || require('events').EventEmitter; +var async = require('async'); + var bitcore = require('bitcore'); var bignum = bitcore.Bignum; var coinUtil = bitcore.util; var buffertools = bitcore.buffertools; var Builder = bitcore.TransactionBuilder; -var http = require('http'); -var async = require('async'); -var EventEmitter = imports.EventEmitter || require('events').EventEmitter; -var copay = copay || require('../../../copay'); var SecureRandom = bitcore.SecureRandom; var Base58Check = bitcore.Base58.base58Check; +var AddressIndex = require('./AddressIndex'); +var PublicKeyRing = require('./PublicKeyRing'); +var TxProposals = require('./TxProposals'); +var PrivateKey = require('./PrivateKey'); + function Wallet(opts) { var self = this; @@ -75,7 +80,7 @@ Wallet.prototype.connectToAll = function() { Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { this.log('RECV INDEXES:', data); - var inIndexes = copay.AddressIndex.fromObj(data.indexes); + var inIndexes = AddressIndex.fromObj(data.indexes); var hasChanged = this.publicKeyRing.indexes.merge(inIndexes); if (hasChanged) { this.emit('publicKeyRingUpdated'); @@ -86,7 +91,7 @@ Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { this.log('RECV PUBLICKEYRING:', data); - var inPKR = copay.PublicKeyRing.fromObj(data.publicKeyRing); + var inPKR = PublicKeyRing.fromObj(data.publicKeyRing); var wasIncomplete = !this.publicKeyRing.isComplete(); var hasChanged; @@ -115,7 +120,7 @@ Wallet.prototype._handleTxProposals = function(senderId, data, isInbound) { this.log('RECV TXPROPOSAL:', data); var recipients; - var inTxp = copay.TxProposals.fromObj(data.txProposals); + var inTxp = TxProposals.fromObj(data.txProposals); var ids = inTxp.getNtxids(); if (ids.length > 1) { @@ -341,9 +346,9 @@ Wallet.prototype.toObj = function() { Wallet.fromObj = function(o, storage, network, blockchain) { var opts = JSON.parse(JSON.stringify(o.opts)); - opts.publicKeyRing = copay.PublicKeyRing.fromObj(o.publicKeyRing); - opts.txProposals = copay.TxProposals.fromObj(o.txProposals); - opts.privateKey = copay.PrivateKey.fromObj(o.privateKey); + opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); + opts.txProposals = TxProposals.fromObj(o.txProposals); + opts.privateKey = PrivateKey.fromObj(o.privateKey); opts.storage = storage; opts.network = network; From b791d58c6c53fd5b37f72ef3f7fab9fd72e484c0 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 14:31:15 -0300 Subject: [PATCH 08/18] refactors --- js/models/core/Wallet.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 297d2e022..782881046 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -447,13 +447,14 @@ Wallet.prototype.getTxProposals = function() { var ret = []; var copayers = this.getRegisteredCopayerIds(); for (var k in this.txProposals.txps) { - var i = this.txProposals.getTxProposal(k, copayers); - i.signedByUs = i.signedBy[this.getMyCopayerId()] ? true : false; - i.rejectedByUs = i.rejectedBy[this.getMyCopayerId()] ? true : false; - if (this.totalCopayers - i.rejectCount < this.requiredCopayers) - i.finallyRejected = true; + var txp = this.txProposals.getTxProposal(k, copayers); + txp.signedByUs = txp.signedBy[this.getMyCopayerId()] ? true : false; + txp.rejectedByUs = txp.rejectedBy[this.getMyCopayerId()] ? true : false; + if (this.totalCopayers - txp.rejectCount < this.requiredCopayers) { + txp.finallyRejected = true; + } - ret.push(i); + ret.push(txp); } return ret; }; From 7001e21c71578f2ea18e3393265d083eba8a98b9 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 15:30:38 -0300 Subject: [PATCH 09/18] ocha --- js/models/core/TxProposals.js | 155 +++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 60 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index c9a5bcf3c..a4fd20117 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -46,6 +46,68 @@ TxProposal.getSentTs = function() { return this.sentTs; }; +TxProposal.prototype.mergeBuilder = function(other) { + + var v0 = this.builder; + var v1 = other.builder; + + // TODO: enhance this + var before = JSON.stringify(v0.toObj()); + v0.merge(v1); + var after = JSON.stringify(v0.toObj()); + if (after !== before) hasChanged++; +}; + +TxProposal.prototype.mergeMetadata = function(v1) { + + var events = []; + var v0 = this; + Object.keys(v1.seenBy).forEach(function(k) { + if (!v0.seenBy[k]) { + v0.seenBy[k] = v1.seenBy[k]; + events.push({ + type: 'seen', + cId: k, + txId: hash + }); + } + }); + + Object.keys(v1.signedBy).forEach(function(k) { + if (!v0.signedBy[k]) { + v0.signedBy[k] = v1.signedBy[k]; + events.push({ + type: 'signed', + cId: k, + txId: hash + }); + } + }); + + Object.keys(v1.rejectedBy).forEach(function(k) { + if (!v0.rejectedBy[k]) { + v0.rejectedBy[k] = v1.rejectedBy[k]; + events.push({ + type: 'rejected', + cId: k, + txId: hash + }); + } + }); + + if (!v0.sentTxid && v1.sentTxid) { + v0.sentTs = v1.sentTs; + v0.sentTxid = v1.sentTxid; + events.push({ + type: 'broadcast', + txId: hash + }); + } + + return events; + +}; + module.exports = require('soop')(TxProposal); @@ -103,7 +165,11 @@ TxProposals.prototype._startMerge = function(myTxps, theirTxps) { for (var hash in theirTxps) { if (!myTxps[hash]) { ready[hash] = theirTxps[hash]; // only in theirs; - events.push({type: 'new', cid: theirTxps[hash].creator, tx: hash}); + events.push({ + type: 'new', + cid: theirTxps[hash].creator, + tx: hash + }); fromTheirs++; } else { toMerge[hash] = theirTxps[hash]; // need Merging @@ -132,42 +198,14 @@ TxProposals.prototype._startMerge = function(myTxps, theirTxps) { // TODO add signatures. TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { - var toMerge = mergeInfo.toMerge; - var hasChanged = 0; var events = []; Object.keys(toMerge).forEach(function(hash) { var v0 = myTxps[hash]; var v1 = toMerge[hash]; - - Object.keys(v1.seenBy).forEach(function(k) { - if (!v0.seenBy[k]) { - v0.seenBy[k] = v1.seenBy[k]; - events.push({type: 'seen', cId: k, txId: hash}); - } - }); - - Object.keys(v1.signedBy).forEach(function(k) { - if (!v0.signedBy[k]) { - v0.signedBy[k] = v1.signedBy[k]; - events.push({type: 'signed', cId: k, txId: hash}); - } - }); - - Object.keys(v1.rejectedBy).forEach(function(k) { - if (!v0.rejectedBy[k]) { - v0.rejectedBy[k] = v1.rejectedBy[k]; - events.push({type: 'rejected', cId: k, txId: hash}); - } - }); - - if (!v0.sentTxid && v1.sentTxid) { - v0.sentTs = v1.sentTs; - v0.sentTxid = v1.sentTxid; - events.push({type: 'broadcast', txId: hash}); - } - + var newEvents = v0.mergeMetadata(v1); + events.concat(newEvents); }); return { @@ -179,18 +217,36 @@ TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { var toMerge = mergeInfo.toMerge; - var hasChanged = 0; for (var hash in toMerge) { - var v0 = myTxps[hash].builder; - var v1 = toMerge[hash].builder; + var v0 = myTxps[hash]; + var v1 = myTxps[hash]; - // TODO: enhance this - var before = JSON.stringify(v0.toObj()); - v0.merge(v1); - var after = JSON.stringify(v0.toObj()); - if (after !== before) hasChanged++; + v0.mergeBuilder(v1); } + +}; + +TxProposals.prototype.merge = function(t) { + if (this.network.name !== t.network.name) + throw new Error('network mismatch in:', t); + + var myTxps = this.txps; + var theirTxps = t.txps; + + var mergeInfo = this._startMerge(myTxps, theirTxps); + var result = this._mergeMetadata(myTxps, theirTxps, mergeInfo); + result.hasChanged += this._mergeBuilder(myTxps, theirTxps, mergeInfo); + + Object.keys(mergeInfo.toMerge).forEach(function(hash) { + mergeInfo.ready[hash] = myTxps[hash]; + }); + + mergeInfo.stats.hasChanged = result.hasChanged; + mergeInfo.stats.events = result.events; + + this.txps = mergeInfo.ready; + return mergeInfo.stats; }; TxProposals.prototype.add = function(data) { @@ -259,26 +315,5 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { return ret; }; -TxProposals.prototype.merge = function(t) { - if (this.network.name !== t.network.name) - throw new Error('network mismatch in:', t); - - var myTxps = this.txps; - var theirTxps = t.txps; - - var mergeInfo = this._startMerge(myTxps, theirTxps); - var result = this._mergeMetadata(myTxps, theirTxps, mergeInfo); - result.hasChanged += this._mergeBuilder(myTxps, theirTxps, mergeInfo); - - Object.keys(mergeInfo.toMerge).forEach(function(hash) { - mergeInfo.ready[hash] = myTxps[hash]; - }); - - mergeInfo.stats.hasChanged = result.hasChanged; - mergeInfo.stats.events = result.events; - - this.txps = mergeInfo.ready; - return mergeInfo.stats; -}; module.exports = require('soop')(TxProposals); From 27de2896d8d2a69fa4575d90ca073f04dbd1a3ec Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 16:42:49 -0300 Subject: [PATCH 10/18] working on refactor --- js/models/core/TxProposals.js | 13 ++++++++----- js/models/core/Wallet.js | 13 +++---------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index a4fd20117..da985431a 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -22,6 +22,11 @@ function TxProposal(opts) { this.comment = opts.comment || null; } +TxProposal.prototype.getID = function() { + var ntxid = this.builder.build().getNormalizedHash().toString('hex'); + return ntxid; +}; + TxProposal.prototype.toObj = function() { var o = JSON.parse(JSON.stringify(this)); delete o['builder']; @@ -47,7 +52,6 @@ TxProposal.getSentTs = function() { }; TxProposal.prototype.mergeBuilder = function(other) { - var v0 = this.builder; var v1 = other.builder; @@ -59,7 +63,6 @@ TxProposal.prototype.mergeBuilder = function(other) { }; TxProposal.prototype.mergeMetadata = function(v1) { - var events = []; var v0 = this; Object.keys(v1.seenBy).forEach(function(k) { @@ -250,12 +253,12 @@ TxProposals.prototype.merge = function(t) { }; TxProposals.prototype.add = function(data) { - var ntxid = data.builder.build().getNormalizedHash().toString('hex'); - this.txps[ntxid] = new TxProposal(data); + var txp = new TxProposal(data); + var ntxid = txp.getID(); + this.txps[ntxid] = txp; return ntxid; }; - TxProposals.prototype.setSent = function(ntxid, txid) { //sent TxProposals are local an not broadcasted. this.txps[ntxid].setSent(txid); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 782881046..65013ee1c 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -120,17 +120,10 @@ Wallet.prototype._handleTxProposals = function(senderId, data, isInbound) { this.log('RECV TXPROPOSAL:', data); var recipients; - var inTxp = TxProposals.fromObj(data.txProposals); - var ids = inTxp.getNtxids(); + var inTxp = TxProposal.fromObj(data.txProposal); - if (ids.length > 1) { - this.emit('badMessage', senderId); - this.log('Received BAD TxProposal messsage FROM:', senderId); //TODO - return; - } - - var newId = ids[0]; - var mergeInfo = this.txProposals.merge(inTxp, true); + var newId = inTxp.getID(); + var mergeInfo = this.txProposals.merge(inTxp); var addSeen = this.addSeenToTxProposals(); if (mergeInfo.hasChanged || addSeen) { this.log('### BROADCASTING txProposals. '); From 03b4d1647313449e7edc18381186560f566e1b45 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 17:43:06 -0300 Subject: [PATCH 11/18] following the single TxProposal changes --- js/models/core/TxProposals.js | 133 +++++++++++----------------------- js/models/core/Wallet.js | 49 +++++-------- 2 files changed, 61 insertions(+), 121 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index da985431a..84dcaa343 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -51,27 +51,38 @@ TxProposal.getSentTs = function() { return this.sentTs; }; -TxProposal.prototype.mergeBuilder = function(other) { - var v0 = this.builder; - var v1 = other.builder; +TxProposal.prototype.merge = function(other) { + var ret = {}; + ret.events = this.mergeMetadata(other); + ret.hasChanged = this.mergeBuilder(other); // TODO: use this? - // TODO: enhance this - var before = JSON.stringify(v0.toObj()); - v0.merge(v1); - var after = JSON.stringify(v0.toObj()); - if (after !== before) hasChanged++; + return ret; +}; + +TxProposal.prototype.mergeBuilder = function(other) { + var b0 = this.builder; + var b1 = other.builder; + + // TODO: improve this comparison + var before = JSON.stringify(b0.toObj()); + b0.merge(b1); + var after = JSON.stringify(b0.toObj()); + return after !== before; }; TxProposal.prototype.mergeMetadata = function(v1) { var events = []; var v0 = this; + + var ntxid = this.getID(); + Object.keys(v1.seenBy).forEach(function(k) { if (!v0.seenBy[k]) { v0.seenBy[k] = v1.seenBy[k]; events.push({ type: 'seen', cId: k, - txId: hash + txId: ntxid }); } }); @@ -82,7 +93,7 @@ TxProposal.prototype.mergeMetadata = function(v1) { events.push({ type: 'signed', cId: k, - txId: hash + txId: ntxid }); } }); @@ -93,7 +104,7 @@ TxProposal.prototype.mergeMetadata = function(v1) { events.push({ type: 'rejected', cId: k, - txId: hash + txId: ntxid }); } }); @@ -103,7 +114,7 @@ TxProposal.prototype.mergeMetadata = function(v1) { v0.sentTxid = v1.sentTxid; events.push({ type: 'broadcast', - txId: hash + txId: ntxid }); } @@ -157,99 +168,39 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) { }; }; -TxProposals.prototype._startMerge = function(myTxps, theirTxps) { - var fromUs = 0, - fromTheirs = 0, - merged = 0; - var toMerge = {}, - ready = {}, - events = []; - - for (var hash in theirTxps) { - if (!myTxps[hash]) { - ready[hash] = theirTxps[hash]; // only in theirs; - events.push({ - type: 'new', - cid: theirTxps[hash].creator, - tx: hash - }); - fromTheirs++; - } else { - toMerge[hash] = theirTxps[hash]; // need Merging - merged++; - } - } - - for (var hash in myTxps) { - if (!toMerge[hash]) { - ready[hash] = myTxps[hash]; // only in myTxps; - fromUs++; - } - } - +TxProposals.prototype._startMerge = function(inTxp) { return { - stats: { - fromUs: fromUs, - fromTheirs: fromTheirs, - merged: merged, - }, - ready: ready, - toMerge: toMerge, events: events }; }; -// TODO add signatures. -TxProposals.prototype._mergeMetadata = function(myTxps, theirTxps, mergeInfo) { - var toMerge = mergeInfo.toMerge; - var events = []; - - Object.keys(toMerge).forEach(function(hash) { - var v0 = myTxps[hash]; - var v1 = toMerge[hash]; - var newEvents = v0.mergeMetadata(v1); - events.concat(newEvents); - }); - - return { - events: events.concat(mergeInfo.events), - hasChanged: events.length - }; -}; - - TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { var toMerge = mergeInfo.toMerge; - for (var hash in toMerge) { - var v0 = myTxps[hash]; - var v1 = myTxps[hash]; - - v0.mergeBuilder(v1); - } + for (var ntxid in toMerge) {} }; -TxProposals.prototype.merge = function(t) { - if (this.network.name !== t.network.name) - throw new Error('network mismatch in:', t); - +TxProposals.prototype.merge = function(inTxp) { var myTxps = this.txps; - var theirTxps = t.txps; - var mergeInfo = this._startMerge(myTxps, theirTxps); - var result = this._mergeMetadata(myTxps, theirTxps, mergeInfo); - result.hasChanged += this._mergeBuilder(myTxps, theirTxps, mergeInfo); + var ntxid = inTxp.getID(); + var ret; - Object.keys(mergeInfo.toMerge).forEach(function(hash) { - mergeInfo.ready[hash] = myTxps[hash]; - }); - - mergeInfo.stats.hasChanged = result.hasChanged; - mergeInfo.stats.events = result.events; - - this.txps = mergeInfo.ready; - return mergeInfo.stats; + if (myTxps[ntxid]) { + var v0 = myTxps[ntxid]; + var v1 = inTxp; + ret = v0.merge(v1); + } else { + ret.events = {}; + ret.hasChanged = true; + ret.events.push({ + type: 'new', + cid: inTxp.creator, + tx: ntxid + }); + } + return ret; }; TxProposals.prototype.add = function(data) { diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 65013ee1c..920ed9fcd 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -116,24 +116,22 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { }; -Wallet.prototype._handleTxProposals = function(senderId, data, isInbound) { +Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL:', data); - var recipients; - var inTxp = TxProposal.fromObj(data.txProposal); + var inTxp = TxProposals.TxProposal.fromObj(data.txProposal); - var newId = inTxp.getID(); var mergeInfo = this.txProposals.merge(inTxp); - var addSeen = this.addSeenToTxProposals(); - if (mergeInfo.hasChanged || addSeen) { + var added = this.addSeenToTxProposals(); + + if (mergeInfo.hasChanged || added) { this.log('### BROADCASTING txProposals. '); - recipients = null; - this.sendTxProposals(recipients, newId); - } - if (data.lastInBatch) { - this.emit('txProposalsUpdated'); - this.store(); + this.sendTxProposals(null, inTxp.getID()); } + + this.emit('txProposalsUpdated'); + this.store(); + for (var i = 0; i < mergeInfo.events.length; i++) { this.emit('txProposalEvent', mergeInfo.events[i]); } @@ -160,8 +158,8 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { case 'publicKeyRing': this._handlePublicKeyRing(senderId, data, isInbound); break; - case 'txProposals': - this._handleTxProposals(senderId, data, isInbound); + case 'txProposal': + this._handleTxProposal(senderId, data, isInbound); break; case 'indexes': this._handleIndexes(senderId, data, isInbound); @@ -357,23 +355,14 @@ Wallet.prototype.toEncryptedObj = function() { return this.storage.export(walletObj); }; -Wallet.prototype.sendTxProposals = function(recipients, ntxid) { +Wallet.prototype.sendTxProposal = function(recipients, ntxid) { this.log('### SENDING txProposals TO:', recipients || 'All', this.txProposals); - - var toSend = ntxid ? [ntxid] : this.txProposals.getNtxids(); - - var last = toSend[toSend]; - - for (var i in toSend) { - var id = toSend[i]; - var lastInBatch = (i == toSend.length - 1); - this.network.send(recipients, { - type: 'txProposals', - txProposals: this.txProposals.toObj(id), - walletId: this.id, - lastInBatch: lastInBatch, - }); - } + var id = toSend[i]; + this.network.send(recipients, { + type: 'txProposal', + txProposals: this.txProposals.toObj(id), + walletId: this.id, + }); }; Wallet.prototype.sendWalletReady = function(recipients) { From d8e564b0314389b40e6c51793ce418e3bbac0cd2 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 17 Jun 2014 17:48:52 -0300 Subject: [PATCH 12/18] remove old methods --- js/models/core/TxProposals.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 84dcaa343..049412245 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -168,19 +168,6 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) { }; }; -TxProposals.prototype._startMerge = function(inTxp) { - return { - events: events - }; -}; - -TxProposals.prototype._mergeBuilder = function(myTxps, theirTxps, mergeInfo) { - var toMerge = mergeInfo.toMerge; - - for (var ntxid in toMerge) {} - -}; - TxProposals.prototype.merge = function(inTxp) { var myTxps = this.txps; From 1457764930a5b617851ca5bac84d80802b8a1104 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 09:07:32 -0300 Subject: [PATCH 13/18] change 'k' for 'ntxid' --- js/models/core/TxProposals.js | 2 +- test/test.TxProposals.js | 102 +++++++++++++++++----------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 049412245..655b36f9f 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -256,5 +256,5 @@ TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { return ret; }; - +TxProposals.TxProposal = TxProposal; module.exports = require('soop')(TxProposals); diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index 00df86166..8ccaf4cba 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -97,8 +97,8 @@ describe('TxProposals model', function() { priv, pkr )); - var k = Object.keys(w.txps)[0]; - var b = w.txps[k].builder; + var ntxid = Object.keys(w.txps)[0]; + var b = w.txps[ntxid].builder; var tx = b.build(); tx.isComplete().should.equal(false); b.sign(priv2.getAll(pkr.indexes.getReceiveIndex(), pkr.indexes.getChangeIndex())); @@ -211,15 +211,15 @@ describe('TxProposals model', function() { priv, pkr )); - var k = Object.keys(w.txps)[0]; - var tx = w.txps[k].builder.build(); + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - var info = w.merge(w); + var info = w.merge(w.txps[0]); info.events.length.should.equal(0); Object.keys(w.txps).length.should.equal(1); @@ -227,8 +227,8 @@ describe('TxProposals model', function() { tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); }); @@ -261,13 +261,13 @@ describe('TxProposals model', function() { pkr )); - var k = Object.keys(w.txps)[0]; - var tx = w.txps[k].builder.build(); + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(1); - Object.keys(w.txps[k].signedBy).length.should.equal(0); - Object.keys(w.txps[k].seenBy).length.should.equal(1); + Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); + Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); var w2 = new TxProposals({ @@ -285,13 +285,13 @@ describe('TxProposals model', function() { pkr )); - var k = Object.keys(w.txps)[0]; - var tx = w2.txps[k].builder.build(); + var ntxid = Object.keys(w.txps)[0]; + var tx = w2.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w2.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w2.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); var info = w.merge(w2); info.events.length.should.equal(1); @@ -299,11 +299,11 @@ describe('TxProposals model', function() { Object.keys(w.txps).length.should.equal(1); - var tx = w.txps[k].builder.build(); + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); }); @@ -475,12 +475,12 @@ describe('TxProposals model', function() { priv, pkr )); - var k = Object.keys(w.txps)[0]; - var tx = w.txps[k].builder.build(); + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); var w2 = new TxProposals({ @@ -496,11 +496,11 @@ describe('TxProposals model', function() { priv2, pkr )); - var tx = w2.txps[k].builder.build(); + var tx = w2.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w2.txps[k].signedBy[priv2.id] - ts > 0).should.equal(true); - (w2.txps[k].seenBy[priv2.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); var w3 = new TxProposals({ @@ -516,38 +516,38 @@ describe('TxProposals model', function() { priv3, pkr )); - var tx = w3.txps[k].builder.build(); + var tx = w3.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w3.txps[k].signedBy[priv3.id] - ts > 0).should.equal(true); - (w3.txps[k].seenBy[priv3.id] - ts > 0).should.equal(true); + (w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); + (w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); var info = w.merge(w2); info.events.length.should.equal(0); Object.keys(w.txps).length.should.equal(1); - var tx = w.txps[k].builder.build(); + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(1); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].signedBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); var info = w.merge(w3); info.events.length.should.equal(0); - var tx = w.txps[k].builder.build(); + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(true); tx.countInputMissingSignatures(0).should.equal(0); Object.keys(w.txps).length.should.equal(1); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv3.id] - ts > 0).should.equal(true); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].signedBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[k].signedBy[priv3.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); }); @@ -573,12 +573,12 @@ describe('TxProposals model', function() { priv, pkr )); - var k = Object.keys(w.txps)[0]; - var tx = w.txps[k].builder.build(); + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); tx.isComplete().should.equal(false); tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); var o = w.toObj(); should.exist(o); @@ -593,13 +593,13 @@ describe('TxProposals model', function() { var o2 = JSON.parse(JSON.stringify(o)); var w2 = TxProposals.fromObj(o2); w2.walletId.should.equal(w.walletId); - var tx2 = w2.txps[k].builder.build(); + var tx2 = w2.txps[ntxid].builder.build(); tx2.isComplete().should.equal(false); tx2.countInputMissingSignatures(0).should.equal(2); - (w2.txps[k].signedBy[priv.id] - ts > 0).should.equal(true); - (w2.txps[k].seenBy[priv.id] - ts > 0).should.equal(true); - should.exist(w2.txps[k].builder); - should.exist(w2.txps[k].builder.valueInSat); + (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + should.exist(w2.txps[ntxid].builder); + should.exist(w2.txps[ntxid].builder.valueInSat); w2.merge(w); Object.keys(w2.txps).length.should.equal(1); From 889edf4b920086fafd2a8aadeaa50efba5f4b22c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 10:09:40 -0300 Subject: [PATCH 14/18] add preconditions and tests --- js/models/core/TxProposals.js | 9 +-- js/models/core/Wallet.js | 29 ++++++---- package.json | 3 +- test/test.TxProposals.js | 14 ++--- test/test.Wallet.js | 101 ++++++++++++++++++---------------- 5 files changed, 87 insertions(+), 69 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 655b36f9f..83945b7b3 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -54,8 +54,7 @@ TxProposal.getSentTs = function() { TxProposal.prototype.merge = function(other) { var ret = {}; ret.events = this.mergeMetadata(other); - ret.hasChanged = this.mergeBuilder(other); // TODO: use this? - + ret.hasChanged = this.mergeBuilder(other); return ret; }; @@ -151,6 +150,7 @@ TxProposals.prototype.getNtxids = function() { }; TxProposals.prototype.toObj = function(onlyThisNtxid) { + if (onlyThisNtxid) throw new Error(); var ret = []; for (var id in this.txps) { @@ -172,14 +172,15 @@ TxProposals.prototype.merge = function(inTxp) { var myTxps = this.txps; var ntxid = inTxp.getID(); - var ret; + var ret = {}; + ret.events = []; + ret.events.hasChanged = false; if (myTxps[ntxid]) { var v0 = myTxps[ntxid]; var v1 = inTxp; ret = v0.merge(v1); } else { - ret.events = {}; ret.hasChanged = true; ret.events.push({ type: 'new', diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 920ed9fcd..6d415ea56 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -5,6 +5,7 @@ var imports = require('soop').imports(); var http = require('http'); var EventEmitter = imports.EventEmitter || require('events').EventEmitter; var async = require('async'); +var preconditions = require('preconditions').instance(); var bitcore = require('bitcore'); var bignum = bitcore.Bignum; @@ -126,7 +127,7 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { if (mergeInfo.hasChanged || added) { this.log('### BROADCASTING txProposals. '); - this.sendTxProposals(null, inTxp.getID()); + this.sendTxProposal(inTxp.getID()); } this.emit('txProposalsUpdated'); @@ -153,7 +154,7 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { break; case 'walletReady': this.sendPublicKeyRing(senderId); - this.sendTxProposals(senderId); // send old + this.sendAllTxProposals(senderId); // send old txps break; case 'publicKeyRing': this._handlePublicKeyRing(senderId, data, isInbound); @@ -355,12 +356,20 @@ Wallet.prototype.toEncryptedObj = function() { return this.storage.export(walletObj); }; -Wallet.prototype.sendTxProposal = function(recipients, ntxid) { - this.log('### SENDING txProposals TO:', recipients || 'All', this.txProposals); - var id = toSend[i]; +Wallet.prototype.sendAllTxProposals = function(recipients) { + var ntxids = this.txProposals.getNtxids(); + for (var i in ntxids) { + var ntxid = ntxids[i]; + this.sendTxProposal(ntxid, recipients); + } +}; + +Wallet.prototype.sendTxProposal = function(ntxid, recipients) { + preconditions.checkArgument(ntxid); + this.log('### SENDING txProposal '+ntxid+' TO:', recipients || 'All', this.txProposals); this.network.send(recipients, { type: 'txProposal', - txProposals: this.txProposals.toObj(id), + txProposal: this.txProposals.txps[ntxid].toObj(), walletId: this.id, }); }; @@ -450,7 +459,7 @@ Wallet.prototype.reject = function(ntxid) { } txp.rejectedBy[myId] = Date.now(); - this.sendTxProposals(null, ntxid); + this.sendTxProposal(ntxid); this.store(); this.emit('txProposalsUpdated'); }; @@ -475,7 +484,7 @@ Wallet.prototype.sign = function(ntxid, cb) { var ret = false; if (b.signaturesAdded > before) { txp.signedBy[myId] = Date.now(); - self.sendTxProposals(null, ntxid); + self.sendTxProposal(ntxid); self.store(); self.emit('txProposalsUpdated'); ret = true; @@ -503,7 +512,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) { self.log('BITCOIND txid:', txid); if (txid) { self.txProposals.setSent(ntxid, txid); - self.sendTxProposals(null, ntxid); + self.sendTxProposal(ntxid); self.store(); } return cb(txid); @@ -630,7 +639,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb) var ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts); if (ntxid) { self.sendIndexes(); - self.sendTxProposals(null, ntxid); + self.sendTxProposal(ntxid); self.store(); self.emit('txProposalsUpdated'); } diff --git a/package.json b/package.json index 626a0d965..92d07ce8b 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ }, "dependencies": { "mocha": "^1.18.2", - "mocha-lcov-reporter": "0.0.1" + "mocha-lcov-reporter": "0.0.1", + "preconditions": "^1.0.7" } } diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index 8ccaf4cba..ccd930ec4 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -219,7 +219,7 @@ describe('TxProposals model', function() { (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - var info = w.merge(w.txps[0]); + var info = w.merge(w.txps[ntxid]); info.events.length.should.equal(0); Object.keys(w.txps).length.should.equal(1); @@ -293,7 +293,7 @@ describe('TxProposals model', function() { (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - var info = w.merge(w2); + var info = w.merge(w2.txps[ntxid]); info.events.length.should.equal(1); info.events[0].type.should.equal('signed'); @@ -401,7 +401,7 @@ describe('TxProposals model', function() { (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - var info = w.merge(w2); + var info = w.merge(w2.txps[ntxid]); info.events.length.should.equal(1); info.events[0].type.should.equal('signed'); @@ -431,7 +431,7 @@ describe('TxProposals model', function() { (w3.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); (w3.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - var info = w.merge(w3); + var info = w.merge(w3.txps[ntxid]); info.events.length.should.equal(0); Object.keys(w.txps).length.should.equal(1); @@ -522,7 +522,7 @@ describe('TxProposals model', function() { (w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); (w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); - var info = w.merge(w2); + var info = w.merge(w2.txps[ntxid]); info.events.length.should.equal(0); Object.keys(w.txps).length.should.equal(1); @@ -535,7 +535,7 @@ describe('TxProposals model', function() { (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - var info = w.merge(w3); + var info = w.merge(w3.txps[ntxid]); info.events.length.should.equal(0); var tx = w.txps[ntxid].builder.build(); @@ -601,7 +601,7 @@ describe('TxProposals model', function() { should.exist(w2.txps[ntxid].builder); should.exist(w2.txps[ntxid].builder.valueInSat); - w2.merge(w); + w2.merge(w.txps[ntxid]); Object.keys(w2.txps).length.should.equal(1); }); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index a5d4f06e7..60a4dfe7d 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -37,6 +37,10 @@ describe('Wallet model', function() { }).should. throw(); }); + it('should getNetworkName', function() { + var w = createW(); + w.getNetworkName().should.equal('testnet'); + }); var createW = function(netKey, N) { @@ -358,51 +362,48 @@ describe('Wallet model', function() { it('handle network txProposals correctly', function() { var w = createW(); var txps = { - 'txProposals': { - "txps": [{ - "seenBy": { - "undefined": 1402337282806 + 'txProposal': { + "seenBy": { + "undefined": 1402337282806 + }, + "signedBy": { + "undefined": 1402337282841 + }, + "rejectedBy": {}, + "sentTs": null, + "sentTxid": null, + "inputChainPaths": [], + "builderObj": { + "valueInSat": "1000000000", + "valueOutSat": "123456789", + "feeSat": "10000", + "remainderSat": "876533211", + "hashToScriptMap": { + "3QjgeBsNeiDkoVQxDAw4sSEe9BVaqhmpnd": "5321025c2951a7e94e39f2c3210fb2d71b7d79044f7daa6b007556a6feb4b2059091e921026c56eea4ef632b24c4efe9d5391ea0e15e306450e5b7db26cd1a7a33d9d4e82821026d06e532385186793121633fb365fa2f0c7246b30590db78cba795641c94734521035051d2b742263bc5257c5a4461e1a6fa32c3f1965f5ab5e8b0f0f80c9e28bc702103ddb0d39c1cd88295e7b81050dcab11ca4419bf64fba3ef9d9130ea64d6604ab055ae", + "365dPtmHPAdUUNvPBcnfidYt2Snyaka7AH": "5321020faaf739abda51e822621d42062470c5743a127186369a67fd3303f71727fb1d210237063d3b27c21767054dad203298bfed90cb08dca371950a08b88aa979c33bd22102a3780cb275b6dcb8b379514679387d4578068745f08c5799e4ee1a90aa3a70922102f4fea0cd5bc2418892278f480682690857f4d963deeb95d88febfa255e4ce0b821035d53fa74fee26873c3f5a4acf4dce53ff9d50db59e1ca4acddb82168dd429a3355ae", + "372DueknmmjVR3zcWfEfADp8FwJ3ARhGaN": "532102666c2873c9e6f58ca594cd6c8327a1515db32ebadbaff4fe75e63b917560b7d021031ca2d159ae8868a5eef6d67a5a8a5bcc6fb2e8b711669f1c1e8b05327236a3cf2103798a4ce34929cb450bf88557dd90cae538c67f9d0c76314ae18679200be9c17021039624dec23dc1bb628cea55e949ea26225949a3349346a0732fef9def6f1f75292103d1edaadb8555012b752dd7a5c6672c379ce827e4bc0d4e992ee1ab3488b445b255ae", + "3GLJbHv7RqPeFk2SqWapqJ3XibVibiKi8f": "5321021a33d48b9f5d3adc41004709313277d4c6969268cf41e3f5b695a934c676829a21031b87321307db7a0f6ea22847a538505188f1a2231eba68867e17c5f0e4434c0721035dee0a6e1df66a6c9c7592ef03aa02bba9b06742134d27bd08f356e33f21259c2103768a109d682a76c09f865912e9e64a8689b03c1a231c613d87ec9bd535fd74c22103911038638b9fc52b2d375ce207c8388bd5ee7f2d54ab9b4148bd406c6d7dcad355ae", + "3E1d1z7gJSFeZM2d3k12MJ1jGwrBRy1YTw": "5321027da98ce0407138461f4ad803a1fb67afa4246f06ad0e1256a087daeffd82a8642102e7f87f2b094ec322be2fb99d3ca81fd6ac0ab26ce081e51ab18c507f8b6d1d162102ed5d256036e10568c33e0a15021cc1806e04d7d24c8f020aaae23ec2deecb4302103b6f88231bb5a5f4629e4d097492321f86968c6aeb012196a1fe3b217fe4ae0ac2103f6b5e2c1db3124a5058a0e3e83d5d0c412b9a5b50e2ef97e2b7e1c0af57ab3e355ae", + "3QYueyPXq2QDRQCDo7agTimz9673NKV25E": "5321021543292c2942708ccc83354ebf507044b310ed0d33a19e2327a49be53e0f314221024a1a83f8c50f6cad7c134b9cded509dabf196ae49eca157a39ad95798943dc95210292698fbb97e8f6e67296b22b36367ba85c8101fcbc27bb4b00b43623639212ac2102d86980796027a00ba7aa9b53671762c908962654f969f5dec340071bb30e8d7621038fb3fa5e77dafd24c09d819dbdc7c11dca55b350511bf3bc499c09316a89286e55ae", + "372BzC1GGjziT8zGYbryja3kF2KaTeobRK": "53210214ec25e5cb42e51883d4e615316748feefe91133fcfc0f93f0b5a24a55e0a347210262336210b3173aa4ca90d292990f463e42bdeb2e73112925dc712c5a2e749bcb210277517855f512564f225e63c650dad7720565aa563901d50743be4b0f0267dcc72102d2777a9faf5d2e2b2363e1270d61021bc2e36e8cb19ca9d29dedbba9a0348b532103a57db80b6ae573e5cef2115e36e73e5ef41f8a099bfb5087d80320c04a7db72e55ae" }, - "signedBy": { - "undefined": 1402337282841 - }, - "rejectedBy": {}, - "sentTs": null, - "sentTxid": null, - "inputChainPaths": [], - "builderObj": { - "valueInSat": "1000000000", - "valueOutSat": "123456789", - "feeSat": "10000", - "remainderSat": "876533211", - "hashToScriptMap": { - "3QjgeBsNeiDkoVQxDAw4sSEe9BVaqhmpnd": "5321025c2951a7e94e39f2c3210fb2d71b7d79044f7daa6b007556a6feb4b2059091e921026c56eea4ef632b24c4efe9d5391ea0e15e306450e5b7db26cd1a7a33d9d4e82821026d06e532385186793121633fb365fa2f0c7246b30590db78cba795641c94734521035051d2b742263bc5257c5a4461e1a6fa32c3f1965f5ab5e8b0f0f80c9e28bc702103ddb0d39c1cd88295e7b81050dcab11ca4419bf64fba3ef9d9130ea64d6604ab055ae", - "365dPtmHPAdUUNvPBcnfidYt2Snyaka7AH": "5321020faaf739abda51e822621d42062470c5743a127186369a67fd3303f71727fb1d210237063d3b27c21767054dad203298bfed90cb08dca371950a08b88aa979c33bd22102a3780cb275b6dcb8b379514679387d4578068745f08c5799e4ee1a90aa3a70922102f4fea0cd5bc2418892278f480682690857f4d963deeb95d88febfa255e4ce0b821035d53fa74fee26873c3f5a4acf4dce53ff9d50db59e1ca4acddb82168dd429a3355ae", - "372DueknmmjVR3zcWfEfADp8FwJ3ARhGaN": "532102666c2873c9e6f58ca594cd6c8327a1515db32ebadbaff4fe75e63b917560b7d021031ca2d159ae8868a5eef6d67a5a8a5bcc6fb2e8b711669f1c1e8b05327236a3cf2103798a4ce34929cb450bf88557dd90cae538c67f9d0c76314ae18679200be9c17021039624dec23dc1bb628cea55e949ea26225949a3349346a0732fef9def6f1f75292103d1edaadb8555012b752dd7a5c6672c379ce827e4bc0d4e992ee1ab3488b445b255ae", - "3GLJbHv7RqPeFk2SqWapqJ3XibVibiKi8f": "5321021a33d48b9f5d3adc41004709313277d4c6969268cf41e3f5b695a934c676829a21031b87321307db7a0f6ea22847a538505188f1a2231eba68867e17c5f0e4434c0721035dee0a6e1df66a6c9c7592ef03aa02bba9b06742134d27bd08f356e33f21259c2103768a109d682a76c09f865912e9e64a8689b03c1a231c613d87ec9bd535fd74c22103911038638b9fc52b2d375ce207c8388bd5ee7f2d54ab9b4148bd406c6d7dcad355ae", - "3E1d1z7gJSFeZM2d3k12MJ1jGwrBRy1YTw": "5321027da98ce0407138461f4ad803a1fb67afa4246f06ad0e1256a087daeffd82a8642102e7f87f2b094ec322be2fb99d3ca81fd6ac0ab26ce081e51ab18c507f8b6d1d162102ed5d256036e10568c33e0a15021cc1806e04d7d24c8f020aaae23ec2deecb4302103b6f88231bb5a5f4629e4d097492321f86968c6aeb012196a1fe3b217fe4ae0ac2103f6b5e2c1db3124a5058a0e3e83d5d0c412b9a5b50e2ef97e2b7e1c0af57ab3e355ae", - "3QYueyPXq2QDRQCDo7agTimz9673NKV25E": "5321021543292c2942708ccc83354ebf507044b310ed0d33a19e2327a49be53e0f314221024a1a83f8c50f6cad7c134b9cded509dabf196ae49eca157a39ad95798943dc95210292698fbb97e8f6e67296b22b36367ba85c8101fcbc27bb4b00b43623639212ac2102d86980796027a00ba7aa9b53671762c908962654f969f5dec340071bb30e8d7621038fb3fa5e77dafd24c09d819dbdc7c11dca55b350511bf3bc499c09316a89286e55ae", - "372BzC1GGjziT8zGYbryja3kF2KaTeobRK": "53210214ec25e5cb42e51883d4e615316748feefe91133fcfc0f93f0b5a24a55e0a347210262336210b3173aa4ca90d292990f463e42bdeb2e73112925dc712c5a2e749bcb210277517855f512564f225e63c650dad7720565aa563901d50743be4b0f0267dcc72102d2777a9faf5d2e2b2363e1270d61021bc2e36e8cb19ca9d29dedbba9a0348b532103a57db80b6ae573e5cef2115e36e73e5ef41f8a099bfb5087d80320c04a7db72e55ae" - }, - "selectedUtxos": [{ - "address": "3E1d1z7gJSFeZM2d3k12MJ1jGwrBRy1YTw", - "scriptPubKey": "a91487264aa41e3df76f3156c7fa587fd7d5b1f7b96b87", - "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", - "vout": 1, - "amount": 10, - "confirmations": 7 - }], - "inputsSigned": 0, - "signaturesAdded": 1, - "signhash": 1, - "spendUnconfirmed": false, - "tx": "0100000001c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a01000000fd40010047304402201aad0ea92f929be21d60afb741b76bfcf2aa9614079cb9da76b15b3a1210f07b02205bc5c1895105da3ee682e532d2d49dfd1214fa6123fb9c9d552336f135c77eff0147304402203f02f15bb4ad4bc7d0ca4612fd9e9ae05a1331f3d62f302cf08dba56695f5dcf0220700110562fe228ebba324b120de64a7bbaddf049d67a313f749e9b32ee88787d014cad5321027da98ce0407138461f4ad803a1fb67afa4246f06ad0e1256a087daeffd82a8642102e7f87f2b094ec322be2fb99d3ca81fd6ac0ab26ce081e51ab18c507f8b6d1d162102ed5d256036e10568c33e0a15021cc1806e04d7d24c8f020aaae23ec2deecb4302103b6f88231bb5a5f4629e4d097492321f86968c6aeb012196a1fe3b217fe4ae0ac2103f6b5e2c1db3124a5058a0e3e83d5d0c412b9a5b50e2ef97e2b7e1c0af57ab3e355aeffffffff0215cd5b07000000001976a91434f8e0c5be216025a52addf18a987543cad23f7a88acdbd53e340000000017a914a09f10bc42b61ecf9d3d09550765c228f1cb808a8700000000" - } - }], - "networkName": "testnet" + "selectedUtxos": [{ + "address": "3E1d1z7gJSFeZM2d3k12MJ1jGwrBRy1YTw", + "scriptPubKey": "a91487264aa41e3df76f3156c7fa587fd7d5b1f7b96b87", + "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", + "vout": 1, + "amount": 10, + "confirmations": 7 + }], + "inputsSigned": 0, + "signaturesAdded": 1, + "signhash": 1, + "spendUnconfirmed": false, + "tx": "0100000001c1cf12ab89729d19d3cdec8ae531b5038d56c741006a105d532b3a7afa65c12a01000000fd40010047304402201aad0ea92f929be21d60afb741b76bfcf2aa9614079cb9da76b15b3a1210f07b02205bc5c1895105da3ee682e532d2d49dfd1214fa6123fb9c9d552336f135c77eff0147304402203f02f15bb4ad4bc7d0ca4612fd9e9ae05a1331f3d62f302cf08dba56695f5dcf0220700110562fe228ebba324b120de64a7bbaddf049d67a313f749e9b32ee88787d014cad5321027da98ce0407138461f4ad803a1fb67afa4246f06ad0e1256a087daeffd82a8642102e7f87f2b094ec322be2fb99d3ca81fd6ac0ab26ce081e51ab18c507f8b6d1d162102ed5d256036e10568c33e0a15021cc1806e04d7d24c8f020aaae23ec2deecb4302103b6f88231bb5a5f4629e4d097492321f86968c6aeb012196a1fe3b217fe4ae0ac2103f6b5e2c1db3124a5058a0e3e83d5d0c412b9a5b50e2ef97e2b7e1c0af57ab3e355aeffffffff0215cd5b07000000001976a91434f8e0c5be216025a52addf18a987543cad23f7a88acdbd53e340000000017a914a09f10bc42b61ecf9d3d09550765c228f1cb808a8700000000" + } } }; - w._handleTxProposals('senderID', txps, true); + w._handleTxProposal('senderID', txps, true); Object.keys(w.txProposals.txps).length.should.equal(1); w.getTxProposals().length.should.equal(1); }); @@ -514,7 +515,6 @@ describe('Wallet model', function() { }); - // tx handling var createUTXO = function(w) { @@ -577,7 +577,6 @@ describe('Wallet model', function() { }); }); it('should create & sign & send a transaction', function(done) { - var w = createW2(null, 1); var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); @@ -588,9 +587,17 @@ describe('Wallet model', function() { }); }); }); - it('#getNetworkName', function() { - var w = createW(); - w.getNetworkName().should.equal('testnet'); + it('should send TxProposal', function(done) { + var w = createW2(); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, amountSatStr, null, function(ntxid) { + w.sendTxProposal.should.throw('Illegal Argument.'); + (function() { + w.sendTxProposal(ntxid) + }).should.not.throw(); + done(); + }); }); var mockFakeActivity = function(w, f) { From b7af51ceeee0cd3390c43dd0bc104d1fe6ec681e Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 10:21:26 -0300 Subject: [PATCH 15/18] all tests passing --- js/models/core/TxProposals.js | 1 + js/models/core/Wallet.js | 5 +++-- test/test.Wallet.js | 20 ++++++++++++++++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 83945b7b3..2851abf36 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -181,6 +181,7 @@ TxProposals.prototype.merge = function(inTxp) { var v1 = inTxp; ret = v0.merge(v1); } else { + this.txps[ntxid] = inTxp; ret.hasChanged = true; ret.events.push({ type: 'new', diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 6d415ea56..b997913a6 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -366,6 +366,7 @@ 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.network.send(recipients, { type: 'txProposal', @@ -437,8 +438,8 @@ Wallet.prototype.generateAddress = function(isChange, cb) { Wallet.prototype.getTxProposals = function() { var ret = []; var copayers = this.getRegisteredCopayerIds(); - for (var k in this.txProposals.txps) { - var txp = this.txProposals.getTxProposal(k, copayers); + for (var ntxid in this.txProposals.txps) { + var txp = this.txProposals.getTxProposal(ntxid, copayers); txp.signedByUs = txp.signedBy[this.getMyCopayerId()] ? true : false; txp.rejectedByUs = txp.rejectedBy[this.getMyCopayerId()] ? true : false; if (this.totalCopayers - txp.rejectCount < this.requiredCopayers) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 60a4dfe7d..bfb333ef6 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -361,7 +361,7 @@ describe('Wallet model', function() { it('handle network txProposals correctly', function() { var w = createW(); - var txps = { + var txp = { 'txProposal': { "seenBy": { "undefined": 1402337282806 @@ -403,7 +403,7 @@ describe('Wallet model', function() { } } }; - w._handleTxProposal('senderID', txps, true); + w._handleTxProposal('senderID', txp, true); Object.keys(w.txProposals.txps).length.should.equal(1); w.getTxProposals().length.should.equal(1); }); @@ -592,9 +592,21 @@ describe('Wallet model', function() { var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { - w.sendTxProposal.should.throw('Illegal Argument.'); + w.sendTxProposal.bind(w).should.throw('Illegal Argument.'); (function() { - w.sendTxProposal(ntxid) + w.sendTxProposal(ntxid); + }).should.not.throw(); + done(); + }); + }); + it('should send all TxProposal', function(done) { + var w = createW2(); + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, amountSatStr, null, function(ntxid) { + w.sendAllTxProposals.bind(w).should.not.throw(); + (function() { + w.sendAllTxProposals(); }).should.not.throw(); done(); }); From e09d8e11d4c890a0c99b99218cf3ea016f6a0a01 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 10:35:33 -0300 Subject: [PATCH 16/18] optimize txp protocol --- js/models/core/Wallet.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index b997913a6..528c0ffa4 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -125,11 +125,6 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { var mergeInfo = this.txProposals.merge(inTxp); var added = this.addSeenToTxProposals(); - if (mergeInfo.hasChanged || added) { - this.log('### BROADCASTING txProposals. '); - this.sendTxProposal(inTxp.getID()); - } - this.emit('txProposalsUpdated'); this.store(); From d800a98df0de3f18db62fd2a9d2b9849acd3709c Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 11:08:50 -0300 Subject: [PATCH 17/18] fix timeout test --- test/test.Wallet.js | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/test.Wallet.js b/test/test.Wallet.js index bfb333ef6..579afc1a7 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -297,23 +297,13 @@ describe('Wallet model', function() { it('call reconnect after interval', function(done) { this.timeout(10000); var w = createW2(); - var testTime = 1000; - var callCount = 0; - var cT = w.reconnectDelay; - var t = 0; - - do { - callCount++; - t += cT; - cT *= 2; - } while (t < testTime); - var spy = sinon.spy(w, 'scheduleConnect'); + var callCount = 3; w.netStart(); setTimeout(function() { sinon.assert.callCount(spy, callCount); done(); - }, testTime); + }, w.reconnectDelay*callCount*(callCount+1)/2); }); it('handle network indexes correctly', function() { From 0c03af53a89eb734016ea7e5ae92f1b2d19abb34 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Wed, 18 Jun 2014 13:01:50 -0300 Subject: [PATCH 18/18] fixed karma test bug --- js/controllers/header.js | 6 +++--- js/services/backupService.js | 5 +++-- js/services/controllerUtils.js | 8 ++++---- js/services/notifications.js | 17 +++++++++++------ js/services/video.js | 6 ++++-- karma.conf.js | 16 +++++++--------- package.json | 7 ++++--- test/unit/services/servicesSpec.js | 9 ++++++++- 8 files changed, 44 insertions(+), 30 deletions(-) diff --git a/js/controllers/header.js b/js/controllers/header.js index 1afd1ec80..ba4a0d521 100644 --- a/js/controllers/header.js +++ b/js/controllers/header.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('copayApp.controllers').controller('HeaderController', - function($scope, $rootScope, $location, $notification, $http, controllerUtils) { + function($scope, $rootScope, $location, notification, $http, controllerUtils) { $scope.menu = [ { 'title': 'Addresses', @@ -64,7 +64,7 @@ angular.module('copayApp.controllers').controller('HeaderController', } if (currentAddr) { //var beep = new Audio('sound/transaction.mp3'); - $notification.funds('Received fund', currentAddr, receivedFund); + notification.funds('Received fund', currentAddr, receivedFund); //beep.play(); } } @@ -72,7 +72,7 @@ angular.module('copayApp.controllers').controller('HeaderController', $rootScope.$watch('txAlertCount', function(txAlertCount) { if (txAlertCount && txAlertCount > 0) { - $notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); + notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); } }); diff --git a/js/services/backupService.js b/js/services/backupService.js index 5485a0e12..572cad348 100644 --- a/js/services/backupService.js +++ b/js/services/backupService.js @@ -1,7 +1,8 @@ 'use strict'; -var BackupService = function($notification) { - this.notifications = $notification; + +var BackupService = function(notification) { + this.notifications = notification; }; BackupService.prototype.getName = function(wallet) { diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 5a7c6817d..66787d476 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -2,7 +2,7 @@ var bitcore = require('bitcore'); angular.module('copayApp.services') - .factory('controllerUtils', function($rootScope, $sce, $location, $notification, $timeout, Socket, video) { + .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video) { var root = {}; root.getVideoMutedStatus = function(copayer) { @@ -87,7 +87,7 @@ angular.module('copayApp.services') $rootScope.$digest(); }; - $notification.enableHtml5Mode(); // for chrome: if support, enable it + notification.enableHtml5Mode(); // for chrome: if support, enable it w.on('badMessage', function(peerId) { $rootScope.$flashMessage = { @@ -126,11 +126,11 @@ angular.module('copayApp.services') switch (e.type) { case 'signed': var user = w.publicKeyRing.nicknameForCopayer(e.cId); - $notification.info('Transaction Update', 'A transaction was signed by ' + user); + notification.info('Transaction Update', 'A transaction was signed by ' + user); break; case 'rejected': var user = w.publicKeyRing.nicknameForCopayer(e.cId); - $notification.info('Transaction Update', 'A transaction was rejected by ' + user); + notification.info('Transaction Update', 'A transaction was rejected by ' + user); break; } }); diff --git a/js/services/notifications.js b/js/services/notifications.js index 1d75fb20c..8d390949f 100644 --- a/js/services/notifications.js +++ b/js/services/notifications.js @@ -1,9 +1,9 @@ 'use strict'; angular.module('copayApp.services'). - factory('$notification', ['$timeout',function($timeout){ + factory('notification', ['$timeout',function($timeout){ - var notifications = JSON.parse(localStorage.getItem('$notifications')) || [], + var notifications = JSON.parse(localStorage.getItem('notifications')) || [], queue = []; var settings = { @@ -186,7 +186,7 @@ angular.module('copayApp.services'). save: function(){ // Save all the notifications into localStorage if(settings.localStorage){ - localStorage.setItem('$notifications', JSON.stringify(notifications)); + localStorage.setItem('notifications', JSON.stringify(notifications)); } }, @@ -201,7 +201,7 @@ angular.module('copayApp.services'). }; }]). - directive('notifications', ['$notification', '$compile', function($notification, $compile){ + directive('notifications', function(notification, $compile){ /** * * It should also parse the arguments passed to it that specify @@ -245,7 +245,7 @@ angular.module('copayApp.services'). template: html, link: link, controller: ['$scope', function NotificationsCtrl( $scope ){ - $scope.queue = $notification.getQueue(); + $scope.queue = notification.getQueue(); $scope.removeNotification = function(noti){ $scope.queue.splice($scope.queue.indexOf(noti), 1); @@ -254,4 +254,9 @@ angular.module('copayApp.services'). ] }; - }]); + }); + + + + + diff --git a/js/services/video.js b/js/services/video.js index acff9327f..fce1ee2b6 100644 --- a/js/services/video.js +++ b/js/services/video.js @@ -7,7 +7,9 @@ var Video = function() { this.mediaConnections = {}; this.localStream = null; - this.onlineSound = new Audio('sound/online.wav'); + if (typeof Audio !== 'undefined') { + this.onlineSound = new Audio('sound/online.wav'); + } }; Video.prototype.setOwnPeer = function(peer, wallet, cb) { @@ -64,7 +66,7 @@ Video.prototype._addCall = function(mediaConnection, cb) { // Wait for stream on the call, then set peer video display mediaConnection.on('stream', function(stream) { - self.onlineSound.play(); + if (self.onlineSound) self.onlineSound.play(); cb(null, peerID, URL.createObjectURL(stream)); }); diff --git a/karma.conf.js b/karma.conf.js index 077882857..48b6655b3 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -39,15 +39,6 @@ module.exports = function(config) { 'lib/qrcode-decoder-js/lib/qrcode-decoder.min.js', 'js/copayBundle.js', - //Test-Specific Code - 'lib/chai/chai.js', - 'test/lib/chai-should.js', - 'test/lib/chai-expect.js', - 'test/mocks/FakeWallet.js', - - //Mocha stuff - 'test/mocha.conf.js', - //App-specific Code 'js/app.js', 'js/routes.js', @@ -57,6 +48,13 @@ module.exports = function(config) { 'js/controllers/*.js', 'js/init.js', + //Test-Specific Code + 'lib/chai/chai.js', + 'test/lib/chai-should.js', + 'test/lib/chai-expect.js', + 'test/mocks/FakeWallet.js', + + 'test/mocha.conf.js', //test files 'test/unit/**/*.js' diff --git a/package.json b/package.json index 92d07ce8b..bec53ce4f 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,11 @@ "browserify": "3.32.1", "buffertools": "2.0.1", "chai": "1.9.1", + "cli-color": "0.3.2", "commander": "2.1.0", "coveralls": "2.10.0", "express": "4.0.0", + "github-releases": "0.2.0", "grunt-browserify": "2.0.8", "grunt-contrib-watch": "0.5.3", "grunt-markdown": "0.5.0", @@ -46,15 +48,14 @@ "karma": "0.12.9", "karma-chrome-launcher": "0.1.3", "karma-mocha": "0.1.3", + "karma-phantomjs-launcher": "^0.1.4", "mocha": "1.18.2", "mocha-lcov-reporter": "0.0.1", "node-cryptojs-aes": "0.4.0", "sinon": "1.9.1", "soop": "0.1.5", "travis-cov": "0.2.5", - "uglifyify": "1.2.3", - "github-releases": "0.2.0", - "cli-color": "0.3.2" + "uglifyify": "1.2.3" }, "dependencies": { "mocha": "^1.18.2", diff --git a/test/unit/services/servicesSpec.js b/test/unit/services/servicesSpec.js index 23086e7a9..615222b54 100644 --- a/test/unit/services/servicesSpec.js +++ b/test/unit/services/servicesSpec.js @@ -3,6 +3,8 @@ // // // +var sinon = require('sinon'); + describe('Check config', function() { it('unit should be set to BITS in config.js', function() { expect(config.unitToSatoshi).to.equal(100); @@ -87,9 +89,14 @@ describe("Unit: controllerUtils", function() { }); +describe("Unit: Notification Service", function() { + beforeEach(angular.mock.module('copayApp.services')); + it('should contain a notification service', inject(function(notification) { + expect(notification).not.to.equal(null); + })); +}); describe("Unit: Backup Service", function() { - var sinon = require('sinon'); beforeEach(angular.mock.module('copayApp.services')); it('should contain a backup service', inject(function(backupService) { expect(backupService).not.to.equal(null);