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] = {
|
sessions[deviceFingerprint] = {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
reaper: setTimeout(function() {
|
reaper: setTimeout(function() {
|
||||||
db.getTransaction(rawTrade.txId, function(err, tx) {
|
// NOTE: at this point we either have bills ONLY *or a partial tx*
|
||||||
_sendBitcoins(tx, function() { });
|
// TODO: deal with #1 from ^
|
||||||
|
db.getPendingTransactions(rawTrade.txId, function(err, txs) {
|
||||||
|
// NOTE: returns ARRAY of txs
|
||||||
|
_sendBitcoins(txs[0], function() { });
|
||||||
delete sessions[deviceFingerprint];
|
delete sessions[deviceFingerprint];
|
||||||
});
|
});
|
||||||
}, SESSION_TIMEOUT)
|
}, SESSION_TIMEOUT)
|
||||||
|
|
@ -193,21 +196,6 @@ exports.trade = function trade(rawTrade, deviceFingerprint) {
|
||||||
// record (vel log) inserted bill
|
// record (vel log) inserted bill
|
||||||
db.recordBill(deviceFingerprint, rawTrade);
|
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)
|
// add bill to trader queue (if trader is enabled)
|
||||||
if (traderPlugin) {
|
if (traderPlugin) {
|
||||||
tradesQueue.push({
|
tradesQueue.push({
|
||||||
|
|
@ -258,11 +246,14 @@ function _sendBitcoins(tx, callback) {
|
||||||
'insufficientFunds' :
|
'insufficientFunds' :
|
||||||
'failed';
|
'failed';
|
||||||
|
|
||||||
db.reportTransactionError(tx, err.message, status);
|
// report insufficient funds error
|
||||||
|
db.changeTxStatus(tx.txId, status, {error: err.message});
|
||||||
return callback(err);
|
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();
|
pollBalance();
|
||||||
callback(null, txHash);
|
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
|
* Polling livecycle
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,21 @@ var PG_ERRORS = {
|
||||||
|
|
||||||
var client = null;
|
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) {
|
exports.init = function init(conString) {
|
||||||
if (client !== null) return;
|
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) {
|
exports.recordBill = function recordBill(deviceFingerprint, rec, cb) {
|
||||||
client.query('INSERT INTO bills (device_fingerprint, denomination, currency_code, ' +
|
var fields = [
|
||||||
'satoshis, to_address, transaction_id, device_time) ' +
|
'device_fingerprint',
|
||||||
'VALUES ($1, $2, $3, $4, $5, $6, $7)',
|
'currency_code',
|
||||||
[deviceFingerprint, rec.fiat, rec.currency, rec.satoshis, rec.toAddress, rec.txId, rec.deviceTime],
|
'to_address',
|
||||||
cb);
|
'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) {
|
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event, cb) {
|
||||||
|
|
@ -38,51 +77,54 @@ exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event,
|
||||||
cb);
|
cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
// each received "partial transaction" contains sum of all previous bills
|
function _getTransactions(txId, onlyPending, cb) {
|
||||||
// (vel. no need to do any server-side summing)
|
var query = 'SELECT * FROM transactions WHERE id=$1';
|
||||||
function updatePartialTransaction(values, cb) {
|
var values = [txId];
|
||||||
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);
|
|
||||||
|
|
||||||
cb(null, results.rows.length >= 0 && results.rows[0]);
|
if (onlyPending) {
|
||||||
});
|
query += ' AND status=$2 AND tx_hash IS NULL';
|
||||||
};
|
values.push('pending');
|
||||||
exports.fetchTransaction = function fetchTransaction(txId, cb) {
|
}
|
||||||
exports.getTransaction(txId, function(err, tx) {
|
|
||||||
|
client.query(query, values, function(err, results) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!tx)
|
if (results.rows.length === 0)
|
||||||
return cb(new Error('Couldn\'t find transaction.'));
|
return cb(new Error('Couldn\'t find transaction'));
|
||||||
|
|
||||||
cb(null, {
|
cb(null, results.rows);
|
||||||
txHash: tx.tx_hash,
|
|
||||||
err: tx.error,
|
|
||||||
status: tx.status
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
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 = [
|
var values = [
|
||||||
tx.txId,
|
tx.txId,
|
||||||
status,
|
tx.status || 'pending',
|
||||||
deviceFingerprint,
|
deviceFingerprint,
|
||||||
tx.toAddress,
|
tx.toAddress,
|
||||||
tx.satoshis,
|
tx.satoshis,
|
||||||
|
|
@ -90,23 +132,19 @@ exports.summonTransaction = function summonTransaction(deviceFingerprint, tx, cb
|
||||||
tx.fiat
|
tx.fiat
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (tx.part && tx.part > 1) {
|
||||||
|
fields.push('part');
|
||||||
|
values.push(tx.part);
|
||||||
|
}
|
||||||
|
|
||||||
// First attampt an INSERT
|
// First attampt an INSERT
|
||||||
// If it worked, go ahead with transaction
|
// If it worked, go ahead with transaction
|
||||||
// If duplicate and partial update with new bills
|
client.query(getInsertQuery('transactions', fields),
|
||||||
// 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)',
|
|
||||||
values,
|
values,
|
||||||
function(err) {
|
function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (PG_ERRORS[err.code] === 'uniqueViolation') {
|
if (PG_ERRORS[err.code] === 'uniqueViolation')
|
||||||
if (status === 'partial')
|
return exports.getTransactions(tx.txId, cb);
|
||||||
return updatePartialTransaction(values, cb);
|
|
||||||
|
|
||||||
return exports.fetchTransaction(tx.txId, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(err);
|
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) {
|
var query = 'UPDATE transactions SET status=$1';
|
||||||
client.query('UPDATE transactions SET status=$1, error=$2 WHERE id=$3',
|
var values = [
|
||||||
[status, errString, tx.txId]);
|
newStatus
|
||||||
};
|
];
|
||||||
|
|
||||||
exports.completeTransaction = function completeTransaction(tx, txHash) {
|
var n = 2;
|
||||||
if (txHash)
|
|
||||||
client.query('UPDATE transactions SET tx_hash=$1, status=$2, completed=now() WHERE id=$3',
|
if (newStatus === 'error') {
|
||||||
[txHash, 'completed', tx.txId]);
|
query += ', error=$' + n++;
|
||||||
else
|
values.push(more.error);
|
||||||
client.query('UPDATE transactions SET status=$1, error=$2 WHERE id=$3',
|
}
|
||||||
['failed', 'No txHash received', tx.txId]);
|
|
||||||
|
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