Start the refactor

This commit is contained in:
Maciej Małecki 2014-04-10 12:09:29 +02:00
parent e5b94527a9
commit f376e96ab2
10 changed files with 608 additions and 2 deletions

145
lib/protocol/api/api.js Normal file
View file

@ -0,0 +1,145 @@
'use strict';
require('date-utils');
//var async = require('async');
var winston = require('winston');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var path = require('path');
var _transferExchange;
var _tickerExchange;
var _tradeExchange;
var _rates = {};
var _config;
var _commission;
var _config;
var SATOSHI_FACTOR = Math.pow(10, 8);
exports.ticker = require('./ticker');
exports.trade = require('./trade');
exports.send = require('./send');
exports.balance = require('./balance');
exports._tradeExchange = null;
exports._transferExchange = null;
exports.findExchange = function (name) {
var exchange;
try {
exchange = require('lamassu-' + name);
} catch (err) {
if (!err.message.match(/Cannot find module/)) throw err;
exchange = require(path.join(path.dirname(__dirname), 'exchanges', name));
}
return exchange;
};
exports.findTicker = function (name) {
var exchange = exports.findExchange(name);
return exchange.ticker || exchange;
};
exports.findTrader = function (name) {
var exchange = exports.findExchange(name);
return exchange.trader || exchange;
};
exports.findWallet = function (name) {
var exchange = exports.findExchange(name);
return exchange.wallet || exchange;
};
exports.triggerBalance = function triggerBalance() {
this.balance.triggerBalance();
};
exports.init = function(config) {
_config = config;
if (config.settings.lowBalanceMargin < 1) {
throw new Error('`settings.lowBalanceMargin` has to be >= 1');
}
var tickerExchangeCode = config.plugins.current.ticker;
var tickerExchangeConfig = config.plugins.settings[tickerExchangeCode] || {};
tickerExchangeConfig.currency = config.settings.currency;
_tickerExchange = exports.findTicker(tickerExchangeCode).factory(tickerExchangeConfig);
var tradeExchangeCode = config.plugins.current.trade;
if (tradeExchangeCode) {
var tradeExchangeConfig = config.plugins.settings[tradeExchangeCode];
_tradeExchange = exports.findTrader(tradeExchangeCode).factory(tradeExchangeConfig);
}
var transferExchangeCode = config.plugins.current.transfer;
var transferExchangeConfig = config.plugins.settings[transferExchangeCode];
_commission = config.settings.commission;
_transferExchange = exports.findWallet(transferExchangeCode).factory(transferExchangeConfig);
var doRequestTradeExchange = _tradeExchange && tradeExchangeCode !== transferExchangeCode;
exports._tradeExchange = _tradeExchange;
exports._transferExchange = _transferExchange;
exports.ticker.init(config, exports, _tickerExchange);
exports.trade.init(config, exports, _tradeExchange, exports.ticker);
exports.send.init(config, exports, _transferExchange, exports.ticker);
exports.balance.init(config, exports, _transferExchange,
doRequestTradeExchange ? _tradeExchange : null);
};
/**
* return fiat balance
*
* in input to this function, balance has the following parameters...
*
* balance.transferBalance - in satoshis
* balance.tradeBalance - in USD
*
* Have added conversion here, but this really needs to be thought through, lamassu-bitstamp should perhaps
* return balance in satoshis
*/
exports.fiatBalance = function(rate, balance, transferSatoshis, tradeFiat, callback) {
if (!rate || !balance) return 0;
// The rate is actually our commission times real rate.
rate = _commission * rate;
// `lowBalanceMargin` is our safety net. It's a number > 1, and we divide
// all our balances by it to provide a safety margin.
var lowBalanceMargin = _config.settings.lowBalanceMargin;
// `balance.transferBalance` is the balance of our transfer account (the one
// we use to send Bitcoins to clients). `transferSatoshis` is the number
// of satoshis we're expected to send for this transaction. By subtracting
// them, we get `adjustedTransferBalance`, amount of satoshis we'll have
// after the transaction.
var adjustedTransferBalance = balance.transferBalance - transferSatoshis;
// Since `adjustedTransferBalance` is in Satoshis, we need to turn it into
// Bitcoins and then fiat to learn how much fiat currency we can exchange.
//
// Unit validity proof: [ $ ] = [ (B * 10^8) / 10^8 * $/B ]
// [ $ ] = [ B * $/B ]
// [ $ ] = [ $ ]
var fiatTransferBalance = ((adjustedTransferBalance / SATOSHI_FACTOR) * rate) / lowBalanceMargin;
// If this server is also configured to trade received fiat for Bitcoins,
// we also need to calculate if we have enough funds on our trade exchange.
if (balance.tradeBalance === null) return fiatTransferBalance;
var tradeBalance = balance.tradeBalance;
// We need to secure `tradeFiat` (amount of fiat in this transaction) and
// enough fiat to cover our trading queue (trades aren't executed immediately).
var adjustedFiat = tradeFiat + exports.trade.queueFiatBalance(rate);
// So we subtract `adjustedFiat` from `tradeBalance` and again, apply
// `lowBalanceMargin`.
var fiatTradeBalance = (tradeBalance - adjustedFiat) / lowBalanceMargin;
// And we return the smallest number.
return Math.min(fiatTransferBalance, fiatTradeBalance);
};

View file

@ -0,0 +1,49 @@
'use strict';
var _transferExchange;
var _tradeExchange;
var _api;
var _config;
var _balance = null;
var _balanceTriggers = [];
var winston = require('winston');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var async = require('async');
exports.init = function(config, api, transferExchange, tradeExchange) {
_api = api;
_config = config;
_transferExchange = transferExchange;
_tradeExchange = tradeExchange;
_balanceTriggers = [function (cb) { _transferExchange.balance(cb); }];
if (tradeExchange)
_balanceTriggers.push(function(cb) { _tradeExchange.balance(cb); });
_pollBalance();
setInterval(_pollBalance, 60 * 1000);
};
exports.balance = function balance() {
return _balance;
};
exports.triggerBalance = _pollBalance;
function _pollBalance() {
logger.info('collecting balance');
async.parallel(_balanceTriggers, function(err, results) {
if (err) return;
_balance = {
transferBalance: results[0],
tradeBalance: results.length === 2 ? results[1] : null,
timestamp: Date.now()
};
logger.info('Balance update:', _balance);
});
}

37
lib/protocol/api/send.js Normal file
View file

@ -0,0 +1,37 @@
'use strict';
var _transferExchange;
var _api;
var _config;
var _conString = process.env.DATABASE_URL || 'postgres://lamassu:lamassu@localhost/lamassu';
var _db = require('../db/postgresql_interface').factory(_conString);
exports.init = function(config, api, transferExchange) {
_api = api;
_config = config;
_transferExchange = transferExchange;
};
exports.setDomain = function(domain) {
_transferExchange.setDomain(domain);
};
exports.sendBitcoins = function sendBitcoins(deviceFingerprint, tx, cb) {
_db.summonTransaction(deviceFingerprint, tx, function (err, isNew, txHash) {
if (err) return cb(err);
if (isNew) return _transferExchange.sendBitcoins(tx.toAddress, tx.satoshis,
_config.settings.transactionFee, function(err, txHash) {
if (err) {
_db.reportTransactionError(tx, err);
return cb(err);
}
cb(null, txHash);
_db.completeTransaction(tx, txHash);
_api.triggerBalance();
});
// transaction exists, but txHash might be null,
// in which case ATM should continue polling
cb(null, txHash);
});
};

View file

@ -0,0 +1,33 @@
'use strict';
require('date-utils');
var winston = require('winston');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var _tickerExchange;
var _api;
var _rates = {};
var _pollRate = function(currency) {
logger.info('polling for rate...');
_tickerExchange.ticker(currency, function(err, rate) {
if (err) return;
logger.info('Rate update:', rate);
_rates[currency] = {rate: rate, timestamp: new Date()};
});
};
exports.init = function(config, api, tickerExchange) {
_api = api;
_tickerExchange = tickerExchange;
_pollRate(config.settings.currency);
setInterval(function () {
_pollRate(config.settings.currency);
}, 60 * 1000);
};
exports.rate = function(currency) {
if (!_rates[currency]) return null;
return _rates[currency];
};

100
lib/protocol/api/trade.js Normal file
View file

@ -0,0 +1,100 @@
'use strict';
require('date-utils');
var winston = require('winston');
var _ = require('underscore');
var logger = new (winston.Logger)({transports:[new (winston.transports.Console)()]});
var _tradeExchange;
var _ticker;
var _tradeQueue = [];
var _api;
var _config;
var SATOSHI_FACTOR = Math.pow(10, 8);
var _consolidateTrades = function() {
var queue = _tradeQueue;
var tradeRec = {
fiat: 0,
satoshis: 0,
currency: 'USD'
};
while (true) {
var lastRec = queue.shift();
if (!lastRec) {
break;
}
tradeRec.fiat += lastRec.fiat;
tradeRec.satoshis += lastRec.satoshis;
tradeRec.currency = lastRec.currency;
}
return tradeRec;
};
/**
* TODO: add error reporting
*/
var _purchase = function(trade) {
_ticker.rate(trade.currency, function(err, rate) {
_tradeExchange.purchase(trade.satoshis, rate, function(err) {
_api.triggerBalance();
});
});
};
exports.init = function(config, api, tradeExchange, ticker) {
_config = config;
_api = api;
_tradeExchange = tradeExchange;
_ticker = ticker;
var interval = setInterval(function() {
exports.executeTrades();
}, _config.settings.tradeInterval);
interval.unref();
};
exports.trade = function(fiat, satoshis, currency, cb) {
_tradeQueue.push({fiat: fiat, satoshis: satoshis, currency: currency});
cb(null);
};
exports.queueFiatBalance = function(exchangeRate) {
var satoshis = _.reduce(_tradeQueue, function(memo, rec) {
return memo + rec.satoshis;
}, 0);
return (satoshis / SATOSHI_FACTOR) * exchangeRate;
};
exports.executeTrades = function() {
if (!_tradeExchange) return;
logger.info('checking for trades');
if (!_config.plugins.current.trade) {
logger.info('NO ENGINE');
return;
}
var trade = _consolidateTrades();
logger.info('consolidated: ' + JSON.stringify(trade));
if (trade.fiat === 0) {
logger.info('reject fiat 0');
return;
}
if (trade.fiat < _config.settings.minimumTradeFiat) {
// throw it back in the water
logger.info('reject fiat too small');
_tradeQueue.unshift(trade);
return;
}
logger.info('making a trade: %d', trade.satoshis / Math.pow(10,8));
_purchase(trade);
};