diff --git a/.travis.yml b/.travis.yml index aee558ffc..5a0bbe949 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,14 @@ 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= + template: + - '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message} (Details/Change view)' + format: html + on_success: never 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/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/blockchain/Insight.js b/js/models/blockchain/Insight.js index ae94b95dc..4fa24ca5e 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -163,11 +163,34 @@ 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._requestNode = function(options, callback) { if (options.method === 'POST') { options.headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': options.data.length, + }; } 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/TxProposals.js b/js/models/core/TxProposals.js index c9a5bcf3c..2851abf36 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']; @@ -46,6 +51,76 @@ TxProposal.getSentTs = function() { return this.sentTs; }; +TxProposal.prototype.merge = function(other) { + var ret = {}; + ret.events = this.mergeMetadata(other); + ret.hasChanged = this.mergeBuilder(other); + 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: ntxid + }); + } + }); + + Object.keys(v1.signedBy).forEach(function(k) { + if (!v0.signedBy[k]) { + v0.signedBy[k] = v1.signedBy[k]; + events.push({ + type: 'signed', + cId: k, + txId: ntxid + }); + } + }); + + Object.keys(v1.rejectedBy).forEach(function(k) { + if (!v0.rejectedBy[k]) { + v0.rejectedBy[k] = v1.rejectedBy[k]; + events.push({ + type: 'rejected', + cId: k, + txId: ntxid + }); + } + }); + + if (!v0.sentTxid && v1.sentTxid) { + v0.sentTs = v1.sentTs; + v0.sentTxid = v1.sentTxid; + events.push({ + type: 'broadcast', + txId: ntxid + }); + } + + return events; + +}; + module.exports = require('soop')(TxProposal); @@ -75,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) { @@ -92,114 +168,37 @@ TxProposals.prototype.toObj = function(onlyThisNtxid) { }; }; -TxProposals.prototype._startMerge = function(myTxps, theirTxps) { - var fromUs = 0, - fromTheirs = 0, - merged = 0; - var toMerge = {}, - ready = {}, - events = []; +TxProposals.prototype.merge = function(inTxp) { + var myTxps = this.txps; - 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++; - } - } + var ntxid = inTxp.getID(); + var ret = {}; + ret.events = []; + ret.events.hasChanged = false; - for (var hash in myTxps) { - if (!toMerge[hash]) { - ready[hash] = myTxps[hash]; // only in myTxps; - fromUs++; - } - } - - 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 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}); - } + if (myTxps[ntxid]) { + var v0 = myTxps[ntxid]; + var v1 = inTxp; + ret = v0.merge(v1); + } else { + this.txps[ntxid] = inTxp; + ret.hasChanged = true; + ret.events.push({ + type: 'new', + cid: inTxp.creator, + tx: ntxid }); - - 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: events.concat(mergeInfo.events), - hasChanged: events.length - }; -}; - - -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; - - // TODO: enhance this - var before = JSON.stringify(v0.toObj()); - v0.merge(v1); - var after = JSON.stringify(v0.toObj()); - if (after !== before) hasChanged++; } + return ret; }; 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); @@ -259,26 +258,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; -}; - +TxProposals.TxProposal = TxProposal; module.exports = require('soop')(TxProposals); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 2bb53df60..528c0ffa4 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -2,17 +2,24 @@ 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; var coinUtil = bitcore.util; var buffertools = bitcore.buffertools; var Builder = bitcore.TransactionBuilder; -var http = require('http'); -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; @@ -74,7 +81,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'); @@ -85,7 +92,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; @@ -110,31 +117,17 @@ 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 = copay.TxProposals.fromObj(data.txProposals); - var ids = inTxp.getNtxids(); + var inTxp = TxProposals.TxProposal.fromObj(data.txProposal); - if (ids.length > 1) { - this.emit('badMessage', senderId); - this.log('Received BAD TxProposal messsage FROM:', senderId); //TODO - return; - } + var mergeInfo = this.txProposals.merge(inTxp); + var added = this.addSeenToTxProposals(); + + this.emit('txProposalsUpdated'); + this.store(); - var newId = ids[0]; - var mergeInfo = this.txProposals.merge(inTxp, true); - var addSeen = this.addSeenToTxProposals(); - if (mergeInfo.hasChanged || addSeen) { - this.log('### BROADCASTING txProposals. '); - recipients = null; - this.sendTxProposals(recipients, newId); - } - if (data.lastInBatch) { - this.emit('txProposalsUpdated'); - this.store(); - } for (var i = 0; i < mergeInfo.events.length; i++) { this.emit('txProposalEvent', mergeInfo.events[i]); } @@ -156,13 +149,13 @@ 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); break; - case 'txProposals': - this._handleTxProposals(senderId, data, isInbound); + case 'txProposal': + this._handleTxProposal(senderId, data, isInbound); break; case 'indexes': this._handleIndexes(senderId, data, isInbound); @@ -340,9 +333,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; @@ -358,25 +351,25 @@ Wallet.prototype.toEncryptedObj = function() { return this.storage.export(walletObj); }; -Wallet.prototype.sendTxProposals = 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, - }); +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); + preconditions.checkState(this.txProposals.txps[ntxid]); + this.log('### SENDING txProposal '+ntxid+' TO:', recipients || 'All', this.txProposals); + this.network.send(recipients, { + type: 'txProposal', + txProposal: this.txProposals.txps[ntxid].toObj(), + walletId: this.id, + }); +}; + Wallet.prototype.sendWalletReady = function(recipients) { this.log('### SENDING WalletReady TO:', recipients); @@ -440,14 +433,15 @@ Wallet.prototype.generateAddress = function(isChange, cb) { 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; + 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) { + txp.finallyRejected = true; + } - ret.push(i); + ret.push(txp); } return ret; }; @@ -461,7 +455,7 @@ Wallet.prototype.reject = function(ntxid) { } txp.rejectedBy[myId] = Date.now(); - this.sendTxProposals(null, ntxid); + this.sendTxProposal(ntxid); this.store(); this.emit('txProposalsUpdated'); }; @@ -486,7 +480,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; @@ -514,7 +508,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); @@ -641,7 +635,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'); } @@ -711,6 +705,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..8576bb901 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -74,6 +74,16 @@ WalletFactory.prototype.fromEncryptedObj = function(base64, password) { 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; 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 626a0d965..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,18 +48,18 @@ "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", - "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 00df86166..ccd930ec4 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[ntxid]); 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,25 +285,25 @@ 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); + var info = w.merge(w2.txps[ntxid]); info.events.length.should.equal(1); info.events[0].type.should.equal('signed'); 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); }); @@ -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); @@ -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); + var info = w.merge(w2.txps[ntxid]); 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); + var info = w.merge(w3.txps[ntxid]); 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,15 +593,15 @@ 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); + 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 c1557b3b6..579afc1a7 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) { @@ -293,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() { @@ -357,52 +351,49 @@ describe('Wallet model', function() { it('handle network txProposals correctly', function() { var w = createW(); - var txps = { - 'txProposals': { - "txps": [{ - "seenBy": { - "undefined": 1402337282806 + var txp = { + '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', txp, true); Object.keys(w.txProposals.txps).length.should.equal(1); w.getTxProposals().length.should.equal(1); }); @@ -514,7 +505,6 @@ describe('Wallet model', function() { }); - // tx handling var createUTXO = function(w) { @@ -577,7 +567,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 +577,113 @@ 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.bind(w).should.throw('Illegal Argument.'); + (function() { + 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(); + }); + }); + + 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