From bd9cd4f0e9b768777cf8d4dc77c714cd7a85caeb Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 14 Feb 2016 19:50:54 +0200 Subject: [PATCH 01/34] .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e2f17926..81235ece 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ options.mine.js .vagrant raqia.json +scratch/ From 593bcd6ed0f995767e4cb9d1db78260d7c9e4137 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 17 Mar 2016 02:01:13 +0200 Subject: [PATCH 02/34] add local server and reboot functionality --- bin/ssu | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/app.js | 14 +++++++-- lib/routes.js | 36 ++++++++++++++++++++-- 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100755 bin/ssu diff --git a/bin/ssu b/bin/ssu new file mode 100755 index 00000000..6d7b4a50 --- /dev/null +++ b/bin/ssu @@ -0,0 +1,83 @@ +#!/usr/bin/env node + +'use strict' + +var wreck = require('wreck') +var argv = process.argv.slice(2) + +var cmd = argv[0] +var fingerprint = argv[1] + +if (!cmd || !fingerprint) { + console.log('Command line utility for lamassu-server') + console.log('\nUsage: ssu reboot ') + console.log('This will remotely reboot your lamassu-machine.') + process.exit(1) +} + +var opts = {json: true} +wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err, res, payload) { + if (err) { + console.log('Please make sure that lamassu-server is running on this box.') + process.exit(2) + } + + if (!payload.pid) { + console.log('The requested lamassu-machine is not connected.') + process.exit(3) + } + + var pid = payload.pid + + if (Date.now() - payload.ts > 10000) { + console.log('lamassu-machine is not connected to server.') + process.exit(6) + } + + var opts2 = { + headers: {'Content-Type': 'application/json'}, + payload: JSON.stringify({pid: pid, fingerprint: fingerprint}) + } + + wreck.post('http://localhost:7070/reboot', opts2, function (err2, res) { + if (err2) { + console.log('Please make sure that lamassu-server is running on this box.') + process.exit(2) + } + + if (res.statusCode !== 200) { + console.log('Communication error') + return + } + + console.log('Rebooting...') + + var ts = null + + setTimeout(function () { + if (Date.now() - ts < 10000) { + console.log('lamassu-machine did not reboot but is still contacting server.') + process.exit(4) + } + + console.log('lamassu-machine rebooted but is not coming back up.') + process.exit(5) + }, 30000) + + setInterval(function () { + wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err3, res, payload) { + if (err3) { + console.log('lamassu-server appears to be down.') + process.exit(2) + } + + ts = payload.ts + + if (payload.pid !== pid) { + console.log('lamassu-machine is back up!') + process.exit(0) + } + }) + }, 5000) + }) +}) diff --git a/lib/app.js b/lib/app.js index 709f78d2..6488da6c 100644 --- a/lib/app.js +++ b/lib/app.js @@ -54,9 +54,9 @@ module.exports = function(options) { } plugins.configure(config); next(); - }); + }); }; - + var authMiddleware; if (options.https) { @@ -97,8 +97,14 @@ module.exports = function(options) { if (options.mock) logger.info('In mock mode'); + var localApp = express() + localApp.use(express.bodyParser()) + var localServer = http.createServer(localApp) + var localPort = 7070 + routes.init({ app: app, + localApp: localApp, lamassuConfig: lamassuConfig, plugins: plugins, authMiddleware: authMiddleware, @@ -106,5 +112,9 @@ module.exports = function(options) { mock: options.mock }); + localServer.listen(7070, function () { + console.log('lamassu-server is listening on local port %d', localPort) + }) + return server; }; diff --git a/lib/routes.js b/lib/routes.js index 1647cbd2..e2cf2f11 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -17,11 +17,18 @@ module.exports = { var STALE_TICKER = 180000; var STALE_BALANCE = 180000; +var pids = {} +var reboots = {} + function poll(req, res) { var rateRec = plugins.getDeviceRate(); var balanceRec = plugins.getBalance(); + var fingerprint = getFingerprint(req) + var pid = req.query.pid - logger.debug('poll request from: %s', getFingerprint(req)); + if (pid) pids[fingerprint] = {pid: pid, ts: Date.now()} + + logger.debug('poll request from: %s', fingerprint); // `rateRec` and `balanceRec` are both objects, so there's no danger // of misinterpreting rate or balance === 0 as 'Server initializing'. @@ -59,6 +66,9 @@ function poll(req, res) { plugins.pollQueries(session(req), function(err, results) { if (err) return logger.error(err); var cartridges = results.cartridges; + + var reboot = reboots[fingerprint] === pid + var response = { err: null, rate: rate * settings.commission, @@ -70,7 +80,8 @@ function poll(req, res) { cartridges: cartridges, twoWayMode: cartridges ? true : false, zeroConfLimit: settings.zeroConfLimit, - fiatTxLimit: settings.fiatTxLimit + fiatTxLimit: settings.fiatTxLimit, + reboot: reboot }; if (response.idVerificationEnabled) @@ -185,6 +196,7 @@ function init(localConfig) { var authMiddleware = localConfig.authMiddleware; var reloadConfigMiddleware = localConfig.reloadConfigMiddleware; var app = localConfig.app; + var localApp = localConfig.localApp app.get('/poll', authMiddleware, reloadConfigMiddleware, poll); @@ -200,6 +212,26 @@ function init(localConfig) { app.post('/pair', pair); app.get('/raqia', raqia); + localApp.get('/pid', function (req, res) { + var machineFingerprint = req.query.fingerprint + var pidRec = pids[machineFingerprint] + res.json(pidRec) + }) + + localApp.post('/reboot', function (req, res) { + console.log('DEBUG1') + var pid = req.body.pid + var fingerprint = req.body.fingerprint + console.log('pid: %s, fingerprint: %s', pid, fingerprint) + + if (!fingerprint || !pid) { + return res.send(400) + } + + reboots[fingerprint] = pid + res.send(200) + }) + return app; } From e3e091b873d556c1405a3d396b25d109f115e265 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 17 Mar 2016 17:11:35 +0200 Subject: [PATCH 03/34] fix old version bug --- bin/ssu | 4 ++-- lib/routes.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/ssu b/bin/ssu index 6d7b4a50..b035b82c 100755 --- a/bin/ssu +++ b/bin/ssu @@ -22,8 +22,8 @@ wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function process.exit(2) } - if (!payload.pid) { - console.log('The requested lamassu-machine is not connected.') + if (!payload || !payload.pid) { + console.log('The requested lamassu-machine appears to be running an old version.') process.exit(3) } diff --git a/lib/routes.js b/lib/routes.js index e2cf2f11..4bcc5939 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -26,7 +26,7 @@ function poll(req, res) { var fingerprint = getFingerprint(req) var pid = req.query.pid - if (pid) pids[fingerprint] = {pid: pid, ts: Date.now()} + pids[fingerprint] = {pid: pid, ts: Date.now()} logger.debug('poll request from: %s', fingerprint); From 1c1ed95c48ec02756f93aa5cdbe8147e0a7b7ea6 Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 17 Mar 2016 17:48:10 +0000 Subject: [PATCH 04/34] Add back wallet balance and ticker rate logging --- lib/plugins.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/plugins.js b/lib/plugins.js index 0867c43c..492ea1ab 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -409,7 +409,8 @@ function stopTrader() { } function pollBalance(cb) { - + logger.debug('collecting balance'); + var jobs = { transferBalance: walletPlugin.balance }; @@ -426,6 +427,7 @@ function pollBalance(cb) { return cb && cb(err); } + logger.debug('Balance update:', balance); balance.timestamp = Date.now(); lastBalances = balance; @@ -434,12 +436,15 @@ function pollBalance(cb) { } function pollRate(cb) { + logger.debug('polling for rates (%s)', tickerPlugin.NAME); + tickerPlugin.ticker(deviceCurrency, function(err, resRates) { if (err) { logger.error(err); return cb && cb(err); } + logger.debug('got rates: %j', resRates); resRates.timestamp = new Date(); lastRates = resRates; From 79b776ad80d9c209ac67984b3d96d886c721195f Mon Sep 17 00:00:00 2001 From: Neal Conner Date: Thu, 17 Mar 2016 18:23:40 +0000 Subject: [PATCH 05/34] 2.2.11, add snapcard, ssu bin, bump bitgo --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 17d10ec4..16525db2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "2.2.10", + "version": "2.2.11", "license": "unlicense", "author": "Lamassu (https://lamassu.is)", "engines": { @@ -27,6 +27,7 @@ "lamassu-coinapult": "^0.4.5", "lamassu-coinfloor": "^0.1.2", "lamassu-bitgo": "^0.1.3", + "lamassu-snapcard": "^0.1.7", "lodash": "^2.4.1", "minimist": "0.0.8", "node-uuid": "^1.4.2", @@ -39,7 +40,8 @@ }, "bin": { "lamassu-server": "./bin/lamassu-server", - "ssu-raqia": "./bin/ssu-raqia" + "ssu-raqia": "./bin/ssu-raqia", + "ssu": "./bin/ssu" }, "scripts": { "test": "mocha --recursive test" From 496548d8a98071a6701d94c7d44c864d6ff724df Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 18 Mar 2016 11:33:33 +0200 Subject: [PATCH 06/34] fix tests --- test/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins.js b/test/plugins.js index bf8aeaa4..687941d4 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -125,7 +125,7 @@ describe('Plugins', function() { }); it('should return config', function() { - var config = plugins.getCachedConfig(); + var config = plugins.getConfig(); should.exist(config); /* jshint expr: true */ config.should.be.an.Object; From 668ba7d08cb67b2a260f39b01018b091139b7805 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 18 Mar 2016 11:41:26 +0200 Subject: [PATCH 07/34] 2.2.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16525db2..3fc9f4f5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "2.2.11", + "version": "2.2.12", "license": "unlicense", "author": "Lamassu (https://lamassu.is)", "engines": { From c8ba96515fa2ed72d46c921b5bb5771626b3eb93 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 27 Mar 2016 16:57:18 +0200 Subject: [PATCH 08/34] WIP --- lib/plugins.js | 188 +++++++++++++++++++++++++++---------------------- 1 file changed, 103 insertions(+), 85 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 492ea1ab..84da6173 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -18,9 +18,9 @@ var DEPOSIT_TIMEOUT = 130 * 1000; var db = null; -var tickerPlugin = null; +var tickerPlugins = {}; var traderPlugin = null; -var walletPlugin = null; +var walletPlugins = {}; var idVerifierPlugin = null; var infoPlugin = null; @@ -29,14 +29,9 @@ var currentlyUsedPlugins = {}; var cachedConfig = null; var deviceCurrency = 'USD'; -var lastBalances = null; +var lastBalances = {}; var lastRates = {}; -var balanceInterval = null; -var rateInterval = null; -var tradeInterval = null; -var reapTxInterval = null; - var tradesQueue = []; // that's basically a constructor @@ -104,10 +99,15 @@ function loadPlugin(name, config) { return plugin; } -function loadOrConfigPlugin(pluginHandle, pluginType, currency, +function loadOrConfigPlugin(pluginHandle, pluginType, cryptoCoin, currency, onChangeCallback) { - var currentName = cachedConfig.exchanges.plugins.current[pluginType]; - var pluginChanged = currentlyUsedPlugins[pluginType] !== currentName; + + if (!cryptoCoin) cryptoCoin = 'any' + var currentName = cryptoCoin === 'any' || cryptoCoin === 'BTC' + ? cachedConfig.exchanges.plugins.current[pluginType] + : cachedConfig.exchanges.plugins.current[cryptoCoin][pluginType] + + var pluginChanged = currentlyUsedPlugins[cryptoCoin][pluginType] !== currentName; if (!currentName) pluginHandle = null; else { // some plugins may be disabled @@ -119,9 +119,10 @@ function loadOrConfigPlugin(pluginHandle, pluginType, currency, if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig); else { pluginHandle = loadPlugin(currentName, pluginConfig); - currentlyUsedPlugins[pluginType] = currentName - logger.debug('plugin(%s) loaded: %s', pluginType, pluginHandle.NAME || - currentName); + currentlyUsedPlugins[cryptoCoin] ||= {} + currentlyUsedPlugins[cryptoCoin][pluginType] = currentName + logger.debug('[%s] plugin(%s) loaded: %s', cryptoCoin, pluginType, pluginHandle.NAME || + currentName); } } @@ -138,34 +139,40 @@ exports.configure = function configure(config) { cachedConfig = config; deviceCurrency = config.exchanges.settings.currency; + cryptoCoins = config.exchanges.settings.coins || ['BTC']; - // TICKER [required] configure (or load) - loadOrConfigPlugin( - tickerPlugin, - 'ticker', - deviceCurrency, // device currency - function onTickerChange(newTicker) { - tickerPlugin = newTicker; - pollRate(); - } - ); + cryptoCoins.forEach(function (cryptoCoin) { + // TICKER [required] configure (or load) + loadOrConfigPlugin( + tickerPlugins[cryptoCoin], + 'ticker', + cryptoCoin, + deviceCurrency, // device currency + function onTickerChange(newTicker) { + tickerPlugins[cryptoCoin] = newTicker; + pollRate(cryptoCoin); + } + ); - // WALLET [required] configure (or load) - loadOrConfigPlugin( - walletPlugin, - 'transfer', - null, - function onWalletChange(newWallet) { - walletPlugin = newWallet; - pollBalance(); - } - ); + // WALLET [required] configure (or load) + loadOrConfigPlugin( + walletPlugins[cryptoCoin], + 'transfer', + cryptoCoin, + null, + function onWalletChange(newWallet) { + walletPlugins[cryptoCoin] = newWallet; + pollBalance(cryptoCoin); + } + ); + }) // TRADER [optional] configure (or load) traderPlugin = loadOrConfigPlugin( traderPlugin, 'trade', null, + null, function onTraderChange(newTrader) { traderPlugin = newTrader; if (newTrader === null) stopTrader(); @@ -222,26 +229,35 @@ exports.pollQueries = function pollQueries(session, cb) { }); }; -function _sendBitcoins(toAddress, satoshis, cb) { +function _sendCoins(toAddress, cryptoUnits, cryptoCoin, cb) { + var walletPlugin = walletPlugins[cryptoCoin] var transactionFee = cachedConfig.exchanges.settings.transactionFee; - walletPlugin.sendBitcoins(toAddress, satoshis, transactionFee, cb); + if (cryptoCoin === 'BTC') + walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb); + else + walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb); } function executeTx(session, tx, authority, cb) { db.addOutgoingTx(session, tx, function(err, toSend) { if (err) return cb(err); - var satoshisToSend = toSend.satoshis; - if (satoshisToSend === 0) + var cryptoUnitsToSend = toSend.cryptoUnits; + if (cryptoUnitsToSend === 0) return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}); - _sendBitcoins(tx.toAddress, satoshisToSend, function(_err, txHash) { + _sendCoins(tx.toAddress, cryptoUnitsToSend, function(_err, txHash) { var fee = null; // Need to fill this out in plugins - if (_err) toSend = {satoshis: 0, fiat: 0}; + if (_err) toSend = {cryptoUnits: new BigNumber(0), fiat: 0}; db.sentCoins(session, tx, authority, toSend, fee, _err, txHash); if (_err) return cb(_err); - pollBalance(); + var cryptoCoin = tx.coin + ? tx.coin.unitCode + : 'BTC' + + pollBalance('BTC'); + cb(null, { statusCode: 201, // Created txHash: txHash, @@ -326,6 +342,13 @@ exports.cashOut = function cashOut(session, tx, cb) { label: 'TX ' + Date.now(), account: 'deposit' }; + + var cryptoCoin = tx.coin + ? tx.coin.unitCode + : 'BTC' + + var walletPlugin = walletPlugins[cryptoCoin] + walletPlugin.newAddress(tmpInfo, function(err, address) { if (err) return cb(err); @@ -341,11 +364,12 @@ exports.dispenseAck = function dispenseAck(session, rec) { db.addDispense(session, rec.tx, rec.cartridges); }; -exports.fiatBalance = function fiatBalance() { - var rawRate = exports.getDeviceRate().rates.ask; +exports.fiatBalance = function fiatBalance(cryptoCoin) { + var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask; var commission = cachedConfig.exchanges.settings.commission; + var lastBalance = lastBalances[cryptoCoin] - if (!rawRate || !lastBalances) return null; + if (!rawRate || !lastBalance) return null; // The rate is actually our commission times real rate. var rate = commission * rawRate; @@ -356,16 +380,9 @@ exports.fiatBalance = function fiatBalance() { // `balance.transferBalance` is the balance of our transfer account (the one // we use to send Bitcoins to clients) in satoshis. - var transferBalance = lastBalances.transferBalance.BTC; + var transferBalance = lastBalances.transferBalance; - // Since `transferBalance` is in satoshis, we need to turn it into - // bitcoins and then fiat to learn how much fiat currency we can exchange. - // - // Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ] - // [ $ ] = [ B * $/B ] - // [ $ ] = [ $ ] - var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) / - lowBalanceMargin; + var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin; return fiatTransferBalance; }; @@ -376,14 +393,12 @@ exports.fiatBalance = function fiatBalance() { exports.startPolling = function startPolling() { executeTrades(); - if (!balanceInterval) - balanceInterval = setInterval(pollBalance, POLLING_RATE); + cryptoCoins.forEach(function (coin) { + setInterval(async.apply(pollBalance, coin), POLLING_RATE); + setInterval(async.apply(pollRate, coin), POLLING_RATE); + }); - if (!rateInterval) - rateInterval = setInterval(pollRate, POLLING_RATE); - - if (!reapTxInterval) - reapTxInterval = setInterval(reapTxs, REAP_RATE); + setInterval(reapTxs, REAP_RATE); startTrader(); }; @@ -400,6 +415,7 @@ function startTrader() { ); } } + function stopTrader() { if (tradeInterval) { clearInterval(tradeInterval); @@ -408,36 +424,40 @@ function stopTrader() { } } -function pollBalance(cb) { - logger.debug('collecting balance'); - +function pollBalance(cryptoCoin, cb) { + logger.debug('[%s] collecting balance', cryptoCoin); + + var walletPlugin = walletPlugins[cryptoCoin] var jobs = { transferBalance: walletPlugin.balance }; - // only add if trader is enabled - // if (traderPlugin) { - // // NOTE: we would need to handle when traderCurrency!=deviceCurrency - // jobs.tradeBalance = traderPlugin.balance; - // } - - async.parallel(jobs, function(err, balance) { + walletPlugin.balance(function(err, balance) { if (err) { logger.error(err); return cb && cb(err); } - logger.debug('Balance update:', balance); + logger.debug('[%s] Balance update:', cryptoCoin, balance); balance.timestamp = Date.now(); - lastBalances = balance; + lastBalances[cryptoCoin] = balance; return cb && cb(null, lastBalances); }); } -function pollRate(cb) { - logger.debug('polling for rates (%s)', tickerPlugin.NAME); - +function pollRates (cb) { + var polls = cryptoCoins.map(function (cryptoCoin) { + async.apply(pollRate, cryptoCoin) + }); + + async.parallel(polls, cb); +} + +function pollRate(cryptoCoin, cb) { + logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME); + var tickerPlugin = tickerPlugins[cryptoCoin]; + tickerPlugin.ticker(deviceCurrency, function(err, resRates) { if (err) { logger.error(err); @@ -446,7 +466,7 @@ function pollRate(cb) { logger.debug('got rates: %j', resRates); resRates.timestamp = new Date(); - lastRates = resRates; + lastRates[cryptoCoin] = resRates; return cb && cb(null, lastRates); }); @@ -455,16 +475,14 @@ function pollRate(cb) { /* * Getters | Helpers */ -function getLastRate(currency) { - if (!lastRates) return null; - var tmpCurrency = currency || deviceCurrency; - if (!lastRates[tmpCurrency]) return null; +exports.getDeviceRate = function getDeviceRate(cryptoCoin) { + if (!lastRates[cryptoCoin]) return null; - return lastRates[tmpCurrency]; -} -exports.getDeviceRate = function getDeviceRate() { - return getLastRate(deviceCurrency); + var lastRate = lastRates[cryptoCoin] + if (!lastRate) return null; + + return lastRate[deviceCurrency]; }; exports.getBalance = function getBalance() { @@ -479,7 +497,7 @@ exports.getBalance = function getBalance() { function purchase(trade, cb) { traderPlugin.purchase(trade.satoshis, null, function(err) { if (err) return cb(err); - pollBalance(); + pollBalance('BTC'); if (typeof cb === 'function') cb(); }); } From ed1376fc6f41103f1e686c0aceec9245c79121b1 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 27 Mar 2016 18:13:09 +0200 Subject: [PATCH 09/34] WIP --- lib/plugins.js | 17 ++++++++++++----- lib/routes.js | 44 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 84da6173..cdc3b0e3 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -18,6 +18,8 @@ var DEPOSIT_TIMEOUT = 130 * 1000; var db = null; +var cryptoCoins = null; + var tickerPlugins = {}; var traderPlugin = null; var walletPlugins = {}; @@ -333,7 +335,7 @@ exports.trade = function trade(session, rawTrade, cb) { ], cb); }; -exports.sendBitcoins = function sendBitcoins(session, rawTx, cb) { +exports.sendCoins = function sendCoins(session, rawTx, cb) { executeTx(session, rawTx, 'machine', cb); }; @@ -380,7 +382,7 @@ exports.fiatBalance = function fiatBalance(cryptoCoin) { // `balance.transferBalance` is the balance of our transfer account (the one // we use to send Bitcoins to clients) in satoshis. - var transferBalance = lastBalances.transferBalance; + var transferBalance = lastBalance.transferBalance; var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin; @@ -485,10 +487,11 @@ exports.getDeviceRate = function getDeviceRate(cryptoCoin) { return lastRate[deviceCurrency]; }; -exports.getBalance = function getBalance() { - if (!lastBalances) return null; +exports.getBalance = function getBalance(cryptoCoin) { + var lastBalance = lastBalances[cryptoCoin] + if (!lastBalance) return null; - return lastBalances.transferBalance; + return lastBalance.transferBalance; }; /* @@ -548,3 +551,7 @@ exports.verifyUser = function verifyUser(data, cb) { exports.verifyTx = function verifyTx(data, cb) { idVerifierPlugin.verifyTransaction(data, cb); }; + +exports.getCryptoCoins = function getCryptoCoins () { + return cryptoCoins; +} diff --git a/lib/routes.js b/lib/routes.js index 4bcc5939..f937a609 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -20,9 +20,37 @@ var STALE_BALANCE = 180000; var pids = {} var reboots = {} +function buildRates () { + var cryptoCoins = plugins.getCryptoCoins(); + var cashInCommission = settings.commission; + var cashOutCommission = settings.fiatCommission || cashInCommission; + + var rates = {} + cryptoCoins.forEach(function (coin) { + var rate = plugins.getDeviceRate(coin).rates + rates[coin] = { + cashIn: rate.ask.times(cashInCommission), + cashOut: rate.bid.div(cashOutCommission) + } + }) + + return rates +} + +function buildBalances () { + var cryptoCoins = plugins.getCryptoCoins(); + + var balances = {} + cryptoCoins.forEach(function (coin) { + var balance = plugins.fiatBalance(coin) + balances[coin] = balance + }) + + return balance + +} + function poll(req, res) { - var rateRec = plugins.getDeviceRate(); - var balanceRec = plugins.getBalance(); var fingerprint = getFingerprint(req) var pid = req.query.pid @@ -30,6 +58,12 @@ function poll(req, res) { logger.debug('poll request from: %s', fingerprint); + var rateRec = plugins.getDeviceRate(); + var balanceRec = plugins.getBalance(); + + var rates = buildRates() + var balances = buildBalances() + // `rateRec` and `balanceRec` are both objects, so there's no danger // of misinterpreting rate or balance === 0 as 'Server initializing'. if (!rateRec || !balanceRec) { @@ -81,7 +115,9 @@ function poll(req, res) { twoWayMode: cartridges ? true : false, zeroConfLimit: settings.zeroConfLimit, fiatTxLimit: settings.fiatTxLimit, - reboot: reboot + reboot: reboot, + rates: rates, + balances: balances }; if (response.idVerificationEnabled) @@ -99,7 +135,7 @@ function trade(req, res) { } function send(req, res) { - plugins.sendBitcoins(session(req), req.body, function(err, status) { + plugins.sendCoins(session(req), req.body, function(err, status) { // TODO: use status.statusCode here after confirming machine compatibility // FIX: (joshm) set txHash to status.txId instead of previous status.txHash which wasn't being set // Need to clean up txHash vs txId From 9f31b31af8250d2dd0278ca953ec4be593cb1cee Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 27 Mar 2016 18:33:21 +0200 Subject: [PATCH 10/34] updated some files to standard formatting --- lib/plugins.js | 534 ++++++++++++++++++++++++------------------------- lib/routes.js | 226 ++++++++++----------- 2 files changed, 375 insertions(+), 385 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index cdc3b0e3..104e14cf 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -1,52 +1,53 @@ -'use strict'; +'use strict' -var _ = require('lodash'); -var async = require('async'); -var logger = require('./logger'); +var _ = require('lodash') +var async = require('async') +var BigNumber = require('bignumber.js') +var logger = require('./logger') +var argv = require('minimist')(process.argv.slice(2)) -var argv = require('minimist')(process.argv.slice(2)); +var tradeInterval = null -var SATOSHI_FACTOR = 1e8; -var POLLING_RATE = 60 * 1000; // poll each minute -var REAP_RATE = 2 * 1000; -var PENDING_TIMEOUT = 70 * 1000; +var SATOSHI_FACTOR = 1e8 +var POLLING_RATE = 60 * 1000 // poll each minute +var REAP_RATE = 2 * 1000 +var PENDING_TIMEOUT = 70 * 1000 -if (argv.timeout) PENDING_TIMEOUT = argv.timeout / 1000; +if (argv.timeout) PENDING_TIMEOUT = argv.timeout / 1000 // TODO: might have to update this if user is allowed to extend monitoring time -var DEPOSIT_TIMEOUT = 130 * 1000; +var DEPOSIT_TIMEOUT = 130 * 1000 -var db = null; +var db = null -var cryptoCoins = null; +var cryptoCoins = null -var tickerPlugins = {}; -var traderPlugin = null; -var walletPlugins = {}; -var idVerifierPlugin = null; -var infoPlugin = null; +var tickerPlugins = {} +var traderPlugin = null +var walletPlugins = {} +var idVerifierPlugin = null +var infoPlugin = null -var currentlyUsedPlugins = {}; +var currentlyUsedPlugins = {} -var cachedConfig = null; -var deviceCurrency = 'USD'; +var cachedConfig = null +var deviceCurrency = 'USD' -var lastBalances = {}; -var lastRates = {}; +var lastBalances = {} +var lastRates = {} -var tradesQueue = []; +var tradesQueue = [] // that's basically a constructor -exports.init = function init(databaseHandle) { +exports.init = function init (databaseHandle) { if (!databaseHandle) { - throw new Error('\'db\' is required'); + throw new Error('\'db\' is required') } - db = databaseHandle; -}; - -function loadPlugin(name, config) { + db = databaseHandle +} +function loadPlugin (name, config) { // plugins definitions var moduleMethods = { ticker: ['ticker'], @@ -54,94 +55,95 @@ function loadPlugin(name, config) { wallet: ['balance', 'sendBitcoins', 'newAddress'], idVerifier: ['verifyUser', 'verifyTransaction'], info: ['checkAddress'] - }; + } - var plugin = null; + var plugin = null // each used plugin MUST be installed try { - plugin = require('lamassu-' + name); + plugin = require('lamassu-' + name) } catch (_) { throw new Error(name + ' module is not installed. ' + - 'Try running \'npm install --save lamassu-' + name + '\' first'); + 'Try running \'npm install --save lamassu-' + name + '\' first') } // each plugin MUST implement those if (typeof plugin.SUPPORTED_MODULES !== 'undefined') { - if (plugin.SUPPORTED_MODULES === 'string') - plugin.SUPPORTED_MODULES = [plugin.SUPPORTED_MODULES]; + if (plugin.SUPPORTED_MODULES === 'string') { + plugin.SUPPORTED_MODULES = [plugin.SUPPORTED_MODULES] + } } - if (!(plugin.SUPPORTED_MODULES instanceof Array)) + if (!(plugin.SUPPORTED_MODULES instanceof Array)) { throw new Error('\'' + name + '\' fails to implement *required* ' + - '\'SUPPORTED_MODULES\' constant'); + '\'SUPPORTED_MODULES\' constant') + } - plugin.SUPPORTED_MODULES.forEach(function(moduleName) { - moduleMethods[moduleName].forEach(function(methodName) { + plugin.SUPPORTED_MODULES.forEach(function (moduleName) { + moduleMethods[moduleName].forEach(function (methodName) { if (typeof plugin[methodName] !== 'function') { throw new Error('\'' + name + '\' declares \'' + moduleName + - '\', but fails to implement \'' + methodName + '\' method'); + '\', but fails to implement \'' + methodName + '\' method') } - }); - }); + }) + }) // each plugin SHOULD implement those - if (typeof plugin.NAME === 'undefined') + if (typeof plugin.NAME === 'undefined') { logger.warn(new Error('\'' + name + - '\' fails to implement *recommended* \'NAME\' field')); + '\' fails to implement *recommended* \'NAME\' field')) + } if (typeof plugin.config !== 'function') { logger.warn(new Error('\'' + name + - '\' fails to implement *recommended* \'config\' method')); - plugin.config = function() {}; + '\' fails to implement *recommended* \'config\' method')) + plugin.config = function () {} } else if (config !== null) { - plugin.config(config); // only when plugin supports it, and config is passed + plugin.config(config) // only when plugin supports it, and config is passed } - return plugin; + return plugin } -function loadOrConfigPlugin(pluginHandle, pluginType, cryptoCoin, currency, +function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCoin, currency, onChangeCallback) { - if (!cryptoCoin) cryptoCoin = 'any' var currentName = cryptoCoin === 'any' || cryptoCoin === 'BTC' ? cachedConfig.exchanges.plugins.current[pluginType] : cachedConfig.exchanges.plugins.current[cryptoCoin][pluginType] - var pluginChanged = currentlyUsedPlugins[cryptoCoin][pluginType] !== currentName; + var pluginChanged = currentlyUsedPlugins[cryptoCoin][pluginType] !== currentName - if (!currentName) pluginHandle = null; + if (!currentName) pluginHandle = null else { // some plugins may be disabled var pluginConfig = cachedConfig.exchanges.plugins.settings[currentName] || - {}; + {} - if (currency) pluginConfig.currency = currency; + if (currency) pluginConfig.currency = currency - if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig); + if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig) else { - pluginHandle = loadPlugin(currentName, pluginConfig); - currentlyUsedPlugins[cryptoCoin] ||= {} + pluginHandle = loadPlugin(currentName, pluginConfig) + currentlyUsedPlugins[cryptoCoin] = currentlyUsedPlugins[cryptoCoin] || {} currentlyUsedPlugins[cryptoCoin][pluginType] = currentName logger.debug('[%s] plugin(%s) loaded: %s', cryptoCoin, pluginType, pluginHandle.NAME || - currentName); + currentName) } } - if (typeof onChangeCallback === 'function') - onChangeCallback(pluginHandle, currency); + if (typeof onChangeCallback === 'function') onChangeCallback(pluginHandle, currency) - return pluginHandle; + return pluginHandle } -exports.configure = function configure(config) { +exports.configure = function configure (config) { if (config.exchanges.settings.lowBalanceMargin < 1) { - throw new Error('\'settings.lowBalanceMargin\' has to be >= 1'); + throw new Error('\'settings.lowBalanceMargin\' has to be >= 1') } - cachedConfig = config; - deviceCurrency = config.exchanges.settings.currency; - cryptoCoins = config.exchanges.settings.coins || ['BTC']; + cachedConfig = config + deviceCurrency = config.exchanges.settings.currency + cryptoCoins = config.exchanges.settings.coins || ['BTC'] cryptoCoins.forEach(function (cryptoCoin) { // TICKER [required] configure (or load) @@ -150,11 +152,11 @@ exports.configure = function configure(config) { 'ticker', cryptoCoin, deviceCurrency, // device currency - function onTickerChange(newTicker) { - tickerPlugins[cryptoCoin] = newTicker; - pollRate(cryptoCoin); + function onTickerChange (newTicker) { + tickerPlugins[cryptoCoin] = newTicker + pollRate(cryptoCoin) } - ); + ) // WALLET [required] configure (or load) loadOrConfigPlugin( @@ -162,11 +164,11 @@ exports.configure = function configure(config) { 'transfer', cryptoCoin, null, - function onWalletChange(newWallet) { - walletPlugins[cryptoCoin] = newWallet; - pollBalance(cryptoCoin); + function onWalletChange (newWallet) { + walletPlugins[cryptoCoin] = newWallet + pollBalance(cryptoCoin) } - ); + ) }) // TRADER [optional] configure (or load) @@ -175,33 +177,33 @@ exports.configure = function configure(config) { 'trade', null, null, - function onTraderChange(newTrader) { - traderPlugin = newTrader; - if (newTrader === null) stopTrader(); - else startTrader(); + function onTraderChange (newTrader) { + traderPlugin = newTrader + if (newTrader === null) stopTrader() + else startTrader() } - ); + ) // ID VERIFIER [optional] configure (or load) idVerifierPlugin = loadOrConfigPlugin( idVerifierPlugin, 'idVerifier' - ); + ) infoPlugin = loadOrConfigPlugin( infoPlugin, 'info' - ); -}; -exports.getConfig = function getConfig() { - return cachedConfig; -}; + ) +} +exports.getConfig = function getConfig () { + return cachedConfig +} -exports.logEvent = function event(session, rawEvent) { - db.recordDeviceEvent(session, rawEvent); -}; +exports.logEvent = function event (session, rawEvent) { + db.recordDeviceEvent(session, rawEvent) +} -function buildCartridges(cartridges, virtualCartridges, rec) { +function buildCartridges (cartridges, virtualCartridges, rec) { return { cartridges: [ { @@ -215,110 +217,107 @@ function buildCartridges(cartridges, virtualCartridges, rec) { ], virtualCartridges: virtualCartridges, id: rec.id - }; + } } -exports.pollQueries = function pollQueries(session, cb) { - var cartridges = cachedConfig.exchanges.settings.cartridges; - if (!cartridges) return cb(null, {}); - var virtualCartridges = cachedConfig.exchanges.settings.virtualCartridges; +exports.pollQueries = function pollQueries (session, cb) { + var cartridges = cachedConfig.exchanges.settings.cartridges + if (!cartridges) return cb(null, {}) + var virtualCartridges = cachedConfig.exchanges.settings.virtualCartridges - db.cartridgeCounts(session, function(err, result) { - if (err) return cb(err); + db.cartridgeCounts(session, function (err, result) { + if (err) return cb(err) return cb(null, { cartridges: buildCartridges(cartridges, virtualCartridges, result) - }); - }); -}; - -function _sendCoins(toAddress, cryptoUnits, cryptoCoin, cb) { - var walletPlugin = walletPlugins[cryptoCoin] - var transactionFee = cachedConfig.exchanges.settings.transactionFee; - if (cryptoCoin === 'BTC') - walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb); - else - walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb); + }) + }) } -function executeTx(session, tx, authority, cb) { - db.addOutgoingTx(session, tx, function(err, toSend) { - if (err) return cb(err); - var cryptoUnitsToSend = toSend.cryptoUnits; - if (cryptoUnitsToSend === 0) - return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}); +function _sendCoins (toAddress, cryptoUnits, cryptoCoin, cb) { + var walletPlugin = walletPlugins[cryptoCoin] + var transactionFee = cachedConfig.exchanges.settings.transactionFee + if (cryptoCoin === 'BTC') { + walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb) + } else { + walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb) + } +} - _sendCoins(tx.toAddress, cryptoUnitsToSend, function(_err, txHash) { - var fee = null; // Need to fill this out in plugins - if (_err) toSend = {cryptoUnits: new BigNumber(0), fiat: 0}; - db.sentCoins(session, tx, authority, toSend, fee, _err, txHash); +function executeTx (session, tx, authority, cb) { + db.addOutgoingTx(session, tx, function (err, toSend) { + if (err) return cb(err) + var cryptoUnitsToSend = toSend.cryptoUnits + if (cryptoUnitsToSend === 0) { + return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}) + } - if (_err) return cb(_err); + _sendCoins(tx.toAddress, cryptoUnitsToSend, function (_err, txHash) { + var fee = null // Need to fill this out in plugins + if (_err) toSend = {cryptoUnits: new BigNumber(0), fiat: 0} + db.sentCoins(session, tx, authority, toSend, fee, _err, txHash) - var cryptoCoin = tx.coin - ? tx.coin.unitCode - : 'BTC' + if (_err) return cb(_err) - pollBalance('BTC'); + pollBalance('BTC') cb(null, { statusCode: 201, // Created txHash: txHash, txId: tx.txId - }); - }); - }); + }) + }) + }) } -function reapOutgoingTx(session, tx) { - executeTx(session, tx, 'timeout', function(err) { - if (err) logger.error(err); - }); +function reapOutgoingTx (session, tx) { + executeTx(session, tx, 'timeout', function (err) { + if (err) logger.error(err) + }) } -function reapTx(row) { - var session = {fingerprint: row.device_fingerprint, id: row.session_id}; +function reapTx (row) { + var session = {fingerprint: row.device_fingerprint, id: row.session_id} var tx = { fiat: 0, satoshis: row.satoshis, toAddress: row.to_address, currencyCode: row.currency_code, incoming: row.incoming - }; - if (!row.incoming) reapOutgoingTx(session, tx); + } + if (!row.incoming) reapOutgoingTx(session, tx) } -function reapTxs() { - db.removeOldPending(DEPOSIT_TIMEOUT); +function reapTxs () { + db.removeOldPending(DEPOSIT_TIMEOUT) // NOTE: No harm in processing old pending tx, we don't need to wait for // removeOldPending to complete. - db.pendingTxs(PENDING_TIMEOUT, function(err, results) { - if (err) return logger.warn(err); - var rows = results.rows; - var rowCount = rows.length; + db.pendingTxs(PENDING_TIMEOUT, function (err, results) { + if (err) return logger.warn(err) + var rows = results.rows + var rowCount = rows.length for (var i = 0; i < rowCount; i++) { - var row = rows[i]; - reapTx(row); + var row = rows[i] + reapTx(row) } - }); + }) } // TODO: Run these in parallel and return success -exports.trade = function trade(session, rawTrade, cb) { - +exports.trade = function trade (session, rawTrade, cb) { // TODO: move this to DB, too // add bill to trader queue (if trader is enabled) if (traderPlugin) { tradesQueue.push({ currency: rawTrade.currency, satoshis: rawTrade.satoshis - }); + }) } if (!rawTrade.toAddress) { - var newRawTrade = _.cloneDeep(rawTrade); - newRawTrade.toAddress = 'remit'; - return db.recordBill(session, newRawTrade, cb); + var newRawTrade = _.cloneDeep(rawTrade) + newRawTrade.toAddress = 'remit' + return db.recordBill(session, newRawTrade, cb) } var tx = { @@ -327,23 +326,23 @@ exports.trade = function trade(session, rawTrade, cb) { satoshis: 0, toAddress: rawTrade.toAddress, currencyCode: rawTrade.currency - }; + } async.parallel([ async.apply(db.addOutgoingPending, session, tx.currencyCode, tx.toAddress), async.apply(db.recordBill, session, rawTrade) - ], cb); -}; + ], cb) +} -exports.sendCoins = function sendCoins(session, rawTx, cb) { - executeTx(session, rawTx, 'machine', cb); -}; +exports.sendCoins = function sendCoins (session, rawTx, cb) { + executeTx(session, rawTx, 'machine', cb) +} -exports.cashOut = function cashOut(session, tx, cb) { +exports.cashOut = function cashOut (session, tx, cb) { var tmpInfo = { label: 'TX ' + Date.now(), account: 'deposit' - }; + } var cryptoCoin = tx.coin ? tx.coin.unitCode @@ -351,61 +350,61 @@ exports.cashOut = function cashOut(session, tx, cb) { var walletPlugin = walletPlugins[cryptoCoin] - walletPlugin.newAddress(tmpInfo, function(err, address) { - if (err) return cb(err); + walletPlugin.newAddress(tmpInfo, function (err, address) { + if (err) return cb(err) - var newTx = _.clone(tx); - newTx.toAddress = address; - db.addInitialIncoming(session, newTx, function(_err) { - cb(_err, address); - }); - }); -}; + var newTx = _.clone(tx) + newTx.toAddress = address + db.addInitialIncoming(session, newTx, function (_err) { + cb(_err, address) + }) + }) +} -exports.dispenseAck = function dispenseAck(session, rec) { - db.addDispense(session, rec.tx, rec.cartridges); -}; +exports.dispenseAck = function dispenseAck (session, rec) { + db.addDispense(session, rec.tx, rec.cartridges) +} -exports.fiatBalance = function fiatBalance(cryptoCoin) { - var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask; - var commission = cachedConfig.exchanges.settings.commission; +exports.fiatBalance = function fiatBalance (cryptoCoin) { + var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask + var commission = cachedConfig.exchanges.settings.commission var lastBalance = lastBalances[cryptoCoin] - if (!rawRate || !lastBalance) return null; + if (!rawRate || !lastBalance) return null // The rate is actually our commission times real rate. - var rate = commission * rawRate; + var rate = commission * rawRate // `lowBalanceMargin` is our safety net. It's a number > 1, and we divide // all our balances by it to provide a safety margin. - var lowBalanceMargin = cachedConfig.exchanges.settings.lowBalanceMargin; + var lowBalanceMargin = cachedConfig.exchanges.settings.lowBalanceMargin // `balance.transferBalance` is the balance of our transfer account (the one // we use to send Bitcoins to clients) in satoshis. - var transferBalance = lastBalance.transferBalance; + var transferBalance = lastBalance.transferBalance - var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin; + var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin - return fiatTransferBalance; -}; + return fiatTransferBalance +} /* * Polling livecycle */ -exports.startPolling = function startPolling() { - executeTrades(); +exports.startPolling = function startPolling () { + executeTrades() cryptoCoins.forEach(function (coin) { - setInterval(async.apply(pollBalance, coin), POLLING_RATE); - setInterval(async.apply(pollRate, coin), POLLING_RATE); - }); + setInterval(async.apply(pollBalance, coin), POLLING_RATE) + setInterval(async.apply(pollRate, coin), POLLING_RATE) + }) - setInterval(reapTxs, REAP_RATE); + setInterval(reapTxs, REAP_RATE) - startTrader(); -}; + startTrader() +} -function startTrader() { +function startTrader () { // Always start trading, even if we don't have a trade exchange configured, // since configuration can always change in `Trader#configure`. // `Trader#executeTrades` returns early if we don't have a trade exchange @@ -414,144 +413,133 @@ function startTrader() { tradeInterval = setInterval( executeTrades, cachedConfig.exchanges.settings.tradeInterval - ); + ) } } -function stopTrader() { +function stopTrader () { if (tradeInterval) { - clearInterval(tradeInterval); - tradeInterval = null; - tradesQueue = []; + clearInterval(tradeInterval) + tradeInterval = null + tradesQueue = [] } } -function pollBalance(cryptoCoin, cb) { - logger.debug('[%s] collecting balance', cryptoCoin); +function pollBalance (cryptoCoin, cb) { + logger.debug('[%s] collecting balance', cryptoCoin) var walletPlugin = walletPlugins[cryptoCoin] - var jobs = { - transferBalance: walletPlugin.balance - }; - walletPlugin.balance(function(err, balance) { + walletPlugin.balance(function (err, balance) { if (err) { - logger.error(err); - return cb && cb(err); + logger.error(err) + return cb && cb(err) } - logger.debug('[%s] Balance update:', cryptoCoin, balance); - balance.timestamp = Date.now(); - lastBalances[cryptoCoin] = balance; + logger.debug('[%s] Balance update:', cryptoCoin, balance) + balance.timestamp = Date.now() + lastBalances[cryptoCoin] = balance - return cb && cb(null, lastBalances); - }); + return cb && cb(null, lastBalances) + }) } -function pollRates (cb) { - var polls = cryptoCoins.map(function (cryptoCoin) { - async.apply(pollRate, cryptoCoin) - }); +function pollRate (cryptoCoin, cb) { + logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME) + var tickerPlugin = tickerPlugins[cryptoCoin] - async.parallel(polls, cb); -} - -function pollRate(cryptoCoin, cb) { - logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME); - var tickerPlugin = tickerPlugins[cryptoCoin]; - - tickerPlugin.ticker(deviceCurrency, function(err, resRates) { + tickerPlugin.ticker(deviceCurrency, function (err, resRates) { if (err) { - logger.error(err); - return cb && cb(err); + logger.error(err) + return cb && cb(err) } - logger.debug('got rates: %j', resRates); - resRates.timestamp = new Date(); - lastRates[cryptoCoin] = resRates; + logger.debug('got rates: %j', resRates) + resRates.timestamp = new Date() + lastRates[cryptoCoin] = resRates - return cb && cb(null, lastRates); - }); + return cb && cb(null, lastRates) + }) } /* * Getters | Helpers */ -exports.getDeviceRate = function getDeviceRate(cryptoCoin) { - if (!lastRates[cryptoCoin]) return null; +exports.getDeviceRate = function getDeviceRate (cryptoCoin) { + if (!lastRates[cryptoCoin]) return null var lastRate = lastRates[cryptoCoin] - if (!lastRate) return null; + if (!lastRate) return null - return lastRate[deviceCurrency]; -}; + return lastRate[deviceCurrency] +} -exports.getBalance = function getBalance(cryptoCoin) { +exports.getBalance = function getBalance (cryptoCoin) { var lastBalance = lastBalances[cryptoCoin] - if (!lastBalance) return null; + if (!lastBalance) return null - return lastBalance.transferBalance; -}; + return lastBalance.transferBalance +} /* * Trader functions */ -function purchase(trade, cb) { - traderPlugin.purchase(trade.satoshis, null, function(err) { - if (err) return cb(err); - pollBalance('BTC'); - if (typeof cb === 'function') cb(); - }); +function purchase (trade, cb) { + traderPlugin.purchase(trade.satoshis, null, function (err) { + if (err) return cb(err) + pollBalance('BTC') + if (typeof cb === 'function') cb() + }) } -function consolidateTrades() { +function consolidateTrades () { // NOTE: value in satoshis stays the same no matter the currency var consolidatedTrade = { currency: deviceCurrency, - satoshis: tradesQueue.reduce(function(prev, current) { - return prev + current.satoshis; + satoshis: tradesQueue.reduce(function (prev, current) { + return prev + current.satoshis }, 0) - }; - - tradesQueue = []; - - logger.debug('consolidated: ', JSON.stringify(consolidatedTrade)); - return consolidatedTrade; -} - -function executeTrades() { - if (!traderPlugin) return; - - logger.debug('checking for trades'); - - var trade = consolidateTrades(); - - if (trade.satoshis === 0) { - logger.debug('rejecting 0 trade'); - return; } - logger.debug('making a trade: %d', trade.satoshis / SATOSHI_FACTOR); - purchase(trade, function(err) { + tradesQueue = [] + + logger.debug('consolidated: ', JSON.stringify(consolidatedTrade)) + return consolidatedTrade +} + +function executeTrades () { + if (!traderPlugin) return + + logger.debug('checking for trades') + + var trade = consolidateTrades() + + if (trade.satoshis === 0) { + logger.debug('rejecting 0 trade') + return + } + + logger.debug('making a trade: %d', trade.satoshis / SATOSHI_FACTOR) + purchase(trade, function (err) { if (err) { - tradesQueue.push(trade); - if (err.name !== 'orderTooSmall') logger.error(err); + tradesQueue.push(trade) + if (err.name !== 'orderTooSmall') logger.error(err) } - }); + }) } /* * ID Verifier functions */ -exports.verifyUser = function verifyUser(data, cb) { - idVerifierPlugin.verifyUser(data, cb); -}; +exports.verifyUser = function verifyUser (data, cb) { + idVerifierPlugin.verifyUser(data, cb) +} -exports.verifyTx = function verifyTx(data, cb) { - idVerifierPlugin.verifyTransaction(data, cb); -}; +exports.verifyTx = function verifyTx (data, cb) { + idVerifierPlugin.verifyTransaction(data, cb) +} exports.getCryptoCoins = function getCryptoCoins () { - return cryptoCoins; + return cryptoCoins } diff --git a/lib/routes.js b/lib/routes.js index f937a609..8f21d706 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,29 +1,32 @@ -'use strict'; +'use strict' -var logger = require('./logger'); +var logger = require('./logger') -var mock = false; +var mock = false -var plugins; -var lamassuConfig; +var plugins +var lamassuConfig module.exports = { init: init, getFingerprint: getFingerprint -}; +} // Make sure these are higher than polling interval // or there will be a lot of errors -var STALE_TICKER = 180000; -var STALE_BALANCE = 180000; +var STALE_TICKER = 180000 +var STALE_BALANCE = 180000 var pids = {} var reboots = {} function buildRates () { - var cryptoCoins = plugins.getCryptoCoins(); - var cashInCommission = settings.commission; - var cashOutCommission = settings.fiatCommission || cashInCommission; + var cryptoCoins = plugins.getCryptoCoins() + var config = plugins.getConfig() + var settings = config.exchanges.settings + + var cashInCommission = settings.commission + var cashOutCommission = settings.fiatCommission || cashInCommission var rates = {} cryptoCoins.forEach(function (coin) { @@ -38,7 +41,7 @@ function buildRates () { } function buildBalances () { - var cryptoCoins = plugins.getCryptoCoins(); + var cryptoCoins = plugins.getCryptoCoins() var balances = {} cryptoCoins.forEach(function (coin) { @@ -46,20 +49,19 @@ function buildBalances () { balances[coin] = balance }) - return balance - + return balances } -function poll(req, res) { +function poll (req, res) { var fingerprint = getFingerprint(req) var pid = req.query.pid pids[fingerprint] = {pid: pid, ts: Date.now()} - logger.debug('poll request from: %s', fingerprint); + logger.debug('poll request from: %s', fingerprint) - var rateRec = plugins.getDeviceRate(); - var balanceRec = plugins.getBalance(); + var rateRec = plugins.getDeviceRate() + var balanceRec = plugins.getBalance() var rates = buildRates() var balances = buildBalances() @@ -67,39 +69,38 @@ function poll(req, res) { // `rateRec` and `balanceRec` are both objects, so there's no danger // of misinterpreting rate or balance === 0 as 'Server initializing'. if (!rateRec || !balanceRec) { - return res.json({err: 'Server initializing'}); + return res.json({err: 'Server initializing'}) } - var now = Date.now(); + var now = Date.now() if (now - rateRec.timestamp > STALE_TICKER) { - return res.json({err: 'Stale ticker'}); + return res.json({err: 'Stale ticker'}) } if (now - balanceRec.timestamp > STALE_BALANCE) { - return res.json({err: 'Stale balance'}); + return res.json({err: 'Stale balance'}) } - var rate = rateRec.rates.ask; - var fiatRate = rateRec.rates.bid || rate; + var rate = rateRec.rates.ask + var fiatRate = rateRec.rates.bid || rate - if (rate === null) return res.json({err: 'No rate available'}); - if (!fiatRate) - logger.warn('No bid rate, using ask rate'); + if (rate === null) return res.json({err: 'No rate available'}) + if (!fiatRate) logger.warn('No bid rate, using ask rate') - var fiatBalance = plugins.fiatBalance(); + var fiatBalance = plugins.fiatBalance() if (fiatBalance === null) { - logger.warn('No balance available.'); - return res.json({err: 'No balance available'}); + logger.warn('No balance available.') + return res.json({err: 'No balance available'}) } - var config = plugins.getConfig(); - var settings = config.exchanges.settings; - var complianceSettings = settings.compliance; - var fiatCommission = settings.fiatCommission || settings.commission; + var config = plugins.getConfig() + var settings = config.exchanges.settings + var complianceSettings = settings.compliance + var fiatCommission = settings.fiatCommission || settings.commission - plugins.pollQueries(session(req), function(err, results) { - if (err) return logger.error(err); - var cartridges = results.cartridges; + plugins.pollQueries(session(req), function (err, results) { + if (err) return logger.error(err) + var cartridges = results.cartridges var reboot = reboots[fingerprint] === pid @@ -112,30 +113,31 @@ function poll(req, res) { txLimit: parseInt(complianceSettings.maximum.limit, 10), idVerificationEnabled: complianceSettings.idVerificationEnabled, cartridges: cartridges, - twoWayMode: cartridges ? true : false, + twoWayMode: !!cartridges, zeroConfLimit: settings.zeroConfLimit, fiatTxLimit: settings.fiatTxLimit, reboot: reboot, rates: rates, balances: balances - }; + } - if (response.idVerificationEnabled) - response.idVerificationLimit = complianceSettings.idVerificationLimit; + if (response.idVerificationEnabled) { + response.idVerificationLimit = complianceSettings.idVerificationLimit + } - res.json(response); - }); + res.json(response) + }) } -function trade(req, res) { - plugins.trade(session(req), req.body, function(err) { - var statusCode = err ? 500 : 201; - res.json(statusCode, {err: err}); - }); +function trade (req, res) { + plugins.trade(session(req), req.body, function (err) { + var statusCode = err ? 500 : 201 + res.json(statusCode, {err: err}) + }) } -function send(req, res) { - plugins.sendCoins(session(req), req.body, function(err, status) { +function send (req, res) { + plugins.sendCoins(session(req), req.body, function (err, status) { // TODO: use status.statusCode here after confirming machine compatibility // FIX: (joshm) set txHash to status.txId instead of previous status.txHash which wasn't being set // Need to clean up txHash vs txId @@ -144,109 +146,109 @@ function send(req, res) { err: err && err.message, txHash: status && status.txHash, txId: status && status.txId - }); - }); + }) + }) } -function cashOut(req, res) { - logger.info({tx: req.body, cmd: 'cashOut'}); - plugins.cashOut(session(req), req.body, function(err, bitcoinAddress) { - if (err) logger.error(err); +function cashOut (req, res) { + logger.info({tx: req.body, cmd: 'cashOut'}) + plugins.cashOut(session(req), req.body, function (err, bitcoinAddress) { + if (err) logger.error(err) res.json({ err: err && err.message, errType: err && err.name, bitcoinAddress: bitcoinAddress - }); - }); + }) + }) } -function dispenseAck(req, res) { - plugins.dispenseAck(session(req), req.body); - res.json(200); +function dispenseAck (req, res) { + plugins.dispenseAck(session(req), req.body) + res.json(200) } -function deviceEvent(req, res) { - plugins.logEvent(session(req), req.body); - res.json({err: null}); +function deviceEvent (req, res) { + plugins.logEvent(session(req), req.body) + res.json({err: null}) } -function verifyUser(req, res) { - if (mock) return res.json({success: true}); +function verifyUser (req, res) { + if (mock) return res.json({success: true}) plugins.verifyUser(req.body, function (err, idResult) { if (err) { - logger.error(err); - return res.json({err: 'Verification failed'}); + logger.error(err) + return res.json({err: 'Verification failed'}) } - res.json(idResult); - }); + res.json(idResult) + }) } -function verifyTx(req, res) { - if (mock) return res.json({success: true}); +function verifyTx (req, res) { + if (mock) return res.json({success: true}) plugins.verifyTx(req.body, function (err, idResult) { if (err) { - logger.error(err); - return res.json({err: 'Verification failed'}); + logger.error(err) + return res.json({err: 'Verification failed'}) } - res.json(idResult); - }); + res.json(idResult) + }) } -function pair(req, res) { - var token = req.body.token; - var name = req.body.name; +function pair (req, res) { + var token = req.body.token + var name = req.body.name lamassuConfig.pair( token, getFingerprint(req), name, - function(err) { - if (err) return res.json(500, { err: err.message }); + function (err) { + if (err) return res.json(500, { err: err.message }) - res.json(200); + res.json(200) } - ); + ) } -function raqia(req, res) { - var raqiaCreds; +function raqia (req, res) { + var raqiaCreds try { - var raqiaRec = require('../raqia.json'); - raqiaCreds = raqiaRec[getFingerprint(req)].apiKeys[0]; - } catch(ex) { - raqiaCreds = null; + var raqiaRec = require('../raqia.json') + raqiaCreds = raqiaRec[getFingerprint(req)].apiKeys[0] + } catch (ex) { + raqiaCreds = null } - res.json(raqiaCreds || {}); + res.json(raqiaCreds || {}) } -function init(localConfig) { - lamassuConfig = localConfig.lamassuConfig; - plugins = localConfig.plugins; - mock = localConfig.mock; +function init (localConfig) { + lamassuConfig = localConfig.lamassuConfig + plugins = localConfig.plugins + mock = localConfig.mock - var authMiddleware = localConfig.authMiddleware; - var reloadConfigMiddleware = localConfig.reloadConfigMiddleware; - var app = localConfig.app; + var authMiddleware = localConfig.authMiddleware + var reloadConfigMiddleware = localConfig.reloadConfigMiddleware + var app = localConfig.app var localApp = localConfig.localApp - app.get('/poll', authMiddleware, reloadConfigMiddleware, poll); + app.get('/poll', authMiddleware, reloadConfigMiddleware, poll) - app.post('/trade', authMiddleware, trade); - app.post('/send', authMiddleware, send); + app.post('/trade', authMiddleware, trade) + app.post('/send', authMiddleware, send) - app.post('/cash_out', authMiddleware, cashOut); - app.post('/dispense_ack', authMiddleware, dispenseAck); + app.post('/cash_out', authMiddleware, cashOut) + app.post('/dispense_ack', authMiddleware, dispenseAck) - app.post('/event', authMiddleware, deviceEvent); - app.post('/verify_user', authMiddleware, verifyUser); - app.post('/verify_transaction', authMiddleware, verifyTx); - app.post('/pair', pair); - app.get('/raqia', raqia); + app.post('/event', authMiddleware, deviceEvent) + app.post('/verify_user', authMiddleware, verifyUser) + app.post('/verify_transaction', authMiddleware, verifyTx) + app.post('/pair', pair) + app.get('/raqia', raqia) localApp.get('/pid', function (req, res) { var machineFingerprint = req.query.fingerprint @@ -268,14 +270,14 @@ function init(localConfig) { res.send(200) }) - return app; + return app } -function session(req) { - return {fingerprint: getFingerprint(req), id: req.get('session-id')}; +function session (req) { + return {fingerprint: getFingerprint(req), id: req.get('session-id')} } -function getFingerprint(req) { +function getFingerprint (req) { return (typeof req.connection.getPeerCertificate === 'function' && - req.connection.getPeerCertificate().fingerprint) || 'unknown'; + req.connection.getPeerCertificate().fingerprint) || 'unknown' } From fd4e5b5f3474a64c9e969fb7b3b52ea8dd5e824a Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 27 Mar 2016 18:47:08 +0200 Subject: [PATCH 11/34] WIP --- lib/routes.js | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) diff --git a/lib/routes.js b/lib/routes.js index 8f21d706..b03cae4d 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -12,10 +12,12 @@ module.exports = { getFingerprint: getFingerprint } +/* // Make sure these are higher than polling interval // or there will be a lot of errors var STALE_TICKER = 180000 var STALE_BALANCE = 180000 +*/ var pids = {} var reboots = {} @@ -60,43 +62,12 @@ function poll (req, res) { logger.debug('poll request from: %s', fingerprint) - var rateRec = plugins.getDeviceRate() - var balanceRec = plugins.getBalance() - var rates = buildRates() var balances = buildBalances() - // `rateRec` and `balanceRec` are both objects, so there's no danger - // of misinterpreting rate or balance === 0 as 'Server initializing'. - if (!rateRec || !balanceRec) { - return res.json({err: 'Server initializing'}) - } - - var now = Date.now() - if (now - rateRec.timestamp > STALE_TICKER) { - return res.json({err: 'Stale ticker'}) - } - - if (now - balanceRec.timestamp > STALE_BALANCE) { - return res.json({err: 'Stale balance'}) - } - - var rate = rateRec.rates.ask - var fiatRate = rateRec.rates.bid || rate - - if (rate === null) return res.json({err: 'No rate available'}) - if (!fiatRate) logger.warn('No bid rate, using ask rate') - - var fiatBalance = plugins.fiatBalance() - if (fiatBalance === null) { - logger.warn('No balance available.') - return res.json({err: 'No balance available'}) - } - var config = plugins.getConfig() var settings = config.exchanges.settings var complianceSettings = settings.compliance - var fiatCommission = settings.fiatCommission || settings.commission plugins.pollQueries(session(req), function (err, results) { if (err) return logger.error(err) @@ -106,9 +77,9 @@ function poll (req, res) { var response = { err: null, - rate: rate * settings.commission, - fiatRate: fiatRate / fiatCommission, - fiat: fiatBalance, + rate: rates.BTC.cashIn, + fiatRate: rates.BTC.cashOut, + fiat: balances.BTC, locale: config.brain.locale, txLimit: parseInt(complianceSettings.maximum.limit, 10), idVerificationEnabled: complianceSettings.idVerificationEnabled, From 7be7c3f8e67d7a3938b33b4fb66fa2ac3fda96ca Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 28 Mar 2016 00:52:15 +0200 Subject: [PATCH 12/34] kraken plugin WIP --- lib/plugins.js | 20 ++++++++++++----- lib/plugins/kraken.js | 51 +++++++++++++++++++++++++++++++++++++++++++ package.json | 12 ++++++---- 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 lib/plugins/kraken.js diff --git a/lib/plugins.js b/lib/plugins.js index 104e14cf..128abb5a 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -63,8 +63,12 @@ function loadPlugin (name, config) { try { plugin = require('lamassu-' + name) } catch (_) { - throw new Error(name + ' module is not installed. ' + - 'Try running \'npm install --save lamassu-' + name + '\' first') + try { + require('plugins/' + name) + } catch (_) { + throw new Error(name + ' module is not installed. ' + + 'Try running \'npm install --save lamassu-' + name + '\' first') + } } // each plugin MUST implement those @@ -116,8 +120,7 @@ function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCoin, currency, if (!currentName) pluginHandle = null else { // some plugins may be disabled - var pluginConfig = cachedConfig.exchanges.plugins.settings[currentName] || - {} + var pluginConfig = cachedConfig.exchanges.plugins.settings[currentName] || {} if (currency) pluginConfig.currency = currency @@ -448,7 +451,14 @@ function pollRate (cryptoCoin, cb) { logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME) var tickerPlugin = tickerPlugins[cryptoCoin] - tickerPlugin.ticker(deviceCurrency, function (err, resRates) { + var currencies = deviceCurrency + if (typeof currencies === 'string') currencies = [currencies] + + var tickerF = cryptoCoin === 'BTC' + ? async.apply(tickerPlugin.ticker, currencies) + : async.apply(tickerPlugin.ticker, currencies, cryptoCoin) + + tickerF(function (err, resRates) { if (err) { logger.error(err) return cb && cb(err) diff --git a/lib/plugins/kraken.js b/lib/plugins/kraken.js new file mode 100644 index 00000000..29b61d8f --- /dev/null +++ b/lib/plugins/kraken.js @@ -0,0 +1,51 @@ +require('es6-promise').polyfill() +var axios = require('axios') +var _ = require('lodash') +var BigNumber = require('bignumber.js') +BigNumber.config({DECIMAL_PLACES: 30}) + +exports.NAME = 'Kraken' +exports.SUPPORTED_MODULES = ['ticker'] + +// var pluginConfig = {} + +// https://bitpay.com/api/rates + +exports.config = function config (localConfig) { +// pluginConfig = localConfig +} + +function findCurrency (fxRates, currency) { + return new BigNumber(_.find(fxRates, function (r) { return r.code === currency }).rate) +} + +exports.ticker = function ticker (currencies, cryptoCoin, callback) { + return axios.get('https://bitpay.com/api/rates') + .then(function (response) { + var fxRates = response.data + + return axios.get('https://api.kraken.com/0/public/Ticker?pair=ETHUSD') + .then(function (response2) { + var usdRate = findCurrency(fxRates, 'USD') + var rates = response2.data.result.XETHZUSD + var res = {} + var cryptoCoinFactor = new BigNumber(10).pow(cryptoCoin.unitScale) + + currencies.forEach(function (currency) { + var fxRate = findCurrency(fxRates, currency).div(usdRate) + res[currency] = { + ask: fxRate.times(rates.a[0]).div(cryptoCoinFactor), + bid: fxRate.times(rates.b[0]).div(cryptoCoinFactor) + } + }) + + callback(null, res) + }) + }) + .catch(callback) +} + +exports.ticker(['USD', 'ILS', 'EUR'], {unitScale: 18}, function (err, res) { + if (err) return console.log(err.stack) + console.log(JSON.stringify(res, null, 2)) +}) diff --git a/package.json b/package.json index 3fc9f4f5..b25ad8c9 100644 --- a/package.json +++ b/package.json @@ -10,23 +10,27 @@ }, "dependencies": { "async": "~0.2.9", + "axios": "^0.9.1", + "bignumber.js": "^2.3.0", + "bluebird": "^3.3.4", "bunyan": "~0.22.3", + "es6-promise": "^3.1.2", "express": "~3.4.7", "inquirer": "^0.8.0", "joi": "^5.1.0", "lamassu-bitcoinaverage": "~1.0.0", "lamassu-bitcoind": "^1.1.0", + "lamassu-bitgo": "^0.1.3", "lamassu-bitpay": "~1.0.0", "lamassu-bitstamp": "^1.0.2", "lamassu-blockchain": "^1.1.3", "lamassu-blockcypher": "~0.1.0", + "lamassu-coinapult": "^0.4.5", + "lamassu-coinbase": "^1.0.4", "lamassu-coindesk": "~1.0.0", + "lamassu-coinfloor": "^0.1.2", "lamassu-config": "~0.4.0", "lamassu-identitymind": "^1.0.1", - "lamassu-coinbase": "^1.0.4", - "lamassu-coinapult": "^0.4.5", - "lamassu-coinfloor": "^0.1.2", - "lamassu-bitgo": "^0.1.3", "lamassu-snapcard": "^0.1.7", "lodash": "^2.4.1", "minimist": "0.0.8", From 591ffcf1622edbba58a19cfbad8bd21fca597ff8 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 29 Mar 2016 17:58:27 +0100 Subject: [PATCH 13/34] WIP --- bin/ssu | 173 +++++++++++++++++++++++++++++------------- lib/plugins.js | 3 + lib/plugins/kraken.js | 3 +- package.json | 4 +- 4 files changed, 129 insertions(+), 54 deletions(-) diff --git a/bin/ssu b/bin/ssu index b035b82c..bf8e4fe5 100755 --- a/bin/ssu +++ b/bin/ssu @@ -4,80 +4,149 @@ var wreck = require('wreck') var argv = process.argv.slice(2) +var Promise = require('es6-promise') +var pgp = require('pg-promise')({ + promiseLib: Promise +}) + +var fs = require('fs') var cmd = argv[0] -var fingerprint = argv[1] -if (!cmd || !fingerprint) { +if (!cmd) bail() + +function bail () { console.log('Command line utility for lamassu-server') - console.log('\nUsage: ssu reboot ') + console.log('\nssu reboot ') console.log('This will remotely reboot your lamassu-machine.') + console.log('\nssu crypto ') + console.log('This will configure a new cryptocurrency.') process.exit(1) } -var opts = {json: true} -wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err, res, payload) { - if (err) { - console.log('Please make sure that lamassu-server is running on this box.') - process.exit(2) +switch (cmd) { + case 'reboot': + reboot() + break + case 'crypto': + crypto() + break + default: + +} + +function reboot () { + var fingerprint = argv[1] + + if (!fingerprint) { + console.log('Fingerprint required') + process.exit(1) } - if (!payload || !payload.pid) { - console.log('The requested lamassu-machine appears to be running an old version.') - process.exit(3) - } - - var pid = payload.pid - - if (Date.now() - payload.ts > 10000) { - console.log('lamassu-machine is not connected to server.') - process.exit(6) - } - - var opts2 = { - headers: {'Content-Type': 'application/json'}, - payload: JSON.stringify({pid: pid, fingerprint: fingerprint}) - } - - wreck.post('http://localhost:7070/reboot', opts2, function (err2, res) { - if (err2) { + var opts = {json: true} + wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err, res, payload) { + if (err) { console.log('Please make sure that lamassu-server is running on this box.') process.exit(2) } - if (res.statusCode !== 200) { - console.log('Communication error') - return + if (!payload || !payload.pid) { + console.log('The requested lamassu-machine appears to be running an old version.') + process.exit(3) } - console.log('Rebooting...') + var pid = payload.pid - var ts = null + if (Date.now() - payload.ts > 10000) { + console.log('lamassu-machine is not connected to server.') + process.exit(6) + } - setTimeout(function () { - if (Date.now() - ts < 10000) { - console.log('lamassu-machine did not reboot but is still contacting server.') - process.exit(4) + var opts2 = { + headers: {'Content-Type': 'application/json'}, + payload: JSON.stringify({pid: pid, fingerprint: fingerprint}) + } + + wreck.post('http://localhost:7070/reboot', opts2, function (err2, res) { + if (err2) { + console.log('Please make sure that lamassu-server is running on this box.') + process.exit(2) } - console.log('lamassu-machine rebooted but is not coming back up.') - process.exit(5) - }, 30000) + if (res.statusCode !== 200) { + console.log('Communication error') + return + } - setInterval(function () { - wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err3, res, payload) { - if (err3) { - console.log('lamassu-server appears to be down.') - process.exit(2) + console.log('Rebooting...') + + var ts = null + + setTimeout(function () { + if (Date.now() - ts < 10000) { + console.log('lamassu-machine did not reboot but is still contacting server.') + process.exit(4) } - ts = payload.ts + console.log('lamassu-machine rebooted but is not coming back up.') + process.exit(5) + }, 30000) - if (payload.pid !== pid) { - console.log('lamassu-machine is back up!') - process.exit(0) - } - }) - }, 5000) + setInterval(function () { + wreck.get('http://localhost:7070/pid?fingerprint=' + fingerprint, opts, function (err3, res, payload) { + if (err3) { + console.log('lamassu-server appears to be down.') + process.exit(2) + } + + ts = payload.ts + + if (payload.pid !== pid) { + console.log('lamassu-machine is back up!') + process.exit(0) + } + }) + }, 5000) + }) }) -}) +} + +function crypto () { + var code = argv[1] + var tickerPlugin = argv[2] + var walletPlugin = argv[3] + + if (!code || !tickerPlugin || !walletPlugin) { + console.log('\nssu crypto ') + console.log('This will configure a new cryptocurrency.') + process.exit(1) + } + + code = code.toUpperCase() + + var psqlUrl + try { + psqlUrl = process.env.DATABASE_URL || JSON.parse(fs.readFileSync('/etc/lamassu.json')).postgresql + } catch (ex) { + psqlUrl = 'psql://lamassu:lamassu@localhost/lamassu' + } + var db = pgp(psqlUrl) + + return db.one('select data from user_config where type=$1', 'exchanges') + .then(function (data) { + var config = data.data + config.exchanges.plugins.current[code] = { + ticker: tickerPlugin, + transfer: walletPlugin + } + return db.none('update user_config set data=$1 where type=$2', [config, 'exchanges']) + }) + .then(function () { + console.log('success') + pgp.end() + }) + .catch(function (err) { + console.log(err.stack) + pgp.end() + }) +} diff --git a/lib/plugins.js b/lib/plugins.js index 128abb5a..92050280 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -2,7 +2,10 @@ var _ = require('lodash') var async = require('async') + var BigNumber = require('bignumber.js') +BigNumber.config({DECIMAL_PLACES: 40}) + var logger = require('./logger') var argv = require('minimist')(process.argv.slice(2)) diff --git a/lib/plugins/kraken.js b/lib/plugins/kraken.js index 29b61d8f..29376ca4 100644 --- a/lib/plugins/kraken.js +++ b/lib/plugins/kraken.js @@ -1,8 +1,9 @@ require('es6-promise').polyfill() var axios = require('axios') var _ = require('lodash') + var BigNumber = require('bignumber.js') -BigNumber.config({DECIMAL_PLACES: 30}) +BigNumber.config({DECIMAL_PLACES: 40}) exports.NAME = 'Kraken' exports.SUPPORTED_MODULES = ['ticker'] diff --git a/package.json b/package.json index b25ad8c9..79659a18 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ "lodash": "^2.4.1", "minimist": "0.0.8", "node-uuid": "^1.4.2", - "pg": "~2.11.1", + "pg": "^4.5.1", + "pg-promise": "^3.4.3", + "web3": "^0.15.3", "wreck": "5.1.0" }, "repository": { From 22c2acfe615c8046ec9b1192759dadde7adf4904 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 4 Apr 2016 16:19:41 +0100 Subject: [PATCH 14/34] WIP --- lib/plugins/kraken.js | 5 ----- lib/plugins/web3.js | 25 +++++++++++++++++++++++++ package.json | 2 +- 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 lib/plugins/web3.js diff --git a/lib/plugins/kraken.js b/lib/plugins/kraken.js index 29376ca4..a843af62 100644 --- a/lib/plugins/kraken.js +++ b/lib/plugins/kraken.js @@ -8,12 +8,7 @@ BigNumber.config({DECIMAL_PLACES: 40}) exports.NAME = 'Kraken' exports.SUPPORTED_MODULES = ['ticker'] -// var pluginConfig = {} - -// https://bitpay.com/api/rates - exports.config = function config (localConfig) { -// pluginConfig = localConfig } function findCurrency (fxRates, currency) { diff --git a/lib/plugins/web3.js b/lib/plugins/web3.js new file mode 100644 index 00000000..b4a970f2 --- /dev/null +++ b/lib/plugins/web3.js @@ -0,0 +1,25 @@ +var Web3 = require('web3') + +var web3 = new Web3() + +if (!web3.isConnected()) { + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')) +} + +// Note: it's still called sendBitcoins for backwards compatibility, but this +// is for any currency +exports.sendBitcoins = function sendBitcoins (address, satoshis, fee, callback) { + web3.eth.sendTransaction({ + to: address, + value: satoshis + }, callback) +} + +exports.balance = function balance (cb) { + var coinbase = web3.eth.coinbase + web3.eth.getBalance(coinbase, 'pending', cb) +} + +exports.newAddress = function newAddress (info, callback) { + throw new Error('Not implemented') +} diff --git a/package.json b/package.json index 79659a18..fc919553 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "axios": "^0.9.1", "bignumber.js": "^2.3.0", "bluebird": "^3.3.4", - "bunyan": "~0.22.3", "es6-promise": "^3.1.2", + "ethereumjs-wallet": "^0.5.1", "express": "~3.4.7", "inquirer": "^0.8.0", "joi": "^5.1.0", From e4623592997ebebcd09c970597cd88a63b6aa851 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 5 Apr 2016 02:48:21 +0100 Subject: [PATCH 15/34] WIP --- lib/plugins.js | 76 +++++++++++++++++++++++++------------------ lib/plugins/kraken.js | 47 -------------------------- lib/plugins/web3.js | 25 -------------- lib/routes.js | 3 +- todo.txt | 5 +++ 5 files changed, 52 insertions(+), 104 deletions(-) delete mode 100644 lib/plugins/kraken.js delete mode 100644 lib/plugins/web3.js create mode 100644 todo.txt diff --git a/lib/plugins.js b/lib/plugins.js index 92050280..53fa3a91 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -16,6 +16,13 @@ var POLLING_RATE = 60 * 1000 // poll each minute var REAP_RATE = 2 * 1000 var PENDING_TIMEOUT = 70 * 1000 +var BTC_COIN = { + unitCode: 'BTC', + displayCode: 'mBTC', + unitScale: 8, + displayScale: 5 +} + if (argv.timeout) PENDING_TIMEOUT = argv.timeout / 1000 // TODO: might have to update this if user is allowed to extend monitoring time @@ -114,12 +121,13 @@ function loadPlugin (name, config) { function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCoin, currency, onChangeCallback) { - if (!cryptoCoin) cryptoCoin = 'any' - var currentName = cryptoCoin === 'any' || cryptoCoin === 'BTC' - ? cachedConfig.exchanges.plugins.current[pluginType] - : cachedConfig.exchanges.plugins.current[cryptoCoin][pluginType] + var cryptoCode = cryptoCoin ? cryptoCoin.unitCode : 'any' - var pluginChanged = currentlyUsedPlugins[cryptoCoin][pluginType] !== currentName + var currentName = cryptoCode === 'any' || cryptoCode === 'BTC' + ? cachedConfig.exchanges.plugins.current[pluginType] + : cachedConfig.exchanges.plugins.current[cryptoCode][pluginType] + + var pluginChanged = currentlyUsedPlugins[cryptoCode][pluginType] !== currentName if (!currentName) pluginHandle = null else { // some plugins may be disabled @@ -130,9 +138,9 @@ function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCoin, currency, if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig) else { pluginHandle = loadPlugin(currentName, pluginConfig) - currentlyUsedPlugins[cryptoCoin] = currentlyUsedPlugins[cryptoCoin] || {} - currentlyUsedPlugins[cryptoCoin][pluginType] = currentName - logger.debug('[%s] plugin(%s) loaded: %s', cryptoCoin, pluginType, pluginHandle.NAME || + currentlyUsedPlugins[cryptoCode] = currentlyUsedPlugins[cryptoCode] || {} + currentlyUsedPlugins[cryptoCode][pluginType] = currentName + logger.debug('[%s] plugin(%s) loaded: %s', cryptoCode, pluginType, pluginHandle.NAME || currentName) } } @@ -149,29 +157,30 @@ exports.configure = function configure (config) { cachedConfig = config deviceCurrency = config.exchanges.settings.currency - cryptoCoins = config.exchanges.settings.coins || ['BTC'] + cryptoCoins = config.exchanges.settings.coins || [BTC_COIN] cryptoCoins.forEach(function (cryptoCoin) { // TICKER [required] configure (or load) + var cryptoCode = cryptoCoin.unitCode loadOrConfigPlugin( - tickerPlugins[cryptoCoin], + tickerPlugins[cryptoCode], 'ticker', cryptoCoin, deviceCurrency, // device currency function onTickerChange (newTicker) { - tickerPlugins[cryptoCoin] = newTicker + tickerPlugins[cryptoCode] = newTicker pollRate(cryptoCoin) } ) // WALLET [required] configure (or load) loadOrConfigPlugin( - walletPlugins[cryptoCoin], + walletPlugins[cryptoCode], 'transfer', cryptoCoin, null, function onWalletChange (newWallet) { - walletPlugins[cryptoCoin] = newWallet + walletPlugins[cryptoCode] = newWallet pollBalance(cryptoCoin) } ) @@ -240,9 +249,10 @@ exports.pollQueries = function pollQueries (session, cb) { } function _sendCoins (toAddress, cryptoUnits, cryptoCoin, cb) { - var walletPlugin = walletPlugins[cryptoCoin] + var cryptoCode = cryptoCoin.unitCode + var walletPlugin = walletPlugins[cryptoCode] var transactionFee = cachedConfig.exchanges.settings.transactionFee - if (cryptoCoin === 'BTC') { + if (cryptoCode === 'BTC') { walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb) } else { walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb) @@ -350,11 +360,10 @@ exports.cashOut = function cashOut (session, tx, cb) { account: 'deposit' } - var cryptoCoin = tx.coin - ? tx.coin.unitCode - : 'BTC' + var cryptoCoin = tx.coin || BTC_COIN + var cryptoCode = cryptoCoin.unitCode - var walletPlugin = walletPlugins[cryptoCoin] + var walletPlugin = walletPlugins[cryptoCode] walletPlugin.newAddress(tmpInfo, function (err, address) { if (err) return cb(err) @@ -372,9 +381,10 @@ exports.dispenseAck = function dispenseAck (session, rec) { } exports.fiatBalance = function fiatBalance (cryptoCoin) { + var cryptoCode = cryptoCoin.unitCode var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask var commission = cachedConfig.exchanges.settings.commission - var lastBalance = lastBalances[cryptoCoin] + var lastBalance = lastBalances[cryptoCode] if (!rawRate || !lastBalance) return null @@ -432,9 +442,10 @@ function stopTrader () { } function pollBalance (cryptoCoin, cb) { - logger.debug('[%s] collecting balance', cryptoCoin) + var cryptoCode = cryptoCoin.unitCode + logger.debug('[%s] collecting balance', cryptoCode) - var walletPlugin = walletPlugins[cryptoCoin] + var walletPlugin = walletPlugins[cryptoCode] walletPlugin.balance(function (err, balance) { if (err) { @@ -442,22 +453,23 @@ function pollBalance (cryptoCoin, cb) { return cb && cb(err) } - logger.debug('[%s] Balance update:', cryptoCoin, balance) + logger.debug('[%s] Balance update:', cryptoCode, balance) balance.timestamp = Date.now() - lastBalances[cryptoCoin] = balance + lastBalances[cryptoCode] = balance return cb && cb(null, lastBalances) }) } function pollRate (cryptoCoin, cb) { - logger.debug('[%s] polling for rates (%s)', cryptoCoin, tickerPlugin.NAME) - var tickerPlugin = tickerPlugins[cryptoCoin] + var cryptoCode = cryptoCoin.unitCode + logger.debug('[%s] polling for rates (%s)', cryptoCode, tickerPlugin.NAME) + var tickerPlugin = tickerPlugins[cryptoCode] var currencies = deviceCurrency if (typeof currencies === 'string') currencies = [currencies] - var tickerF = cryptoCoin === 'BTC' + var tickerF = cryptoCode === 'BTC' ? async.apply(tickerPlugin.ticker, currencies) : async.apply(tickerPlugin.ticker, currencies, cryptoCoin) @@ -469,7 +481,7 @@ function pollRate (cryptoCoin, cb) { logger.debug('got rates: %j', resRates) resRates.timestamp = new Date() - lastRates[cryptoCoin] = resRates + lastRates[cryptoCode] = resRates return cb && cb(null, lastRates) }) @@ -480,16 +492,18 @@ function pollRate (cryptoCoin, cb) { */ exports.getDeviceRate = function getDeviceRate (cryptoCoin) { - if (!lastRates[cryptoCoin]) return null + var cryptoCode = cryptoCoin.unitCode + if (!lastRates[cryptoCode]) return null - var lastRate = lastRates[cryptoCoin] + var lastRate = lastRates[cryptoCode] if (!lastRate) return null return lastRate[deviceCurrency] } exports.getBalance = function getBalance (cryptoCoin) { - var lastBalance = lastBalances[cryptoCoin] + var cryptoCode = cryptoCoin.unitCode + var lastBalance = lastBalances[cryptoCode] if (!lastBalance) return null return lastBalance.transferBalance diff --git a/lib/plugins/kraken.js b/lib/plugins/kraken.js deleted file mode 100644 index a843af62..00000000 --- a/lib/plugins/kraken.js +++ /dev/null @@ -1,47 +0,0 @@ -require('es6-promise').polyfill() -var axios = require('axios') -var _ = require('lodash') - -var BigNumber = require('bignumber.js') -BigNumber.config({DECIMAL_PLACES: 40}) - -exports.NAME = 'Kraken' -exports.SUPPORTED_MODULES = ['ticker'] - -exports.config = function config (localConfig) { -} - -function findCurrency (fxRates, currency) { - return new BigNumber(_.find(fxRates, function (r) { return r.code === currency }).rate) -} - -exports.ticker = function ticker (currencies, cryptoCoin, callback) { - return axios.get('https://bitpay.com/api/rates') - .then(function (response) { - var fxRates = response.data - - return axios.get('https://api.kraken.com/0/public/Ticker?pair=ETHUSD') - .then(function (response2) { - var usdRate = findCurrency(fxRates, 'USD') - var rates = response2.data.result.XETHZUSD - var res = {} - var cryptoCoinFactor = new BigNumber(10).pow(cryptoCoin.unitScale) - - currencies.forEach(function (currency) { - var fxRate = findCurrency(fxRates, currency).div(usdRate) - res[currency] = { - ask: fxRate.times(rates.a[0]).div(cryptoCoinFactor), - bid: fxRate.times(rates.b[0]).div(cryptoCoinFactor) - } - }) - - callback(null, res) - }) - }) - .catch(callback) -} - -exports.ticker(['USD', 'ILS', 'EUR'], {unitScale: 18}, function (err, res) { - if (err) return console.log(err.stack) - console.log(JSON.stringify(res, null, 2)) -}) diff --git a/lib/plugins/web3.js b/lib/plugins/web3.js deleted file mode 100644 index b4a970f2..00000000 --- a/lib/plugins/web3.js +++ /dev/null @@ -1,25 +0,0 @@ -var Web3 = require('web3') - -var web3 = new Web3() - -if (!web3.isConnected()) { - web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')) -} - -// Note: it's still called sendBitcoins for backwards compatibility, but this -// is for any currency -exports.sendBitcoins = function sendBitcoins (address, satoshis, fee, callback) { - web3.eth.sendTransaction({ - to: address, - value: satoshis - }, callback) -} - -exports.balance = function balance (cb) { - var coinbase = web3.eth.coinbase - web3.eth.getBalance(coinbase, 'pending', cb) -} - -exports.newAddress = function newAddress (info, callback) { - throw new Error('Not implemented') -} diff --git a/lib/routes.js b/lib/routes.js index b03cae4d..fa4dfd7b 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -32,8 +32,9 @@ function buildRates () { var rates = {} cryptoCoins.forEach(function (coin) { + var cryptoCode = coin.unitCode var rate = plugins.getDeviceRate(coin).rates - rates[coin] = { + rates[cryptoCode] = { cashIn: rate.ask.times(cashInCommission), cashOut: rate.bid.div(cashOutCommission) } diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..d4a1eb1b --- /dev/null +++ b/todo.txt @@ -0,0 +1,5 @@ +- specify crypto and fiat for trades +- make sure ticker is in full coins +- rethink scale names: is unit satoshi or btc? +- cryptoCoin is record, not code +- getDeviceRate should return bignumber From 8d44c1d383273024a6183de67877308784bb9def Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 5 Apr 2016 11:41:16 +0100 Subject: [PATCH 16/34] renamed cryptoUnits to cryptoAtoms for clarity --- lib/plugins.js | 14 +++++++------- todo.txt | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 53fa3a91..a04e7fd2 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -248,28 +248,28 @@ exports.pollQueries = function pollQueries (session, cb) { }) } -function _sendCoins (toAddress, cryptoUnits, cryptoCoin, cb) { +function _sendCoins (toAddress, cryptoAtoms, cryptoCoin, cb) { var cryptoCode = cryptoCoin.unitCode var walletPlugin = walletPlugins[cryptoCode] var transactionFee = cachedConfig.exchanges.settings.transactionFee if (cryptoCode === 'BTC') { - walletPlugin.sendBitcoins(toAddress, cryptoUnits, transactionFee, cb) + walletPlugin.sendBitcoins(toAddress, cryptoAtoms, transactionFee, cb) } else { - walletPlugin.sendCoins(toAddress, cryptoUnits, cryptoCoin, transactionFee, cb) + walletPlugin.sendCoins(toAddress, cryptoAtoms, cryptoCoin, transactionFee, cb) } } function executeTx (session, tx, authority, cb) { db.addOutgoingTx(session, tx, function (err, toSend) { if (err) return cb(err) - var cryptoUnitsToSend = toSend.cryptoUnits - if (cryptoUnitsToSend === 0) { + var cryptoAtomsToSend = toSend.cryptoAtoms + if (cryptoAtomsToSend === 0) { return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}) } - _sendCoins(tx.toAddress, cryptoUnitsToSend, function (_err, txHash) { + _sendCoins(tx.toAddress, cryptoAtomsToSend, function (_err, txHash) { var fee = null // Need to fill this out in plugins - if (_err) toSend = {cryptoUnits: new BigNumber(0), fiat: 0} + if (_err) toSend = {cryptoAtoms: new BigNumber(0), fiat: 0} db.sentCoins(session, tx, authority, toSend, fee, _err, txHash) if (_err) return cb(_err) diff --git a/todo.txt b/todo.txt index d4a1eb1b..7a9f9bc1 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,5 @@ +- rethink scale names: is unit satoshi or btc? cryptoAtom, cryptoAtom +- cryptoCoin is record, not code - specify crypto and fiat for trades - make sure ticker is in full coins -- rethink scale names: is unit satoshi or btc? -- cryptoCoin is record, not code - getDeviceRate should return bignumber From f55e9355ef7a0c76462f7c0175f62d0075278b66 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 5 Apr 2016 17:41:12 +0100 Subject: [PATCH 17/34] WIP -- refactoring --- lib/plugins.js | 141 ++++++++++++++++++++++++------------------------- lib/routes.js | 15 +++--- todo.txt | 10 +++- 3 files changed, 84 insertions(+), 82 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index a04e7fd2..db66437b 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -11,18 +11,10 @@ var argv = require('minimist')(process.argv.slice(2)) var tradeInterval = null -var SATOSHI_FACTOR = 1e8 var POLLING_RATE = 60 * 1000 // poll each minute var REAP_RATE = 2 * 1000 var PENDING_TIMEOUT = 70 * 1000 -var BTC_COIN = { - unitCode: 'BTC', - displayCode: 'mBTC', - unitScale: 8, - displayScale: 5 -} - if (argv.timeout) PENDING_TIMEOUT = argv.timeout / 1000 // TODO: might have to update this if user is allowed to extend monitoring time @@ -30,10 +22,10 @@ var DEPOSIT_TIMEOUT = 130 * 1000 var db = null -var cryptoCoins = null +var cryptoCodes = null var tickerPlugins = {} -var traderPlugin = null +var traderPlugins = {} var walletPlugins = {} var idVerifierPlugin = null var infoPlugin = null @@ -46,7 +38,7 @@ var deviceCurrency = 'USD' var lastBalances = {} var lastRates = {} -var tradesQueue = [] +var tradesQueues = {} // that's basically a constructor exports.init = function init (databaseHandle) { @@ -119,9 +111,9 @@ function loadPlugin (name, config) { return plugin } -function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCoin, currency, +function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCode, currency, onChangeCallback) { - var cryptoCode = cryptoCoin ? cryptoCoin.unitCode : 'any' + cryptoCode = cryptoCode || 'BTC' var currentName = cryptoCode === 'any' || cryptoCode === 'BTC' ? cachedConfig.exchanges.plugins.current[pluginType] @@ -157,19 +149,18 @@ exports.configure = function configure (config) { cachedConfig = config deviceCurrency = config.exchanges.settings.currency - cryptoCoins = config.exchanges.settings.coins || [BTC_COIN] + cryptoCodes = config.exchanges.settings.coins || 'BTC' - cryptoCoins.forEach(function (cryptoCoin) { + cryptoCodes.forEach(function (cryptoCode) { // TICKER [required] configure (or load) - var cryptoCode = cryptoCoin.unitCode loadOrConfigPlugin( tickerPlugins[cryptoCode], 'ticker', - cryptoCoin, + cryptoCode, deviceCurrency, // device currency function onTickerChange (newTicker) { tickerPlugins[cryptoCode] = newTicker - pollRate(cryptoCoin) + pollRate(cryptoCode) } ) @@ -177,28 +168,29 @@ exports.configure = function configure (config) { loadOrConfigPlugin( walletPlugins[cryptoCode], 'transfer', - cryptoCoin, + cryptoCode, null, function onWalletChange (newWallet) { walletPlugins[cryptoCode] = newWallet - pollBalance(cryptoCoin) + pollBalance(cryptoCode) + } + ) + + tradesQueues[cryptoCode] = [] + + loadOrConfigPlugin( + traderPlugins[cryptoCode], + 'trade', + cryptoCode, + null, + function onTraderChange (newTrader) { + traderPlugins[cryptoCode] = newTrader + if (newTrader === null) stopTrader(cryptoCode) + else startTrader(cryptoCode) } ) }) - // TRADER [optional] configure (or load) - traderPlugin = loadOrConfigPlugin( - traderPlugin, - 'trade', - null, - null, - function onTraderChange (newTrader) { - traderPlugin = newTrader - if (newTrader === null) stopTrader() - else startTrader() - } - ) - // ID VERIFIER [optional] configure (or load) idVerifierPlugin = loadOrConfigPlugin( idVerifierPlugin, @@ -248,14 +240,13 @@ exports.pollQueries = function pollQueries (session, cb) { }) } -function _sendCoins (toAddress, cryptoAtoms, cryptoCoin, cb) { - var cryptoCode = cryptoCoin.unitCode +function _sendCoins (toAddress, cryptoAtoms, cryptoCode, cb) { var walletPlugin = walletPlugins[cryptoCode] var transactionFee = cachedConfig.exchanges.settings.transactionFee if (cryptoCode === 'BTC') { walletPlugin.sendBitcoins(toAddress, cryptoAtoms, transactionFee, cb) } else { - walletPlugin.sendCoins(toAddress, cryptoAtoms, cryptoCoin, transactionFee, cb) + walletPlugin.sendCoins(toAddress, cryptoAtoms, cryptoCode, transactionFee, cb) } } @@ -323,10 +314,15 @@ function reapTxs () { exports.trade = function trade (session, rawTrade, cb) { // TODO: move this to DB, too // add bill to trader queue (if trader is enabled) + var cryptoCode = rawTrade.cryptoCode || 'BTC' + var traderPlugin = traderPlugins[cryptoCode] + var tradesQueue = tradesQueues[cryptoCode] + if (traderPlugin) { tradesQueue.push({ currency: rawTrade.currency, - satoshis: rawTrade.satoshis + cryptoAtoms: rawTrade.cryptoAtoms, + cryptoCode: rawTrade.cryptoCode }) } @@ -360,9 +356,7 @@ exports.cashOut = function cashOut (session, tx, cb) { account: 'deposit' } - var cryptoCoin = tx.coin || BTC_COIN - var cryptoCode = cryptoCoin.unitCode - + var cryptoCode = tx.cryptoCode || 'BTC' var walletPlugin = walletPlugins[cryptoCode] walletPlugin.newAddress(tmpInfo, function (err, address) { @@ -380,9 +374,8 @@ exports.dispenseAck = function dispenseAck (session, rec) { db.addDispense(session, rec.tx, rec.cartridges) } -exports.fiatBalance = function fiatBalance (cryptoCoin) { - var cryptoCode = cryptoCoin.unitCode - var rawRate = exports.getDeviceRate(cryptoCoin).rates.ask +exports.fiatBalance = function fiatBalance (cryptoCode) { + var rawRate = exports.getDeviceRate(cryptoCode).rates.ask var commission = cachedConfig.exchanges.settings.commission var lastBalance = lastBalances[cryptoCode] @@ -410,9 +403,9 @@ exports.fiatBalance = function fiatBalance (cryptoCoin) { exports.startPolling = function startPolling () { executeTrades() - cryptoCoins.forEach(function (coin) { - setInterval(async.apply(pollBalance, coin), POLLING_RATE) - setInterval(async.apply(pollRate, coin), POLLING_RATE) + cryptoCodes.forEach(function (cryptoCode) { + setInterval(async.apply(pollBalance, cryptoCode), POLLING_RATE) + setInterval(async.apply(pollRate, cryptoCode), POLLING_RATE) }) setInterval(reapTxs, REAP_RATE) @@ -420,29 +413,30 @@ exports.startPolling = function startPolling () { startTrader() } -function startTrader () { +function startTrader (cryptoCode) { // Always start trading, even if we don't have a trade exchange configured, // since configuration can always change in `Trader#configure`. // `Trader#executeTrades` returns early if we don't have a trade exchange // configured at the moment. + var traderPlugin = traderPlugins[cryptoCode] + if (traderPlugin && !tradeInterval) { tradeInterval = setInterval( - executeTrades, + function () { executeTrades(cryptoCode) }, cachedConfig.exchanges.settings.tradeInterval ) } } -function stopTrader () { +function stopTrader (cryptoCode) { if (tradeInterval) { clearInterval(tradeInterval) tradeInterval = null - tradesQueue = [] + tradesQueues[cryptoCode] = [] } } -function pollBalance (cryptoCoin, cb) { - var cryptoCode = cryptoCoin.unitCode +function pollBalance (cryptoCode, cb) { logger.debug('[%s] collecting balance', cryptoCode) var walletPlugin = walletPlugins[cryptoCode] @@ -461,8 +455,7 @@ function pollBalance (cryptoCoin, cb) { }) } -function pollRate (cryptoCoin, cb) { - var cryptoCode = cryptoCoin.unitCode +function pollRate (cryptoCode, cb) { logger.debug('[%s] polling for rates (%s)', cryptoCode, tickerPlugin.NAME) var tickerPlugin = tickerPlugins[cryptoCode] @@ -471,7 +464,7 @@ function pollRate (cryptoCoin, cb) { var tickerF = cryptoCode === 'BTC' ? async.apply(tickerPlugin.ticker, currencies) - : async.apply(tickerPlugin.ticker, currencies, cryptoCoin) + : async.apply(tickerPlugin.ticker, currencies, cryptoCode) tickerF(function (err, resRates) { if (err) { @@ -491,8 +484,7 @@ function pollRate (cryptoCoin, cb) { * Getters | Helpers */ -exports.getDeviceRate = function getDeviceRate (cryptoCoin) { - var cryptoCode = cryptoCoin.unitCode +exports.getDeviceRate = function getDeviceRate (cryptoCode) { if (!lastRates[cryptoCode]) return null var lastRate = lastRates[cryptoCode] @@ -501,8 +493,7 @@ exports.getDeviceRate = function getDeviceRate (cryptoCoin) { return lastRate[deviceCurrency] } -exports.getBalance = function getBalance (cryptoCoin) { - var cryptoCode = cryptoCoin.unitCode +exports.getBalance = function getBalance (cryptoCode) { var lastBalance = lastBalances[cryptoCode] if (!lastBalance) return null @@ -513,44 +504,50 @@ exports.getBalance = function getBalance (cryptoCoin) { * Trader functions */ function purchase (trade, cb) { + var cryptoCode = trade.cryptoCode + var traderPlugin = traderPlugins[cryptoCode] traderPlugin.purchase(trade.satoshis, null, function (err) { if (err) return cb(err) - pollBalance('BTC') + pollBalance(cryptoCode) if (typeof cb === 'function') cb() }) } -function consolidateTrades () { +function consolidateTrades (cryptoCode) { // NOTE: value in satoshis stays the same no matter the currency + var cryptoAtoms = tradesQueues[cryptoCode].reduce(function (prev, current) { + return current.cryptoAtoms.plus(prev) + }, new BigNumber(0)) + var consolidatedTrade = { currency: deviceCurrency, - satoshis: tradesQueue.reduce(function (prev, current) { - return prev + current.satoshis - }, 0) + cryptoAtoms: cryptoAtoms, + cryptoCode: cryptoCode } - tradesQueue = [] + tradesQueues[cryptoCode] = [] logger.debug('consolidated: ', JSON.stringify(consolidatedTrade)) return consolidatedTrade } -function executeTrades () { +function executeTrades (cryptoCode) { + var traderPlugin = traderPlugins[cryptoCode] if (!traderPlugin) return logger.debug('checking for trades') - var trade = consolidateTrades() + var trade = consolidateTrades(cryptoCode) - if (trade.satoshis === 0) { + if (trade.cryptoAtoms.eq(0)) { logger.debug('rejecting 0 trade') return } - logger.debug('making a trade: %d', trade.satoshis / SATOSHI_FACTOR) + logger.debug('making a trade: %d', trade.cryptoAtoms.toString()) purchase(trade, function (err) { if (err) { - tradesQueue.push(trade) + tradesQueues[cryptoCode].push(trade) if (err.name !== 'orderTooSmall') logger.error(err) } }) @@ -567,6 +564,6 @@ exports.verifyTx = function verifyTx (data, cb) { idVerifierPlugin.verifyTransaction(data, cb) } -exports.getCryptoCoins = function getCryptoCoins () { - return cryptoCoins +exports.getcryptoCodes = function getcryptoCodes () { + return cryptoCodes } diff --git a/lib/routes.js b/lib/routes.js index fa4dfd7b..c8121280 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -23,7 +23,7 @@ var pids = {} var reboots = {} function buildRates () { - var cryptoCoins = plugins.getCryptoCoins() + var cryptoCodes = plugins.getcryptoCodes() var config = plugins.getConfig() var settings = config.exchanges.settings @@ -31,9 +31,8 @@ function buildRates () { var cashOutCommission = settings.fiatCommission || cashInCommission var rates = {} - cryptoCoins.forEach(function (coin) { - var cryptoCode = coin.unitCode - var rate = plugins.getDeviceRate(coin).rates + cryptoCodes.forEach(function (cryptoCode) { + var rate = plugins.getDeviceRate(cryptoCode).rates rates[cryptoCode] = { cashIn: rate.ask.times(cashInCommission), cashOut: rate.bid.div(cashOutCommission) @@ -44,12 +43,12 @@ function buildRates () { } function buildBalances () { - var cryptoCoins = plugins.getCryptoCoins() + var cryptoCodes = plugins.getcryptoCodes() var balances = {} - cryptoCoins.forEach(function (coin) { - var balance = plugins.fiatBalance(coin) - balances[coin] = balance + cryptoCodes.forEach(function (cryptoCode) { + var balance = plugins.fiatBalance(cryptoCode) + balances[cryptoCode] = balance }) return balances diff --git a/todo.txt b/todo.txt index 7a9f9bc1..d0493c65 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,11 @@ -- rethink scale names: is unit satoshi or btc? cryptoAtom, cryptoAtom -- cryptoCoin is record, not code - specify crypto and fiat for trades - make sure ticker is in full coins - getDeviceRate should return bignumber +- test with l-m + +backwards compatibility: + +- new l-m must be backwards compatible with old l-s + +- parse out bignumber when loading in routes +- making a trade -- convert to units From f03151d6707060b9c59c589862914091fc2aa795 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 5 Apr 2016 17:53:42 +0100 Subject: [PATCH 18/34] WIP --- lib/plugins.js | 19 ++++++++----------- todo.txt | 2 ++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index db66437b..c92f9ab3 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -332,16 +332,8 @@ exports.trade = function trade (session, rawTrade, cb) { return db.recordBill(session, newRawTrade, cb) } - var tx = { - txId: rawTrade.txId, - fiat: 0, - satoshis: 0, - toAddress: rawTrade.toAddress, - currencyCode: rawTrade.currency - } - async.parallel([ - async.apply(db.addOutgoingPending, session, tx.currencyCode, tx.toAddress), + async.apply(db.addOutgoingPending, session, rawTrade.currency, rawTrade.toAddress), async.apply(db.recordBill, session, rawTrade) ], cb) } @@ -506,7 +498,12 @@ exports.getBalance = function getBalance (cryptoCode) { function purchase (trade, cb) { var cryptoCode = trade.cryptoCode var traderPlugin = traderPlugins[cryptoCode] - traderPlugin.purchase(trade.satoshis, null, function (err) { + var opts = { + cryptoCode: cryptoCode, + fiat: deviceCurrency + } + + traderPlugin.purchase(trade.cryptoAtoms, opts, function (err) { if (err) return cb(err) pollBalance(cryptoCode) if (typeof cb === 'function') cb() @@ -514,7 +511,7 @@ function purchase (trade, cb) { } function consolidateTrades (cryptoCode) { - // NOTE: value in satoshis stays the same no matter the currency + // NOTE: value in cryptoAtoms stays the same no matter the currency var cryptoAtoms = tradesQueues[cryptoCode].reduce(function (prev, current) { return current.cryptoAtoms.plus(prev) }, new BigNumber(0)) diff --git a/todo.txt b/todo.txt index d0493c65..c3230c55 100644 --- a/todo.txt +++ b/todo.txt @@ -9,3 +9,5 @@ backwards compatibility: - parse out bignumber when loading in routes - making a trade -- convert to units +- clean up db stuff satoshis/cryptoAtoms +- clean up other stuff From d6ec60db557e27e63e67394be5462772c6359652 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Wed, 6 Apr 2016 17:25:43 +0100 Subject: [PATCH 19/34] WIP -- running with BTC only --- lib/plugins.js | 14 +++++++++++--- lib/routes.js | 11 +++++++++-- todo.txt | 4 ---- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index c92f9ab3..6c673b98 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -119,6 +119,8 @@ function loadOrConfigPlugin (pluginHandle, pluginType, cryptoCode, currency, ? cachedConfig.exchanges.plugins.current[pluginType] : cachedConfig.exchanges.plugins.current[cryptoCode][pluginType] + currentlyUsedPlugins[cryptoCode] = currentlyUsedPlugins[cryptoCode] || {} + var pluginChanged = currentlyUsedPlugins[cryptoCode][pluginType] !== currentName if (!currentName) pluginHandle = null @@ -149,7 +151,7 @@ exports.configure = function configure (config) { cachedConfig = config deviceCurrency = config.exchanges.settings.currency - cryptoCodes = config.exchanges.settings.coins || 'BTC' + cryptoCodes = config.exchanges.settings.coins || ['BTC'] cryptoCodes.forEach(function (cryptoCode) { // TICKER [required] configure (or load) @@ -448,8 +450,8 @@ function pollBalance (cryptoCode, cb) { } function pollRate (cryptoCode, cb) { - logger.debug('[%s] polling for rates (%s)', cryptoCode, tickerPlugin.NAME) var tickerPlugin = tickerPlugins[cryptoCode] + logger.debug('[%s] polling for rates (%s)', cryptoCode, tickerPlugin.NAME) var currencies = deviceCurrency if (typeof currencies === 'string') currencies = [currencies] @@ -464,8 +466,14 @@ function pollRate (cryptoCode, cb) { return cb && cb(err) } - logger.debug('got rates: %j', resRates) resRates.timestamp = new Date() + var rates = resRates[deviceCurrency].rates + if (rates) { + rates.ask = rates.ask && new BigNumber(rates.ask) + rates.bid = rates.bid && new BigNumber(rates.bid) + } + logger.debug('got rates: %j', resRates) + lastRates[cryptoCode] = resRates return cb && cb(null, lastRates) diff --git a/lib/routes.js b/lib/routes.js index c8121280..8c46c35c 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,5 +1,6 @@ 'use strict' +var BigNumber = require('bignumber.js') var logger = require('./logger') var mock = false @@ -101,14 +102,20 @@ function poll (req, res) { } function trade (req, res) { - plugins.trade(session(req), req.body, function (err) { + var tx = req.body + tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) + + plugins.trade(session(req), tx, function (err) { var statusCode = err ? 500 : 201 res.json(statusCode, {err: err}) }) } function send (req, res) { - plugins.sendCoins(session(req), req.body, function (err, status) { + var tx = req.body + tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) + + plugins.sendCoins(session(req), tx, function (err, status) { // TODO: use status.statusCode here after confirming machine compatibility // FIX: (joshm) set txHash to status.txId instead of previous status.txHash which wasn't being set // Need to clean up txHash vs txId diff --git a/todo.txt b/todo.txt index c3230c55..ec0e1d8f 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,3 @@ -- specify crypto and fiat for trades -- make sure ticker is in full coins - getDeviceRate should return bignumber - test with l-m @@ -7,7 +5,5 @@ backwards compatibility: - new l-m must be backwards compatible with old l-s -- parse out bignumber when loading in routes -- making a trade -- convert to units - clean up db stuff satoshis/cryptoAtoms - clean up other stuff From 184d30209a2cf3eeed299a02344a6d0bad2d18c3 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 02:58:20 +0100 Subject: [PATCH 20/34] WIP --- lib/plugins.js | 54 ++++++++++++++++++++++++------------- lib/postgresql_interface.js | 14 +++++----- lib/routes.js | 8 +++--- todo.txt | 27 +++++++++++++++++++ 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 6c673b98..072454eb 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -40,6 +40,11 @@ var lastRates = {} var tradesQueues = {} +var coins = { + BTC: {unitScale: 8}, + ETH: {unitScale: 18} +} + // that's basically a constructor exports.init = function init (databaseHandle) { if (!databaseHandle) { @@ -53,7 +58,7 @@ function loadPlugin (name, config) { // plugins definitions var moduleMethods = { ticker: ['ticker'], - trader: ['balance', 'purchase', 'sell'], + trader: ['purchase', 'sell'], wallet: ['balance', 'sendBitcoins', 'newAddress'], idVerifier: ['verifyUser', 'verifyTransaction'], info: ['checkAddress'] @@ -151,7 +156,7 @@ exports.configure = function configure (config) { cachedConfig = config deviceCurrency = config.exchanges.settings.currency - cryptoCodes = config.exchanges.settings.coins || ['BTC'] + cryptoCodes = config.exchanges.settings.coins || ['BTC', 'ETH'] cryptoCodes.forEach(function (cryptoCode) { // TICKER [required] configure (or load) @@ -248,21 +253,29 @@ function _sendCoins (toAddress, cryptoAtoms, cryptoCode, cb) { if (cryptoCode === 'BTC') { walletPlugin.sendBitcoins(toAddress, cryptoAtoms, transactionFee, cb) } else { - walletPlugin.sendCoins(toAddress, cryptoAtoms, cryptoCode, transactionFee, cb) + walletPlugin.sendBitcoins(toAddress, cryptoAtoms, cryptoCode, transactionFee, cb) } } function executeTx (session, tx, authority, cb) { db.addOutgoingTx(session, tx, function (err, toSend) { - if (err) return cb(err) - var cryptoAtomsToSend = toSend.cryptoAtoms + if (err) { + logger.error(err) + return cb(err) + } + var cryptoAtomsToSend = toSend.satoshis if (cryptoAtomsToSend === 0) { + logger.debug('No cryptoAtoms to send') return cb(null, {statusCode: 204, txId: tx.txId, txHash: null}) } - _sendCoins(tx.toAddress, cryptoAtomsToSend, function (_err, txHash) { + var cryptoCode = tx.cryptoCode + _sendCoins(tx.toAddress, cryptoAtomsToSend, cryptoCode, function (_err, txHash) { var fee = null // Need to fill this out in plugins - if (_err) toSend = {cryptoAtoms: new BigNumber(0), fiat: 0} + if (_err) { + logger.error(_err) + toSend = {cryptoAtoms: new BigNumber(0), fiat: 0} + } db.sentCoins(session, tx, authority, toSend, fee, _err, txHash) if (_err) return cb(_err) @@ -291,6 +304,7 @@ function reapTx (row) { satoshis: row.satoshis, toAddress: row.to_address, currencyCode: row.currency_code, + cryptoCode: row.crypto_code, incoming: row.incoming } if (!row.incoming) reapOutgoingTx(session, tx) @@ -314,6 +328,8 @@ function reapTxs () { // TODO: Run these in parallel and return success exports.trade = function trade (session, rawTrade, cb) { + logger.debug('DEBUG2') + // TODO: move this to DB, too // add bill to trader queue (if trader is enabled) var cryptoCode = rawTrade.cryptoCode || 'BTC' @@ -328,14 +344,18 @@ exports.trade = function trade (session, rawTrade, cb) { }) } + logger.debug('DEBUG3') + if (!rawTrade.toAddress) { var newRawTrade = _.cloneDeep(rawTrade) newRawTrade.toAddress = 'remit' return db.recordBill(session, newRawTrade, cb) } + logger.debug('DEBUG1') + async.parallel([ - async.apply(db.addOutgoingPending, session, rawTrade.currency, rawTrade.toAddress), + async.apply(db.addOutgoingPending, session, rawTrade.currency, rawTrade.cryptoCode, rawTrade.toAddress), async.apply(db.recordBill, session, rawTrade) ], cb) } @@ -376,17 +396,14 @@ exports.fiatBalance = function fiatBalance (cryptoCode) { if (!rawRate || !lastBalance) return null // The rate is actually our commission times real rate. - var rate = commission * rawRate + var rate = rawRate.times(commission) // `lowBalanceMargin` is our safety net. It's a number > 1, and we divide // all our balances by it to provide a safety margin. var lowBalanceMargin = cachedConfig.exchanges.settings.lowBalanceMargin - // `balance.transferBalance` is the balance of our transfer account (the one - // we use to send Bitcoins to clients) in satoshis. - var transferBalance = lastBalance.transferBalance - - var fiatTransferBalance = (transferBalance * rate) / lowBalanceMargin + var unitScale = new BigNumber(10).pow(coins[cryptoCode].unitScale) + var fiatTransferBalance = lastBalance.div(unitScale).times(rate).div(lowBalanceMargin) return fiatTransferBalance } @@ -441,9 +458,9 @@ function pollBalance (cryptoCode, cb) { return cb && cb(err) } - logger.debug('[%s] Balance update:', cryptoCode, balance) + logger.debug('[%s] Balance update: %j', cryptoCode, balance) balance.timestamp = Date.now() - lastBalances[cryptoCode] = balance + lastBalances[cryptoCode] = new BigNumber(balance[cryptoCode]) return cb && cb(null, lastBalances) }) @@ -472,7 +489,7 @@ function pollRate (cryptoCode, cb) { rates.ask = rates.ask && new BigNumber(rates.ask) rates.bid = rates.bid && new BigNumber(rates.bid) } - logger.debug('got rates: %j', resRates) + logger.debug('[%s] got rates: %j', cryptoCode, resRates) lastRates[cryptoCode] = resRates @@ -495,9 +512,8 @@ exports.getDeviceRate = function getDeviceRate (cryptoCode) { exports.getBalance = function getBalance (cryptoCode) { var lastBalance = lastBalances[cryptoCode] - if (!lastBalance) return null - return lastBalance.transferBalance + return lastBalance } /* diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index ca58093d..bcae7cbd 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -284,6 +284,7 @@ function insertTx(client, session, incoming, tx, satoshis, fiat, stage, 'to_address', 'satoshis', 'currency_code', + 'crypto_code', 'fiat', 'tx_hash', 'error' @@ -298,6 +299,7 @@ function insertTx(client, session, incoming, tx, satoshis, fiat, stage, tx.toAddress, satoshis, tx.currencyCode, + tx.cryptoCode, fiat, tx.txHash, tx.error @@ -322,13 +324,13 @@ function refreshPendingTx(client, session, cb) { }); } -function addPendingTx(client, session, incoming, currencyCode, toAddress, +function addPendingTx(client, session, incoming, currencyCode, cryptoCode, toAddress, satoshis, cb) { var fields = ['device_fingerprint', 'session_id', 'incoming', - 'currency_code', 'to_address', 'satoshis']; + 'currency_code', 'crypto_code', 'to_address', 'satoshis']; var sql = getInsertQuery('pending_transactions', fields, false); var values = [session.fingerprint, session.id, incoming, currencyCode, - toAddress, satoshis]; + cryptoCode, toAddress, satoshis]; query(client, sql, values, function(err) { cb(err); }); @@ -402,14 +404,14 @@ function ensureNotFinal(client, session, cb) { } exports.addOutgoingPending = function addOutgoingPending(session, currencyCode, - toAddress, cb) { + cryptoCode, toAddress, cb) { connect(function(cerr, client, done) { if (cerr) return cb(cerr); async.series([ async.apply(silentQuery, client, 'BEGIN', null), async.apply(ensureNotFinal, client, session), - async.apply(addPendingTx, client, session, false, currencyCode, toAddress, + async.apply(addPendingTx, client, session, false, currencyCode, cryptoCode, toAddress, 0) ], function(err) { if (err) { @@ -442,7 +444,7 @@ exports.addInitialIncoming = function addInitialIncoming(session, tx, cb) { async.series([ async.apply(silentQuery, client, 'BEGIN', null), async.apply(addPendingTx, client, session, true, tx.currencyCode, - tx.toAddress, tx.satoshis), + tx.cryptoCode, tx.toAddress, tx.satoshis), async.apply(insertIncoming, client, session, tx, tx.satoshis, tx.fiat, 'initial_request', 'pending') ], function(err) { diff --git a/lib/routes.js b/lib/routes.js index 8c46c35c..c6520654 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -46,13 +46,13 @@ function buildRates () { function buildBalances () { var cryptoCodes = plugins.getcryptoCodes() - var balances = {} + var _balances = {} cryptoCodes.forEach(function (cryptoCode) { var balance = plugins.fiatBalance(cryptoCode) - balances[cryptoCode] = balance + _balances[cryptoCode] = balance }) - return balances + return _balances } function poll (req, res) { @@ -106,6 +106,7 @@ function trade (req, res) { tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) plugins.trade(session(req), tx, function (err) { + if (err) logger.error(err) var statusCode = err ? 500 : 201 res.json(statusCode, {err: err}) }) @@ -235,7 +236,6 @@ function init (localConfig) { }) localApp.post('/reboot', function (req, res) { - console.log('DEBUG1') var pid = req.body.pid var fingerprint = req.body.fingerprint console.log('pid: %s, fingerprint: %s', pid, fingerprint) diff --git a/todo.txt b/todo.txt index ec0e1d8f..f2fa57a4 100644 --- a/todo.txt +++ b/todo.txt @@ -7,3 +7,30 @@ backwards compatibility: - clean up db stuff satoshis/cryptoAtoms - clean up other stuff + +- add 'ETH' to config in ssu crypto: config.exchanges.settings.coins + +[2016-04-06T19:58:17.827Z] ERROR: lamassu-server/39374 on MacBook-Pro: null value in column "satoshis" violates not-null constraint + error: null value in column "satoshis" violates not-null constraint + at Connection.parseE (/Users/josh/projects/lamassu-server/node_modules/pg/lib/connection.js:539:11) + at Connection.parseMessage (/Users/josh/projects/lamassu-server/node_modules/pg/lib/connection.js:366:17) + at Socket. (/Users/josh/projects/lamassu-server/node_modules/pg/lib/connection.js:105:22) + at Socket.emit (events.js:95:17) + at Socket. (_stream_readable.js:765:14) + at Socket.emit (events.js:92:17) + at emitReadable_ (_stream_readable.js:427:10) + at emitReadable (_stream_readable.js:423:5) + at readableAddChunk (_stream_readable.js:166:9) + at Socket.Readable.push (_stream_readable.js:128:10) + +alter table transactions alter satoshis TYPE bigint; +alter table transactions add crypto_code text default 'BTC'; +alter table pending_transactions add crypto_code text default 'BTC'; +alter table pending_transactions alter satoshis TYPE bigint; +alter table bills add crypto_code text default 'BTC'; +alter table bills alter satoshis TYPE bigint; + +- get cryptoCode from pending tx +- update cryptoCode for bills insert +- insert correct satoshis in pending tx -- actually this is supposed to be 0 +- fix satoshi math in postgres module by moving to bignumber math From 14497d81c604b97012150c017a429f1cb485b120 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 12:00:56 +0100 Subject: [PATCH 21/34] WIP --- lib/postgresql_interface.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index bcae7cbd..0d514e60 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -3,6 +3,7 @@ // TODO: Consider using serializable transactions for true ACID +var BigNumber = require('bignumber.js') var pg = require('pg'); var async = require('async'); var _ = require('lodash'); @@ -173,27 +174,30 @@ function billsAndTxs(client, session, cb) { // we need to parse, since we know these won't be huge numbers. cb(null, { billsFiat: parseInt(results[0].rows[0].fiat), - billsSatoshis: parseInt(results[0].rows[0].satoshis), + billsSatoshis: new BigNumber(results[0].rows[0].satoshis), txFiat: parseInt(results[1].rows[0].fiat), - txSatoshis: parseInt(results[1].rows[0].satoshis) + txSatoshis: new BigNumber(results[1].rows[0].satoshis) }); }); } function computeSendAmount(tx, totals) { var fiatRemaining = (tx.fiat || totals.billsFiat) - totals.txFiat; - var satoshisRemaining = (tx.satoshis || totals.billsSatoshis) - - totals.txSatoshis; + + var satoshisRemaining = tx.satoshis.eq(0) + ? totals.billsSatoshis.minus(totals.txSatoshis) + : tx.satoshis.minus(totals.txSatoshis) + var result = { fiat: fiatRemaining, satoshis: satoshisRemaining }; - if (result.fiat < 0 || result.satoshis < 0) { + if (result.fiat < 0 || result.satoshis.lt(0)) { logger.warn({tx: tx, totals: totals, result: result}, 'computeSendAmount result < 0, this shouldn\'t happen. ' + 'Maybe timeout arrived after machineSend.'); result.fiat = 0; - result.satoshis = 0; + result.satoshis = new BigNumber(0); } return result; } @@ -240,7 +244,7 @@ function insertOutgoingTx(client, session, tx, totals, cb) { var authority = tx.fiat ? 'machine' : 'timeout'; var satoshis = sendAmount.satoshis; var fiat = sendAmount.fiat; - if (satoshis === 0) return cb(null, {satoshis: 0, fiat: 0}); + if (satoshis.eq(0)) return cb(null, {satoshis: new BigNumber(0), fiat: 0}); insertOutgoing(client, session, tx, satoshis, fiat, stage, authority, function(err) { @@ -264,7 +268,7 @@ function insertOutgoingCompleteTx(client, session, tx, cb) { function insertIncoming(client, session, tx, satoshis, fiat, stage, authority, cb) { - var realSatoshis = satoshis || 0; + var realSatoshis = satoshis || new BigNumber(0); insertTx(client, session, true, tx, realSatoshis, fiat, stage, authority, cb); } From f357b6080ecffe15c96064ede36ee7e97fccbb7a Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 12:02:14 +0100 Subject: [PATCH 22/34] changed postgresql_interface.js to standard format --- lib/postgresql_interface.js | 598 ++++++++++++++++++------------------ 1 file changed, 297 insertions(+), 301 deletions(-) diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 0d514e60..e75b2c08 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -1,72 +1,69 @@ /* @flow weak */ -'use strict'; +'use strict' // TODO: Consider using serializable transactions for true ACID var BigNumber = require('bignumber.js') -var pg = require('pg'); -var async = require('async'); -var _ = require('lodash'); +var pg = require('pg') +var async = require('async') +var _ = require('lodash') -var logger = require('./logger'); +var logger = require('./logger') /* function inspect(rec) { - console.log(require('util').inspect(rec, {depth: null, colors: true})); + console.log(require('util').inspect(rec, {depth: null, colors: true})) } */ -var SATOSHI_FACTOR = 1e8; - -function isUniqueViolation(err) { - return err.code === '23505'; +function isUniqueViolation (err) { + return err.code === '23505' } -function isLowSeverity(err) { - return isUniqueViolation(err) || err.severity === 'low'; +function isLowSeverity (err) { + return isUniqueViolation(err) || err.severity === 'low' } -var conString = null; +var conString = null -function rollback(client, done) { - client.query('ROLLBACK', function(err) { - return done(err); - }); +function rollback (client, done) { + client.query('ROLLBACK', function (err) { + return done(err) + }) } -function getInsertQuery(tableName, fields, hasId) { - +function getInsertQuery (tableName, fields, hasId) { // outputs string like: '$1, $2, $3...' with proper No of items - var placeholders = fields.map(function(_, i) { - return '$' + (i + 1); - }).join(', '); + var placeholders = fields.map(function (_, i) { + return '$' + (i + 1) + }).join(', ') var query = 'INSERT INTO ' + tableName + ' (' + fields.join(', ') + ')' + ' VALUES' + - ' (' + placeholders + ')'; + ' (' + placeholders + ')' - if (hasId) query += ' RETURNING id'; + if (hasId) query += ' RETURNING id' - return query; + return query } -exports.init = function init(_conString) { - conString = _conString; +exports.init = function init (_conString) { + conString = _conString if (!conString) { - throw new Error('Postgres connection string is required'); + throw new Error('Postgres connection string is required') } -}; +} -function connect(cb) { - pg.connect(conString, function(err, client, done) { - if (err) logger.error(err); - cb(err, client, done); - }); +function connect (cb) { + pg.connect(conString, function (err, client, done) { + if (err) logger.error(err) + cb(err, client, done) + }) } // logs inputted bill and overall tx status (if available) -exports.recordBill = function recordBill(session, rec, cb) { +exports.recordBill = function recordBill (session, rec, cb) { var fields = [ 'id', 'device_fingerprint', @@ -76,7 +73,7 @@ exports.recordBill = function recordBill(session, rec, cb) { 'device_time', 'satoshis', 'denomination' - ]; + ] var values = [ rec.uuid, @@ -87,198 +84,196 @@ exports.recordBill = function recordBill(session, rec, cb) { rec.deviceTime, rec.satoshis, rec.fiat - ]; + ] - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); - query(client, getInsertQuery('bills', fields, false), values, function(err) { - done(); + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) + query(client, getInsertQuery('bills', fields, false), values, function (err) { + done() if (err && isUniqueViolation(err)) { - logger.warn('Attempt to report bill twice'); - return cb(); + logger.warn('Attempt to report bill twice') + return cb() } - cb(err); - }); - }); -}; - -exports.recordDeviceEvent = function recordDeviceEvent(session, event) { - connect(function(cerr, client, done) { - if (cerr) return; - var sql = 'INSERT INTO device_events (device_fingerprint, event_type, ' + - 'note, device_time) VALUES ($1, $2, $3, $4)'; - var values = [session.fingerprint, event.eventType, event.note, - event.deviceTime]; - client.query(sql, values, done); - }); -}; - -function query(client, queryStr, values, cb) { - if (!cb) { - cb = values; - values = []; - } - - client.query(queryStr, values, function(err, results) { - if (err) { - if (!isLowSeverity(err)) { - console.log(queryStr); - console.log(values); - } - return cb(err); - } - cb(null, results); - }); + cb(err) + }) + }) } -function silentQuery(client, queryStr, values, cb) { +exports.recordDeviceEvent = function recordDeviceEvent (session, event) { + connect(function (cerr, client, done) { + if (cerr) return + var sql = 'INSERT INTO device_events (device_fingerprint, event_type, ' + + 'note, device_time) VALUES ($1, $2, $3, $4)' + var values = [session.fingerprint, event.eventType, event.note, + event.deviceTime] + client.query(sql, values, done) + }) +} + +function query (client, queryStr, values, cb) { if (!cb) { - cb = values; - values = []; + cb = values + values = [] } - client.query(queryStr, values, function(err) { + client.query(queryStr, values, function (err, results) { if (err) { if (!isLowSeverity(err)) { - console.log(queryStr); - console.log(values); + console.log(queryStr) + console.log(values) } - cb(err); + return cb(err) } - cb(); - }); + cb(null, results) + }) +} + +function silentQuery (client, queryStr, values, cb) { + if (!cb) { + cb = values + values = [] + } + + client.query(queryStr, values, function (err) { + if (err) { + if (!isLowSeverity(err)) { + console.log(queryStr) + console.log(values) + } + cb(err) + } + cb() + }) } // OPTIMIZE: No need to query bills if tx.fiat and tx.satoshis are set -function billsAndTxs(client, session, cb) { - var sessionId = session.id; - var fingerprint = session.fingerprint; +function billsAndTxs (client, session, cb) { + var sessionId = session.id + var fingerprint = session.fingerprint var billsQuery = 'SELECT COALESCE(SUM(denomination), 0) as fiat, ' + 'COALESCE(SUM(satoshis), 0) AS satoshis ' + 'FROM bills ' + - 'WHERE device_fingerprint=$1 AND session_id=$2'; - var billsValues = [fingerprint, sessionId]; + 'WHERE device_fingerprint=$1 AND session_id=$2' + var billsValues = [fingerprint, sessionId] var txQuery = 'SELECT COALESCE(SUM(fiat), 0) AS fiat, ' + 'COALESCE(SUM(satoshis), 0) AS satoshis ' + 'FROM transactions ' + - 'WHERE device_fingerprint=$1 AND session_id=$2 AND stage=$3'; - var txValues = [fingerprint, sessionId, 'partial_request']; + 'WHERE device_fingerprint=$1 AND session_id=$2 AND stage=$3' + var txValues = [fingerprint, sessionId, 'partial_request'] async.parallel([ async.apply(query, client, billsQuery, billsValues), async.apply(query, client, txQuery, txValues) - ], function(err, results) { - if (err) return cb(err); + ], function (err, results) { + if (err) return cb(err) // Note: PG SUM function returns int8, which is returned as a string, so // we need to parse, since we know these won't be huge numbers. cb(null, { - billsFiat: parseInt(results[0].rows[0].fiat), + billsFiat: parseInt(results[0].rows[0].fiat, 10), billsSatoshis: new BigNumber(results[0].rows[0].satoshis), - txFiat: parseInt(results[1].rows[0].fiat), + txFiat: parseInt(results[1].rows[0].fiat, 10), txSatoshis: new BigNumber(results[1].rows[0].satoshis) - }); - }); + }) + }) } -function computeSendAmount(tx, totals) { - var fiatRemaining = (tx.fiat || totals.billsFiat) - totals.txFiat; +function computeSendAmount (tx, totals) { + var fiatRemaining = (tx.fiat || totals.billsFiat) - totals.txFiat var satoshisRemaining = tx.satoshis.eq(0) - ? totals.billsSatoshis.minus(totals.txSatoshis) - : tx.satoshis.minus(totals.txSatoshis) + ? totals.billsSatoshis.minus(totals.txSatoshis) + : tx.satoshis.minus(totals.txSatoshis) var result = { fiat: fiatRemaining, satoshis: satoshisRemaining - }; + } if (result.fiat < 0 || result.satoshis.lt(0)) { logger.warn({tx: tx, totals: totals, result: result}, - 'computeSendAmount result < 0, this shouldn\'t happen. ' + - 'Maybe timeout arrived after machineSend.'); - result.fiat = 0; - result.satoshis = new BigNumber(0); + "computeSendAmount result < 0, this shouldn't happen. " + + 'Maybe timeout arrived after machineSend.') + result.fiat = 0 + result.satoshis = new BigNumber(0) } - return result; + return result } -exports.removeOldPending = function removeOldPending(timeoutMS) { - connect(function(cerr, client, done) { - if (cerr) return; +exports.removeOldPending = function removeOldPending (timeoutMS) { + connect(function (cerr, client, done) { + if (cerr) return var sql = 'DELETE FROM pending_transactions ' + - 'WHERE incoming AND extract(EPOCH FROM now() - updated) > $1'; - var timeoutS = timeoutMS / 1000; - var values = [timeoutS]; - query(client, sql, values, function(err) { - done(); - if (err) logger.error(err); - }); - }); -}; + 'WHERE incoming AND extract(EPOCH FROM now() - updated) > $1' + var timeoutS = timeoutMS / 1000 + var values = [timeoutS] + query(client, sql, values, function (err) { + done() + if (err) logger.error(err) + }) + }) +} -exports.pendingTxs = function pendingTxs(timeoutMS, cb) { - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); +exports.pendingTxs = function pendingTxs (timeoutMS, cb) { + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) var sql = 'SELECT * ' + 'FROM pending_transactions ' + 'WHERE (incoming OR extract(EPOCH FROM now() - updated) > $1) ' + - 'ORDER BY updated ASC'; - var timeoutS = timeoutMS / 1000; - var values = [timeoutS]; - query(client, sql, values, function(err, results) { - done(); - cb(err, results); - }); - }); -}; - -function removePendingTx(client, session, cb) { - var sql = 'DELETE FROM pending_transactions ' + - 'WHERE device_fingerprint=$1 AND session_id=$2'; - silentQuery(client, sql, [session.fingerprint, session.id], cb); + 'ORDER BY updated ASC' + var timeoutS = timeoutMS / 1000 + var values = [timeoutS] + query(client, sql, values, function (err, results) { + done() + cb(err, results) + }) + }) } -function insertOutgoingTx(client, session, tx, totals, cb) { - var sendAmount = computeSendAmount(tx, totals); - var stage = 'partial_request'; - var authority = tx.fiat ? 'machine' : 'timeout'; - var satoshis = sendAmount.satoshis; - var fiat = sendAmount.fiat; - if (satoshis.eq(0)) return cb(null, {satoshis: new BigNumber(0), fiat: 0}); +function removePendingTx (client, session, cb) { + var sql = 'DELETE FROM pending_transactions ' + + 'WHERE device_fingerprint=$1 AND session_id=$2' + silentQuery(client, sql, [session.fingerprint, session.id], cb) +} + +function insertOutgoingTx (client, session, tx, totals, cb) { + var sendAmount = computeSendAmount(tx, totals) + var stage = 'partial_request' + var authority = tx.fiat ? 'machine' : 'timeout' + var satoshis = sendAmount.satoshis + var fiat = sendAmount.fiat + if (satoshis.eq(0)) return cb(null, {satoshis: new BigNumber(0), fiat: 0}) insertOutgoing(client, session, tx, satoshis, fiat, stage, authority, - function(err) { - - if (err) return cb(err); - cb(null, {satoshis: satoshis, fiat: fiat}); - }); + function (err) { + if (err) return cb(err) + cb(null, {satoshis: satoshis, fiat: fiat}) + }) } -function insertOutgoingCompleteTx(client, session, tx, cb) { - +function insertOutgoingCompleteTx (client, session, tx, cb) { // Only relevant for machine source transactions, not timeouts - if (!tx.fiat) return cb(); + if (!tx.fiat) return cb() - var stage = 'final_request'; - var authority = 'machine'; - var satoshis = tx.satoshis; - var fiat = tx.fiat; - insertOutgoing(client, session, tx, satoshis, fiat, stage, authority, cb); + var stage = 'final_request' + var authority = 'machine' + var satoshis = tx.satoshis + var fiat = tx.fiat + insertOutgoing(client, session, tx, satoshis, fiat, stage, authority, cb) } -function insertIncoming(client, session, tx, satoshis, fiat, stage, authority, - cb) { - var realSatoshis = satoshis || new BigNumber(0); - insertTx(client, session, true, tx, realSatoshis, fiat, stage, authority, cb); +function insertIncoming (client, session, tx, satoshis, fiat, stage, authority, + cb) { + var realSatoshis = satoshis || new BigNumber(0) + insertTx(client, session, true, tx, realSatoshis, fiat, stage, authority, cb) } -function insertOutgoing(client, session, tx, satoshis, fiat, stage, authority, - cb) { - insertTx(client, session, false, tx, satoshis, fiat, stage, authority, cb); +function insertOutgoing (client, session, tx, satoshis, fiat, stage, authority, + cb) { + insertTx(client, session, false, tx, satoshis, fiat, stage, authority, cb) } -function insertTx(client, session, incoming, tx, satoshis, fiat, stage, - authority, cb) { +function insertTx (client, session, incoming, tx, satoshis, fiat, stage, + authority, cb) { var fields = [ 'session_id', 'stage', @@ -292,7 +287,7 @@ function insertTx(client, session, incoming, tx, satoshis, fiat, stage, 'fiat', 'tx_hash', 'error' - ]; + ] var values = [ session.id, @@ -307,226 +302,227 @@ function insertTx(client, session, incoming, tx, satoshis, fiat, stage, fiat, tx.txHash, tx.error - ]; + ] query(client, getInsertQuery('transactions', fields, true), values, - function(err, results) { - if (err) return cb(err); - cb(null, results.rows[0].id); - }); + function (err, results) { + if (err) return cb(err) + cb(null, results.rows[0].id) + }) } -function refreshPendingTx(client, session, cb) { +function refreshPendingTx (client, session, cb) { var sql = 'UPDATE pending_transactions SET updated=now() ' + - 'WHERE device_fingerprint=$1 AND session_id=$2'; - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); - query(client, sql, [session.fingerprint, session.id], function(err) { - done(err); - cb(err); - }); - }); + 'WHERE device_fingerprint=$1 AND session_id=$2' + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) + query(client, sql, [session.fingerprint, session.id], function (err) { + done(err) + cb(err) + }) + }) } -function addPendingTx(client, session, incoming, currencyCode, cryptoCode, toAddress, - satoshis, cb) { +function addPendingTx (client, session, incoming, currencyCode, cryptoCode, toAddress, + satoshis, cb) { var fields = ['device_fingerprint', 'session_id', 'incoming', - 'currency_code', 'crypto_code', 'to_address', 'satoshis']; - var sql = getInsertQuery('pending_transactions', fields, false); + 'currency_code', 'crypto_code', 'to_address', 'satoshis'] + var sql = getInsertQuery('pending_transactions', fields, false) var values = [session.fingerprint, session.id, incoming, currencyCode, - cryptoCode, toAddress, satoshis]; - query(client, sql, values, function(err) { - cb(err); - }); + cryptoCode, toAddress, satoshis] + query(client, sql, values, function (err) { + cb(err) + }) } -function buildOutgoingTx(client, session, tx, cb) { +function buildOutgoingTx (client, session, tx, cb) { async.waterfall([ async.apply(billsAndTxs, client, session), async.apply(insertOutgoingTx, client, session, tx) - ], cb); + ], cb) } // Calling function should only send bitcoins if result.satoshisToSend > 0 -exports.addOutgoingTx = function addOutgoingTx(session, tx, cb) { - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); +exports.addOutgoingTx = function addOutgoingTx (session, tx, cb) { + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) async.series([ async.apply(silentQuery, client, 'BEGIN'), async.apply(silentQuery, client, 'LOCK TABLE transactions'), async.apply(insertOutgoingCompleteTx, client, session, tx), async.apply(removePendingTx, client, session), async.apply(buildOutgoingTx, client, session, tx) - ], function(err, results) { + ], function (err, results) { if (err) { - rollback(client, done); - return cb(err); + rollback(client, done) + return cb(err) } - silentQuery(client, 'COMMIT', [], function() { - done(); - var toSend = results[4]; - cb(null, toSend); - }); - }); - }); -}; + silentQuery(client, 'COMMIT', [], function () { + done() + var toSend = results[4] + cb(null, toSend) + }) + }) + }) +} -exports.sentCoins = function sentCoins(session, tx, authority, toSend, fee, +exports.sentCoins = function sentCoins (session, tx, authority, toSend, fee, error, txHash) { - connect(function(cerr, client, done) { - if (cerr) return logger.error(cerr); + connect(function (cerr, client, done) { + if (cerr) return logger.error(cerr) - var newTx = _.clone(tx); - newTx.txHash = txHash; - newTx.error = error; + var newTx = _.clone(tx) + newTx.txHash = txHash + newTx.error = error insertOutgoing(client, session, newTx, toSend.satoshis, toSend.fiat, - 'partial_send', authority, function(err) { - done(); - if (err) logger.error(err); - }); - }); -}; + 'partial_send', authority, function (err) { + done() + if (err) logger.error(err) + }) + }) +} -function ensureNotFinal(client, session, cb) { +function ensureNotFinal (client, session, cb) { var sql = 'SELECT id FROM transactions ' + 'WHERE device_fingerprint=$1 AND session_id=$2 AND incoming=$3 ' + 'AND stage=$4' + - 'LIMIT 1'; - var values = [session.fingerprint, session.id, false, 'final_request']; + 'LIMIT 1' + var values = [session.fingerprint, session.id, false, 'final_request'] - client.query(sql, values, function(err, results) { - var error; - if (err) return cb(err); + client.query(sql, values, function (err, results) { + var error + if (err) return cb(err) if (results.rows.length > 0) { - error = new Error('Final request already exists'); - error.name = 'staleBill'; - error.severity = 'low'; - return cb(error); + error = new Error('Final request already exists') + error.name = 'staleBill' + error.severity = 'low' + return cb(error) } - cb(); - }); + cb() + }) } -exports.addOutgoingPending = function addOutgoingPending(session, currencyCode, - cryptoCode, toAddress, cb) { - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); +exports.addOutgoingPending = function addOutgoingPending (session, currencyCode, + cryptoCode, toAddress, cb) { + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) async.series([ async.apply(silentQuery, client, 'BEGIN', null), async.apply(ensureNotFinal, client, session), async.apply(addPendingTx, client, session, false, currencyCode, cryptoCode, toAddress, 0) - ], function(err) { + ], function (err) { if (err) { - return rollback(client, function(rberr) { - done(rberr); + return rollback(client, function (rberr) { + done(rberr) if (isUniqueViolation(err)) { // Pending tx exists, refresh it. - return refreshPendingTx(client, session, cb); + return refreshPendingTx(client, session, cb) } if (err.name === 'staleBill') { - logger.info('Received a bill insert after send coins request'); - return cb(); + logger.info('Received a bill insert after send coins request') + return cb() } - logger.error(err); - return cb(err); - }); + logger.error(err) + return cb(err) + }) } - silentQuery(client, 'COMMIT', null, function() { - done(); - cb(); - }); - }); - }); -}; + silentQuery(client, 'COMMIT', null, function () { + done() + cb() + }) + }) + }) +} -exports.addInitialIncoming = function addInitialIncoming(session, tx, cb) { - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); +exports.addInitialIncoming = function addInitialIncoming (session, tx, cb) { + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) async.series([ async.apply(silentQuery, client, 'BEGIN', null), async.apply(addPendingTx, client, session, true, tx.currencyCode, tx.cryptoCode, tx.toAddress, tx.satoshis), async.apply(insertIncoming, client, session, tx, tx.satoshis, tx.fiat, 'initial_request', 'pending') - ], function(err) { + ], function (err) { if (err) { - rollback(client, done); - return cb(err); + rollback(client, done) + return cb(err) } - silentQuery(client, 'COMMIT', null, function() { - done(); - cb(); - }); - }); - }); -}; + silentQuery(client, 'COMMIT', null, function () { + done() + cb() + }) + }) + }) +} -function insertDispense(client, session, tx, cartridges, transactionId, cb) { +function insertDispense (client, session, tx, cartridges, transactionId, cb) { var fields = [ 'device_fingerprint', 'transaction_id', 'dispense1', 'reject1', 'count1', 'dispense2', 'reject2', 'count2', 'refill', 'error' - ]; + ] - var sql = getInsertQuery('dispenses', fields, true); + var sql = getInsertQuery('dispenses', fields, true) - var dispense1 = tx.bills[0].actualDispense; - var dispense2 = tx.bills[1].actualDispense; - var reject1 = tx.bills[0].rejected; - var reject2 = tx.bills[1].rejected; - var count1 = cartridges[0].count; - var count2 = cartridges[1].count; + var dispense1 = tx.bills[0].actualDispense + var dispense2 = tx.bills[1].actualDispense + var reject1 = tx.bills[0].rejected + var reject2 = tx.bills[1].rejected + var count1 = cartridges[0].count + var count2 = cartridges[1].count var values = [ session.fingerprint, transactionId, dispense1, reject1, count1, dispense2, reject2, count2, false, tx.error - ]; - client.query(sql, values, cb); + ] + client.query(sql, values, cb) } -exports.addDispense = function addDispense(session, tx, cartridges) { - connect(function(cerr, client, done) { - if (cerr) return; +exports.addDispense = function addDispense (session, tx, cartridges) { + connect(function (cerr, client, done) { + if (cerr) return async.waterfall([ async.apply(insertIncoming, client, session, tx, 0, tx.fiat, 'dispense', 'authorized'), async.apply(insertDispense, client, session, tx, cartridges) - ], function(err) { - done(); - if (err) logger.error(err); - }); - }); -}; + ], function (err) { + done() + if (err) logger.error(err) + }) + }) +} -exports.cartridgeCounts = function cartridgeCounts(session, cb) { - connect(function(cerr, client, done) { - if (cerr) return cb(cerr); +exports.cartridgeCounts = function cartridgeCounts (session, cb) { + connect(function (cerr, client, done) { + if (cerr) return cb(cerr) var sql = 'SELECT id, count1, count2 FROM dispenses ' + 'WHERE device_fingerprint=$1 AND refill=$2 ' + - 'ORDER BY id DESC LIMIT 1'; - query(client, sql, [session.fingerprint, true], function(err, results) { - done(); - if (err) return cb(err); - var counts = results.rows.length === 1 ? - [results.rows[0].count1, results.rows[0].count2] : - [0, 0]; - cb(null, {id: results.rows[0].id, counts: counts}); - }); - }); -}; + 'ORDER BY id DESC LIMIT 1' + query(client, sql, [session.fingerprint, true], function (err, results) { + done() + if (err) return cb(err) + var counts = results.rows.length === 1 + ? [results.rows[0].count1, results.rows[0].count2] + : [0, 0] + + cb(null, {id: results.rows[0].id, counts: counts}) + }) + }) +} /* -exports.init('postgres://lamassu:lamassu@localhost/lamassu'); +exports.init('postgres://lamassu:lamassu@localhost/lamassu') connect(function(err, client, done) { - var sql = 'select * from transactions where id=$1'; + var sql = 'select * from transactions where id=$1' query(client, sql, [130], function(_err, results) { - done(); - console.dir(results.rows[0]); - }); -}); + done() + console.dir(results.rows[0]) + }) +}) */ From e2d619c56da90b74074d25b1dd0f7a814aecd8fe Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 13:25:34 +0100 Subject: [PATCH 23/34] cash-in working --- lib/plugins.js | 4 ++-- lib/postgresql_interface.js | 6 +++--- lib/routes.js | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 072454eb..64980173 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -251,7 +251,7 @@ function _sendCoins (toAddress, cryptoAtoms, cryptoCode, cb) { var walletPlugin = walletPlugins[cryptoCode] var transactionFee = cachedConfig.exchanges.settings.transactionFee if (cryptoCode === 'BTC') { - walletPlugin.sendBitcoins(toAddress, cryptoAtoms, transactionFee, cb) + walletPlugin.sendBitcoins(toAddress, cryptoAtoms.truncated().toNumber(), transactionFee, cb) } else { walletPlugin.sendBitcoins(toAddress, cryptoAtoms, cryptoCode, transactionFee, cb) } @@ -301,7 +301,7 @@ function reapTx (row) { var session = {fingerprint: row.device_fingerprint, id: row.session_id} var tx = { fiat: 0, - satoshis: row.satoshis, + satoshis: new BigNumber(row.satoshis), toAddress: row.to_address, currencyCode: row.currency_code, cryptoCode: row.crypto_code, diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index e75b2c08..e27f6487 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -182,8 +182,8 @@ function computeSendAmount (tx, totals) { var fiatRemaining = (tx.fiat || totals.billsFiat) - totals.txFiat var satoshisRemaining = tx.satoshis.eq(0) - ? totals.billsSatoshis.minus(totals.txSatoshis) - : tx.satoshis.minus(totals.txSatoshis) + ? totals.billsSatoshis.minus(totals.txSatoshis) + : tx.satoshis.minus(totals.txSatoshis) var result = { fiat: fiatRemaining, @@ -296,7 +296,7 @@ function insertTx (client, session, incoming, tx, satoshis, fiat, stage, incoming, session.fingerprint, tx.toAddress, - satoshis, + satoshis.toString(), tx.currencyCode, tx.cryptoCode, fiat, diff --git a/lib/routes.js b/lib/routes.js index c6520654..ad298f9a 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -115,6 +115,7 @@ function trade (req, res) { function send (req, res) { var tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) + tx.satoshis = tx.cryptoAtoms plugins.sendCoins(session(req), tx, function (err, status) { // TODO: use status.statusCode here after confirming machine compatibility From e227716ad94ce12270b956f31cf1d5aecbc4cf23 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 16:17:33 +0100 Subject: [PATCH 24/34] cash out works for btc --- lib/postgresql_interface.js | 2 +- lib/routes.js | 4 ++++ todo.txt | 3 --- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index e27f6487..7a089448 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -329,7 +329,7 @@ function addPendingTx (client, session, incoming, currencyCode, cryptoCode, toAd 'currency_code', 'crypto_code', 'to_address', 'satoshis'] var sql = getInsertQuery('pending_transactions', fields, false) var values = [session.fingerprint, session.id, incoming, currencyCode, - cryptoCode, toAddress, satoshis] + cryptoCode, toAddress, satoshis.toString()] query(client, sql, values, function (err) { cb(err) }) diff --git a/lib/routes.js b/lib/routes.js index ad298f9a..74213fc8 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -132,6 +132,10 @@ function send (req, res) { function cashOut (req, res) { logger.info({tx: req.body, cmd: 'cashOut'}) + var tx = req.body + tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) + tx.satoshis = tx.cryptoAtoms + plugins.cashOut(session(req), req.body, function (err, bitcoinAddress) { if (err) logger.error(err) diff --git a/todo.txt b/todo.txt index f2fa57a4..82026517 100644 --- a/todo.txt +++ b/todo.txt @@ -30,7 +30,4 @@ alter table pending_transactions alter satoshis TYPE bigint; alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; -- get cryptoCode from pending tx - update cryptoCode for bills insert -- insert correct satoshis in pending tx -- actually this is supposed to be 0 -- fix satoshi math in postgres module by moving to bignumber math From 4e5b1173d6813928c573fa6f2b2a869ab3e25177 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 16:23:15 +0100 Subject: [PATCH 25/34] record cryptoCode for bills table --- lib/postgresql_interface.js | 2 ++ todo.txt | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 7a089448..779dc33e 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -68,6 +68,7 @@ exports.recordBill = function recordBill (session, rec, cb) { 'id', 'device_fingerprint', 'currency_code', + 'crypto_code', 'to_address', 'session_id', 'device_time', @@ -79,6 +80,7 @@ exports.recordBill = function recordBill (session, rec, cb) { rec.uuid, session.fingerprint, rec.currency, + rec.cryptoCode, rec.toAddress, session.id, rec.deviceTime, diff --git a/todo.txt b/todo.txt index 82026517..defc3673 100644 --- a/todo.txt +++ b/todo.txt @@ -31,3 +31,5 @@ alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; - update cryptoCode for bills insert +- test against old server +- remove debug From 564895ea22e05a868b6303305dbb03c1828aed2c Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 7 Apr 2016 17:02:21 +0100 Subject: [PATCH 26/34] todo --- todo.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/todo.txt b/todo.txt index defc3673..64f0215d 100644 --- a/todo.txt +++ b/todo.txt @@ -30,6 +30,5 @@ alter table pending_transactions alter satoshis TYPE bigint; alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; -- update cryptoCode for bills insert - test against old server - remove debug From 1a10c3aba8ebe5e16d7f6af117ae3063a89d4032 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Fri, 8 Apr 2016 02:28:51 +0100 Subject: [PATCH 27/34] load coins from config --- bin/ssu | 1 + lib/routes.js | 3 ++- todo.txt | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/ssu b/bin/ssu index bf8e4fe5..057a4e1e 100755 --- a/bin/ssu +++ b/bin/ssu @@ -139,6 +139,7 @@ function crypto () { ticker: tickerPlugin, transfer: walletPlugin } + config.exchanges.settings.coins = ['BTC', code] return db.none('update user_config set data=$1 where type=$2', [config, 'exchanges']) }) .then(function () { diff --git a/lib/routes.js b/lib/routes.js index 74213fc8..b7d382c5 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -90,7 +90,8 @@ function poll (req, res) { fiatTxLimit: settings.fiatTxLimit, reboot: reboot, rates: rates, - balances: balances + balances: balances, + coins: settings.coins } if (response.idVerificationEnabled) { diff --git a/todo.txt b/todo.txt index 64f0215d..db724706 100644 --- a/todo.txt +++ b/todo.txt @@ -30,5 +30,5 @@ alter table pending_transactions alter satoshis TYPE bigint; alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; -- test against old server - remove debug +- test trading From 92bed1492766adf174432e480adb4cfcfa74c184 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 10 Apr 2016 02:39:56 +0200 Subject: [PATCH 28/34] real trader works --- bin/ssu | 8 +++++--- lib/plugins.js | 52 ++++++++++++++++++++++++++++---------------------- todo.txt | 14 +++++++++++++- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/bin/ssu b/bin/ssu index 057a4e1e..dfe761b2 100755 --- a/bin/ssu +++ b/bin/ssu @@ -19,7 +19,7 @@ function bail () { console.log('Command line utility for lamassu-server') console.log('\nssu reboot ') console.log('This will remotely reboot your lamassu-machine.') - console.log('\nssu crypto ') + console.log('\nssu crypto []') console.log('This will configure a new cryptocurrency.') process.exit(1) } @@ -115,9 +115,10 @@ function crypto () { var code = argv[1] var tickerPlugin = argv[2] var walletPlugin = argv[3] + var traderPlugin = argv[4] if (!code || !tickerPlugin || !walletPlugin) { - console.log('\nssu crypto ') + console.log('\nssu crypto []') console.log('This will configure a new cryptocurrency.') process.exit(1) } @@ -137,7 +138,8 @@ function crypto () { var config = data.data config.exchanges.plugins.current[code] = { ticker: tickerPlugin, - transfer: walletPlugin + transfer: walletPlugin, + trader: traderPlugin } config.exchanges.settings.coins = ['BTC', code] return db.none('update user_config set data=$1 where type=$2', [config, 'exchanges']) diff --git a/lib/plugins.js b/lib/plugins.js index 64980173..fb7c7808 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -9,7 +9,7 @@ BigNumber.config({DECIMAL_PLACES: 40}) var logger = require('./logger') var argv = require('minimist')(process.argv.slice(2)) -var tradeInterval = null +var tradeIntervals = {} var POLLING_RATE = 60 * 1000 // poll each minute var REAP_RATE = 2 * 1000 @@ -183,11 +183,11 @@ exports.configure = function configure (config) { } ) - tradesQueues[cryptoCode] = [] + tradesQueues[cryptoCode] = tradesQueues[cryptoCode] || [] loadOrConfigPlugin( traderPlugins[cryptoCode], - 'trade', + 'trader', cryptoCode, null, function onTraderChange (newTrader) { @@ -334,13 +334,13 @@ exports.trade = function trade (session, rawTrade, cb) { // add bill to trader queue (if trader is enabled) var cryptoCode = rawTrade.cryptoCode || 'BTC' var traderPlugin = traderPlugins[cryptoCode] - var tradesQueue = tradesQueues[cryptoCode] if (traderPlugin) { - tradesQueue.push({ + logger.debug('[%s] Pushing trade: %d', cryptoCode, rawTrade.cryptoAtoms) + tradesQueues[cryptoCode].push({ currency: rawTrade.currency, cryptoAtoms: rawTrade.cryptoAtoms, - cryptoCode: rawTrade.cryptoCode + cryptoCode: cryptoCode }) } @@ -417,11 +417,10 @@ exports.startPolling = function startPolling () { cryptoCodes.forEach(function (cryptoCode) { setInterval(async.apply(pollBalance, cryptoCode), POLLING_RATE) setInterval(async.apply(pollRate, cryptoCode), POLLING_RATE) + startTrader(cryptoCode) }) setInterval(reapTxs, REAP_RATE) - - startTrader() } function startTrader (cryptoCode) { @@ -430,21 +429,23 @@ function startTrader (cryptoCode) { // `Trader#executeTrades` returns early if we don't have a trade exchange // configured at the moment. var traderPlugin = traderPlugins[cryptoCode] + if (!traderPlugin || tradeIntervals[cryptoCode]) return - if (traderPlugin && !tradeInterval) { - tradeInterval = setInterval( - function () { executeTrades(cryptoCode) }, - cachedConfig.exchanges.settings.tradeInterval - ) - } + logger.debug('[%s] startTrader', cryptoCode) + + tradeIntervals[cryptoCode] = setInterval( + function () { executeTrades(cryptoCode) }, + cachedConfig.exchanges.settings.tradeInterval + ) } function stopTrader (cryptoCode) { - if (tradeInterval) { - clearInterval(tradeInterval) - tradeInterval = null - tradesQueues[cryptoCode] = [] - } + if (!tradeIntervals[cryptoCode]) return + + logger.debug('[%s] stopTrader', cryptoCode) + clearInterval(tradeIntervals[cryptoCode]) + tradeIntervals[cryptoCode] = null + tradesQueues[cryptoCode] = [] } function pollBalance (cryptoCode, cb) { @@ -536,8 +537,11 @@ function purchase (trade, cb) { function consolidateTrades (cryptoCode) { // NOTE: value in cryptoAtoms stays the same no matter the currency + + logger.debug('tradesQueues size: %d', tradesQueues[cryptoCode].length) + logger.debug('tradesQueues head: %j', tradesQueues[cryptoCode][0]) var cryptoAtoms = tradesQueues[cryptoCode].reduce(function (prev, current) { - return current.cryptoAtoms.plus(prev) + return prev.plus(current.cryptoAtoms) }, new BigNumber(0)) var consolidatedTrade = { @@ -548,7 +552,7 @@ function consolidateTrades (cryptoCode) { tradesQueues[cryptoCode] = [] - logger.debug('consolidated: ', JSON.stringify(consolidatedTrade)) + logger.debug('[%s] consolidated: %j', cryptoCode, consolidatedTrade) return consolidatedTrade } @@ -556,21 +560,23 @@ function executeTrades (cryptoCode) { var traderPlugin = traderPlugins[cryptoCode] if (!traderPlugin) return - logger.debug('checking for trades') + logger.debug('[%s] checking for trades', cryptoCode) var trade = consolidateTrades(cryptoCode) if (trade.cryptoAtoms.eq(0)) { - logger.debug('rejecting 0 trade') + logger.debug('[%s] rejecting 0 trade', cryptoCode) return } logger.debug('making a trade: %d', trade.cryptoAtoms.toString()) purchase(trade, function (err) { if (err) { + logger.debug(err) tradesQueues[cryptoCode].push(trade) if (err.name !== 'orderTooSmall') logger.error(err) } + logger.debug('Successful trade.') }) } diff --git a/todo.txt b/todo.txt index db724706..3f235870 100644 --- a/todo.txt +++ b/todo.txt @@ -30,5 +30,17 @@ alter table pending_transactions alter satoshis TYPE bigint; alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; +- handle geth send failure better +- unlock geth account - remove debug -- test trading +- implement coin selection screen +- ask neal to work on config scripts + +TypeError: Cannot read property 'rates' of null + at /Users/josh/projects/lamassu-server/lib/routes.js:36:49 + at Array.forEach (native) + at buildRates (/Users/josh/projects/lamassu-server/lib/routes.js:35:15) + at poll (/Users/josh/projects/lamassu-server/lib/routes.js:66:15) + at callbacks (/Users/josh/projects/lamassu-server/node_modules/express/lib/router/index.js:164:37) + at /Users/josh/projects/lamassu-server/lib/app.js:56:7 + at /Users/josh/projects/lamassu-server/node_modules/lamassu-config/lib/main.js:142:5 From c897275714ac1c213307c904ac895c683cd946d3 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 11 Apr 2016 16:23:07 +0200 Subject: [PATCH 29/34] update ssu command to allow plugin configuration --- bin/ssu | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/bin/ssu b/bin/ssu index dfe761b2..81a3d430 100755 --- a/bin/ssu +++ b/bin/ssu @@ -8,6 +8,7 @@ var Promise = require('es6-promise') var pgp = require('pg-promise')({ promiseLib: Promise }) +var inquirer = require('inquirer') var fs = require('fs') @@ -21,6 +22,8 @@ function bail () { console.log('This will remotely reboot your lamassu-machine.') console.log('\nssu crypto []') console.log('This will configure a new cryptocurrency.') + console.log('\nssu config ') + console.log('Configure a plugin setting.') process.exit(1) } @@ -31,6 +34,9 @@ switch (cmd) { case 'crypto': crypto() break + case 'config': + configure() + break default: } @@ -153,3 +159,51 @@ function crypto () { pgp.end() }) } + +function configure () { + var plugin = argv[1] + var key = argv[2] + + if (!plugin || !key) { + console.log('\nssu config ') + console.log('Configure a plugin setting.') + process.exit(1) + } + + inquirer.prompt([{ + type: 'password', + name: 'value', + message: 'Enter value for ' + key + ': ', + validate: function (val) { + return !val || val.length === 0 + ? 'Please enter a value for ' + key + : true + } + }]).then(function (result) { + var value = result.value + + var psqlUrl + try { + psqlUrl = process.env.DATABASE_URL || JSON.parse(fs.readFileSync('/etc/lamassu.json')).postgresql + } catch (ex) { + psqlUrl = 'psql://lamassu:lamassu@localhost/lamassu' + } + var db = pgp(psqlUrl) + + return db.one('select data from user_config where type=$1', 'exchanges') + .then(function (data) { + var config = data.data + config.exchanges.plugins.settings[plugin] = config.exchanges.plugins.settings[plugin] || {} + config.exchanges.plugins.settings[plugin][key] = value + return db.none('update user_config set data=$1 where type=$2', [config, 'exchanges']) + }) + .then(function () { + console.log('success') + pgp.end() + }) + .catch(function (err) { + console.log(err.stack) + pgp.end() + }) + }) +} diff --git a/package.json b/package.json index fc919553..bcb23e61 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "es6-promise": "^3.1.2", "ethereumjs-wallet": "^0.5.1", "express": "~3.4.7", - "inquirer": "^0.8.0", + "inquirer": "^1.0.0", "joi": "^5.1.0", "lamassu-bitcoinaverage": "~1.0.0", "lamassu-bitcoind": "^1.1.0", @@ -37,6 +37,8 @@ "node-uuid": "^1.4.2", "pg": "^4.5.1", "pg-promise": "^3.4.3", + "prompt": "^1.0.0", + "promptly": "^1.1.0", "web3": "^0.15.3", "wreck": "5.1.0" }, From c790456a8789ee441e345c7027d08d466d8f9f3a Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 11 Apr 2016 17:09:35 +0200 Subject: [PATCH 30/34] add migration for crypto support --- migrations/005-addCrypto.js | 18 ++++++++++++++++++ todo.txt | 1 - 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 migrations/005-addCrypto.js diff --git a/migrations/005-addCrypto.js b/migrations/005-addCrypto.js new file mode 100644 index 00000000..eaa551c7 --- /dev/null +++ b/migrations/005-addCrypto.js @@ -0,0 +1,18 @@ +var db = require('./db') + +exports.up = function (next) { + var sqls = [ + 'alter table transactions alter satoshis TYPE bigint', + "alter table transactions add crypto_code text default 'BTC'", + "alter table pending_transactions add crypto_code text default 'BTC'", + 'alter table pending_transactions alter satoshis TYPE bigint', + "alter table bills add crypto_code text default 'BTC'", + 'alter table bills alter satoshis TYPE bigint' + ] + + db.multi(sqls, next) +} + +exports.down = function (next) { + next() +} diff --git a/todo.txt b/todo.txt index 3f235870..cddca56c 100644 --- a/todo.txt +++ b/todo.txt @@ -31,7 +31,6 @@ alter table bills add crypto_code text default 'BTC'; alter table bills alter satoshis TYPE bigint; - handle geth send failure better -- unlock geth account - remove debug - implement coin selection screen - ask neal to work on config scripts From 26cbefdf7cbada93976d02fa13ba6f96febf3902 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 18 Apr 2016 00:53:26 +0300 Subject: [PATCH 31/34] check for null rates --- lib/routes.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/routes.js b/lib/routes.js index b7d382c5..84102e49 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -33,7 +33,9 @@ function buildRates () { var rates = {} cryptoCodes.forEach(function (cryptoCode) { - var rate = plugins.getDeviceRate(cryptoCode).rates + var _rate = plugins.getDeviceRate(cryptoCode) + if (!_rate) return + var rate = _rate.rates rates[cryptoCode] = { cashIn: rate.ask.times(cashInCommission), cashOut: rate.bid.div(cashOutCommission) From 03b6524022b852811f578d7ca057db5a5b0d8751 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 18 Apr 2016 01:19:34 +0300 Subject: [PATCH 32/34] todo --- todo.txt | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/todo.txt b/todo.txt index cddca56c..c3351e1e 100644 --- a/todo.txt +++ b/todo.txt @@ -35,11 +35,19 @@ alter table bills alter satoshis TYPE bigint; - implement coin selection screen - ask neal to work on config scripts -TypeError: Cannot read property 'rates' of null - at /Users/josh/projects/lamassu-server/lib/routes.js:36:49 - at Array.forEach (native) - at buildRates (/Users/josh/projects/lamassu-server/lib/routes.js:35:15) - at poll (/Users/josh/projects/lamassu-server/lib/routes.js:66:15) - at callbacks (/Users/josh/projects/lamassu-server/node_modules/express/lib/router/index.js:164:37) - at /Users/josh/projects/lamassu-server/lib/app.js:56:7 - at /Users/josh/projects/lamassu-server/node_modules/lamassu-config/lib/main.js:142:5 +[2016-04-17T22:10:57.917Z] ERROR: lamassu-server/32185 on MacBook-Pro.local: could not unlock signer account + Error: could not unlock signer account + at Object.module.exports.InvalidResponse (/Users/josh/projects/lamassu-geth/node_modules/web3/lib/web3/errors.js:35:16) + at /Users/josh/projects/lamassu-geth/node_modules/web3/lib/web3/requestmanager.js:86:36 + at request.onreadystatechange (/Users/josh/projects/lamassu-geth/node_modules/web3/lib/web3/httpprovider.js:114:13) + at dispatchEvent (/Users/josh/projects/lamassu-geth/node_modules/web3/node_modules/xmlhttprequest/lib/XMLHttpRequest.js:591:25) + at setState (/Users/josh/projects/lamassu-geth/node_modules/web3/node_modules/xmlhttprequest/lib/XMLHttpRequest.js:610:14) + at IncomingMessage. (/Users/josh/projects/lamassu-geth/node_modules/web3/node_modules/xmlhttprequest/lib/XMLHttpRequest.js:447:13) + at emitNone (events.js:72:20) + at IncomingMessage.emit (events.js:166:7) + at endReadableNT (_stream_readable.js:913:12) + at nextTickCallbackWith2Args (node.js:442:9) +/Users/josh/projects/lamassu-server/lib/postgresql_interface.js:301 + satoshis.toString(), + ^ +better handling of this error From e2f605e1079a12e6e80b9b9e0944020d6d88e9a0 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Mon, 18 Apr 2016 19:47:47 +0300 Subject: [PATCH 33/34] 2.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcb23e61..a5a64ce8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "2.2.12", + "version": "2.3.0", "license": "unlicense", "author": "Lamassu (https://lamassu.is)", "engines": { From 417a604036fe6a4c6f69ab3c2307c13f1d6ee570 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Tue, 19 Apr 2016 19:11:21 +0300 Subject: [PATCH 34/34] remove DECIMAL_PLACES --- lib/plugins.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins.js b/lib/plugins.js index fb7c7808..e6cecb4f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -4,7 +4,6 @@ var _ = require('lodash') var async = require('async') var BigNumber = require('bignumber.js') -BigNumber.config({DECIMAL_PLACES: 40}) var logger = require('./logger') var argv = require('minimist')(process.argv.slice(2))