Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: prioritize short paths during findBestHop #54

Merged
merged 2 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Route {
// are not included in this.paths, so for instance a path:
// * localLedger -> trustLine -> destinationLedger has this.paths === [ [] ], and path length 2
// * localLedger -> trustLine -> anotherTrustLine -> destinationLedger has this.paths === [ ['anotherTrustLine'] ] and path length 3
return max + 2
return this.nextLedger === this.destinationLedger ? max + 2 : max + 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means we count a route trustLine -> localLedger as length 2, so counting the number of ledgers involved. I originally thought of it as the number of connections, so this.nextLedger === this.destinationLedger ? max + 1 : max + 2 but I guess your way of counting makes (more) sense.

}

/**
Expand Down
66 changes: 45 additions & 21 deletions src/lib/routing-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,31 @@ class RoutingTable {
return undefined
}

let bestHop = null
let bestValue = null
let bestRoute = null
let bestHop
routes.forEach((route, nextHop) => {
const value = route.amountAt(sourceAmount)
// If we have a route but not a curve, pick a route at random
// and get a remote quote. In the future we may refactor this
// so that multiple next hop options can be returned, and all
// can be asked for a quote, but for now, it's just the last.
if (value === undefined || !bestValue || value.gt(bestValue)) {
bestHop = nextHop
bestValue = value && value.toString()
bestRoute = route
}
bestHop = getBetterHop(bestHop, {
nextHop,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBetterHop takes two arguments, function getBetterHop (currentHop, otherHop).
Here, you pass bestHop as the currentHop, and { nextHop, ... } as the otherHop.
Does that mean (if both arguments of getBetterHop are of the same type) that bestHop also has a field nextHop? nextHop comes from routes.forEach((route, nextHop) => {, so it's a route.
Now I no longer remember what data type a 'hop' is. Is it a ledger name string? Or a Route object?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all of currentHop, otherHop, nextHop and bestHop have the same data type? If not, then that's confusing.

route,
value: route.amountAt(sourceAmount),
pathLength: route.maxPathLength()
})
})

if (bestHop) {
debug('findBestHopForSourceAmount to ' + destination + ' for ' + sourceAmount + ' found route through ' + bestHop)
debug('findBestHopForSourceAmount to ' + destination + ' for ' + sourceAmount + ' found route through ' + bestHop.nextHop)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, bestHop.nextHop - I don't know what that means, the next Hop of the best Hop. Which one is the Route object here and which one is the peer ledger name String?

} else {
debug('findBestHopForSourceAmount could not find route to ' + destination + ' for ' + sourceAmount + '. Current routing table: ' + JSON.stringify(this.destinations.toJSON()))
}

return { bestHop, bestValue, bestRoute }
return {
bestHop: bestHop.nextHop,
bestValue: bestHop.value && bestHop.value.toString(),
bestRoute: bestHop.route
}
}

findBestHopForDestinationAmount (destination, destinationAmount) {
Expand All @@ -103,30 +105,52 @@ class RoutingTable {
return undefined
}

let bestHop = null
let bestCost = Infinity
let bestRoute = null
let bestHop
routes.forEach((route, nextHop) => {
const cost = route.amountReverse(destinationAmount)
if (cost.equals(Infinity)) return
// If we have a route but not a curve, pick a route at random
// and get a remote quote. In the future we may refactor this
// so that multiple next hop options can be returned, and all
// can be asked for a quote, but for now, it's just the last.
if (cost === undefined || cost.lt(bestCost)) {
bestHop = nextHop
bestCost = cost && cost.toString()
bestRoute = route
}
bestHop = getBetterHop(bestHop, {
nextHop,
route,
cost,
pathLength: route.maxPathLength()
})
})

if (bestHop) {
debug('findBestHopForDestinationAmount to ' + destination + ' for ' + destinationAmount + ' found route through ' + bestHop)
return { bestHop, bestCost, bestRoute }
debug('findBestHopForDestinationAmount to ' + destination + ' for ' + destinationAmount + ' found route through ' + bestHop.nextHop)
return {
bestHop: bestHop.nextHop,
bestCost: bestHop.cost && bestHop.cost.toString(),
bestRoute: bestHop.route
}
} else {
debug('findBestHopForDestinationAmount could not find route to ' + destination + ' for ' + destinationAmount + '. Current routing table: ' + JSON.stringify(this.destinations.toJSON()))
return undefined
}
}
}

/**
* If both hops score equally, return `currentHop`.
* It doesn't actually matter which is returned in that case, so long as it is consistent.
*/
function getBetterHop (currentHop, otherHop) {
if (!currentHop) return otherHop
if (currentHop.pathLength < otherHop.pathLength) return currentHop
if (otherHop.pathLength < currentHop.pathLength) return otherHop
if (otherHop.value !== undefined) {
return otherHop.value.gt(currentHop.value) ? otherHop : currentHop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will throw an error if currentHop.value is undefined

}
if (otherHop.cost !== undefined) {
return otherHop.cost.lt(currentHop.cost) ? otherHop : currentHop
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will throw an error if currentHop.cost is undefined

}
// No curve
return currentHop
}

module.exports = RoutingTable
11 changes: 11 additions & 0 deletions test/routing-table.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Route = require('../src/lib/route')

const ledgerA = 'ledgerA.'
const ledgerB = 'ledgerB.'
const ledgerC = 'ledgerB.'
const markB = ledgerB + 'mark'
const maryB = ledgerB + 'mary'

Expand Down Expand Up @@ -76,6 +77,16 @@ describe('RoutingTable', function () {
const table = new RoutingTable()
assert.strictEqual(table.findBestHopForSourceAmount(ledgerB, 10), undefined)
})

it('prefers short routes', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you have a test case for each of the comparisons in getBetterHop?

const table = new RoutingTable()
const routeMark = new Route([[0, 0], [100, 999]], {sourceLedger: ledgerA, nextLedger: ledgerB, destinationLedger: ledgerB})
const routeMary = new Route([[0, 0], [100, 100]], {sourceLedger: ledgerA, nextLedger: ledgerC, destinationLedger: ledgerB})
table.addRoute(ledgerB, markB, routeMark)
table.addRoute(ledgerB, ledgerC + 'mary', routeMary)
assert.deepEqual(table.findBestHopForSourceAmount(ledgerB, 50),
{ bestHop: markB, bestValue: '499', bestRoute: routeMark })
})
})

describe('findBestHopForDestinationAmount', function () {
Expand Down