diff --git a/lib/cash-out-helper.js b/lib/cash-out-helper.js index bff09505..b5edd152 100644 --- a/lib/cash-out-helper.js +++ b/lib/cash-out-helper.js @@ -88,7 +88,7 @@ function redeemableTxs (deviceId) { and redeem=$2 and dispense=$3 and provisioned_1 is not null - and (extract(epoch from (now() - greatest(created, confirmation_time))) * 1000) < $4` + and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4` return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE]) .then(_.map(toObj)) diff --git a/lib/cash-out-tx.js b/lib/cash-out-tx.js index 7698a4fb..5a95306f 100644 --- a/lib/cash-out-tx.js +++ b/lib/cash-out-tx.js @@ -8,6 +8,7 @@ const logger = require('./logger') const plugins = require('./plugins') const helper = require('./cash-out-helper') const socket = require('./socket-client') +const E = require('./error') module.exports = { post, @@ -17,8 +18,8 @@ module.exports = { cancel } -const UPDATEABLE_FIELDS = ['txHash', 'status', 'dispense', 'dispenseConfirmed', - 'notified', 'redeem', 'phone', 'error', 'swept'] +const UPDATEABLE_FIELDS = ['txHash', 'txVersion', 'status', 'dispense', 'dispenseConfirmed', + 'notified', 'redeem', 'phone', 'error', 'swept', 'publishedAt', 'confirmedAt'] const STALE_INCOMING_TX_AGE = T.week const STALE_LIVE_INCOMING_TX_AGE = 10 * T.minutes @@ -37,7 +38,11 @@ function httpError (msg, code) { return err } -function post (tx, pi) { +function selfPost (tx, pi) { + return post(tx, pi, false) +} + +function post (tx, pi, fromClient = true) { const TransactionMode = pgp.txMode.TransactionMode const isolationLevel = pgp.txMode.isolationLevel const tmSRD = new TransactionMode({tiLevel: isolationLevel.serializable}) @@ -48,6 +53,9 @@ function post (tx, pi) { return t.oneOrNone(sql, [tx.id]) .then(toObj) .then(oldTx => { + const isStale = fromClient && oldTx && (oldTx.txVersion >= tx.txVersion) + if (isStale) throw new E.StaleTxError('Stale tx') + return preProcess(oldTx, tx, pi) .then(preProcessedTx => upsert(oldTx, preProcessedTx)) }) @@ -280,8 +288,33 @@ function postProcess (txVector, pi) { return Promise.resolve({}) } +function isPublished (status) { + return _.includes(status, ['published', 'rejected', 'authorized', 'instant', 'confirmed']) +} + +function isConfirmed (status) { + return status === 'confirmed' +} + function updateStatus (oldTx, newTx) { - return _.set('status', ratchetStatus(oldTx.status, newTx.status), newTx) + const oldStatus = oldTx.status + const newStatus = ratchetStatus(oldStatus, newTx.status) + + const publishedAt = !oldTx.publishedAt && isPublished(newStatus) + ? 'now()^' + : undefined + + const confirmedAt = !oldTx.confirmedAt && isConfirmed(newStatus) + ? 'now()^' + : undefined + + const updateRec = { + publishedAt, + confirmedAt, + status: newStatus + } + + return _.merge(newTx, updateRec) } function ratchetStatus (oldStatus, newStatus) { @@ -311,8 +344,8 @@ function processTxStatus (tx, settings) { const pi = plugins(settings, tx.deviceId) return pi.getStatus(tx) - .then(res => _.set('status', res.status, tx)) - .then(_tx => post(_tx, pi)) + .then(res => _.assign(tx, {status: res.status})) + .then(_tx => selfPost(_tx, pi)) } function monitorLiveIncoming (settings) { diff --git a/lib/plugins/wallet/mock-wallet/mock-wallet.js b/lib/plugins/wallet/mock-wallet/mock-wallet.js index 445ae11b..bf5d1f21 100644 --- a/lib/plugins/wallet/mock-wallet/mock-wallet.js +++ b/lib/plugins/wallet/mock-wallet/mock-wallet.js @@ -7,7 +7,7 @@ const NAME = 'FakeWallet' const SECONDS = 1000 const PUBLISH_TIME = 2 * SECONDS const AUTHORIZE_TIME = PUBLISH_TIME + 6 * SECONDS -const CONFIRM_TIME = AUTHORIZE_TIME + 180 * SECONDS +const CONFIRM_TIME = AUTHORIZE_TIME + 120 * SECONDS let t0 diff --git a/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js index 76280f29..e6999dfb 100644 --- a/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js +++ b/lib/plugins/zero-conf/mock-zero-conf/mock-zero-conf.js @@ -5,7 +5,7 @@ function authorize (account, toAddress, cryptoAtoms, cryptoCode) { .then(() => { if (cryptoCode !== 'BTC') throw new Error('Unsupported crypto: ' + cryptoCode) - const isAuthorized = true + const isAuthorized = false return isAuthorized }) } diff --git a/lib/route-helpers.js b/lib/route-helpers.js index cc6a29d3..2fe8fafe 100644 --- a/lib/route-helpers.js +++ b/lib/route-helpers.js @@ -50,7 +50,7 @@ function toCashOutTx (row) { function fetchPhoneTx (phone) { const sql = `select * from cash_out_txs where phone=$1 and dispense=$2 - and (extract(epoch from (coalesce(confirmation_time, now()) - created))) * 1000 < $3` + and (extract(epoch from (coalesce(confirmed_at, now()) - created))) * 1000 < $3` const values = [phone, false, TRANSACTION_EXPIRATION] diff --git a/lib/routes.js b/lib/routes.js index b1c1a8b6..b670a30a 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -188,19 +188,6 @@ function pair (req, res, next) { .catch(next) } -function phoneCode (req, res, next) { - const pi = plugins(req.settings, req.deviceId) - const phone = req.body.phone - - return pi.getPhoneCode(phone) - .then(code => respond(req, res, {code})) - .catch(err => { - if (err.name === 'BadNumberError') throw httpError('Bad number', 410) - throw err - }) - .catch(next) -} - function errorHandler (err, req, res, next) { const statusCode = err.name === 'HTTPError' ? err.code || 500 diff --git a/lib/wallet.js b/lib/wallet.js index 402240b9..8c1c9faa 100644 --- a/lib/wallet.js +++ b/lib/wallet.js @@ -7,7 +7,6 @@ const pify = require('pify') const fs = pify(require('fs')) const options = require('./options') const ph = require('./plugin-helper') -const db = require('./db') const FETCH_INTERVAL = 5000 const INSUFFICIENT_FUNDS_CODE = 570 @@ -111,27 +110,14 @@ function authorizeZeroConf (settings, tx, machineId) { return zeroConf.authorize(account, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode) } -function getPublishAge (txId) { - const sql = `select extract(epoch from (now() - created)) * 1000 as age - from cash_out_actions - where tx_id=$1 - and action=$2` - - return db.oneOrNone(sql, [txId, 'published']) - .then(row => row && row.age) -} - function getStatus (settings, tx, machineId) { return getWalletStatus(settings, tx) .then((statusRec) => { if (statusRec.status === 'authorized') { - const promises = [ - getPublishAge(tx.id), - authorizeZeroConf(settings, tx, machineId) - ] + return authorizeZeroConf(settings, tx, machineId) + .then(isAuthorized => { + const publishAge = Date.now() - tx.publishedAt - return Promise.all(promises) - .then(([publishAge, isAuthorized]) => { const unauthorizedStatus = publishAge < ZERO_CONF_EXPIRATION ? 'published' : 'rejected' diff --git a/migrations/1505044429557-add_cash_out_txs_published_at.js b/migrations/1505044429557-add_cash_out_txs_published_at.js new file mode 100644 index 00000000..451ed8db --- /dev/null +++ b/migrations/1505044429557-add_cash_out_txs_published_at.js @@ -0,0 +1,14 @@ +var db = require('./db') + +exports.up = function (next) { + const sql = [ + 'alter table cash_out_txs add column published_at timestamptz', + 'alter table cash_out_txs rename column confirmation_time to confirmed_at' + ] + + db.multi(sql, next) +} + +exports.down = function (next) { + next() +}