WIP
This commit is contained in:
parent
0cec3670a9
commit
e12238c4fe
4 changed files with 163 additions and 73 deletions
66
.jscsrc
Normal file
66
.jscsrc
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"requireCurlyBraces": [
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireOperatorBeforeLineBreak": true,
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
|
||||
"maximumLineLength": {
|
||||
"value": 80,
|
||||
"allowComments": true,
|
||||
"allowRegex": true
|
||||
},
|
||||
"validateIndentation": 2,
|
||||
"validateQuoteMarks": "'",
|
||||
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"disallowSpaceAfterPrefixUnaryOperators": true,
|
||||
"disallowMultipleVarDecl": true,
|
||||
|
||||
"requireSpaceAfterKeywords": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"switch",
|
||||
"return",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireSpaceBeforeBinaryOperators": [
|
||||
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
|
||||
"&=", "|=", "^=", "+=",
|
||||
|
||||
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
|
||||
"|", "^", "&&", "||", "===", "==", ">=",
|
||||
"<=", "<", ">", "!=", "!=="
|
||||
],
|
||||
"requireSpaceAfterBinaryOperators": true,
|
||||
"requireSpacesInConditionalExpression": true,
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"requireLineFeedAtFileEnd": true,
|
||||
"requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"disallowSpacesInAnonymousFunctionExpression": {
|
||||
"beforeOpeningRoundBrace": true
|
||||
},
|
||||
"disallowSpacesInsideObjectBrackets": "all",
|
||||
"disallowSpacesInsideArrayBrackets": "all",
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
|
||||
|
||||
"validateJSDoc": {
|
||||
"checkParamNames": true,
|
||||
"requireParamTypes": true
|
||||
},
|
||||
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowNewlineBeforeBlockStatements": true
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ var infoPlugin = null;
|
|||
|
||||
var currentlyUsedPlugins = {};
|
||||
|
||||
|
||||
var cachedConfig = null;
|
||||
var deviceCurrency = 'USD';
|
||||
|
||||
|
|
@ -33,7 +32,6 @@ var reapTxInterval = null;
|
|||
var tradesQueue = [];
|
||||
var dispenseStatuses = {};
|
||||
|
||||
|
||||
// that's basically a constructor
|
||||
exports.init = function init(databaseHandle) {
|
||||
if (!databaseHandle) {
|
||||
|
|
@ -43,16 +41,15 @@ exports.init = function init(databaseHandle) {
|
|||
db = databaseHandle;
|
||||
};
|
||||
|
||||
|
||||
function loadPlugin(name, config) {
|
||||
|
||||
// plugins definitions
|
||||
var moduleMethods = {
|
||||
ticker: [ 'ticker' ],
|
||||
trader: [ 'balance', 'purchase', 'sell' ],
|
||||
wallet: [ 'balance', 'sendBitcoins', 'newAddress' ],
|
||||
idVerifier: [ 'verifyUser', 'verifyTransaction' ],
|
||||
info: [ 'getAddressLastTx', 'getTx' ]
|
||||
ticker: ['ticker'],
|
||||
trader: ['balance', 'purchase', 'sell'],
|
||||
wallet: ['balance', 'sendBitcoins', 'newAddress'],
|
||||
idVerifier: ['verifyUser', 'verifyTransaction'],
|
||||
info: ['getAddressLastTx', 'getTx']
|
||||
};
|
||||
|
||||
var plugin = null;
|
||||
|
|
@ -60,67 +57,72 @@ function loadPlugin(name, config) {
|
|||
// each used plugin MUST be installed
|
||||
try {
|
||||
plugin = require('lamassu-' + name);
|
||||
|
||||
} catch(_) {
|
||||
throw new Error(name + ' module is not installed. Try running \'npm install --save lamassu-' + name + '\' first');
|
||||
} catch (_) {
|
||||
throw new Error(name + ' module is not installed. ' +
|
||||
'Try running \'npm install --save lamassu-' + name + '\' first');
|
||||
}
|
||||
|
||||
|
||||
// each plugin MUST implement those
|
||||
if (typeof plugin.SUPPORTED_MODULES !== 'undefined') {
|
||||
if(plugin.SUPPORTED_MODULES === 'string')
|
||||
if (plugin.SUPPORTED_MODULES === 'string')
|
||||
plugin.SUPPORTED_MODULES = [plugin.SUPPORTED_MODULES];
|
||||
}
|
||||
|
||||
if(!(plugin.SUPPORTED_MODULES instanceof Array))
|
||||
throw new Error('\'' + name + '\' fails to implement *required* \'SUPPORTED_MODULES\' constant');
|
||||
if (!(plugin.SUPPORTED_MODULES instanceof Array))
|
||||
throw new Error('\'' + name + '\' fails to implement *required* ' +
|
||||
'\'SUPPORTED_MODULES\' constant');
|
||||
|
||||
plugin.SUPPORTED_MODULES.forEach(function(moduleName) {
|
||||
moduleMethods[moduleName].forEach(function(methodName) {
|
||||
if (typeof plugin[methodName] !== 'function') {
|
||||
throw new Error('\'' + name + '\' declares \'' + moduleName + '\', but fails to implement \'' + methodName + '\' method');
|
||||
throw new Error('\'' + name + '\' declares \'' + moduleName +
|
||||
'\', but fails to implement \'' + methodName + '\' method');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// each plugin SHOULD implement those
|
||||
if (typeof plugin.NAME === 'undefined')
|
||||
logger.warn(new Error('\'' + name + '\' fails to implement *recommended* \'NAME\' field'));
|
||||
logger.warn(new Error('\'' + name +
|
||||
'\' fails to implement *recommended* \'NAME\' field'));
|
||||
|
||||
if (typeof plugin.config !== 'function') {
|
||||
logger.warn(new Error('\'' + name + '\' fails to implement *recommended* \'config\' method'));
|
||||
logger.warn(new Error('\'' + name +
|
||||
'\' fails to implement *recommended* \'config\' method'));
|
||||
plugin.config = function() {};
|
||||
} else if (config !== null)
|
||||
} else if (config !== null) {
|
||||
plugin.config(config); // only when plugin supports it, and config is passed
|
||||
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
function loadOrConfigPlugin(pluginHandle, pluginType, currency, onChangeCallback) {
|
||||
function loadOrConfigPlugin(pluginHandle, pluginType, currency,
|
||||
onChangeCallback) {
|
||||
var currentName = cachedConfig.exchanges.plugins.current[pluginType];
|
||||
var pluginChanged = currentlyUsedPlugins[pluginType] !== currentName;
|
||||
|
||||
if (!currentName) pluginHandle = null;
|
||||
else { // some plugins may be disabled
|
||||
var pluginConfig = cachedConfig.exchanges.plugins.settings[currentName] || {};
|
||||
var pluginConfig = cachedConfig.exchanges.plugins.settings[currentName] ||
|
||||
{};
|
||||
|
||||
if (currency) pluginConfig.currency = currency;
|
||||
|
||||
if (pluginHandle && !pluginChanged) pluginHandle.config(pluginConfig);
|
||||
else {
|
||||
pluginHandle = loadPlugin(currentName, pluginConfig);
|
||||
logger.debug('plugin(%s) loaded: %s', pluginType, pluginHandle.NAME || currentName);
|
||||
logger.debug('plugin(%s) loaded: %s', pluginType, pluginHandle.NAME ||
|
||||
currentName);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof onChangeCallback === 'function') onChangeCallback(pluginHandle, currency);
|
||||
if (typeof onChangeCallback === 'function')
|
||||
onChangeCallback(pluginHandle, currency);
|
||||
|
||||
return pluginHandle;
|
||||
}
|
||||
|
||||
|
||||
exports.configure = function configure(config) {
|
||||
if (config.exchanges.settings.lowBalanceMargin < 1) {
|
||||
throw new Error('\'settings.lowBalanceMargin\' has to be >= 1');
|
||||
|
|
@ -217,9 +219,10 @@ function reapOutgoingTx(deviceFingerprint, tx) {
|
|||
}
|
||||
|
||||
function reapIncomingTx(deviceFingerprint, tx) {
|
||||
infoPlugin.checkAddress(tx.toAddress, function(err, status, satoshisReceived) {
|
||||
infoPlugin.checkAddress(tx.toAddress, function(err, status,
|
||||
satoshisReceived) {
|
||||
if (status === 'notSeen') return;
|
||||
db.addIngoingTx(deviceFingerprint, tx, status, satoshisReceived);
|
||||
db.addIncomingTx(deviceFingerprint, tx, status, satoshisReceived);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +280,6 @@ exports.sendBitcoins = function sendBitcoins(deviceFingerprint, rawTx, cb) {
|
|||
executeTx(deviceFingerprint, rawTx, cb);
|
||||
};
|
||||
|
||||
|
||||
// sets given status both "locally" (dispenseStatuses) and saves to db
|
||||
function _setDispenseStatus(deviceFingerprint, tx, status, deposit) {
|
||||
tx.status = status;
|
||||
|
|
@ -313,14 +315,10 @@ exports.cashOut = function cashOut(deviceFingerprint, tx, cb) {
|
|||
account: 'deposit'
|
||||
};
|
||||
walletPlugin.newAddress(tmpInfo, function(err, address) {
|
||||
if (err)
|
||||
return cb(new Error(err));
|
||||
if (err) return cb(err);
|
||||
|
||||
tx.toAddress = address;
|
||||
tx.incoming = true;
|
||||
|
||||
db.addPendingTx(deviceFingerprint, tx, function(err) {
|
||||
cb(err, address);
|
||||
db.addInitialIncoming(deviceFingerprint, tx, address, function(_err) {
|
||||
cb(_err, address);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
@ -333,7 +331,6 @@ exports.dispenseStatus = function dispenseStatus(deviceFingerprint) {
|
|||
return dispenseStatuses[deviceFingerprint];
|
||||
};
|
||||
|
||||
|
||||
exports.fiatBalance = function fiatBalance() {
|
||||
var rawRate = exports.getDeviceRate().rates.ask;
|
||||
var commission = cachedConfig.exchanges.settings.commission;
|
||||
|
|
@ -357,12 +354,12 @@ exports.fiatBalance = function fiatBalance() {
|
|||
// Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ]
|
||||
// [ $ ] = [ B * $/B ]
|
||||
// [ $ ] = [ $ ]
|
||||
var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin;
|
||||
var fiatTransferBalance = ((transferBalance / SATOSHI_FACTOR) * rate) /
|
||||
lowBalanceMargin;
|
||||
|
||||
return fiatTransferBalance;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Polling livecycle
|
||||
*/
|
||||
|
|
@ -401,7 +398,6 @@ function stopTrader() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function pollBalance(cb) {
|
||||
logger.debug('collecting balance');
|
||||
|
||||
|
|
@ -446,7 +442,6 @@ function pollRate(cb) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Getters | Helpers
|
||||
*/
|
||||
|
|
@ -515,7 +510,6 @@ function executeTrades() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ID Verifier functions
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ function getInsertQuery(tableName, fields, hasId) {
|
|||
return query;
|
||||
}
|
||||
|
||||
|
||||
exports.init = function init(_conString) {
|
||||
conString = _conString;
|
||||
if (!conString) {
|
||||
|
|
@ -86,13 +85,15 @@ exports.recordBill = function recordBill(deviceFingerprint, rec, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint, event) {
|
||||
exports.recordDeviceEvent = function recordDeviceEvent(deviceFingerprint,
|
||||
event) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return;
|
||||
client.query('INSERT INTO device_events (device_fingerprint, event_type, note, device_time)' +
|
||||
'VALUES ($1, $2, $3, $4)',
|
||||
[deviceFingerprint, event.eventType, event.note, event.deviceTime],
|
||||
done);
|
||||
var sql = 'INSERT INTO device_events (device_fingerprint, event_type, ' +
|
||||
'note, device_time) VALUES ($1, $2, $3, $4)';
|
||||
var values = [deviceFingerprint, event.eventType, event.note,
|
||||
event.deviceTime];
|
||||
client.query(sql, values, done);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -144,7 +145,8 @@ function computeSendAmount(tx, totals) {
|
|||
};
|
||||
if (result.fiat < 0 || result.satoshis < 0) {
|
||||
logger.warn({tx: tx, totals: totals, result: result},
|
||||
'computeSendAmount result < 0, this shouldn\'t happen. Maybe timeout arrived after machineSend.');
|
||||
'computeSendAmount result < 0, this shouldn\'t happen. ' +
|
||||
'Maybe timeout arrived after machineSend.');
|
||||
result.fiat = 0;
|
||||
result.satoshis = 0;
|
||||
}
|
||||
|
|
@ -165,9 +167,9 @@ exports.pendingTxs = function pendingTxs(timeoutMS, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
function removePendingTx(client, tx, cb) {
|
||||
function removePendingTx(client, sessionId, cb) {
|
||||
var sql = 'DELETE FROM pending_transactions WHERE session_id=$1';
|
||||
silentQuery(client, sql, [tx.txId], cb);
|
||||
silentQuery(client, sql, [sessionId], cb);
|
||||
}
|
||||
|
||||
function insertOutgoingTx(client, deviceFingerprint, tx, totals, cb) {
|
||||
|
|
@ -225,13 +227,14 @@ function insertTx(client, deviceFingerprint, tx, satoshis, fiat, stage,
|
|||
query(client, getInsertQuery('transactions', fields, true), values, cb);
|
||||
}
|
||||
|
||||
exports.addPendingTx = function addPendingTx(deviceFingerprint, sessionId,
|
||||
incoming, cb) {
|
||||
function addPendingTx(deviceFingerprint, sessionId,
|
||||
incoming, currencyCode, toAddress, cb) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return cb(err);
|
||||
var fields = ['session_id', 'incoming'];
|
||||
var fields = ['session_id', 'incoming', 'currency_code', 'to_address'];
|
||||
var sql = getInsertQuery('pending_transactions', fields);
|
||||
query(client, sql, [sessionId, incoming], function(_err) {
|
||||
var values = [sessionId, incoming, currencyCode, toAddress];
|
||||
query(client, sql, values, function(_err) {
|
||||
done();
|
||||
|
||||
// If pending tx already exists, do nothing
|
||||
|
|
@ -241,7 +244,7 @@ exports.addPendingTx = function addPendingTx(deviceFingerprint, sessionId,
|
|||
cb(_err);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Calling function should only send bitcoins if result.satoshisToSend > 0
|
||||
exports.addOutgoingTx = function addOutgoingTx(deviceFingerprint, tx, cb) {
|
||||
|
|
@ -249,9 +252,10 @@ exports.addOutgoingTx = function addOutgoingTx(deviceFingerprint, tx, cb) {
|
|||
if (err) return cb(err);
|
||||
async.waterfall([
|
||||
async.apply(silentQuery, client, 'BEGIN', null),
|
||||
async.apply(insertOutgoingCompleteTx, client, deviceFingerprint, tx)
|
||||
async.apply(removePendingTx, client, tx),
|
||||
async.apply(billsAndTxs, client, tx.txId, tx.currencyCode, deviceFingerprint),
|
||||
async.apply(insertOutgoingCompleteTx, client, deviceFingerprint, tx),
|
||||
async.apply(removePendingTx, client, tx.sessionId),
|
||||
async.apply(billsAndTxs, client, tx.txId, tx.currencyCode,
|
||||
deviceFingerprint),
|
||||
async.apply(insertOutgoingTx, client, deviceFingerprint, tx),
|
||||
], function(err, satoshisToSend) {
|
||||
if (err) {
|
||||
|
|
@ -266,32 +270,56 @@ exports.addOutgoingTx = function addOutgoingTx(deviceFingerprint, tx, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
function removeIncomingPendingTx(client, tx, status, cb) {
|
||||
if (status !== 'published') return removePendingTx(client, tx, cb);
|
||||
cb();
|
||||
}
|
||||
|
||||
exports.addIncomingTx = function addIncomingTx(deviceFingerprint, tx, status,
|
||||
exports.addIncomingTx = function addIncomingTx(deviceFingerprint, tx, source,
|
||||
satoshisReceived, cb) {
|
||||
connect(function(err, client, done) {
|
||||
function maybeRemovePending(client, sessionId, source, cb) {
|
||||
if (source === 'published') return cb();
|
||||
removePendingTx(client, sessionId, cb);
|
||||
}
|
||||
|
||||
if (err) return cb(err);
|
||||
async.waterfall([
|
||||
async.apply(silentQuery, client, 'BEGIN', null),
|
||||
async.apply(removeOutgoingPendingTx, client, tx, status),
|
||||
async.apply(insertTx, client, tx, satoshisReceived, 0, 'deposit')
|
||||
], function(err, result) {
|
||||
async.apply(maybeRemovePending, client, tx.sessionId, source),
|
||||
async.apply(insertTx, client, tx, satoshisReceived, 0, 'deposit', source)
|
||||
], function(err) {
|
||||
if (err) {
|
||||
rollback(client, done);
|
||||
return cb(err);
|
||||
}
|
||||
silentQuery(client, 'COMMIT', null, function() {
|
||||
done();
|
||||
cb(null, result);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.addInitialIncoming = function addInitialIncoming(deviceFingerprint, tx,
|
||||
address, cb) {
|
||||
connect(function(err, client, done) {
|
||||
if (err) return cb(err);
|
||||
async.waterfall([
|
||||
async.apply(silentQuery, client, 'BEGIN', null),
|
||||
async.apply(addPendingTx, client, deviceFingerprint, tx.sessionId,
|
||||
tx.incoming, tx.currencyCode, tx.toAddress),
|
||||
async.apply(insertTx, client, tx, tx.satoshis, tx.fiat,
|
||||
'initial_request', 'pending')
|
||||
], function(err) {
|
||||
if (err) {
|
||||
rollback(client, done);
|
||||
return cb(err);
|
||||
}
|
||||
silentQuery(client, 'COMMIT', null, function() {
|
||||
done();
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
exports.decrementCartridges =
|
||||
function decrementCartridges(fingerprint, cartridge1, cartridge2, cb) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ exports.up = function(next){
|
|||
var stages = ['initial_request', 'partial_request', 'final_request',
|
||||
'partial_send', 'deposit', 'dispense_request', 'dispense'].
|
||||
map(singleQuotify).join(',');
|
||||
var sources = ['timeout', 'machine', 'published', 'authorized', 'rejected'].
|
||||
map(singleQuotify).join(',');
|
||||
var sources = ['timeout', 'machine', 'pending', 'published',
|
||||
'authorized', 'rejected'].map(singleQuotify).join(',');
|
||||
var sqls = [
|
||||
'CREATE TYPE transaction_stage AS ENUM (' + stages + ')',
|
||||
'CREATE TYPE transaction_source AS ENUM (' + sources + ')',
|
||||
|
|
@ -37,8 +37,10 @@ exports.up = function(next){
|
|||
|
||||
'CREATE TABLE pending_transactions ( ' +
|
||||
'id serial PRIMARY KEY, ' +
|
||||
'session_id uuid UNIQUE, ' +
|
||||
'incoming boolean, ' +
|
||||
'session_id uuid UNIQUE NOT NULL, ' +
|
||||
'incoming boolean NOT NULL, ' +
|
||||
'currency_code text NOT NULL, ' +
|
||||
'to_address text NOT NULL, ' +
|
||||
'created timestamp NOT NULL DEFAULT now() ' +
|
||||
')',
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue