feat(txs): WIP rebuild of offline txs, next part

This commit is contained in:
Damian Mee 2014-09-13 07:52:24 +02:00
parent 67c5caeeff
commit bb4336b78f
2 changed files with 148 additions and 82 deletions

View file

@ -9,6 +9,21 @@ var PG_ERRORS = {
var client = null;
function getInsertQuery(tableName, fields) {
// outputs string like: '$1, $2, $3...' with proper No of items
var placeholders = fields.map(function(_, i) {
return '$' + (i + 1);
}).join(', ');
return 'INSERT INTO ' + tableName +
' (' + fields.join(', ') + ')' +
' VALUES' +
' (' + placeholders + ')';
}
exports.init = function init(conString) {
if (client !== null) return;
@ -23,12 +38,36 @@ exports.init = function init(conString) {
};
// logs inputted bill and overall tx status (if available)
exports.recordBill = function recordBill(deviceFingerprint, rec, cb) {
client.query('INSERT INTO bills (device_fingerprint, denomination, currency_code, ' +
'satoshis, to_address, transaction_id, device_time) ' +
'VALUES ($1, $2, $3, $4, $5, $6, $7)',
[deviceFingerprint, rec.fiat, rec.currency, rec.satoshis, rec.toAddress, rec.txId, rec.deviceTime],
cb);
var fields = [
'device_fingerprint',
'currency_code',
'to_address',
'transaction_id',
'device_time',
'satoshis',
'denomination'
];
var values = [
deviceFingerprint,
rec.currency,
rec.toAddress,
res.txId,
rec.deviceTime,
rec.satoshis,
rec.fiat
];
if (rec.partialTx) {
values.push(rec.partialTx.satoshis, rec.partialTx.fiat);
fields.push('total_satoshis', 'total_fiat');
}
client.query(getInsertQuery('bills', fields), values, cb);
};
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event, cb) {
@ -38,51 +77,54 @@ exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event,
cb);
};
// each received "partial transaction" contains sum of all previous bills
// (vel. no need to do any server-side summing)
function updatePartialTransaction(values, cb) {
var values2 = [
values[4],
values[6],
values[0],
'partial'
];
client.query('UPDATE transactions SET ' +
'satoshis=$1, ' +
'fiat=$2 ' +
'WHERE id=$3 AND status=$4',
values2,
cb);
}
exports.getTransaction = function getTransaction(txId, cb) {
client.query('SELECT * FROM transactions WHERE id=$1',
[txId],
function(err, results) {
if (err) return cb(err);
function _getTransactions(txId, onlyPending, cb) {
var query = 'SELECT * FROM transactions WHERE id=$1';
var values = [txId];
cb(null, results.rows.length >= 0 && results.rows[0]);
});
};
exports.fetchTransaction = function fetchTransaction(txId, cb) {
exports.getTransaction(txId, function(err, tx) {
if (onlyPending) {
query += ' AND status=$2 AND tx_hash IS NULL';
values.push('pending');
}
client.query(query, values, function(err, results) {
if (err) return cb(err);
if (!tx)
return cb(new Error('Couldn\'t find transaction.'));
if (results.rows.length === 0)
return cb(new Error('Couldn\'t find transaction'));
cb(null, {
txHash: tx.tx_hash,
err: tx.error,
status: tx.status
});
cb(null, results.rows);
});
}
// returns complete [txs]
exports.getTransactions = function getTransactions(txId, cb) {
_getTransactions(txId, false, cb);
};
// TODO: this should probably return bills, associated with failed/inexistent tx
exports.getPendingTransactions = function getPendingTransactions(txId, cb) {
// should return
// is_completed === false
// amount from bils is less than sum of all parts with the same txId
// latest bill with txId not present in transactions
//
_getTransactions(txId, true, cb);
};
exports.summonTransaction = function summonTransaction(deviceFingerprint, tx, cb) {
var status = tx.status || 'pending';
var fields = [
'id',
'status',
'device_fingerprint',
'to_address',
'satoshis',
'currency_code',
'fiat'
];
var values = [
tx.txId,
status,
tx.status || 'pending',
deviceFingerprint,
tx.toAddress,
tx.satoshis,
@ -90,23 +132,19 @@ exports.summonTransaction = function summonTransaction(deviceFingerprint, tx, cb
tx.fiat
];
if (tx.part && tx.part > 1) {
fields.push('part');
values.push(tx.part);
}
// First attampt an INSERT
// If it worked, go ahead with transaction
// If duplicate and partial update with new bills
// If duplicate, but not partial fetch status and return
client.query('INSERT INTO transactions ' +
'(id, status, device_fingerprint, to_address, satoshis, currency_code, fiat) ' +
'VALUES ($1, $2, $3, $4, $5, $6, $7)',
client.query(getInsertQuery('transactions', fields),
values,
function(err) {
if (err) {
if (PG_ERRORS[err.code] === 'uniqueViolation') {
if (status === 'partial')
return updatePartialTransaction(values, cb);
return exports.fetchTransaction(tx.txId, cb);
}
if (PG_ERRORS[err.code] === 'uniqueViolation')
return exports.getTransactions(tx.txId, cb);
return cb(err);
}
@ -115,17 +153,29 @@ exports.summonTransaction = function summonTransaction(deviceFingerprint, tx, cb
});
};
// `@more` can contain `part`, `hash`, or `error`
exports.changeTxStatus = function changeTxStatus(txId, newStatus, more, cb) {
cb = typeof cb === 'function' ? cb : function() {};
exports.reportTransactionError = function reportTransactionError(tx, errString, status) {
client.query('UPDATE transactions SET status=$1, error=$2 WHERE id=$3',
[status, errString, tx.txId]);
};
var query = 'UPDATE transactions SET status=$1';
var values = [
newStatus
];
exports.completeTransaction = function completeTransaction(tx, txHash) {
if (txHash)
client.query('UPDATE transactions SET tx_hash=$1, status=$2, completed=now() WHERE id=$3',
[txHash, 'completed', tx.txId]);
else
client.query('UPDATE transactions SET status=$1, error=$2 WHERE id=$3',
['failed', 'No txHash received', tx.txId]);
var n = 2;
if (newStatus === 'error') {
query += ', error=$' + n++;
values.push(more.error);
}
if (newStatus === 'completed') {
query += ', tx_hash=$' + n++;
values.push(more.hash);
}
query += ' WHERE id=$' + n;
values.push(txId);
client.query(query, values, cb);
};