Skip to content

Commit

Permalink
[FEATURE] Support pathfinding by source amount
Browse files Browse the repository at this point in the history
Also adds tests for `QuotingClient#quotePathFrom{Destination,Source}`.

Paves the way for interledger-deprecated/five-bells-sender#24.
  • Loading branch information
sentientwaffle committed Feb 10, 2016
1 parent 0e05819 commit 8d5005b
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 51 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -57,6 +57,7 @@
"eslint-plugin-standard": "^1.3.0",
"istanbul": "^0.4.0",
"mocha": "^2.3.2",
"nock": "^7.0.2",
"randomgraph": "^0.1.3",
"spec-xunit-file": "0.0.1-3",
"supertest": "^1.1.0"
Expand Down
23 changes: 15 additions & 8 deletions src/pathfinder.js
Expand Up @@ -67,9 +67,11 @@ class Pathfinder {

log.info('findPath found ' + paths.length + ' paths')

const pathsQuotes = yield this.quotingClient.quotePathsFromDestination({
destinationAccount: params.destinationAccount,
destinationAmount: params.destinationAmount
const pathsQuotes = yield this.quotingClient.quotePaths({
source_account: params.sourceAccount,
source_amount: params.sourceAmount,
destination_account: params.destinationAccount,
destination_amount: params.destinationAmount
// TODO add other params like destination expiry duration
}, paths)

Expand All @@ -82,11 +84,16 @@ class Pathfinder {
return cheapestPath
}

// params -
// sourceLedger
// destinationLedger
// destinationAmount
// destinationAccount (optional)
/**
* @param {Object} params
* @param {String} params.sourceLedger
* @param {String} params.destinationLedger
* @param {String} params.sourceAmount - either this or destinationAmount is required
* @param {String} params.destinationAmount - either this or sourceAmount is required
* @param {String} params.sourceAccount - provided with sourceAmount (optional)
* @param {String} params.destinationAccount - provided with destinationAmount (optional)
* @returns {Promise<[Quote]>}
*/
findPath (params) { return co(this._findPath.bind(this), params) }
}

Expand Down
102 changes: 59 additions & 43 deletions src/quoting-client.js
Expand Up @@ -2,63 +2,77 @@

const request = require('superagent')
const uuid = require('uuid4')
const _ = require('lodash')

class QuotingClient {
constructor (crawler) {
const _this = this

this.crawler = crawler
this.pairs = {}

this.crawler.on('pair', function (pair) {
_this.handleTradingPair(pair)
})
this.crawler.on('pair', this._handleTradingPair.bind(this))
}

handleTradingPair (pair) {
_handleTradingPair (pair) {
this.pairs[pair.source + ';' + pair.destination] = pair.uri
}

* quotePathFromDestination (params, path) {
path = path.slice()

let payments = []
let destination = path.pop()
let destinationAccount = params.destinationAccount
let destinationAmount = params.destinationAmount
let destinationExpiryDuration = params.destinationExpiryDuration ||
path.length + 2

while (path.length) {
let source = path.pop()
let trader = this.pairs[source + ';' + destination]
quotePaths (params, paths) {
if (params.destination_amount) {
return this.quotePathsFromDestination(params, paths)
}
if (params.source_amount) {
return this.quotePathsFromSource(params, paths)
}
throw new Error('requires sourceAmount or destinationAmount')
}

let query = {
source_ledger: source,
destination_ledger: destination,
destination_account: destinationAccount,
destination_amount: destinationAmount,
destination_expiry_duration: destinationExpiryDuration
}
quotePathsFromDestination (params, paths) {
return paths.map((path) => this.quotePathFromDestination(params, path))
}

let payment = yield this.getTraderQuote(trader, query)
let paymentUuid = uuid()
payment.id = trader + '/payments/' + paymentUuid
quotePathsFromSource (params, paths) {
return paths.map((path) => this.quotePathFromSource(params, path))
}

* quotePathFromDestination (params, path) {
params = _.clone(params)
path = path.slice()
const payments = []
params.destination_expiry_duration = params.destination_expiry_duration || path.length + 1
params.destination_ledger = path.pop()
params.source_ledger = path.pop()
while (params.source_ledger) {
const payment = yield this.getTraderQuote(params)
payments.unshift(payment)
destination = source
destinationAmount = payment.source_transfers[0].credits[0].amount
destinationAccount = payment.source_transfers[0].credits[0].account
destinationExpiryDuration = payment.source_transfers[0].expiry_duration
params = {
source_ledger: path.pop(),
destination_ledger: params.source_ledger,
destination_amount: payment.source_transfers[0].credits[0].amount,
destination_account: payment.source_transfers[0].credits[0].account,
destination_expiry_duration: payment.source_transfers[0].expiry_duration
}
}

return payments
}

quotePathsFromDestination (params, paths) {
return paths.map((path) => {
return this.quotePathFromDestination(params, path)
})
* quotePathFromSource (params, path) {
params = _.clone(params)
path = path.slice()
const payments = []
params.source_expiry_duration = params.source_expiry_duration || 2 * path.length
params.source_ledger = path.shift()
params.destination_ledger = path.shift()
while (params.destination_ledger) {
const payment = yield this.getTraderQuote(params)
payments.push(payment)
params = {
source_ledger: params.destination_ledger,
destination_ledger: path.shift(),
source_amount: payment.destination_transfers[0].debits[0].amount,
source_account: payment.destination_transfers[0].debits[0].account,
source_expiry_duration: payment.destination_transfers[0].expiry_duration
}
}
return payments
}

/**
Expand All @@ -71,15 +85,17 @@ class QuotingClient {
* destination_asset
* destination_amount or source_amount
*/
* getTraderQuote (trader, quoteOpts) {
let res = yield request
* getTraderQuote (quoteOpts) {
const trader = this.pairs[quoteOpts.source_ledger + ';' + quoteOpts.destination_ledger]
const res = yield request
.get(trader + '/quote')
.query(quoteOpts)
if (res.status >= 400) {
throw new Error('Server Error: ' + res.status + ' ' + res.body)
}

return res.body
const payment = res.body
payment.id = trader + '/payments/' + uuid()
return payment
}
}

Expand Down
39 changes: 39 additions & 0 deletions test/fixtures/from-destination.json
@@ -0,0 +1,39 @@
{
"quoteArgs23": {
"source_ledger": "http://ledger2.example",
"destination_ledger": "http://ledger3.example",
"destination_account": "http://ledger3.example/accounts/bob",
"destination_amount": "100",
"destination_expiry_duration": 1000
},

"quoteArgs12": {
"source_ledger": "http://ledger1.example",
"destination_ledger": "http://ledger2.example",
"destination_account": "http://ledger2.example/accounts/trader23",
"destination_amount": "90",
"destination_expiry_duration": 2000
},

"quote23": {
"source_transfers": [{
"debits": [{"amount": "100"}],
"credits": [{
"account": "http://ledger2.example/accounts/trader23",
"amount": "90"
}],
"expiry_duration": 2000
}]
},

"quote12": {
"source_transfers": [{
"debits": [{"amount": "90"}],
"credits": [{
"account": "http://ledger1.example/accounts/trader12",
"amount": "80"
}],
"expiry_duration": 3000
}]
}
}
39 changes: 39 additions & 0 deletions test/fixtures/from-source.json
@@ -0,0 +1,39 @@
{
"quoteArgs12": {
"source_ledger": "http://ledger1.example",
"destination_ledger": "http://ledger2.example",
"source_account": "http://ledger1.example/accounts/alice",
"source_amount": "80",
"source_expiry_duration": 3000
},

"quoteArgs23": {
"source_ledger": "http://ledger2.example",
"destination_ledger": "http://ledger3.example",
"source_account": "http://ledger2.example/accounts/trader12",
"source_amount": "90",
"source_expiry_duration": 2000
},

"quote12": {
"destination_transfers": [{
"debits": [{
"account": "http://ledger2.example/accounts/trader12",
"amount": "90"
}],
"credits": [{"amount": "90"}],
"expiry_duration": 2000
}]
},

"quote23": {
"destination_transfers": [{
"debits": [{
"account": "http://ledger3.example/accounts/trader23",
"amount": "100"
}],
"credits": [{"amount": "90"}],
"expiry_duration": 1000
}]
}
}

0 comments on commit 8d5005b

Please sign in to comment.