Skip to content

Commit

Permalink
[BREAKING] destination routing
Browse files Browse the repository at this point in the history
  • Loading branch information
sentientwaffle committed May 9, 2016
1 parent 519e7aa commit 41860af
Show file tree
Hide file tree
Showing 26 changed files with 1,500 additions and 725 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ ex.
```

* `CONNECTOR_FX_SPREAD` (default: `0.002` =.2%) How much of a spread to add on top of the reference exchange rate. This determines the connector's margin.
* `CONNECTOR_SLIPPAGE` (default: `0.001` = 0.1%) The ratio for overestimating exchange rates to prevent payment failure if the rate changes.
* `CONNECTOR_MIN_MESSAGE_WINDOW` (default: `1`) Minimum time the connector wants to budget for getting a message to the ledgers its trading on. In seconds.
* `CONNECTOR_MAX_HOLD_TIME` (default: `10`) Maximum duration (seconds) the connector is willing to place funds on hold while waiting for the outcome of a transaction.
* `CONNECTOR_AUTH_CLIENT_CERT_ENABLED` (default `0`) whether or not to enable TLS Client Certificate authentication (requires HTTPS).
Expand All @@ -107,8 +108,6 @@ ex.
* `CONNECTOR_TLS_CERTIFICATE` (default: none) the path to the server certificate file. Required if using HTTPS.
* `CONNECTOR_TLS_CRL` (default: none) the path to the server certificate revokation list file. Optional if using HTTPS.
* `CONNECTOR_TLS_CA` (default: none) the path to a trusted certificate to be used in addition to using the [default list](https://github.com/nodejs/node/blob/v4.3.0/src/node_root_certs.h). Optional if using HTTPS.
* `CONNECTOR_QUOTE_FULL_PATH` (default: `''`) Feature to enable pathfinding for `/quote`.
* `CONNECTOR_BROADCAST_ROUTES` (default: `''`) Feature to enable propagation of routing tables.

#### Auto-funding

Expand Down
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@
"co-defer": "^1.0.0",
"co-request": "^1.0.0",
"five-bells-condition": "^3.2.0",
"five-bells-pathfind": "~5.0.0",
"five-bells-routing": "~1.0.0",
"five-bells-shared": "^14.1.2",
"five-bells-routing": "~2.0.0",
"five-bells-shared": "^15.0.0",
"koa": "^1.0.0",
"koa-compress": "^1.0.6",
"koa-cors": "0.0.16",
Expand Down
5 changes: 1 addition & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ function listen (koaApp, config, ledgers) {
// subscribes to all the ledgers in the background
co(function * () {
yield backend.connect()
if (config.features.broadcastRoutes) {
yield routeBroadcaster.start()
}
yield routeBroadcaster.start()
yield subscriptions.subscribePairs(config.get('tradingPairs'), ledgers, config)
}).catch(function (err) {
log('app').error(typeof err === 'object' && err.stack || err)
Expand Down Expand Up @@ -91,7 +89,6 @@ function createApp (config, ledgers) {
koaApp.use(route.get('/pairs', pairs.getCollection))

koaApp.use(route.get('/quote', quote.get))
koaApp.use(route.get('/quote_local', quote.getLocal))
koaApp.use(route.post('/routes', routes.post))

koaApp.use(route.post('/notifications', notifications.post))
Expand Down
18 changes: 6 additions & 12 deletions src/controllers/quote.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,13 @@ const InvalidAmountSpecifiedError = require('../errors/invalid-amount-specified-

exports.get = function * () {
validateAmounts(this.query.source_amount, this.query.destination_amount)
if (!this.config.features.quoteFullPath) {
this.body = yield model.getLocalQuote(this.query, this.ledgers, this.config)
return
if (!this.query.source_account && !this.query.source_ledger) {
throw new InvalidUriParameterError('Missing required parameter: source_ledger or source_account')
}

if (!this.query.source_account) throw new InvalidUriParameterError('Missing required parameter: source_account')
if (!this.query.destination_account) throw new InvalidUriParameterError('Missing required parameter: destination_account')
this.body = yield model.getFullQuote(this.query, this.ledgers, this.config)
}

exports.getLocal = function * () {
validateAmounts(this.query.source_amount, this.query.destination_amount)
this.body = yield model.getLocalQuote(this.query, this.ledgers, this.config)
if (!this.query.destination_account && !this.query.destination_ledger) {
throw new InvalidUriParameterError('Missing required parameter: destination_ledger or destination_account')
}
this.body = yield model.getFullQuote(this.query, this.config)
}

function validateAmounts (sourceAmount, destinationAmount) {
Expand Down
11 changes: 3 additions & 8 deletions src/controllers/routes.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
'use strict'

const requestUtil = require('five-bells-shared/utils/request')
const routing = require('five-bells-routing')
const config = require('../services/config')
const routingTables = require('../services/routing-tables')
const routeBroadcaster = require('../services/route-broadcaster')
const knownConnectors = {}

exports.post = function * () {
const routes = yield requestUtil.validateBody(this, 'Routes')

// TODO verify that POSTer of these routes matches route.connector.
for (const route of routes) {
routingTables.addRoute(
route.source_ledger,
route.destination_ledger,
route.connector,
new routing.Route(route.points))
routingTables.addRoute(route)
}

const connector = routes[0] && routes[0].connector
if (config.features.broadcastRoutes && connector && !knownConnectors[connector]) {
if (connector && !knownConnectors[connector]) {
yield routeBroadcaster.broadcast()
knownConnectors[connector] = true
}
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
balanceCache: balanceCache,
RoutingTables: require('./lib/routing-tables'),
RouteBroadcaster: require('./lib/route-broadcaster'),
RouteBuilder: require('./lib/route-builder'),
loadConnectorConfig: require('./lib/config'),
config: require('./services/config'),
logger: require('./common').log,
Expand Down
9 changes: 5 additions & 4 deletions src/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ function getLocalConfig () {
JSON.parse(Config.getEnv(envPrefix, 'PAIRS') || 'false') || generateDefaultPairs(ledgers)

const features = {}
features.quoteFullPath = Config.castBool(Config.getEnv(envPrefix, 'QUOTE_FULL_PATH'))
features.broadcastRoutes = Config.castBool(Config.getEnv(envPrefix, 'BROADCAST_ROUTES'))
features.debugAutoFund = Config.castBool(Config.getEnv(envPrefix, 'DEBUG_AUTOFUND'))

const adminEnv = parseAdminEnv()
Expand All @@ -196,12 +194,14 @@ function getLocalConfig () {

const expiry = {}
expiry.minMessageWindow =
Config.getEnv(envPrefix, 'MIN_MESSAGE_WINDOW') || 1 // seconds
expiry.maxHoldTime = Config.getEnv(envPrefix, 'MAX_HOLD_TIME') || 10 // seconds
+Config.getEnv(envPrefix, 'MIN_MESSAGE_WINDOW') || 1 // seconds
expiry.maxHoldTime = +Config.getEnv(envPrefix, 'MAX_HOLD_TIME') || 10 // seconds

// The spread is added to every quoted rate
const fxSpread = Number(Config.getEnv(envPrefix, 'FX_SPREAD')) || 0.002 // = .2%

const slippage = +Config.getEnv(envPrefix, 'SLIPPAGE') || 0.001 // = 0.1%

// BACKEND_URI must be defined for backends that connect to an external
// component to retrieve the rate or amounts (it is therefore required
// when using the ilp-quote backend)
Expand Down Expand Up @@ -234,6 +234,7 @@ function getLocalConfig () {
backend,
ledgerCredentials,
fxSpread,
slippage,
expiry,
features,
admin,
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ledgers/five-bells-ledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ function FiveBellsLedger (options) {
this.config = options.config
}

FiveBellsLedger.validateTransfer = function (transfer) { validator.validate('TransferTemplate', transfer) }
FiveBellsLedger.prototype.validateTransfer = function (transfer) {
validator.validate('TransferTemplate', transfer)
}

// template - {amount}
FiveBellsLedger.prototype.makeFundTemplate = function (template) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/ledgers/multiledger.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Multiledger.prototype.getType = function (ledgerId) {
// /////////////////////////////////////////////////////////////////////////////

Multiledger.prototype.validateTransfer = function (transfer) {
return this.ledger_types[transfer.type].validateTransfer(transfer)
return this.getLedger(transfer.ledger).validateTransfer(transfer)
}

Multiledger.prototype.makeFundTemplate = function (ledger, template) {
Expand Down
107 changes: 84 additions & 23 deletions src/lib/route-broadcaster.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,70 @@
'use strict'

const co = require('co')
const defer = require('co-defer')
const _ = require('lodash')
const request = require('co-request')
const BROADCAST_INTERVAL = 30 * 1000 // milliseconds

class RouteBroadcaster {
constructor (baseURI, pairs, tables) {
this.baseURI = baseURI
this.tables = tables
/**
* @param {RoutingTables} routingTables
* @param {Backend} backend
* @param {Object} config
* @param {Object} config.ledgerCredentials
* @param {Object} config.tradingPairs
* @param {Number} config.minMessageWindow
*/
constructor (routingTables, backend, config) {
this.baseURI = routingTables.baseURI
this.routingTables = routingTables
this.backend = backend
this.ledgerCredentials = config.ledgerCredentials
this.tradingPairs = config.tradingPairs
this.minMessageWindow = config.minMessageWindow
this.adjacentConnectors = {}
this.adjacentLedgers = {}
for (const pair of pairs) {
this.adjacentLedgers[pair[0]] = true
this.adjacentLedgers[pair[1]] = true
for (const pair of config.tradingPairs) {
const destinationLedger = pair[1].split('@')[1]
this.adjacentLedgers[destinationLedger] = true
}
}

* start () {
yield this.crawlLedgers()
setInterval(() => this.tables.removeExpiredRoutes(), 1000)
defer.setInterval(this.broadcast.bind(this), BROADCAST_INTERVAL)
yield this.reloadLocalRoutes()
yield this.broadcast()
setInterval(() => this.routingTables.removeExpiredRoutes(), 1000)
defer.setInterval(() => {
return this.reloadLocalRoutes().then(this.broadcast.bind(this))
}, BROADCAST_INTERVAL)
}

broadcast () {
const routes = this.tables.toJSON()
const routes = this.routingTables.toJSON()
return Promise.all(
Object.keys(this.adjacentConnectors).map(function (adjacentConnector) {
return request({
method: 'POST',
uri: adjacentConnector + '/routes',
body: routes,
json: true
}).then((res) => {
if (res.statusCode !== 200) {
throw new Error('Unexpected status code: ' + res.statusCode)
}
})
}))
Object.keys(this.adjacentConnectors).map(
(adjacentConnector) => this._broadcastTo(adjacentConnector, routes)))
}

_broadcastTo (adjacentConnector, routes) {
return request({
method: 'POST',
uri: adjacentConnector + '/routes',
body: routes,
json: true
}).then((res) => {
if (res.statusCode !== 200) {
throw new Error('Unexpected status code: ' + res.statusCode)
}
})
}

crawlLedgers () {
return Object.keys(this.adjacentLedgers).map(this.crawlLedger, this)
return Object.keys(this.adjacentLedgers).map(this._crawlLedger, this)
}

* crawlLedger (ledger) {
* _crawlLedger (ledger) {
const res = yield request({
method: 'GET',
uri: ledger + '/connectors',
Expand All @@ -60,6 +80,47 @@ class RouteBroadcaster {
this.adjacentConnectors[connector] = true
}
}

/**
* @returns {Promise}
*/
reloadLocalRoutes () {
return this._getLocalRoutes().then(
(routes) => this.routingTables.addLocalRoutes(routes))
}

_getLocalRoutes () {
return Promise.all(this.tradingPairs.map((pair) => {
return this._tradingPairToQuote(pair)
.then((quote) => this._quoteToLocalRoute(quote))
}))
}

_tradingPairToQuote (pair) {
const sourceLedger = pair[0].split('@')[1]
const destinationLedger = pair[1].split('@')[1]
// TODO change the backend API to return curves, not points
return co(this.backend.getQuote.bind(this.backend), {
source_ledger: sourceLedger,
destination_ledger: destinationLedger,
source_amount: 100000000
})
}

_quoteToLocalRoute (quote) {
return {
source_ledger: quote.source_ledger,
destination_ledger: quote.destination_ledger,
connector: this.baseURI,
min_message_window: this.minMessageWindow,
source_account: this.ledgerCredentials[quote.source_ledger].account_uri,
destination_account: this.ledgerCredentials[quote.destination_ledger].account_uri,
points: [
[0, 0],
[+quote.source_amount, +quote.destination_amount]
]
}
}
}

module.exports = RouteBroadcaster
Loading

0 comments on commit 41860af

Please sign in to comment.