diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c63c085..0967ef42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,2 +1 @@ -{ -} +{} diff --git a/lib/bn.js b/lib/bn.js new file mode 100644 index 00000000..a9016d39 --- /dev/null +++ b/lib/bn.js @@ -0,0 +1,6 @@ +const BigNumber = require('bignumber.js') + +BigNumber.config({ROUNDING_MODE: BigNumber.ROUND_HALF_EVEN}) + +function BN (s) { return new BigNumber(s) } +module.exports = BN diff --git a/lib/cash-in-tx.js b/lib/cash-in-tx.js new file mode 100644 index 00000000..689d0987 --- /dev/null +++ b/lib/cash-in-tx.js @@ -0,0 +1,86 @@ +const _ = require('lodash/fp') +const pgp = require('pg-promise')() +const db = require('./db') +const BN = require('./bn') + +module.exports = {postCashIn} + +const UPDATEABLE_FIELDS = ['fee', 'txHash', 'phone', 'error'] + +function postCashIn (tx, pi) { + const TransactionMode = pgp.txMode.TransactionMode + const isolationLevel = pgp.txMode.isolationLevel + const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) + + function transaction (t) { + const sql = 'select * from cash_in_txs where id=$1' + return t.oneOrNone(sql, [tx.id]) + .then(row => upsert(row, tx)) + } + + transaction.txMode = tmSRD + + return db.tx(transaction) + .then(txVector => postProcess(txVector, pi)) + .then(changes => update(tx.id, changes)) +} + +function diff (oldTx, newTx) { + let updatedTx = {} + + UPDATEABLE_FIELDS.forEach(fieldKey => { + if (_.isEqual(oldTx[fieldKey], newTx[fieldKey])) return + updatedTx[fieldKey] = newTx[fieldKey] + }) + + return updatedTx +} + +function toObj (row) { + const keys = _.keys(row) + let newObj = {} + + keys.forEach(key => { + const objKey = _.camelCase(key) + if (key === 'crypto_atoms' || key === 'fiat') { + newObj[objKey] = BN(row[key]) + return + } + + newObj[objKey] = row[key] + }) + + return newObj +} + +function upsert (row, tx) { + const oldTx = toObj(row) + + if (oldTx) return insert(tx) + return update(tx.id, diff(oldTx, tx)) +} + +function insert (tx) { + const dbTx = _.mapKeys(_.snakeCase, tx) + + const sql = pgp.helpers.insert(dbTx, null, 'cash_in_txs') + return db.none(sql) +} + +function update (txId, changes) { + const dbChanges = _.mapKeys(_.snakeCase, changes) + const sql = pgp.helpers.update(dbChanges, null, 'cash_in_txs') + + pgp.as.format(' where id=$1', [txId]) + + return db.none(sql) +} + +function postProcess (txVector, pi) { + const [oldTx, newTx] = txVector + + if (newTx.sent && !oldTx.sent) { + return pi.sendCoins(newTx) + .then(txHash => ({txHash})) + .catch(error => ({error})) + } +} diff --git a/lib/plugins.js b/lib/plugins.js index 6572d748..736e4c14 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -133,8 +133,8 @@ function plugins (settings) { } // NOTE: This will fail if we have already sent coins because there will be - // a dbm unique dbm record in the table already. - function executeTx (deviceId, tx) { + // a unique dbm record in the table already. + function sendCoins (deviceId, tx) { return dbm.addOutgoingTx(deviceId, tx) .then(() => wallet.sendCoins(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)) .then(txHash => { @@ -185,10 +185,6 @@ function plugins (settings) { return dbm.machineEvent(event) } - function sendCoins (deviceId, rawTx) { - return executeTx(deviceId, rawTx) - } - function cashOut (deviceId, tx) { const cryptoCode = tx.cryptoCode diff --git a/lib/routes.js b/lib/routes.js index 5d93604d..e0fbf18a 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -374,6 +374,7 @@ app.post('/verify_user', verifyUser) app.post('/verify_transaction', verifyTx) app.post('/phone_code', phoneCode) +app.post('/tx', postTx) app.use(errorHandler) app.use((req, res) => res.status(404).json({err: 'No such route'})) diff --git a/lib/tx.js b/lib/tx.js index 521bc9a3..dd1283a2 100644 --- a/lib/tx.js +++ b/lib/tx.js @@ -8,12 +8,12 @@ function postCashIn (tx) { function transaction (t) { const sql = 'select * from cash_in_txs where id=$1' - return t.one(sql, [tx.id]) + return t.oneOrNone(sql, [tx.id]) .then(row => { - const newTx = executeTxChange(tx, row) + const newTx = executeCashInTxChange(tx, row) - if (row) return updateCashOutTx(newTx) - insertCashOutTx(newTx) + if (row) return updateCashInTx(newTx) + insertCashInTx(newTx) }) } @@ -24,13 +24,14 @@ function postCashIn (tx) { } function postCashOut (tx) { - + throw new Error('not implemented') } function post (tx) { if (tx.direction === 'cashIn') return postCashIn(tx) if (tx.direction === 'cashOut') return postCashOut(tx) - throw new Error('No such tx direction: %s', tx.direction) + + return Promise.reject(new Error('No such tx direction: %s', tx.direction)) } module.exports = {post} diff --git a/migrations/022-add_cash_in_sent.js b/migrations/022-add_cash_in_sent.js new file mode 100644 index 00000000..c3b1c982 --- /dev/null +++ b/migrations/022-add_cash_in_sent.js @@ -0,0 +1,12 @@ +var db = require('./db') + +exports.up = function (next) { + var sql = [ + 'alter table cash_in_txs add column sent boolean not null default false' + ] + db.multi(sql, next) +} + +exports.down = function (next) { + next() +}