From 06b63e0311040b6089ae429afeeec2ccb2244650 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Fri, 25 Aug 2017 16:04:14 -0300 Subject: [PATCH] all methods working with coin --- config.js | 30 ++++-- lib/blockchainexplorer.js | 20 +++- lib/blockchainexplorers/insight.js | 10 +- lib/blockchainmonitor.js | 25 ++--- lib/model/txproposal.js | 14 ++- lib/model/txproposal_legacy.js | 4 - lib/model/wallet.js | 6 +- lib/server.js | 143 +++++++++++++++++++---------- test/integration/bcmonitor.js | 6 +- test/integration/server.js | 1 + test/model/txproposal.js | 11 ++- test/storage.js | 2 + 12 files changed, 176 insertions(+), 96 deletions(-) diff --git a/config.js b/config.js index f2dff51..d0c8194 100644 --- a/config.js +++ b/config.js @@ -38,16 +38,28 @@ var config = { }, }, blockchainExplorerOpts: { - livenet: { - provider: 'insight', - url: 'https://insight.bitpay.com:443', + btc: { + livenet: { + provider: 'insight', + url: 'https://insight.bitpay.com:443', + }, + testnet: { + provider: 'insight', + url: 'https://test-insight.bitpay.com:443', + // url: 'http://localhost:3001', + // Multiple servers (in priority order) + // url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'], + }, }, - testnet: { - provider: 'insight', - url: 'https://test-insight.bitpay.com:443', - // url: 'http://localhost:3001', - // Multiple servers (in priority order) - // url: ['http://a.b.c', 'https://test-insight.bitpay.com:443'], + bch: { + livenet: { + provider: 'insight', + url: 'https://cashexplorer.bitcoin.com/api/', + }, + testnet: { + provider: 'insight', + url: '', + }, }, }, pushNotificationsOpts: { diff --git a/lib/blockchainexplorer.js b/lib/blockchainexplorer.js index 428b9a9..3b8bdc5 100644 --- a/lib/blockchainexplorer.js +++ b/lib/blockchainexplorer.js @@ -6,11 +6,20 @@ var log = require('npmlog'); log.debug = log.verbose; var Insight = require('./blockchainexplorers/insight'); +var Common = require('./common'); +var Constants = Common.Constants, + Defaults = Common.Defaults, + Utils = Common.Utils; var PROVIDERS = { 'insight': { - 'livenet': 'https://insight.bitpay.com:443', - 'testnet': 'https://test-insight.bitpay.com:443', + 'btc': { + 'livenet': 'https://insight.bitpay.com:443', + 'testnet': 'https://test-insight.bitpay.com:443', + }, + 'bch': { + 'livenet': 'https://insight.bitpay.com:443', + }, }, }; @@ -18,16 +27,19 @@ function BlockChainExplorer(opts) { $.checkArgument(opts); var provider = opts.provider || 'insight'; + var coin = opts.coin || Defaults.COIN; var network = opts.network || 'livenet'; $.checkState(PROVIDERS[provider], 'Provider ' + provider + ' not supported'); - $.checkState(_.contains(_.keys(PROVIDERS[provider]), network), 'Network ' + network + ' not supported by this provider'); + $.checkState(_.contains(_.keys(PROVIDERS[provider]), coin), 'Coin ' + coin + ' not supported by this provider'); + $.checkState(_.contains(_.keys(PROVIDERS[provider][coin]), network), 'Network ' + network + ' not supported by this provider for coin ' + coin); - var url = opts.url || PROVIDERS[provider][network]; + var url = opts.url || PROVIDERS[provider][coin][network]; switch (provider) { case 'insight': return new Insight({ + coin: coin, network: network, url: url, apiPrefix: opts.apiPrefix, diff --git a/lib/blockchainexplorers/insight.js b/lib/blockchainexplorers/insight.js index 059a024..1a5edb9 100644 --- a/lib/blockchainexplorers/insight.js +++ b/lib/blockchainexplorers/insight.js @@ -6,13 +6,19 @@ var log = require('npmlog'); log.debug = log.verbose; var io = require('socket.io-client'); var requestList = require('./request-list'); +var Common = require('../common'); +var Constants = Common.Constants, + Defaults = Common.Defaults, + Utils = Common.Utils; function Insight(opts) { $.checkArgument(opts); - $.checkArgument(_.contains(['livenet', 'testnet'], opts.network)); + $.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS)); + $.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS)); $.checkArgument(opts.url); this.apiPrefix = opts.apiPrefix || '/api'; + this.coin = opts.coin || Defaults.COIN; this.network = opts.network || 'livenet'; this.hosts = opts.url; this.userAgent = opts.userAgent || 'bws'; @@ -39,7 +45,7 @@ Insight.prototype._doRequest = function(args, cb) { }; Insight.prototype.getConnectionInfo = function() { - return 'Insight (' + this.network + ') @ ' + this.hosts; + return 'Insight (' + this.coin + '/' + this.network + ') @ ' + this.hosts; }; /** diff --git a/lib/blockchainmonitor.js b/lib/blockchainmonitor.js index daa2f39..b2e7ff3 100644 --- a/lib/blockchainmonitor.js +++ b/lib/blockchainmonitor.js @@ -30,21 +30,22 @@ BlockchainMonitor.prototype.start = function(opts, cb) { _.map(_.values(Constants.NETWORKS), function(network) { var explorer; if (opts.blockchainExplorers) { - explorer = opts.blockchainExplorers[network]; + explorer = opts.blockchainExplorers['btc'][network]; } else { var config = {} - if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts[network]) { - config = opts.blockchainExplorerOpts[network]; + if (opts.blockchainExplorerOpts && opts.blockchainExplorerOpts['btc'] && opts.blockchainExplorerOpts['btc'][network]) { + config = opts.blockchainExplorerOpts['btc'][network]; } var explorer = new BlockchainExplorer({ provider: config.provider, + coin: 'btc', network: network, url: config.url, userAgent: WalletService.getServiceVersion(), }); } $.checkState(explorer); - self._initExplorer(network, explorer); + self._initExplorer('btc', network, explorer); self.explorers[network] = explorer; }); done(); @@ -74,7 +75,7 @@ BlockchainMonitor.prototype.start = function(opts, cb) { }); }; -BlockchainMonitor.prototype._initExplorer = function(network, explorer) { +BlockchainMonitor.prototype._initExplorer = function(coin, network, explorer) { var self = this; var socket = explorer.initSocket(); @@ -87,7 +88,7 @@ BlockchainMonitor.prototype._initExplorer = function(network, explorer) { log.error('Error connecting to ' + explorer.getConnectionInfo()); }); socket.on('tx', _.bind(self._handleIncomingTx, self)); - socket.on('block', _.bind(self._handleNewBlock, self, network)); + socket.on('block', _.bind(self._handleNewBlock, self, coin, network)); }; BlockchainMonitor.prototype._handleThirdPartyBroadcasts = function(data, processIt) { @@ -208,7 +209,7 @@ BlockchainMonitor.prototype._handleIncomingTx = function(data) { this._handleIncomingPayments(data); }; -BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) { +BlockchainMonitor.prototype._notifyNewBlock = function(coin, network, hash) { var self = this; log.info('New ' + network + ' block: ' + hash); @@ -217,6 +218,7 @@ BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) { walletId: network, // use network name as wallet id for global notifications data: { hash: hash, + coin: coin, network: network, }, }); @@ -228,7 +230,7 @@ BlockchainMonitor.prototype._notifyNewBlock = function(network, hash) { }); }; -BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) { +BlockchainMonitor.prototype._handleTxConfirmations = function(coin, network, hash) { var self = this; function processTriggeredSubs(subs, cb) { @@ -244,6 +246,7 @@ BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) { creatorId: sub.copayerId, data: { txid: sub.txid, + coin: coin, network: network, // TODO: amount }, @@ -280,9 +283,9 @@ BlockchainMonitor.prototype._handleTxConfirmations = function(network, hash) { }); }; -BlockchainMonitor.prototype._handleNewBlock = function(network, hash) { - this._notifyNewBlock(network, hash); - this._handleTxConfirmations(network, hash); +BlockchainMonitor.prototype._handleNewBlock = function(coin, network, hash) { + this._notifyNewBlock(coin, network, hash); + this._handleTxConfirmations(coin, network, hash); }; BlockchainMonitor.prototype._storeAndBroadcastNotification = function(notification, cb) { diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index 1a4e82f..925c78b 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -22,6 +22,9 @@ function TxProposal() {}; TxProposal.create = function(opts) { opts = opts || {}; + $.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS)); + $.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS)); + var x = new TxProposal(); x.version = 3; @@ -31,6 +34,8 @@ TxProposal.create = function(opts) { x.id = opts.id || Uuid.v4(); x.walletId = opts.walletId; x.creatorId = opts.creatorId; + x.coin = opts.coin; + x.network = opts.network; x.message = opts.message; x.payProUrl = opts.payProUrl; x.changeAddress = opts.changeAddress; @@ -57,10 +62,6 @@ TxProposal.create = function(opts) { x.customData = opts.customData; x.amount = x.getTotalAmount(); - try { - x.network = opts.network || Bitcore.Address(x.outputs[0].toAddress).toObject().network; - } catch (ex) {} - $.checkState(Utils.checkValueInCollection(x.network, Constants.NETWORKS)); x.setInputs(opts.inputs); x.fee = opts.fee; @@ -80,6 +81,7 @@ TxProposal.fromObj = function(obj) { x.id = obj.id; x.walletId = obj.walletId; x.creatorId = obj.creatorId; + x.coin = obj.coin || Defaults.COIN; x.network = obj.network; x.outputs = obj.outputs; x.amount = obj.amount; @@ -221,10 +223,6 @@ TxProposal.prototype.getBitcoreTx = function() { return t; }; -TxProposal.prototype.getNetworkName = function() { - return this.network; -}; - TxProposal.prototype.getRawTx = function() { var t = this.getBitcoreTx(); diff --git a/lib/model/txproposal_legacy.js b/lib/model/txproposal_legacy.js index 697c029..c21576f 100644 --- a/lib/model/txproposal_legacy.js +++ b/lib/model/txproposal_legacy.js @@ -93,10 +93,6 @@ TxProposal.prototype.getBitcoreTx = function() { throwUnsupportedError(); }; -TxProposal.prototype.getNetworkName = function() { - return Bitcore.Address(this.changeAddress.address).toObject().network; -}; - TxProposal.prototype.getRawTx = function() { throwUnsupportedError(); }; diff --git a/lib/model/wallet.js b/lib/model/wallet.js index 768d537..b3e930c 100644 --- a/lib/model/wallet.js +++ b/lib/model/wallet.js @@ -23,8 +23,8 @@ Wallet.create = function(opts) { $.shouldBeNumber(opts.m); $.shouldBeNumber(opts.n); - $.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS)); $.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS)); + $.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS)); x.version = '1.0.0'; x.createdOn = Math.floor(Date.now() / 1000); @@ -141,10 +141,6 @@ Wallet.prototype.getCopayer = function(copayerId) { }); }; -Wallet.prototype.getNetworkName = function() { - return this.network; -}; - Wallet.prototype.isComplete = function() { return this.status == 'complete'; }; diff --git a/lib/server.js b/lib/server.js index bb800c8..29bb129 100644 --- a/lib/server.js +++ b/lib/server.js @@ -12,6 +12,10 @@ var EmailValidator = require('email-validator'); var Stringify = require('json-stable-stringify'); var Bitcore = require('bitcore-lib'); +var Bitcore_ = { + btc: Bitcore, + bch: require('bitcore-lib-cash') +}; var Common = require('./common'); var Utils = Common.Utils; @@ -158,7 +162,7 @@ WalletService.handleIncomingNotification = function(notification, cb) { if (!notification || notification.type != 'NewBlock') return cb(); - WalletService._clearBlockchainHeightCache(notification.data.network); + WalletService._clearBlockchainHeightCache(notification.data.coin, notification.data.network); return cb(); }; @@ -441,10 +445,19 @@ WalletService.prototype.getWalletFromIdentifier = function(opts, cb) { } // Is identifier a txid form an incomming tx? - async.detectSeries(_.values(Constants.NETWORKS), function(network, nextNetwork) { - var bc = self._getBlockchainExplorer(network); + var coinNetworkPairs = []; + _.each(_.values(Constants.COINS), function(coin) { + _.each(_.values(Constants.NETWORKS), function(network) { + coinNetworkPairs.push({ + coin: coin, + network: network + }); + }); + }); + async.detectSeries(coinNetworkPairs, function(coinNetwork, nextCoinNetwork) { + var bc = self._getBlockchainExplorer(coinNetwork.coin, coinNetwork.network); bc.getTransaction(opts.identifier, function(err, tx) { - if (err || !tx) return nextNetwork(err, false); + if (err || !tx) return nextCoinNetwork(err, false); var outputs = _.first(self._normalizeTxHistory(tx)).outputs; var toAddresses = _.pluck(outputs, 'address'); async.detect(toAddresses, function(addressStr, nextAddress) { @@ -454,7 +467,7 @@ WalletService.prototype.getWalletFromIdentifier = function(opts, cb) { nextAddress(null, true); }); }, function(err) { - nextNetwork(err, !!walletId); + nextCoinNetwork(err, !!walletId); }); }); }, function(err) { @@ -915,7 +928,7 @@ WalletService.prototype._canCreateAddress = function(ignoreMaxGap, cb) { hasActivity: true })) return cb(null, true); - var bc = self._getBlockchainExplorer(latestAddresses[0].network); + var bc = self._getBlockchainExplorer(latestAddresses[0].coin, latestAddresses[0].network); var activityFound = false; var i = latestAddresses.length; async.whilst(function() { @@ -1035,27 +1048,28 @@ WalletService.prototype.verifyMessageSignature = function(opts, cb) { }; -WalletService.prototype._getBlockchainExplorer = function(network) { +WalletService.prototype._getBlockchainExplorer = function(coin, network) { var opts = {}; if (this.blockchainExplorer) return this.blockchainExplorer; - if (this.blockchainExplorerOpts && this.blockchainExplorerOpts[network]) { - opts = this.blockchainExplorerOpts[network]; + if (this.blockchainExplorerOpts && this.blockchainExplorerOpts[coin] && this.blockchainExplorerOpts[coin][network]) { + opts = this.blockchainExplorerOpts[coin][network]; } // TODO: provider should be configurable opts.provider = 'insight'; + opts.coin = coin; opts.network = network; opts.userAgent = WalletService.getServiceVersion(); return new BlockchainExplorer(opts); }; -WalletService.prototype._getUtxos = function(addresses, cb) { +WalletService.prototype._getUtxos = function(coin, addresses, cb) { var self = this; if (addresses.length == 0) return cb(null, []); var networkName = Bitcore.Address(addresses[0]).toObject().network; - var bc = self._getBlockchainExplorer(networkName); + var bc = self._getBlockchainExplorer(coin, networkName); bc.getUtxos(addresses, function(err, utxos) { if (err) return cb(err); @@ -1079,10 +1093,16 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) { return utxo.txid + '|' + utxo.vout }; - var allAddresses, allUtxos, utxoIndex; + var coin, allAddresses, allUtxos, utxoIndex; async.series([ + function(next) { + self.getWallet({}, function(err, wallet) { + coin = wallet.coin; + return next(); + }); + }, function(next) { if (_.isArray(addresses)) { allAddresses = addresses; @@ -1097,7 +1117,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) { if (allAddresses.length == 0) return cb(null, []); var addressStrs = _.pluck(allAddresses, 'address'); - self._getUtxos(addressStrs, function(err, utxos) { + self._getUtxos(coin, addressStrs, function(err, utxos) { if (err) return next(err); if (utxos.length == 0) return cb(null, []); @@ -1159,6 +1179,7 @@ WalletService.prototype._getUtxosForCurrentWallet = function(addresses, cb) { /** * Returns list of UTXOs * @param {Object} opts + * @param {String} [opts.coin='btc'] (optional) * @param {Array} opts.addresses (optional) - List of addresses from where to fetch UTXOs. * @returns {Array} utxos - List of UTXOs. */ @@ -1167,10 +1188,14 @@ WalletService.prototype.getUtxos = function(opts, cb) { opts = opts || {}; + opts.coin = opts.coin || Defaults.COIN; + if (!Utils.checkValueInCollection(opts.coin, Constants.COINS)) + return cb(new ClientError('Invalid coin')); + if (_.isUndefined(opts.addresses)) { self._getUtxosForCurrentWallet(null, cb); } else { - self._getUtxos(opts.addresses, cb); + self._getUtxos(opts.coin, opts.addresses, cb); } }; @@ -1392,6 +1417,7 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) { var txp = Model.TxProposal.create({ walletId: self.walletId, + coin: wallet.coin, network: wallet.network, walletM: wallet.m, walletN: wallet.n, @@ -1442,10 +1468,10 @@ WalletService.prototype.getSendMaxInfo = function(opts, cb) { }); }; -WalletService.prototype._sampleFeeLevels = function(network, points, cb) { +WalletService.prototype._sampleFeeLevels = function(coin, network, points, cb) { var self = this; - var bc = self._getBlockchainExplorer(network); + var bc = self._getBlockchainExplorer(coin, network); bc.estimateFee(points, function(err, result) { if (err) { log.error('Error estimating fee', err); @@ -1473,6 +1499,7 @@ WalletService.prototype._sampleFeeLevels = function(network, points, cb) { /** * Returns fee levels for the current state of the network. * @param {Object} opts + * @param {string} [opts.coin = 'btc'] - The coin to estimate fee levels from. * @param {string} [opts.network = 'livenet'] - The Bitcoin network to estimate fee levels from. * @returns {Object} feeLevels - A list of fee levels & associated amount per kB in satoshi. */ @@ -1509,11 +1536,15 @@ WalletService.prototype.getFeeLevels = function(opts, cb) { return result; }; - var network = opts.network || 'livenet'; - if (network != 'livenet' && network != 'testnet') + opts.coin = opts.coin || Defaults.COIN; + if (!Utils.checkValueInCollection(opts.coin, Constants.COINS)) + return cb(new ClientError('Invalid coin')); + + opts.network = opts.network || 'livenet'; + if (!Utils.checkValueInCollection(opts.network, Constants.NETWORKS)) return cb(new ClientError('Invalid network')); - self._sampleFeeLevels(network, samplePoints(), function(err, feeSamples) { + self._sampleFeeLevels(opts.coin, opts.network, samplePoints(), function(err, feeSamples) { var values = _.map(Defaults.FEE_LEVELS, function(level) { var result = { level: level.name, @@ -1565,10 +1596,10 @@ WalletService.prototype._checkTx = function(txp) { return ex; } - if (bitcoreError instanceof Bitcore.errors.Transaction.FeeError) + if (bitcoreError instanceof Bitcore_[txp.coin].errors.Transaction.FeeError) return Errors.INSUFFICIENT_FUNDS_FOR_FEE; - if (bitcoreError instanceof Bitcore.errors.Transaction.DustOutputs) + if (bitcoreError instanceof Bitcore_[txp.coin].errors.Transaction.DustOutputs) return Errors.DUST_AMOUNT; return bitcoreError; }; @@ -1697,7 +1728,7 @@ WalletService.prototype._selectTxInputs = function(txp, utxosToExclude, cb) { var changeAmount = Math.round(total - txpAmount - fee); log.debug('Tx change: ', Utils.formatAmountInBtc(changeAmount)); - var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT); + var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore_[txp.coin].Transaction.DUST_AMOUNT); if (changeAmount > 0 && changeAmount <= dustThreshold) { log.debug('Change below dust threshold (' + Utils.formatAmountInBtc(dustThreshold) + '). Incrementing fee to remove change.'); // Remove dust change by incrementing fee @@ -1850,7 +1881,7 @@ WalletService.prototype._canCreateTx = function(cb) { }; WalletService.prototype._validateOutputs = function(opts, wallet, cb) { - var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore.Transaction.DUST_AMOUNT); + var dustThreshold = Math.max(Defaults.MIN_OUTPUT_AMOUNT, Bitcore_[wallet.coin].Transaction.DUST_AMOUNT); if (_.isEmpty(opts.outputs)) return new ClientError('No outputs were specified'); @@ -1868,7 +1899,7 @@ WalletService.prototype._validateOutputs = function(opts, wallet, cb) { } catch (ex) { return Errors.INVALID_ADDRESS; } - if (toAddress.network != wallet.getNetworkName()) { + if (toAddress.network != wallet.network) { return Errors.INCORRECT_ADDRESS_NETWORK; } @@ -1957,6 +1988,7 @@ WalletService.prototype._getFeePerKb = function(wallet, opts, cb) { if (_.isNumber(opts.feePerKb)) return cb(null, opts.feePerKb); self.getFeeLevels({ + coin: wallet.coin, network: wallet.network }, function(err, levels) { if (err) return cb(err); @@ -2066,6 +2098,8 @@ WalletService.prototype.createTx = function(opts, cb) { id: opts.txProposalId, walletId: self.walletId, creatorId: self.copayerId, + coin: wallet.coin, + network: wallet.network, outputs: opts.outputs, message: opts.message, changeAddress: changeAddress, @@ -2330,8 +2364,8 @@ WalletService.prototype.removePendingTx = function(opts, cb) { }); }; -WalletService.prototype._broadcastRawTx = function(network, raw, cb) { - var bc = this._getBlockchainExplorer(network); +WalletService.prototype._broadcastRawTx = function(coin, network, raw, cb) { + var bc = this._getBlockchainExplorer(coin, network); bc.broadcast(raw, function(err, txid) { if (err) return cb(err); return cb(null, txid); @@ -2341,6 +2375,7 @@ WalletService.prototype._broadcastRawTx = function(network, raw, cb) { /** * Broadcast a raw transaction. * @param {Object} opts + * @param {string} [opts.coin = 'btc'] - The coin for this transaction. * @param {string} [opts.network = 'livenet'] - The Bitcoin network for this transaction. * @param {string} opts.rawTx - Raw tx data. */ @@ -2349,17 +2384,21 @@ WalletService.prototype.broadcastRawTx = function(opts, cb) { if (!checkRequired(opts, ['network', 'rawTx'], cb)) return; - var network = opts.network || 'livenet'; - if (network != 'livenet' && network != 'testnet') + opts.coin = opts.coin || Defaults.COIN; + if (!Utils.checkValueInCollection(opts.coin, Constants.COINS)) + return cb(new ClientError('Invalid coin')); + + opts.network = opts.network || 'livenet'; + if (!Utils.checkValueInCollection(opts.network, Constants.NETWORKS)) return cb(new ClientError('Invalid network')); - self._broadcastRawTx(network, opts.rawTx, cb); + self._broadcastRawTx(opts.coin, opts.network, opts.rawTx, cb); }; WalletService.prototype._checkTxInBlockchain = function(txp, cb) { if (!txp.txid) return cb(); - var bc = this._getBlockchainExplorer(txp.getNetworkName()); + var bc = this._getBlockchainExplorer(txp.coin, txp.network); bc.getTransaction(txp.txid, function(err, tx) { if (err) return cb(err); return cb(null, !!tx); @@ -2487,7 +2526,7 @@ WalletService.prototype.broadcastTx = function(opts, cb) { } catch (ex) { return cb(ex); } - self._broadcastRawTx(txp.getNetworkName(), raw, function(err, txid) { + self._broadcastRawTx(wallet.coin, wallet.network, raw, function(err, txid) { if (err) { var broadcastErr = err; // Check if tx already in blockchain @@ -2693,29 +2732,35 @@ WalletService._cachedBlockheight; WalletService._initBlockchainHeightCache = function() { if (WalletService._cachedBlockheight) return; WalletService._cachedBlockheight = { - livenet: {}, - testnet: {} + btc: { + livenet: {}, + testnet: {} + }, + bch: { + livenet: {}, + testnet: {} + }, }; }; -WalletService._clearBlockchainHeightCache = function(network) { +WalletService._clearBlockchainHeightCache = function(coin, network) { WalletService._initBlockchainHeightCache(); if (!Utils.checkValueInCollection(network, Constants.NETWORKS)) { - log.error('Incorrect network in new block: ' + network); + log.error('Incorrect network in new block: ' + coin + '/' + network); return; } - WalletService._cachedBlockheight[network].current = null; + WalletService._cachedBlockheight[coin][network].current = null; }; -WalletService.prototype._getBlockchainHeight = function(network, cb) { +WalletService.prototype._getBlockchainHeight = function(coin, network, cb) { var self = this; var now = Date.now(); WalletService._initBlockchainHeightCache(); - var cache = WalletService._cachedBlockheight[network]; + var cache = WalletService._cachedBlockheight[coin][network]; function fetchFromBlockchain(cb) { - var bc = self._getBlockchainExplorer(network); + var bc = self._getBlockchainExplorer(coin, network); bc.getBlockchainHeight(function(err, height) { if (!err && height > 0) { cache.current = height; @@ -2883,10 +2928,9 @@ WalletService.prototype.getTxHistory = function(opts, cb) { }); }; - function getNormalizedTxs(addresses, from, to, cb) { + function getNormalizedTxs(wallet, addresses, from, to, cb) { var txs, fromCache, totalItems; var useCache = addresses.length >= Defaults.HISTORY_CACHE_ADDRESS_THRESOLD; - var network = Bitcore.Address(addresses[0].address).toObject().network; async.series([ @@ -2907,7 +2951,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { if (txs) return next(); var addressStrs = _.pluck(addresses, 'address'); - var bc = self._getBlockchainExplorer(network); + var bc = self._getBlockchainExplorer(wallet.coin, wallet.network); bc.getTransactions(addressStrs, from, to, function(err, rawTxs, total) { if (err) return next(err); @@ -2934,7 +2978,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { if (!txs) return next(); // Fix tx confirmations for cached txs - self._getBlockchainHeight(network, function(err, height) { + self._getBlockchainHeight(wallet.coin, wallet.network, function(err, height) { if (err || !height) return next(err); _.each(txs, function(tx) { if (tx.blockheight >= 0) { @@ -2961,6 +3005,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { if (_.isEmpty(unconfirmed)) return cb(); self.getFeeLevels({ + coin: wallet.coin, network: wallet.network }, function(err, levels) { if (err) { @@ -2997,7 +3042,7 @@ WalletService.prototype.getTxHistory = function(opts, cb) { async.waterfall([ function(next) { - getNormalizedTxs(addresses, from, to, next); + getNormalizedTxs(wallet, addresses, from, to, next); }, function(txs, next) { // Fetch all proposals in [t - 7 days, t + 1 day] @@ -3056,12 +3101,12 @@ WalletService.prototype.scan = function(opts, cb) { opts = opts || {}; - function checkActivity(address, network, cb) { - var bc = self._getBlockchainExplorer(network); + function checkActivity(wallet, address, cb) { + var bc = self._getBlockchainExplorer(wallet.coin, wallet.network); bc.getAddressActivity(address, cb); }; - function scanBranch(derivator, cb) { + function scanBranch(wallet, derivator, cb) { var inactiveCounter = 0; var allAddresses = []; var gap = Defaults.SCAN_ADDRESS_GAP; @@ -3070,7 +3115,7 @@ WalletService.prototype.scan = function(opts, cb) { return inactiveCounter < gap; }, function(next) { var address = derivator.derive(); - checkActivity(address.address, address.network, function(err, activity) { + checkActivity(wallet, address.address, function(err, activity) { if (err) return next(err); allAddresses.push(address); @@ -3114,7 +3159,7 @@ WalletService.prototype.scan = function(opts, cb) { }); async.eachSeries(derivators, function(derivator, next) { - scanBranch(derivator, function(err, addresses) { + scanBranch(wallet, derivator, function(err, addresses) { if (err) return next(err); self.storage.storeAddressAndWallet(wallet, addresses, next); }); diff --git a/test/integration/bcmonitor.js b/test/integration/bcmonitor.js index 37020e4..db2c0e4 100644 --- a/test/integration/bcmonitor.js +++ b/test/integration/bcmonitor.js @@ -49,8 +49,10 @@ describe('Blockchain monitor', function() { messageBroker: server.messageBroker, storage: storage, blockchainExplorers: { - 'testnet': blockchainExplorer, - 'livenet': blockchainExplorer + 'btc': { + 'testnet': blockchainExplorer, + 'livenet': blockchainExplorer + } }, }, function(err) { should.not.exist(err); diff --git a/test/integration/server.js b/test/integration/server.js index a930e3c..bf84d8f 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -6636,6 +6636,7 @@ describe('Wallet service', function() { blockchainExplorer.getBlockchainHeight = sinon.stub().callsArgWith(0, null, 2000); server._notify('NewBlock', { + coin: 'btc', network: 'livenet', hash: 'dummy hash', }, { diff --git a/test/model/txproposal.js b/test/model/txproposal.js index 6a8e35a..98e858c 100644 --- a/test/model/txproposal.js +++ b/test/model/txproposal.js @@ -24,6 +24,11 @@ describe('TxProposal', function() { should.exist(txp); txp.amount.should.equal(aTXP().amount); }); + it('should default to BTC coin', function() { + var txp = TxProposal.fromObj(aTXP()); + should.exist(txp); + txp.coin.should.equal('btc'); + }); }); describe('#getBitcoreTx', function() { @@ -108,8 +113,10 @@ var theXPub = 'xpub661MyMwAqRbcFLRkhYzK8eQdoywNHJVsJCMQNDoMks5bZymuMcyDgYfnVQYq2 var theSignatures = ['304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6']; var theRawTx = '0100000001ab069f7073be9b491bb1ad4233a45d2e383082ccc7206df905662d6d8499e66e08000000910047304402201d210f731fa8cb8473ce49554382ad5d950c963d48b173a0591f13ed8cee10ce022027b30dc3a55c46b1f977a72491d338fc14b6d13a7b1a7c5a35950d8543c1ced6014752210319008ffe1b3e208f5ebed8f46495c056763f87b07930a7027a92ee477fb0cb0f2103b5f035af8be40d0db5abb306b7754949ab39032cf99ad177691753b37d10130152aeffffffff0380969800000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac002d3101000000001976a91451224bca38efcaa31d5340917c3f3f713b8b20e488ac70f62b040000000017a914778192003f0e9e1d865c082179cc3dae5464b03d8700000000'; -var aTxpOpts = function(type) { +var aTxpOpts = function() { var opts = { + coin: 'btc', + network: 'livenet', message: 'some message' }; opts.outputs = [{ @@ -125,7 +132,7 @@ var aTxpOpts = function(type) { return opts; }; -var aTXP = function(type) { +var aTXP = function() { var txp = { "version": 3, "createdOn": 1423146231, diff --git a/test/storage.js b/test/storage.js index 6500f2b..f92efde 100644 --- a/test/storage.js +++ b/test/storage.js @@ -150,6 +150,8 @@ describe('Storage', function() { proposals = _.map(_.range(4), function(i) { var tx = Model.TxProposal.create({ walletId: '123', + coin: 'btc', + network: 'livenet', outputs: [{ toAddress: '18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', amount: i + 100,