routing: add findPaths function whcih implements Yen's Algorithm

With this commit we make our routing more robust by looking for the
k-shortest paths rather than a single shortest path and using that
unconditionally. In a nut shell Yen’s algorithm does the following:
   * Find the shortest path from the source to the destination
   * From k=1…K, walk the kth shortest path and find possible
divergence from each node in the path

Our version of Yen’s implemented is slightly modified, rather than
actually increasing the edge weights or remove vertexes from the graph,
we instead use two black-lists: one for edges and the other for
vertexes. Our modified version of Djikstra’s algorithm is then made
aware of these black lists in order to more efficiently implement the
path iteration via spur and root node.
This commit is contained in:
Olaoluwa Osuntokun
2017-03-20 18:22:04 -07:00
parent 56d27f2d58
commit aaa04bb2e5
2 changed files with 196 additions and 0 deletions

View File

@ -375,6 +375,65 @@ func TestBasicGraphPathFinding(t *testing.T) {
}
}
func TestKShortestPathFinding(t *testing.T) {
graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath)
defer cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
sourceNode, err := graph.SourceNode()
if err != nil {
t.Fatalf("unable to fetch source node: %v", err)
}
// In this test we'd like to ensure that our algoirthm to find the
// k-shortest paths from a given source node to any destination node
// works as exepcted.
// In our basic_graph.json, there exist two paths from roasbeef to luo
// ji. Our algorithm should properly find both paths, and also rank
// them in order of their total "distance".
const paymentAmt = btcutil.Amount(100)
target := aliases["luoji"]
paths, err := findPaths(graph, sourceNode, target, paymentAmt)
if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+
"luo ji: %v", err)
}
// The algorithm should've found two paths from roasbeef to luo ji.
if len(paths) != 2 {
t.Fatalf("two path shouldn't been found, instead %v were",
len(paths))
}
// Additinoally, the total hop length of the first path returned should
// be _less_ than that of the second path returned.
if len(paths[0]) > len(paths[1]) {
t.Fatalf("paths found not ordered properly")
}
// Finally, we'll assert the exact expected ordering of both paths
// found.
assertExpectedPath := func(path []*ChannelHop, nodeAliases ...string) {
for i, hop := range path {
if hop.Node.Alias != nodeAliases[i] {
t.Fatalf("expected %v to be pos #%v in hop, "+
"instead %v was", nodeAliases[i], i,
hop.Node.Alias)
}
}
}
// The first route should be a direct route to luo ji.
assertExpectedPath(paths[0], "roasbeef", "luoji")
// The second route should be a route to luo ji via satoshi.
assertExpectedPath(paths[1], "roasbeef", "satoshi", "luoji")
}
func TestNewRoutePathTooLong(t *testing.T) {
// Ensure that potential paths which are over the maximum hop-limit are
// rejected.