feat(txs): WIP rebuild of offline txs, next part
This commit is contained in:
parent
67c5caeeff
commit
bb4336b78f
2 changed files with 148 additions and 82 deletions
|
|
@ -182,8 +182,11 @@ exports.trade = function trade(rawTrade, deviceFingerprint) {
|
|||
sessions[deviceFingerprint] = {
|
||||
timestamp: Date.now(),
|
||||
reaper: setTimeout(function() {
|
||||
db.getTransaction(rawTrade.txId, function(err, tx) {
|
||||
_sendBitcoins(tx, function() { });
|
||||
// NOTE: at this point we either have bills ONLY *or a partial tx*
|
||||
// TODO: deal with #1 from ^
|
||||
db.getPendingTransactions(rawTrade.txId, function(err, txs) {
|
||||
// NOTE: returns ARRAY of txs
|
||||
_sendBitcoins(txs[0], function() { });
|
||||
delete sessions[deviceFingerprint];
|
||||
});
|
||||
}, SESSION_TIMEOUT)
|
||||
|
|
@ -193,21 +196,6 @@ exports.trade = function trade(rawTrade, deviceFingerprint) {
|
|||
// record (vel log) inserted bill
|
||||
db.recordBill(deviceFingerprint, rawTrade);
|
||||
|
||||
// cache partial Transaction (if supported by machine)
|
||||
if (rawTrade.partialTx) {
|
||||
var tx = {
|
||||
txId: rawTrade.txId,
|
||||
status: 'partial',
|
||||
toAddress: rawTrade.toAddress,
|
||||
currencyCode: rawTrade.currency,
|
||||
satoshis: rawTrade.partialTx.satoshis,
|
||||
fiat: rawTrade.partialTx.fiat
|
||||
};
|
||||
db.summonTransaction(deviceFingerprint, tx, function() {
|
||||
logger.debug('partial tx: %j', tx);
|
||||
});
|
||||
}
|
||||
|
||||
// add bill to trader queue (if trader is enabled)
|
||||
if (traderPlugin) {
|
||||
tradesQueue.push({
|
||||
|
|
@ -258,11 +246,14 @@ function _sendBitcoins(tx, callback) {
|
|||
'insufficientFunds' :
|
||||
'failed';
|
||||
|
||||
db.reportTransactionError(tx, err.message, status);
|
||||
// report insufficient funds error
|
||||
db.changeTxStatus(tx.txId, status, {error: err.message});
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
db.completeTransaction(tx, txHash);
|
||||
if (txHash) db.changeTxStatus(tx.txId, 'completed', {hash: txHash});
|
||||
else db.changeTxStatus(tx.txId, 'failed', {error: 'No txHash received'});
|
||||
|
||||
pollBalance();
|
||||
callback(null, txHash);
|
||||
}
|
||||
|
|
@ -295,6 +286,31 @@ exports.sendBitcoins = function sendBitcoins(deviceFingerprint, tx, callback) {
|
|||
});
|
||||
};
|
||||
|
||||
// NOTE: temporarily here
|
||||
exports.sendBitcoinians = function(deviceFingerprint, tx, callback) {
|
||||
db.summonTransaction(deviceFingerprint, tx, function(err, txInfo) {
|
||||
if (err) return callback(err);
|
||||
|
||||
if (txInfo) {
|
||||
if (txInfo.status === 'insufficientFunds') {
|
||||
|
||||
}
|
||||
|
||||
if (txInfo.status === 'executing') {
|
||||
|
||||
}
|
||||
|
||||
// TODO: check `part` and what has already been sent
|
||||
}
|
||||
|
||||
// no error & no tx record exists
|
||||
// TODO: should it be confirmed with bills?
|
||||
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Polling livecycle
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
function _getTransactions(txId, onlyPending, cb) {
|
||||
var query = 'SELECT * FROM transactions WHERE id=$1';
|
||||
var values = [txId];
|
||||
|
||||
if (onlyPending) {
|
||||
query += ' AND status=$2 AND tx_hash IS NULL';
|
||||
values.push('pending');
|
||||
}
|
||||
exports.getTransaction = function getTransaction(txId, cb) {
|
||||
client.query('SELECT * FROM transactions WHERE id=$1',
|
||||
[txId],
|
||||
function(err, results) {
|
||||
|
||||
client.query(query, values, function(err, results) {
|
||||
if (err) return cb(err);
|
||||
|
||||
cb(null, results.rows.length >= 0 && results.rows[0]);
|
||||
if (results.rows.length === 0)
|
||||
return cb(new Error('Couldn\'t find transaction'));
|
||||
|
||||
cb(null, results.rows);
|
||||
});
|
||||
}
|
||||
|
||||
// returns complete [txs]
|
||||
exports.getTransactions = function getTransactions(txId, cb) {
|
||||
_getTransactions(txId, false, cb);
|
||||
};
|
||||
exports.fetchTransaction = function fetchTransaction(txId, cb) {
|
||||
exports.getTransaction(txId, function(err, tx) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (!tx)
|
||||
return cb(new Error('Couldn\'t find transaction.'));
|
||||
// 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);
|
||||
};
|
||||
|
||||
cb(null, {
|
||||
txHash: tx.tx_hash,
|
||||
err: tx.error,
|
||||
status: tx.status
|
||||
});
|
||||
});
|
||||
}
|
||||
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);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue