diff --git a/lib/plugins.js b/lib/plugins.js index afc3c5f5..2cab2f40 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -243,9 +243,7 @@ exports.getConfig = function getConfig () { return cachedConfig } -exports.logEvent = function event (session, rawEvent) { - return db.recordDeviceEvent(session, rawEvent) -} +exports.logEvent = db.recordDeviceEvent function buildCartridges (cartridges, virtualCartridges, rec) { return { @@ -264,12 +262,12 @@ function buildCartridges (cartridges, virtualCartridges, rec) { } } -exports.pollQueries = function pollQueries (session) { +exports.pollQueries = function pollQueries (deviceId) { const cartridges = cachedConfig.exchanges.settings.cartridges if (!cartridges) return Promise.resolve({}) const virtualCartridges = cachedConfig.exchanges.settings.virtualCartridges - return db.cartridgeCounts(session) + return db.cartridgeCounts(deviceId) .then(result => ({ cartridges: buildCartridges(cartridges, virtualCartridges, result) })) @@ -298,8 +296,8 @@ function _sendCoinsCb (toAddress, cryptoAtoms, cryptoCode, cb) { // NOTE: This will fail if we have already sent coins because there will be // a db unique db record in the table already. -function executeTx (fingerprint, tx) { - return db.addOutgoingTx(fingerprint, tx) +function executeTx (deviceId, tx) { + return db.addOutgoingTx(deviceId, tx) .then(() => _sendCoins(tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)) .then(txHash => { const fee = null // Need to fill this out in plugins @@ -316,7 +314,7 @@ function executeTx (fingerprint, tx) { } // TODO: Run these in parallel and return success -exports.trade = function trade (session, rawTrade) { +exports.trade = function trade (deviceId, rawTrade) { // TODO: move this to DB, too // add bill to trader queue (if trader is enabled) const cryptoCode = rawTrade.cryptoCode || 'BTC' @@ -332,41 +330,41 @@ exports.trade = function trade (session, rawTrade) { }) } - return db.recordBill(session, rawTrade) + return db.recordBill(deviceId, rawTrade) } -exports.stateChange = function stateChange (session, rec, cb) { +exports.stateChange = function stateChange (deviceId, deviceTime, rec, cb) { const event = { id: rec.uuid, - fingerprint: session.fingerprint, + fingerprint: deviceId, eventType: 'stateChange', - note: JSON.stringify({state: rec.state, isIdle: rec.isIdle, sessionId: session.id}), - deviceTime: session.deviceTime + note: JSON.stringify({state: rec.state, isIdle: rec.isIdle, txId: rec.txId}), + deviceTime: deviceTime } return db.machineEvent(event) } -exports.recordPing = function recordPing (session, rec, cb) { +exports.recordPing = function recordPing (deviceId, deviceTime, rec, cb) { const event = { id: uuid.v4(), - fingerprint: session.fingerprint, + fingerprint: deviceId, eventType: 'ping', - note: JSON.stringify({state: rec.state, isIdle: rec.idle === 'true', sessionId: session.id}), - deviceTime: session.deviceTime + note: JSON.stringify({state: rec.state, isIdle: rec.idle === 'true', txId: rec.txId}), + deviceTime: deviceTime } return db.machineEvent(event) } -exports.sendCoins = function sendCoins (session, rawTx) { - return executeTx(session.fingerprint, rawTx) +exports.sendCoins = function sendCoins (rawTx) { + return executeTx(rawTx) } -exports.cashOut = function cashOut (session, tx) { +exports.cashOut = function cashOut (tx) { const cryptoCode = tx.cryptoCode || 'BTC' const walletPlugin = walletPlugins[cryptoCode] const serialPromise = walletPlugin.supportsHD - ? db.nextCashOutSerialHD(tx.sessionId, cryptoCode) + ? db.nextCashOutSerialHD(tx.id, cryptoCode) : Promise.resolve() return serialPromise @@ -381,15 +379,13 @@ exports.cashOut = function cashOut (session, tx) { if (err) return reject(err) const newTx = R.assoc('toAddress', address, tx) - return db.addInitialIncoming(session, newTx, address) + return db.addInitialIncoming(newTx, address) .then(() => resolve(address)) }) })) } -exports.dispenseAck = function dispenseAck (session, rec) { - return db.addDispense(session, rec.tx, rec.cartridges) -} +exports.dispenseAck = db.addDispense exports.fiatBalance = function fiatBalance (cryptoCode) { const deviceRate = exports.getDeviceRate(cryptoCode) @@ -765,13 +761,8 @@ exports.getPhoneCode = function getPhoneCode (phone) { .then(() => code) } -exports.updatePhone = function updatePhone (session, tx, notified) { - return db.addIncomingPhone(session, tx, notified) -} - -exports.registerRedeem = function registerRedeem (session) { - return db.updateRedeem(session) -} +exports.updatePhone = db.addIncomingPhone +exports.registerRedeem = db.updateRedeem exports.fetchPhoneTx = function fetchPhoneTx (phone) { return db.fetchPhoneTxs(phone, TRANSACTION_EXPIRATION) @@ -798,9 +789,9 @@ exports.requestDispense = function requestDispense (tx) { return db.addDispenseRequest(tx) } -exports.cachedResponse = (session, path, method) => db.cachedResponse(session, path, method) +exports.cachedResponse = db.cachedResponse -exports.cacheResponse = (session, path, method, body) => db.cacheResponse(session, path, method, body) +exports.cacheResponse = db.cacheResponse function sweepHD (row) { const cryptoCode = row.crypto_code diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 3a4e4d71..fd858276 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -41,14 +41,14 @@ exports.init = function init (conString) { } // logs inputted bill and overall tx status (if available) -exports.recordBill = function recordBill (session, rec) { +exports.recordBill = function recordBill (deviceId, rec) { const fields = [ 'id', 'device_fingerprint', 'currency_code', 'crypto_code', 'to_address', - 'session_id', + 'tx_id', 'device_time', 'satoshis', 'denomination' @@ -56,11 +56,11 @@ exports.recordBill = function recordBill (session, rec) { const values = [ rec.uuid, - session.fingerprint, + deviceId, rec.currency, rec.cryptoCode, rec.toAddress, - session.id, + rec.txId, rec.deviceTime, rec.cryptoAtoms.toString(), rec.fiat @@ -73,10 +73,10 @@ exports.recordBill = function recordBill (session, rec) { }) } -exports.recordDeviceEvent = function recordDeviceEvent (session, event) { - const sql = 'INSERT INTO device_events (device_fingerprint, event_type, ' + +exports.recordDeviceEvent = function recordDeviceEvent (deviceId, event) { + const sql = 'INSERT INTO device_events (device_id, event_type, ' + 'note, device_time) VALUES ($1, $2, $3, $4)' - const values = [session.fingerprint, event.eventType, event.note, + const values = [deviceId, event.eventType, event.note, event.deviceTime] return db.none(sql, values) } @@ -158,11 +158,11 @@ function insertDispense (session, tx, cartridges) { return db.none(sql, values) } -exports.addIncomingPhone = function addIncomingPhone (session, tx, notified) { +exports.addIncomingPhone = function addIncomingPhone (tx, notified) { const sql = `UPDATE cash_out_txs SET phone=$1, notified=$2 - WHERE session_id=$3 + WHERE tx_id=$3 AND phone IS NULL` - const values = [tx.phone, notified, tx.sessionId] + const values = [tx.phone, notified, tx.id] return db.result(sql, values) .then(results => { @@ -171,7 +171,7 @@ exports.addIncomingPhone = function addIncomingPhone (session, tx, notified) { if (noPhone) return {noPhone: noPhone} - return db.none(sql2, [tx.sessionId, 'addedPhone']) + return db.none(sql2, [tx.txId, 'addedPhone']) .then(() => ({noPhone: noPhone})) }) } @@ -244,11 +244,11 @@ exports.addDispense = function addDispense (session, tx, cartridges) { }) } -exports.cartridgeCounts = function cartridgeCounts (session) { +exports.cartridgeCounts = function cartridgeCounts (deviceId) { const sql = 'SELECT id, count1, count2 FROM dispenses ' + 'WHERE device_fingerprint=$1 AND refill=$2 ' + 'ORDER BY id DESC LIMIT 1' - return db.oneOrNone(sql, [session.fingerprint, true]) + return db.oneOrNone(sql, [deviceId, true]) .then(row => { const counts = row ? [row.count1, row.count2] : [0, 0] return {id: row.id, counts: counts} @@ -360,14 +360,14 @@ exports.updateTxStatus = function updateTxStatus (tx, status) { }) } -exports.updateRedeem = function updateRedeem (session) { - const sql = 'UPDATE cash_out_txs SET redeem=$1 WHERE session_id=$2' - const values = [true, session.id] +exports.updateRedeem = function updateRedeem (txId) { + const sql = 'UPDATE cash_out_txs SET redeem=$1 WHERE txId=$2' + const values = [true, txId] return db.none(sql, values) .then(() => { const sql2 = 'insert into cash_out_actions (session_id, action) values ($1, $2)' - return db.none(sql2, [session.id, 'redeem']) + return db.none(sql2, [txId, 'redeem']) }) } @@ -382,29 +382,29 @@ exports.updateNotify = function updateNotify (tx) { }) } -function insertCachedRequest (session, path, method, body) { +function insertCachedRequest (deviceId, txId, path, method, body) { const fields = [ - 'device_fingerprint', - 'session_id', + 'device_id', + 'tx_id', 'path', 'method', 'body' ] const sql = getInsertQuery('cached_responses', fields) - return db.none(sql, [session.fingerprint, session.id, path, method, body]) + return db.none(sql, [deviceId, txId, path, method, body]) } -exports.cachedResponse = function (session, path, method) { +exports.cachedResponse = function (deviceId, txId, path, method) { const sql = `select body from cached_responses - where device_fingerprint=$1 - and session_id=$2 + where device_id=$1 + and tx_id=$2 and path=$3 and method=$4` - const values = [session.fingerprint, session.id, path, method] + const values = [deviceId, txId, path, method] - return insertCachedRequest(session, path, method, {pendingRequest: true}) + return insertCachedRequest(deviceId, txId, path, method, {pendingRequest: true}) .then(() => ({})) .catch(err => { if (!isUniqueViolation(err)) throw err @@ -422,15 +422,15 @@ function pruneCachedResponses () { return db.none(sql, values) } -exports.cacheResponse = function (session, path, method, body) { +exports.cacheResponse = function (deviceId, txId, path, method, body) { const sql = `update cached_responses set body=$1 - where device_fingerprint=$2 - and session_id=$3 + where device_id=$2 + and tx_id=$3 and path=$4 and method=$5` - const values = [body, session.fingerprint, session.id, path, method] + const values = [body, deviceId, txId, path, method] return db.none(sql, values) } diff --git a/lib/routes.js b/lib/routes.js index b822fa2b..42503427 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -10,7 +10,7 @@ let lamassuConfig module.exports = { init, - getFingerprint + getDeviceId } const STALE_TICKER = 3 * 60 * 1000 @@ -57,12 +57,13 @@ function buildBalances () { } function poll (req, res) { - const fingerprint = getFingerprint(req) + const deviceId = getDeviceId(req) + const deviceTime = getDeviceTime(req) const pid = req.query.pid - pids[fingerprint] = {pid, ts: Date.now()} + pids[deviceId] = {pid, ts: Date.now()} - logger.debug('poll request from: %s', fingerprint) + logger.debug('poll request from: %s', deviceId) let rates = {} let balances = {} @@ -74,36 +75,36 @@ function poll (req, res) { const settings = config.exchanges.settings const complianceSettings = settings.compliance - plugins.pollQueries(session(req)) - .then(results => { - const cartridges = results.cartridges + plugins.pollQueries(deviceId) + .then(results => { + const cartridges = results.cartridges - const reboot = reboots[fingerprint] === pid + const reboot = reboots[deviceId] === pid - const response = { - err: null, - locale: config.brain.locale, - txLimit: parseInt(complianceSettings.maximum.limit, 10), - idVerificationEnabled: complianceSettings.idVerificationEnabled, - cartridges, - twoWayMode: !!cartridges, - zeroConfLimit: settings.zeroConfLimit, - fiatTxLimit: settings.fiatTxLimit, - reboot, - rates, - balances, - coins: settings.coins - } + const response = { + err: null, + locale: config.brain.locale, + txLimit: parseInt(complianceSettings.maximum.limit, 10), + idVerificationEnabled: complianceSettings.idVerificationEnabled, + cartridges, + twoWayMode: !!cartridges, + zeroConfLimit: settings.zeroConfLimit, + fiatTxLimit: settings.fiatTxLimit, + reboot, + rates, + balances, + coins: settings.coins + } - if (response.idVerificationEnabled) { - response.idVerificationLimit = complianceSettings.idVerificationLimit - } + if (response.idVerificationEnabled) { + response.idVerificationLimit = complianceSettings.idVerificationLimit + } - res.json(response) - }) - .catch(logger.error) + res.json(response) + }) + .catch(logger.error) - plugins.recordPing(session(req), req.query) + plugins.recordPing(deviceId, deviceTime, req.query) .catch(logger.error) } @@ -111,7 +112,7 @@ function trade (req, res) { const tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) - plugins.trade(session(req), tx) + plugins.trade(getDeviceId(req), tx) .then(() => res.status(201).json({})) .catch(err => { logger.error(err) @@ -120,7 +121,7 @@ function trade (req, res) { } function stateChange (req, res) { - plugins.stateChange(session(req), req.body) + plugins.stateChange(getDeviceId(req), getDeviceTime(req), req.body) .then(() => res.json({success: true})) .catch(err => { console.error(err) @@ -135,7 +136,7 @@ function send (req, res) { // 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 - return plugins.sendCoins(session(req), tx) + return plugins.sendCoins(getDeviceId(req), tx) .then(status => res.json({ txHash: status && status.txHash, txId: status && status.txId @@ -154,24 +155,24 @@ function cashOut (req, res) { const tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) - return plugins.cashOut(session(req), req.body) - .then(cryptoAddress => res.json({bitcoinAddress: cryptoAddress})) - .catch(err => { - res.json({ - err: err.message, - errType: err.name - }) - logger.error(err) + return plugins.cashOut(tx) + .then(cryptoAddress => res.json({toAddress: cryptoAddress})) + .catch(err => { + res.json({ + err: err.message, + errType: err.name }) + logger.error(err) + }) } function dispenseAck (req, res) { - plugins.dispenseAck(session(req), req.body) + plugins.dispenseAck(req.body) res.json({success: true}) } function deviceEvent (req, res) { - plugins.logEvent(session(req), req.body) + plugins.logEvent(getDeviceId(req), req.body) res.json({err: null}) } @@ -207,7 +208,7 @@ function pair (req, res) { lamassuConfig.pair( token, - getFingerprint(req), + getDeviceId(req), name, err => { if (err) { @@ -235,7 +236,9 @@ function phoneCode (req, res) { function updatePhone (req, res) { const notified = req.query.notified === 'true' - return plugins.updatePhone(session(req), req.body, notified) + const tx = req.body + + return plugins.updatePhone(tx, notified) .then(r => res.json(r)) .catch(err => { logger.error(err) @@ -253,7 +256,8 @@ function fetchPhoneTx (req, res) { } function registerRedeem (req, res) { - return plugins.registerRedeem(session(req)) + const txId = req.params.txId + return plugins.registerRedeem(txId) .then(() => res.json({success: true})) .catch(err => { logger.error(err) @@ -263,7 +267,7 @@ function registerRedeem (req, res) { function waitForDispense (req, res) { logger.debug('waitForDispense') - return plugins.fetchTx(session(req)) + return plugins.fetchTx(req.params.txId) .then(tx => { logger.debug('tx fetched') logger.debug(tx) @@ -279,16 +283,25 @@ function waitForDispense (req, res) { function dispense (req, res) { const tx = req.body.tx + const deviceId = getDeviceId(req) - return plugins.requestDispense(tx) + return cachedResponse(deviceId, tx.id, req) + .then(r => { + // Cache hit + if (r.body && r.body.pendingRequest) return res.sendStatus(409) + if (r.body) res.json(r.body) + + // No cache hit + return plugins.requestDispense(tx) .then(r => { return cacheResponse(req, r) - .then(() => res.json(r)) + .then(() => res.json(r)) }) .catch(err => { logger.error(err) res.sendStatus(500) }) + }) } function init (localConfig) { @@ -316,9 +329,9 @@ function init (localConfig) { app.post('/phone_code', authMiddleware, phoneCode) app.post('/update_phone', authMiddleware, updatePhone) app.get('/phone_tx', authMiddleware, fetchPhoneTx) - app.post('/register_redeem', authMiddleware, registerRedeem) - app.get('/await_dispense', authMiddleware, waitForDispense) - app.post('/dispense', authMiddleware, cachedResponse, dispense) + app.post('/register_redeem/:txId', authMiddleware, registerRedeem) + app.get('/await_dispense/:txId', authMiddleware, waitForDispense) + app.post('/dispense', authMiddleware, dispense) localApp.get('/pid', (req, res) => { const machineFingerprint = req.query.fingerprint @@ -342,28 +355,20 @@ function init (localConfig) { return app } -function session (req) { - return { - fingerprint: getFingerprint(req), - id: req.get('session-id'), - deviceTime: Date.parse(req.get('date')) - } +function getDeviceTime (req) { + return Date.parse(req.get('date')) } -function getFingerprint (req) { +function getDeviceId (req) { return (typeof req.connection.getPeerCertificate === 'function' && req.connection.getPeerCertificate().fingerprint) || 'unknown' } -function cachedResponse (req, res, next) { - return plugins.cachedResponse(session(req), req.path, req.method) - .then(r => { - if (!r.body) return next() - if (r.body.pendingRequest) return res.sendStatus(409) - res.json(r.body) - }) +function cachedResponse (deviceId, txId, req) { + return plugins.cachedResponse(deviceId, txId, req.path, req.method) + .then(r => r.body) } -function cacheResponse (req, body) { - return plugins.cacheResponse(session(req), req.path, req.method, body) +function cacheResponse (deviceId, txId, req, body) { + return plugins.cacheResponse(deviceId, txId, req.path, req.method, body) }