From e12238c4fee2b9bd808cabff6309b1efc6305275 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Thu, 20 Nov 2014 23:01:36 -0500 Subject: [PATCH] WIP --- .jscsrc | 66 +++++++++++++++++++++ lib/plugins.js | 78 ++++++++++++------------- lib/postgresql_interface.js | 82 ++++++++++++++++++--------- migrations/004-transactions-reload.js | 10 ++-- 4 files changed, 163 insertions(+), 73 deletions(-) create mode 100644 .jscsrc diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 00000000..516f92a1 --- /dev/null +++ b/.jscsrc @@ -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 +} diff --git a/lib/plugins.js b/lib/plugins.js index 54ceb1ff..3062db89 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -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 */ diff --git a/lib/postgresql_interface.js b/lib/postgresql_interface.js index 4f050162..ced204cb 100644 --- a/lib/postgresql_interface.js +++ b/lib/postgresql_interface.js @@ -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) { diff --git a/migrations/004-transactions-reload.js b/migrations/004-transactions-reload.js index 983d7a08..785a5628 100644 --- a/migrations/004-transactions-reload.js +++ b/migrations/004-transactions-reload.js @@ -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() ' + ')',