From 327768f4ad84617e9a58dcdcb4ede20bb8490b6b Mon Sep 17 00:00:00 2001 From: BitfuryLightning Date: Wed, 5 Oct 2016 16:47:02 -0400 Subject: [PATCH] routing: Move tools inside lnd. Refactor and delete unneeded stuff Use [33]byte for graph vertex representation. Delete unneeded stuff: 1. DeepEqual for graph comparison 2. EdgePath 3. 2-thread BFS 4. Table transfer messages and neighborhood radius 5. Beacons Refactor: 1. Change ID to Vertex 2. Test use table driven approach 3. Add comments 4. Make graph internal representation private 5. Use wire.OutPoint as EdgeId 6. Decouple routing messages from routing implementation 7. Delete Async methods 8. Delete unneeded channels and priority buffer from manager 9. Delete unneeded interfaces in internal graph realisation 10. Renamed ID to Vertex --- cmd/lncli/commands.go | 83 +++- fundingmanager.go | 19 +- glide.lock | 79 ++- glide.yaml | 6 - htlcswitch.go | 11 +- lnwire/lnwire.go | 76 +++ lnwire/message.go | 4 - lnwire/neighbor_hello.go | 12 +- lnwire/neighbor_hello_test.go | 99 ++-- lnwire/neighbor_rst.go | 15 +- lnwire/neighbor_rst_test.go | 44 ++ lnwire/neighbor_upd.go | 15 +- lnwire/neighbor_upd_test.go | 87 ++++ lnwire/routing_table_request.go | 39 -- lnwire/routing_table_transfer.go | 51 -- peer.go | 19 +- routing/manager.go | 470 ++++++++++++++++++ routing/manager_test.go | 239 +++++++++ routing/mocknet.go | 83 ++++ routing/rt/graph/bfs.go | 67 +++ routing/rt/graph/bfs_test.go | 31 ++ routing/rt/graph/dijkstra.go | 67 +++ routing/rt/graph/dijkstra_test.go | 41 ++ routing/rt/graph/graph.go | 357 +++++++++++++ routing/rt/graph/graph_test.go | 166 +++++++ routing/rt/graph/ksp.go | 68 +++ routing/rt/graph/ksp_test.go | 39 ++ routing/rt/graph/path.go | 39 ++ routing/rt/rt.go | 258 ++++++++++ routing/rt/rt_test.go | 160 ++++++ routing/rt/visualizer/config.go | 125 +++++ routing/rt/visualizer/prefix_tree/node.go | 116 +++++ .../rt/visualizer/prefix_tree/prefix_tree.go | 120 +++++ .../prefix_tree/prefix_tree_test.go | 70 +++ routing/rt/visualizer/visualizer.go | 174 +++++++ rpcserver.go | 48 +- server.go | 63 +-- 37 files changed, 3152 insertions(+), 308 deletions(-) create mode 100644 lnwire/neighbor_rst_test.go create mode 100644 lnwire/neighbor_upd_test.go delete mode 100644 lnwire/routing_table_request.go delete mode 100644 lnwire/routing_table_transfer.go create mode 100644 routing/manager.go create mode 100644 routing/manager_test.go create mode 100644 routing/mocknet.go create mode 100644 routing/rt/graph/bfs.go create mode 100644 routing/rt/graph/bfs_test.go create mode 100644 routing/rt/graph/dijkstra.go create mode 100644 routing/rt/graph/dijkstra_test.go create mode 100644 routing/rt/graph/graph.go create mode 100644 routing/rt/graph/graph_test.go create mode 100644 routing/rt/graph/ksp.go create mode 100644 routing/rt/graph/ksp_test.go create mode 100644 routing/rt/graph/path.go create mode 100644 routing/rt/rt.go create mode 100644 routing/rt/rt_test.go create mode 100644 routing/rt/visualizer/config.go create mode 100644 routing/rt/visualizer/prefix_tree/node.go create mode 100644 routing/rt/visualizer/prefix_tree/prefix_tree.go create mode 100644 routing/rt/visualizer/prefix_tree/prefix_tree_test.go create mode 100644 routing/rt/visualizer/visualizer.go diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index e9b4ebd0..b6e0c12d 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -10,16 +10,16 @@ import ( "os" "path/filepath" "strings" - - "github.com/BitfuryLightning/tools/prefix_tree" - "github.com/BitfuryLightning/tools/rt" - "github.com/BitfuryLightning/tools/rt/graph" + "github.com/lightningnetwork/lnd/routing/rt/visualizer/prefix_tree" + "github.com/lightningnetwork/lnd/routing/rt" + "github.com/lightningnetwork/lnd/routing/rt/graph" "github.com/lightningnetwork/lnd/lnrpc" "github.com/roasbeef/btcd/wire" "github.com/urfave/cli" "golang.org/x/net/context" - "github.com/BitfuryLightning/tools/rt/visualizer" + "github.com/lightningnetwork/lnd/routing/rt/visualizer" + "strconv" ) // TODO(roasbeef): cli logic for supporting both positional and unix style @@ -796,6 +796,26 @@ var ShowRoutingTableCommand = cli.Command{ }, } +func outPointFromString(s string) (*wire.OutPoint, error) { + split := strings.Split(s, ":") + if len(split) != 2 { + return nil, fmt.Errorf("Wrong format of OutPoint. Got %v", s) + } + h, err := wire.NewShaHashFromStr(split[0]) + if err!=nil { + return nil, err + } + n, err := strconv.Atoi(split[1]) + if err != nil { + return nil, err + } + if n<0 { + return nil, fmt.Errorf("Got incorrect output number %v", n) + } + return wire.NewOutPoint(h, uint32(n)), nil +} + + func getRoutingTable(ctxb context.Context, client lnrpc.LightningClient) (*rt.RoutingTable, error) { req := &lnrpc.ShowRoutingTableRequest{} resp, err := client.ShowRoutingTable(ctxb, req) @@ -805,11 +825,23 @@ func getRoutingTable(ctxb context.Context, client lnrpc.LightningClient) (*rt.Ro r := rt.NewRoutingTable() for _, channel := range resp.Channels { + outPoint, err := outPointFromString(channel.Outpoint) + if err != nil { + return nil, err + } + id1, err := hex.DecodeString(channel.Id1) + if err != nil { + return nil, err + } + id2, err := hex.DecodeString(channel.Id2) + if err != nil { + return nil, err + } r.AddChannel( - graph.NewID(channel.Id1), - graph.NewID(channel.Id2), - graph.NewEdgeID(channel.Outpoint), - &rt.ChannelInfo{channel.Capacity, channel.Weight}, + graph.NewVertex(id1), + graph.NewVertex(id2), + graph.NewEdgeID(*outPoint), + &graph.ChannelInfo{channel.Capacity, channel.Weight}, ) } return r, nil @@ -903,14 +935,13 @@ func showRoutingTableAsImage(ctx *cli.Context) error { } func writeToTempFile(r *rt.RoutingTable, file *os.File, self string) error { - slc := []graph.ID{graph.NewID(self)} + slc := []graph.Vertex{graph.NewVertex([]byte(self))} viz := visualizer.New(r.G, slc, nil, nil) - viz.ApplyToNode = func(s string) string { return s } - viz.ApplyToEdge = func(info interface{}) string { - if info, ok := info.(*rt.ChannelInfo); ok { - return fmt.Sprintf(`"%v"`, info.Capacity()) - } - return "nil" + viz.ApplyToNode = func(v graph.Vertex) string { + return hex.EncodeToString(v.ToByte()) + } + viz.ApplyToEdge = func(info *graph.ChannelInfo) string { + return fmt.Sprintf(`"%v"`, info.Cpt) } // need to call method if plan to use shortcut, autocomplete, etc viz.BuildPrefixTree() @@ -977,18 +1008,18 @@ func printRTAsTable(r *rt.RoutingTable, humanForm bool) { // Generate prefix tree for shortcuts lightningIdTree = prefix_tree.NewPrefixTree() for _, node := range r.Nodes() { - lightningIdTree.Add(node.String()) + lightningIdTree.Add(hex.EncodeToString(node.ToByte())) } edgeIdTree = prefix_tree.NewPrefixTree() for _, channel := range channels { - edgeIdTree.Add(channel.EdgeID.String()) + edgeIdTree.Add(channel.Id.String()) } } for _, channel := range channels { var source, target, edgeId string - sourceHex := channel.Id1.String() - targetHex := channel.Id2.String() - edgeIdRaw := channel.EdgeID.String() + sourceHex := hex.EncodeToString(channel.Src.ToByte()) + targetHex := hex.EncodeToString(channel.Tgt.ToByte()) + edgeIdRaw := channel.Id.String() if humanForm { source = getShortcut(lightningIdTree, sourceHex, minLen) target = getShortcut(lightningIdTree, targetHex, minLen) @@ -1031,15 +1062,15 @@ func printRTAsJSON(r *rt.RoutingTable) { channelsRaw := r.AllChannels() channels.Channels = make([]ChannelDesc, 0, len(channelsRaw)) for _, channelRaw := range channelsRaw { - sourceHex := channelRaw.Id1.String() - targetHex := channelRaw.Id2.String() + sourceHex := hex.EncodeToString(channelRaw.Src.ToByte()) + targetHex := hex.EncodeToString(channelRaw.Tgt.ToByte()) channels.Channels = append(channels.Channels, ChannelDesc{ ID1: sourceHex, ID2: targetHex, - EdgeId: channelRaw.EdgeID.String(), - Weight: channelRaw.Info.Weight(), - Capacity: channelRaw.Info.Capacity(), + EdgeId: channelRaw.Id.String(), + Weight: channelRaw.Info.Wgt, + Capacity: channelRaw.Info.Cpt, }, ) } diff --git a/fundingmanager.go b/fundingmanager.go index 10e462e1..55669229 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1,7 +1,6 @@ package main import ( - "encoding/hex" "fmt" "sync" "sync/atomic" @@ -14,9 +13,8 @@ import ( "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" - "github.com/BitfuryLightning/tools/rt" - "github.com/BitfuryLightning/tools/rt/graph" "google.golang.org/grpc" + "github.com/lightningnetwork/lnd/routing/rt/graph" ) const ( @@ -622,11 +620,10 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg) chanInfo := openChan.StateSnapshot() capacity := int64(chanInfo.LocalBalance + chanInfo.RemoteBalance) pubSerialized := fmsg.peer.addr.IdentityKey.SerializeCompressed() - vertex := hex.EncodeToString(pubSerialized) fmsg.peer.server.routingMgr.OpenChannel( - graph.NewID(vertex), - graph.NewEdgeID(fundingPoint.String()), - &rt.ChannelInfo{ + graph.NewVertex(pubSerialized), + graph.NewEdgeID(*fundingPoint), + &graph.ChannelInfo{ Cpt: capacity, }, ) @@ -694,11 +691,11 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { // Notify the L3 routing manager of the newly active channel link. capacity := int64(resCtx.reservation.OurContribution().FundingAmount + resCtx.reservation.TheirContribution().FundingAmount) - vertex := hex.EncodeToString(fmsg.peer.addr.IdentityKey.SerializeCompressed()) + vertex := fmsg.peer.addr.IdentityKey.SerializeCompressed() fmsg.peer.server.routingMgr.OpenChannel( - graph.NewID(vertex), - graph.NewEdgeID(resCtx.reservation.FundingOutpoint().String()), - &rt.ChannelInfo{ + graph.NewVertex(vertex), + graph.NewEdgeID(*resCtx.reservation.FundingOutpoint()), + &graph.ChannelInfo{ Cpt: capacity, }, ) diff --git a/glide.lock b/glide.lock index d61acf0e..a5ced703 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 2106ce14ff53c14d3d0d3d8f34e1cf01c01a79eef409ffe871cd5783b77939c8 -updated: 2016-10-27T20:34:19.347013604-07:00 +hash: 0bb53053b11289c7322e08bc1fde538d7538c97180a42fc227fb76582ca39b8e +updated: 2016-11-02T08:18:51.538375911-04:00 imports: - name: github.com/aead/chacha20 version: 7e1038a97ad08a9a16cb88ed7a6778b366ba4d99 @@ -11,20 +11,10 @@ imports: version: d4d8514752339899250316f88a7907468e8eca7e subpackages: - ast - - parser - - token - errors - lexer -- name: github.com/BitfuryLightning/tools - version: b36ae00916b800503504455f7afeb3159bd5ee35 - subpackages: - - routing - - rt - - rt/graph - - prefix_tree - - rt/visualizer - - pbuffer - - pqueue + - parser + - token - name: github.com/boltdb/bolt version: 583e8937c61f1af6513608ccc75c97b6abdf4ff9 - name: github.com/btcsuite/bolt @@ -42,13 +32,13 @@ imports: - name: github.com/btcsuite/golangcrypto version: 53f62d9b43e87a6c56975cf862af7edf33a8d0df subpackages: - - ripemd160 - nacl/secretbox + - pbkdf2 + - poly1305 + - ripemd160 + - salsa20/salsa - scrypt - ssh/terminal - - poly1305 - - salsa20/salsa - - pbkdf2 - name: github.com/btcsuite/seelog version: ae8891d029dd3c269dcfd6f261ad23e761acd99f - name: github.com/btcsuite/websocket @@ -66,16 +56,16 @@ imports: - name: github.com/golang/protobuf version: 98fa357170587e470c5f27d3c3ea0947b71eb455 subpackages: - - proto - jsonpb + - proto - protoc-gen-go/descriptor - name: github.com/grpc-ecosystem/grpc-gateway version: a8f25bd1ab549f8b87afd48aa9181221e9d439bb subpackages: - runtime + - runtime/internal - third_party/googleapis/google/api - utilities - - runtime/internal - name: github.com/howeyc/gopass version: f5387c492211eb133053880d23dfae62aa14123d - name: github.com/lightningnetwork/lightning-onion @@ -87,38 +77,38 @@ imports: - btcec - btcjson - chaincfg + - database + - rpctest - txscript - wire - - rpctest - - database - name: github.com/roasbeef/btcrpcclient version: 2c6c5c5671f6e86d42b84da30cafcd80e3313b41 - name: github.com/roasbeef/btcutil version: 94511fbe6c9bf8ecddb82ccfe8f326e45a4559b0 subpackages: + - base58 - bloom - coinset - hdkeychain - txsort - - base58 - name: github.com/roasbeef/btcwallet version: 7acd18a96697b180b631631108f1a15448de369f subpackages: - chain - - waddrmgr - - wallet - - walletdb/bdb - - walletdb + - internal/helpers + - internal/legacy/keystore + - internal/legacy/rename + - internal/prompt - internal/zero - snacl - - internal/prompt + - waddrmgr + - wallet + - wallet/internal/txsizes - wallet/txauthor - wallet/txrules + - walletdb + - walletdb/bdb - wtxmgr - - internal/legacy/keystore - - internal/helpers - - wallet/internal/txsizes - - internal/legacy/rename - name: github.com/urfave/cli version: a14d7d367bc02b1f57d88de97926727f2d936387 - name: golang.org/x/crypto @@ -126,22 +116,22 @@ imports: subpackages: - hkdf - nacl/secretbox - - ripemd160 - - scrypt - - poly1305 - - salsa20/salsa - pbkdf2 + - poly1305 + - ripemd160 + - salsa20/salsa + - scrypt - ssh/terminal - name: golang.org/x/net version: b336a971b799939dd16ae9b1df8334cb8b977c4d subpackages: - context - http2 - - trace - http2/hpack - idna - - lex/httplex - internal/timeseries + - lex/httplex + - trace - name: golang.org/x/sys version: c200b10b5d5e122be351b67af224adc6128af5bf subpackages: @@ -149,12 +139,17 @@ imports: - name: google.golang.org/grpc version: b7f1379d3cbbbeb2ca3405852012e237aa05459e subpackages: - - grpclog - codes - - metadata - credentials + - grpclog - internal + - metadata - naming - - transport - peer -testImports: [] + - transport +testImports: +- name: github.com/BitfuryLightning/tools + version: ca92fef460d1f0f2ce28932f941bcc86594b3033 + subpackages: + - rt + - rt/graph diff --git a/glide.yaml b/glide.yaml index 763b240f..8daff08c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,11 +1,5 @@ package: github.com/lightningnetwork/lnd import: -- package: github.com/BitfuryLightning/tools - version: b36ae00916b800503504455f7afeb3159bd5ee35 - subpackages: - - routing - - rt - - rt/graph - package: github.com/boltdb/bolt version: ^1.2.1 - package: github.com/btcsuite/btclog diff --git a/htlcswitch.go b/htlcswitch.go index b176e592..0e080eed 100644 --- a/htlcswitch.go +++ b/htlcswitch.go @@ -8,9 +8,8 @@ import ( "time" "golang.org/x/crypto/ripemd160" - - "github.com/BitfuryLightning/tools/routing" - "github.com/BitfuryLightning/tools/rt/graph" + "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/rt/graph" "github.com/btcsuite/fastsha256" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lightning-onion" @@ -537,9 +536,9 @@ func (h *htlcSwitch) handleUnregisterLink(req *unregisterLinkMsg) { // * distinction between connection close and channel close for _, linkChan := range chansRemoved { err := h.router.RemoveChannel( - graph.NewID(hex.EncodeToString(h.gateway)), - graph.NewID(hex.EncodeToString(req.remoteID)), - graph.NewEdgeID(linkChan.String()), + graph.NewVertex(h.gateway), + graph.NewVertex(req.remoteID), + graph.NewEdgeID(*linkChan), ) if err != nil { hswcLog.Errorf("unable to remove channel from "+ diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 69d8dc37..48302f68 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -62,6 +62,14 @@ func (c CreditsAmount) ToSatoshi() int64 { return int64(c / 1000) } +type ChannelOperation struct { + NodePubKey1, NodePubKey2 [33]byte + ChannelId *wire.OutPoint + Capacity int64 + Weight float64 + Operation byte +} + // writeElement is a one-stop shop to write the big endian representation of // any element which is to be serialized for the wire protocol. The passed // io.Writer should be backed by an appropriatly sized byte slice, or be able @@ -193,6 +201,11 @@ func writeElement(w io.Writer, element interface{}) error { if _, err := w.Write(e[:]); err != nil { return err } + case [33]byte: + // TODO(roasbeef): should be factor out to caller logic... + if _, err := w.Write(e[:]); err != nil { + return err + } case wire.BitcoinNet: var b [4]byte binary.BigEndian.PutUint32(b[:], uint32(e)) @@ -282,6 +295,34 @@ func writeElement(w io.Writer, element interface{}) error { return err } // TODO(roasbeef): *MsgTx + case int64, float64: + err := binary.Write(w, binary.BigEndian, e) + if err != nil { + return err + } + case []ChannelOperation: + err := writeElement(w, uint64(len(e))) + if err != nil { + return err + } + for i:=0; i1->2->..->n-1 + nodes := make([]*RoutingManager, 0) + net := NewMockNetwork(false) + net.Start() + for i := 0; i < n; i++ { + node := NewRoutingManager(vertexFromInt(i), nil) + nodes = append(nodes, node) + node.Start() + net.Add(node) + } + + for i := 0; i < n-1; i++ { + nodes[i].OpenChannel(nodes[i+1].Id, sampleEdgeId, nil) + nodes[i+1].OpenChannel(nodes[i].Id, sampleEdgeId, nil) + } + + return net, nodes +} + +func createCompleteNetwork(n int) (*MockNetwork, []*RoutingManager) { + nodes := make([]*RoutingManager, 0) + net := NewMockNetwork(false) + net.Start() + for i := 0; i < n; i++ { + node := NewRoutingManager(vertexFromInt(i), nil) + nodes = append(nodes, node) + node.Start() + net.Add(node) + } + + for i := 0; i < n-1; i++ { + for j := i + 1; j < n; j++ { + nodes[i].OpenChannel(nodes[j].Id, sampleEdgeId, nil) + nodes[j].OpenChannel(nodes[i].Id, sampleEdgeId, nil) + } + } + + return net, nodes +} + +func createNetwork(desc [][2]int, idFunc func(int) graph.Vertex) (*MockNetwork, map[int]*RoutingManager, []graph.Edge) { + // Creates network of nodes from graph description + net := NewMockNetwork(false) + net.Start() + // create unique nodes + nodes := make(map[int]*RoutingManager) + for i := 0; i < len(desc); i++ { + for j := 0; j < 2; j++ { + nodeId := desc[i][j] + if _, ok := nodes[nodeId]; !ok { + var id graph.Vertex + if idFunc != nil { + id = idFunc(nodeId) + } else { + id = vertexFromInt(nodeId) + } + node := NewRoutingManager(id, nil) + nodes[nodeId] = node + node.Start() + net.Add(node) + } + } + } + edges := make([]graph.Edge, 0, len(desc)) + for i := 0; i < len(desc); i++ { + edgeID := edgeIdFromString(fmt.Sprintf("edge-%v", i)) + nodes[desc[i][0]].OpenChannel(nodes[desc[i][1]].Id, edgeID, &graph.ChannelInfo{1, 1}) + nodes[desc[i][1]].OpenChannel(nodes[desc[i][0]].Id, edgeID, &graph.ChannelInfo{1, 1}) + edges = append(edges, graph.NewEdge( + nodes[desc[i][0]].Id, + nodes[desc[i][1]].Id, + edgeID, + &graph.ChannelInfo{1, 1}, + )) + } + return net, nodes, edges +} + +func TestNeighborsScanLinearGraph(t *testing.T) { + n := 4 + net, nodes := createLinearNetwork(n) + time.Sleep(10 * time.Millisecond) + // Each node should know about all channels + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + for k := 0; k < n; k++ { + ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId) + correctAns := abs(j-k) == 1 + if ans != correctAns { + t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns) + } + } + } + } + net.Stop() + +} + +func TestNeighborsScanCompleteGraph(t *testing.T) { + n := 4 + net, nodes := createCompleteNetwork(n) + time.Sleep(10 * time.Millisecond) + // Each node should know about all channels + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + for k := 0; k < n; k++ { + ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId) + correctAns := j != k + if ans != correctAns { + t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns) + } + } + } + } + net.Stop() +} + +func TestNeighborsRemoveChannel(t *testing.T) { + // Create complete graph, than delete channels to make it linear + n := 4 + net, nodes := createCompleteNetwork(n) + time.Sleep(10 * time.Millisecond) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + if abs(i-j) != 1 { + nodes[i].RemoveChannel(nodes[i].Id, nodes[j].Id, sampleEdgeId) + } + } + } + time.Sleep(10 * time.Millisecond) + // Each node should know about all channels + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + for k := 0; k < n; k++ { + ans := nodes[i].HasChannel(nodes[j].Id, nodes[k].Id, sampleEdgeId) + correctAns := abs(j-k) == 1 + if ans != correctAns { + t.Errorf("nodes[%v].HasChannel(%v, %v)==%v, want %v", i, j, k, ans, correctAns) + } + } + } + } + + net.Stop() +} + +func TestFindPath(t *testing.T) { + // Create linear graph + n := 6 + net, nodes := createLinearNetwork(n) + time.Sleep(10 * time.Millisecond) // Each node should know about all channels + + path, err := nodes[0].FindPath(nodes[5].Id) + if err != nil { + t.Errorf("err = %v, want %v", err) + } + correctPath := []graph.Vertex{} + for i := 0; i < n; i++ { + correctPath = append(correctPath, nodes[i].Id) + } + if !reflect.DeepEqual(path, correctPath) { + t.Errorf("path = %v, want %v", path, correctPath) + } + // Case when path do not exist + path, err = nodes[0].FindPath(vertexFromInt(7)) + if path != nil { + t.Errorf("path = %v, want %v", path, nil) + } + if err != graph.PathNotFoundError { + t.Errorf("err = %v, want %v", err, graph.PathNotFoundError) + } + net.Stop() +} + +func TestKShortestPaths(t *testing.T) { + net, nodes, _ := createNetwork([][2]int{ + [2]int{0, 1}, + [2]int{1, 2}, + [2]int{2, 3}, + [2]int{1, 3}, + [2]int{4, 5}, + }, nil) + time.Sleep(10 * time.Millisecond) // Each node should know about all channels + // There was bug in lnd when second search of the same path leads to lncli/lnd freeze + for iter := 1; iter <= 3; iter++ { + paths, err := nodes[0].FindKShortestPaths(nodes[3].Id, 2) + if err != nil { + t.Errorf("err = %v, want %v", err, nil) + } + correctPaths := [][]graph.Vertex{ + []graph.Vertex{vertexFromInt(0), vertexFromInt(1), vertexFromInt(3)}, + []graph.Vertex{vertexFromInt(0), vertexFromInt(1), vertexFromInt(2), vertexFromInt(3)}, + } + if !reflect.DeepEqual(paths, correctPaths) { + t.Errorf("on iteration: %v paths = %v, want %v", iter, paths, correctPaths) + } + } + // Case when path do not exist + paths, _ := nodes[0].FindKShortestPaths(vertexFromInt(7), 3) + if len(paths) != 0 { + t.Errorf("path = %v, want %v", paths, []graph.Vertex{}) + } + net.Stop() +} diff --git a/routing/mocknet.go b/routing/mocknet.go new file mode 100644 index 00000000..36173329 --- /dev/null +++ b/routing/mocknet.go @@ -0,0 +1,83 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package routing + +import ( + "fmt" + "github.com/lightningnetwork/lnd/routing/rt/graph" + "github.com/lightningnetwork/lnd/lnwire" +) + +type MockNetwork struct { + nodes map[graph.Vertex]*RoutingManager + chMsg chan *RoutingMessage + chQuit chan struct{} + printMessages bool +} + +func NewMockNetwork(printMessages bool) *MockNetwork { + return &MockNetwork{ + nodes: make(map[graph.Vertex]*RoutingManager), + chMsg: make(chan *RoutingMessage), + chQuit: make(chan struct{}), + printMessages: printMessages, + } +} + +func (net *MockNetwork) Start() { + go func() { + for { + select { + case msg, ok := <-net.chMsg: + if !ok { + return + } + receiverId := msg.ReceiverID + // TODO: validate ReceiverID + if net.printMessages { + fmt.Println(msg.Msg) + } + if _, ok := net.nodes[receiverId]; ok { + net.nodes[receiverId].ReceiveRoutingMessage(msg.Msg, msg.SenderID) + } + case <-net.chQuit: + return + } + } + }() +} + +func (net *MockNetwork) Stop() { + close(net.chQuit) +} + +func (net *MockNetwork) Add(r *RoutingManager) { + net.nodes[r.Id] = r + chOut := make(chan *RoutingMessage) + if r.config == nil { + r.config = &RoutingConfig{} + } + r.config.SendMessage = func(receiver [33]byte, msg lnwire.Message) error { + chOut <- &RoutingMessage{ + SenderID: r.Id, + ReceiverID: graph.NewVertex(receiver[:]), + Msg: msg, + } + return nil + } + go func() { + for { + select { + case msg, ok := <-chOut: + if !ok { + return + } + net.chMsg <- msg + case <-net.chQuit: + return + } + } + }() +} diff --git a/routing/rt/graph/bfs.go b/routing/rt/graph/bfs.go new file mode 100644 index 00000000..848a5b69 --- /dev/null +++ b/routing/rt/graph/bfs.go @@ -0,0 +1,67 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import "container/list" + +func ShortestPathLen(g *Graph, v1, v2 Vertex) (int, error) { + dist, _, err := bfs(g, v1, v2) + if err != nil { + return 0, err + } + if _, ok := dist[v2]; !ok { + return 0, PathNotFoundError + } + return dist[v2], nil +} + +func ShortestPath(g *Graph, v1, v2 Vertex) ([]Vertex, error) { + _, parent, err := bfs(g, v1, v2) + if err != nil { + return nil, err + } + if _, ok := parent[v2]; !ok { + return nil, PathNotFoundError + } + path, err := Path(v1, v2, parent) + if err != nil { + return nil, err + } + return path, nil +} + +func bfs(g *Graph, v1, v2 Vertex) (map[Vertex]int, map[Vertex]Vertex, error) { + var queue list.List + queue.PushBack(v1) + dist := make(map[Vertex]int) + parent := make(map[Vertex]Vertex) + dist[v1] = 0 + for queue.Len() != 0 { + err := ibfs(g, queue.Front().Value.(Vertex), &queue, dist, parent) + if err != nil { + return nil, nil, err + } + if _, ok := dist[v2]; ok { + break + } + } + return dist, parent, nil +} + +func ibfs(g *Graph, v Vertex, queue *list.List, dist map[Vertex]int, parent map[Vertex]Vertex) error { + queue.Remove(queue.Front()) + targets, err := g.GetNeighbors(v) + if err != nil { + return err + } + for to := range targets { + if _, ok := dist[to]; !ok { + dist[to] = dist[v] + 1 + parent[to] = v + queue.PushBack(to) + } + } + return nil +} diff --git a/routing/rt/graph/bfs_test.go b/routing/rt/graph/bfs_test.go new file mode 100644 index 00000000..3d7c44a7 --- /dev/null +++ b/routing/rt/graph/bfs_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php. + +package graph + +import ( + "reflect" + "testing" +) + +func TestShortestPath(t *testing.T) { + g, v := newTestGraph() + + tests := []struct{ + Source, Target Vertex + ExpectedPath []Vertex + }{ + {v[1], v[4], []Vertex{v[1], v[7], v[3], v[6], v[5], v[4]}}, + {v[5], v[7], []Vertex{v[5], v[6], v[3], v[7]}}, + } + for _, test := range tests { + path, err := ShortestPath(g, test.Source, test.Target) + if err != nil { + t.Errorf("ShortestPath(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err) + } + if !reflect.DeepEqual(path, test.ExpectedPath) { + t.Errorf("ShortestPath(g, %v, %v ) = %v, want %v", test.Source, test.Target, path, test.ExpectedPath) + } + } +} diff --git a/routing/rt/graph/dijkstra.go b/routing/rt/graph/dijkstra.go new file mode 100644 index 00000000..42b59f9f --- /dev/null +++ b/routing/rt/graph/dijkstra.go @@ -0,0 +1,67 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import "math" + +func DijkstraPathWeight(g *Graph, source, target Vertex) (float64, error) { + dist, _, err := dijkstra(g, source, target) + if err != nil { + return 0, err + } + if _, ok := dist[target]; !ok { + return 0, PathNotFoundError + } + return dist[target], nil +} + +func DijkstraPath(g *Graph, source, target Vertex) ([]Vertex, error) { + _, parent, err := dijkstra(g, source, target) + if err != nil { + return nil, err + } + if _, ok := parent[target]; !ok { + return nil, PathNotFoundError + } + path, err := Path(source, target, parent) + if err != nil { + return nil, err + } + return path, nil +} + +func dijkstra(g *Graph, source, target Vertex) (map[Vertex]float64, map[Vertex]Vertex, error) { + dist := make(map[Vertex]float64) + colored := make(map[Vertex]bool) + parent := make(map[Vertex]Vertex) + dist[source] = 0 + for { + bestDist := math.MaxFloat64 + var bestID Vertex + for id, val := range dist { + if val < bestDist && !colored[id] { + bestDist = val + bestID = id + } + } + if bestID == target { + break + } + colored[bestID] = true + targets, err := g.GetNeighbors(bestID) + if err != nil { + return nil, nil, err + } + for id, multiedges := range targets { + for _, edge := range multiedges { + if have, ok := dist[id]; !ok || have > dist[bestID]+edge.Wgt { + dist[id] = dist[bestID] + edge.Wgt + parent[id] = bestID + } + } + } + } + return dist, parent, nil +} diff --git a/routing/rt/graph/dijkstra_test.go b/routing/rt/graph/dijkstra_test.go new file mode 100644 index 00000000..81eba92c --- /dev/null +++ b/routing/rt/graph/dijkstra_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import ( + "reflect" + "testing" +) + +func TestDijkstraPath(t *testing.T) { + g, v := newTestGraph() + + tests := []struct { + Source, Target Vertex + ExpectedPath []Vertex + ExpectedWeight float64 + }{ + {v[1], v[4], []Vertex{v[1], v[7], v[2], v[3], v[6], v[5], v[4]}, 13}, + {v[5], v[7], []Vertex{v[5], v[6], v[3], v[2], v[7]}, 10}, + } + for _, test := range tests { + // Test DijkstraPath + path, err := DijkstraPath(g, test.Source, test.Target) + if err != nil { + t.Errorf("DijkstraPath(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err) + } + if !reflect.DeepEqual(path, test.ExpectedPath) { + t.Errorf("DijkstraPath(g, %v, %v ) = %v, want %v", test.Source, test.Target, path, test.ExpectedPath) + } + // Test DijkstraPathWeight + weight, err := DijkstraPathWeight(g, test.Source, test.Target) + if err != nil { + t.Errorf("DijkstraPathWeight(g, %v, %v ) returns not nil error: %v", test.Source, test.Target, err) + } + if weight != test.ExpectedWeight { + t.Errorf("DijkstraPathWeight(g, %v, %v ) = %v, want %v", test.Source, test.Target, weight, test.ExpectedWeight) + } + } +} diff --git a/routing/rt/graph/graph.go b/routing/rt/graph/graph.go new file mode 100644 index 00000000..66a0360d --- /dev/null +++ b/routing/rt/graph/graph.go @@ -0,0 +1,357 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import ( + "errors" + "fmt" + "math" + "bytes" + "github.com/roasbeef/btcd/wire" +) + +var ( + // CyclicDataError represents internal error. When trying to + // obtain explicit path after BFS. Following parents resulted + // in a loop. + CyclicDataError = errors.New("Cyclic data") + + // PathNotFoundError represents not existing path + // after search + PathNotFoundError = errors.New("Path not found") + + // NodeNotFoundError represents that required vertex + // does not exist in the graph + NodeNotFoundError = errors.New("Node not found") + + // EdgeNotFoundError represents that requested edge + // does not exist in the graph + EdgeNotFoundError = errors.New("Edge not found") +) + +// Vertex represent graph vertex(node) in a +// lightning network +type Vertex struct { + PubKey [33]byte +} + +// NilVertex represents not existing vertex. +// This value should be used similar to nil +var NilVertex = Vertex{PubKey: [33]byte{}} + +// NewVertex creates a new vertex from a compressed public key. +func NewVertex(v []byte) Vertex { + x := Vertex{} + copy(x.PubKey[:], v) + return x +} + +// String creates a string representation of a Vertex. +// Note: it does not hex encode. +func (s Vertex) String() string { + return string(s.PubKey[:]) +} + + +// ToByte33 returns [33]byte +// 33 - is usual byte length of a compressed +// public key +func (s Vertex) ToByte33() [33]byte { + var rez [33]byte + copy(rez[:], s.PubKey[:]) + return rez +} + +// ToByte() returns byte representation of +// the vertex +func (s Vertex) ToByte() []byte { + return s.PubKey[:] +} + +// IsNil compares vertex to nil vertex +func (s Vertex) IsNil() bool { + var z [33]byte + return bytes.Equal(s.PubKey[:], z[:]) +} + +// EdgeID represent edge unique identifier. +type EdgeID wire.OutPoint + +// NilEdgeID represent not existing EdgeID +var NilEdgeID = EdgeID{wire.ShaHash{}, 0} + +// NewEdgeID returns new EdgeID +func NewEdgeID(v wire.OutPoint) EdgeID { + return EdgeID(v) +} + +// String returns string representation of EdgeID +func (e EdgeID) String() string { + return wire.OutPoint(e).String() +} + +// ChannelInfo contains information about edge(channel) +// like capacity or weight +type ChannelInfo struct { + // Capacity in satoshi of the channel + Cpt int64 + // Weight of the channel + Wgt float64 +} + +// Copy() creates a copy of a ChannelInfo struct. +// If c==nil than it returns nil. +// This method is used to safely create copy of a structure +// given by a pointer which may be nil. +func (c *ChannelInfo) Copy() *ChannelInfo { + if c == nil { + return nil + } else { + c1 := *c + return &c1 + } +} + +// Edge represents edge in a graph +type Edge struct { + // Source and Target + Src, Tgt Vertex + + // Edge identifier + Id EdgeID + + // Additional information about edge + Info *ChannelInfo +} + +// NilEdge represents nil (not-existing) edge +var NilEdge = Edge{NilVertex, NilVertex, NilEdgeID, nil} + +// String returns string of an Edge +func (e Edge) String() string { + return fmt.Sprintf("edge[%v %v %v %v]", e.Src, e.Tgt, e.Id, e.Info) +} + +// NewEdge create a new edge +func NewEdge(src, tgt Vertex, id EdgeID, info *ChannelInfo) Edge { + return Edge{ + Src: src, + Tgt: tgt, + Id: id, + Info: info, + } +} + +// Graph is multigraph implementation. +type Graph struct { + adjacencyList map[Vertex]map[Vertex]map[EdgeID]*ChannelInfo +} + +// NewGraph creates a new empty graph. +func NewGraph() *Graph { + g := new(Graph) + g.adjacencyList = make(map[Vertex]map[Vertex]map[EdgeID]*ChannelInfo) + return g +} + +// GetVertexCount returns number of vertexes in a graph. +func (g *Graph) GetVertexCount() int { + return len(g.adjacencyList) +} + +// GetVertexes returns all vertexes in a graph. +func (g *Graph) GetVertexes() []Vertex { + IDs := make([]Vertex, 0, g.GetVertexCount()) + for ID := range g.adjacencyList { + IDs = append(IDs, ID) + } + return IDs +} + +// GetEdges return all edges in a graph. +// For undirected graph it returns each edge twice (for each direction) +// To get all edges in undirected graph use GetUndirectedEdges +func (g *Graph) GetEdges() []Edge { + edges := make([]Edge, 0) + for v1 := range g.adjacencyList { + for v2, multiedges := range g.adjacencyList[v1] { + for id, edge := range multiedges { + edges = append(edges, NewEdge(v1, v2, id, edge)) + } + } + } + return edges +} + +// GetUndirectedEdges returns all edges in an undirected graph. +func (g *Graph) GetUndirectedEdges() []Edge { + edges := make([]Edge, 0) + for v1 := range g.adjacencyList { + for v2, multiedges := range g.adjacencyList[v1] { + if v1.String() <= v2.String() { + for id, edge := range multiedges { + edges = append(edges, NewEdge(v1, v2, id, edge)) + } + } + } + } + return edges +} + +// HasVertex check if graph contain the given vertex. +func (g *Graph) HasVertex(v Vertex) bool { + _, ok := g.adjacencyList[v] + return ok +} + +// HasEdge check if there is edge with a given EdgeID +// between two given vertexes. +func (g *Graph) HasEdge(v1, v2 Vertex, edgeID EdgeID) bool { + if _, ok := g.adjacencyList[v1]; ok { + if multiedges, ok := g.adjacencyList[v1][v2]; ok { + if _, ok := multiedges[edgeID]; ok { + return true + } + } + } + return false +} + +// AddVertex adds vertex to a graph. +// If graph already contains this vertex it does nothing. +// Returns true if vertex previously doesnt't exist. +func (g *Graph) AddVertex(v Vertex) bool { + if g.HasVertex(v) { + return false + } + g.adjacencyList[v] = make(map[Vertex]map[EdgeID]*ChannelInfo) + return true +} + +// RemoveVertex removes vertex from a graph +// If a graph already does not contain this vertex it does nothing +// Returns true if previously existed vertex got deleted +// BUG(mkl): does it correctly deletes edges with this vertex +func (g *Graph) RemoveVertex(v Vertex) bool { + if !g.HasVertex(v) { + return false + } + delete(g.adjacencyList, v) + return true +} + +// AddEdge adds directed edge to the graph +// v1, v2 must exist. If they do not exist this function do nothing +// and return false. If edge with given vertexes and id already exists it +// gets overwritten +func (g *Graph) AddEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool { + if !g.HasVertex(v1) || !g.HasVertex(v2) { + return false + } + tmap := g.adjacencyList[v1] + if tmap[v2] == nil { + tmap[v2] = make(map[EdgeID]*ChannelInfo) + } + tmap[v2][edgeID] = info + return true +} + +// AddUndirectedEdge adds an undirected edge to the graph. +// Vertexes should exists. +func (g *Graph) AddUndirectedEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool { + ok1 := g.AddEdge(v1, v2, edgeID, info) + ok2 := g.AddEdge(v2, v1, edgeID, info) + return ok1 && ok2 +} + +// ReplaceEdge replaces directed edge in the graph +func (g *Graph) ReplaceEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool { + if tmap, ok := g.adjacencyList[v1]; ok { + if _, ok := tmap[v2]; ok { + if _, ok := tmap[v2][edgeID]; ok { + tmap[v2][edgeID] = info + return true + } + } + } + return false +} + +// ReplaceUndirectedEdge replaces undirected edge in the graph. +func (g *Graph) ReplaceUndirectedEdge(v1, v2 Vertex, edgeID EdgeID, info *ChannelInfo) bool { + ok1 := g.ReplaceEdge(v1, v2, edgeID, info) + ok2 := g.ReplaceEdge(v2, v1, edgeID, info) + return ok1 && ok2 +} + +// RemoveEdge removes directed edge in a graph. +func (g *Graph) RemoveEdge(v1, v2 Vertex, edgeID EdgeID) bool { + if _, ok := g.adjacencyList[v1]; ok { + if _, ok := g.adjacencyList[v1][v2]; ok { + tmap := g.adjacencyList[v1][v2] + if _, ok := tmap[edgeID]; ok { + delete(tmap, edgeID) + if len(tmap) == 0 { + delete(g.adjacencyList[v1], v2) + } + return true + } + } + } + return false +} + +// RemoveUndirectedEdge removes undirected edge in a graph. +func (g *Graph) RemoveUndirectedEdge(v1, v2 Vertex, edgeID EdgeID) bool { + ok1 := g.RemoveEdge(v1, v2, edgeID) + ok2 := g.RemoveEdge(v2, v1, edgeID) + return ok1 && ok2 +} + +// GetInfo returns info about edge in a graph. +func (g *Graph) GetInfo(v1, v2 Vertex, edgeID EdgeID) (*ChannelInfo, error) { + if tmap, ok := g.adjacencyList[v1]; ok { + if _, ok := tmap[v2]; ok { + if _, ok := tmap[v2][edgeID]; ok { + return tmap[v2][edgeID], nil + } + } + } + return nil, EdgeNotFoundError +} + +// GetNeighbors returns neighbors of a given vertex in a graph. +// Note: output should not be modified because it will change +// original graph +func (g *Graph) GetNeighbors(v Vertex) (map[Vertex]map[EdgeID]*ChannelInfo, error) { + if !g.HasVertex(v) { + return nil, NodeNotFoundError + } + return g.adjacencyList[v], nil +} + +// MinCostChannel return channel with minimal weight between two vertexes +func (g *Graph) MinCostChannel(v1, v2 Vertex) (*ChannelInfo, error) { + if !g.HasVertex(v1) || !g.HasVertex(v2) { + return nil, NodeNotFoundError + } + if _, ok := g.adjacencyList[v1][v2]; !ok { + return nil, EdgeNotFoundError + } + wgt := math.MaxFloat64 + var easiest *ChannelInfo + for _, edge := range g.adjacencyList[v1][v2] { + if edge.Wgt < wgt { + wgt, easiest = edge.Wgt, edge + } + } + return easiest, nil +} + + +// Bfs do breadth-first search starting from a given vertex +func (g *Graph) Bfs(source Vertex) (map[Vertex]int, map[Vertex]Vertex, error) { + return bfs(g, source, NilVertex) +} diff --git a/routing/rt/graph/graph_test.go b/routing/rt/graph/graph_test.go new file mode 100644 index 00000000..68ba8d9c --- /dev/null +++ b/routing/rt/graph/graph_test.go @@ -0,0 +1,166 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import ( + "reflect" + "testing" + "fmt" +) + +func vertexFromInt(x int) Vertex { + s := fmt.Sprintf("%v", x) + return NewVertex([]byte(s)) +} + +func edgeIdFromString(s string) EdgeID { + e := EdgeID{} + copy(e.Hash[:], []byte(s)) + return e +} + +// newTestGraph returns new graph for testing purposes +// Each time it returns new graph +func newTestGraph() (*Graph, []Vertex) { + g := NewGraph() + v := make([]Vertex, 8) + for i:=1; i<8; i++ { + v[i] = vertexFromInt(i) + g.AddVertex(v[i]) + } + edges := []Edge{ + {v[1], v[7], edgeIdFromString("2"), &ChannelInfo{Wgt:2}}, + {v[7], v[2], edgeIdFromString("1"), &ChannelInfo{Wgt:1}}, + {v[3], v[7], edgeIdFromString("10"), &ChannelInfo{Wgt:10}}, + {v[2], v[3], edgeIdFromString("2"), &ChannelInfo{Wgt:2}}, + {v[3], v[6], edgeIdFromString("4"), &ChannelInfo{Wgt:4}}, + {v[5], v[6], edgeIdFromString("3"), &ChannelInfo{Wgt:3}}, + {v[4], v[5], edgeIdFromString("1"), &ChannelInfo{Wgt:1}}, + } + for _, e := range edges { + g.AddUndirectedEdge(e.Src, e.Tgt, e.Id, e.Info) + } + return g, v +} + +func TestNodeManipulation(t *testing.T) { + g := NewGraph() + if g.HasVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if !g.AddVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if g.AddVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if !g.HasVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if !g.RemoveVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if g.RemoveVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if g.HasVertex(vertexFromInt(2)) { + t.Errorf("expected: %t, actual: %t", false, true) + } +} + +func TestEdgeManipulation(t *testing.T) { + g := NewGraph() + if g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) { + t.Errorf("expected: %t, actual: %t", false, true) + } + g.AddVertex(vertexFromInt(2)) + g.AddVertex(vertexFromInt(3)) + if g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if g.ReplaceEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{1, 2}) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if !g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if !g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if !g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if !g.ReplaceEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{1, 2}) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if info, err := g.GetInfo(vertexFromInt(2), vertexFromInt(3), NilEdgeID); err != nil { + panic(err) + } else if !reflect.DeepEqual(*info, ChannelInfo{1, 2}) { + t.Errorf("expected: %v, actual: %v", ChannelInfo{1, 2}, *info) + } + if !g.RemoveEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) { + t.Errorf("expected: %t, actual: %t", true, false) + } + if g.RemoveEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) { + t.Errorf("expected: %t, actual: %t", false, true) + } + if g.HasEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID) { + t.Errorf("expected: %t, actual: %t", false, true) + } +} + +func TestAllGetMethods(t *testing.T) { + g := NewGraph() + g.AddVertex(vertexFromInt(2)) + g.AddVertex(vertexFromInt(3)) + if vertexCount := g.GetVertexCount(); vertexCount != 2 { + t.Errorf("expected: %d, actual: %d", 2, vertexCount) + } + if vs := g.GetVertexes(); !reflect.DeepEqual(vs, []Vertex{vertexFromInt(2), vertexFromInt(3)}) && + !reflect.DeepEqual(vs, []Vertex{vertexFromInt(3), vertexFromInt(2)}) { + t.Errorf("expected: %v, actual: %v", + []Vertex{vertexFromInt(2), vertexFromInt(3)}, + vs, + ) + } + g.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil) + if edges := g.GetEdges(); !reflect.DeepEqual(edges, []Edge{NewEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil)}) { + t.Errorf("expected: %v, actual: %v", + []Edge{NewEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, nil)}, + edges, + ) + } + + if targets, err := g.GetNeighbors(vertexFromInt(2)); err != nil { + panic(err) + } else if !reflect.DeepEqual(targets, map[Vertex]map[EdgeID]*ChannelInfo{vertexFromInt(3): map[EdgeID]*ChannelInfo{NilEdgeID: nil}}) { + t.Errorf("expected: %v, actual: %v", + map[Vertex]map[EdgeID]*ChannelInfo{vertexFromInt(3): map[EdgeID]*ChannelInfo{NilEdgeID: nil}}, + targets, + ) + } + + g2 := NewGraph() + g2.AddVertex(vertexFromInt(2)) + g2.AddVertex(vertexFromInt(3)) + g2.AddEdge(vertexFromInt(2), vertexFromInt(3), NilEdgeID, &ChannelInfo{Wgt: 42}) + if info, err := g2.GetInfo(vertexFromInt(2), vertexFromInt(3), NilEdgeID); err != nil { + panic(err) + } else if wgt := info.Wgt; wgt != 42 { + t.Errorf("expected: %v, actual: %v", wgt, 42) + } +} + +func TestVertexToByte33(t *testing.T) { + var b1 [33]byte + for i := 0; i < 33; i++ { + b1[i] = byte(i) + } + v := NewVertex(b1[:]) + b2 := v.ToByte33() + if b1 != b2 { + t.Errorf("Wrong result of ID.ToByte33()= %v, want %v", b2, b1) + } +} diff --git a/routing/rt/graph/ksp.go b/routing/rt/graph/ksp.go new file mode 100644 index 00000000..1b9e09f8 --- /dev/null +++ b/routing/rt/graph/ksp.go @@ -0,0 +1,68 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import "math" + +// KShortestPaths finds k shortest paths +// Note: this implementation finds k path not necessary shortest +// It tries to make that distinct and shortest at the same time +func KShortestPaths(g *Graph, source, target Vertex, k int) ([][]Vertex, error) { + ksp := make([][]Vertex, 0, k) + DRY := make(map[string]bool) + actualNodeWeight := make(map[Vertex]float64, g.GetVertexCount()) + for _, id := range g.GetVertexes() { + actualNodeWeight[id] = 1 + } + const UselessIterations = 200 + for cnt := 0; len(ksp) < k && cnt < UselessIterations; cnt++ { + if err := modifyEdgeWeight(g, actualNodeWeight); err != nil { + return nil, err + } + path, err := DijkstraPath(g, source, target) + if err != nil { + return nil, err + } + for _, v := range path { + actualNodeWeight[v]++ + } + key := "" + for _, v := range path { + key += v.String() + } + if !DRY[key] { + DRY[key] = true + ksp = append(ksp, path) + cnt = 0 + } + } + return ksp, nil +} + +func modifyEdgeWeight(g *Graph, actualNodeWeight map[Vertex]float64) error { + for _, v1 := range g.GetVertexes() { + targets, err := g.GetNeighbors(v1) + if err != nil { + return err + } + for v2, multiedges := range targets { + for ID := range multiedges { + wgt := calcEdgeWeight(actualNodeWeight, v1, v2) + g.ReplaceUndirectedEdge(v1, v2, ID, &ChannelInfo{Wgt: wgt}) + } + } + } + return nil +} + +// Calculate new edge weight based on vertex weights. +// It uses empirical formulae +// weight(i, j) = (weight(i) + weight(j)) ^ 6 +// Number 6 was choosen because it gives best results in several simulations +func calcEdgeWeight(actualNodeWeight map[Vertex]float64, v1, v2 Vertex) float64 { + const ExperiementalNumber = 6.0 + wgt := math.Pow(actualNodeWeight[v1]+actualNodeWeight[v2], ExperiementalNumber) + return wgt +} \ No newline at end of file diff --git a/routing/rt/graph/ksp_test.go b/routing/rt/graph/ksp_test.go new file mode 100644 index 00000000..a3ede9fb --- /dev/null +++ b/routing/rt/graph/ksp_test.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import ( + "reflect" + "testing" +) + +func TestKSP(t *testing.T) { + g, v := newTestGraph() + + tests := []struct { + Source, Target Vertex + K int + ExpectedPaths [][]Vertex + }{ + {v[1], v[4], 10, [][]Vertex{ + {v[1], v[7], v[3], v[6], v[5], v[4]}, + {v[1], v[7], v[2], v[3], v[6], v[5], v[4]}, + }, + }, + {v[5], v[7], 1, [][]Vertex{ + {v[5], v[6], v[3], v[7]}, + }, + }, + } + for _, test := range tests { + paths, err := KShortestPaths(g, test.Source, test.Target, test.K) + if err != nil { + t.Errorf("KShortestPaths(g, %v, %v, %v) returns not nil error: %v", test.Source, test.Target, test.K, err) + } + if !reflect.DeepEqual(paths, test.ExpectedPaths) { + t.Errorf("KShortestPaths(g, %v, %v, %v) = %v, want %v", test.Source, test.Target, test.K, paths, test.ExpectedPaths) + } + } +} diff --git a/routing/rt/graph/path.go b/routing/rt/graph/path.go new file mode 100644 index 00000000..6d347518 --- /dev/null +++ b/routing/rt/graph/path.go @@ -0,0 +1,39 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package graph + +import "errors" + +func Path(v1, v2 Vertex, parent map[Vertex]Vertex) ([]Vertex, error) { + path, err := ReversePath(v1, v2, parent) + if err != nil { + return nil, err + } + Reverse(path) + return path, nil +} + +func ReversePath(v1, v2 Vertex, parent map[Vertex]Vertex) ([]Vertex, error) { + path := []Vertex{v2} + for v2 != v1 { + if v2 == parent[v2] { + return nil, CyclicDataError + } + var ok bool + v2, ok = parent[v2] + if !ok { + return nil, errors.New("Invalid key") + } + path = append(path, v2) + } + return path, nil +} + +func Reverse(path []Vertex) { + length := len(path) + for i := 0; i < length/2; i++ { + path[i], path[length-1-i] = path[length-1-i], path[i] + } +} diff --git a/routing/rt/rt.go b/routing/rt/rt.go new file mode 100644 index 00000000..65c87b6d --- /dev/null +++ b/routing/rt/rt.go @@ -0,0 +1,258 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package rt + +import ( + "fmt" + "github.com/lightningnetwork/lnd/routing/rt/graph" +) + +type OperationType byte +const ( + AddChannelOP OperationType = iota + RemoveChannelOp +) + +// String returns string representation +func (t OperationType) String()string { + switch t { + case AddChannelOP: + return "" + case RemoveChannelOp: + return "" + default: + return "" + } +} + +// ChannelOperation represent operation on a graph +// Such as add edge or remove edge +type ChannelOperation struct { + graph.Edge + Operation OperationType // One of ADD_CHANNEL, REMOVE_CHANNEL +} + +// NewChannelOperation returns new ChannelOperation +func NewChannelOperation(src, tgt graph.Vertex, id graph.EdgeID, info *graph.ChannelInfo, opType OperationType) ChannelOperation{ + return ChannelOperation{ + Edge: graph.Edge{ + Src: src, + Tgt: tgt, + Id: id, + Info: info.Copy(), + }, + Operation: opType, + } +} + +// String return string representation +func (c ChannelOperation) String () string { + return fmt.Sprintf( + "ChannelOperation[%v %v %v %v %v]", + c.Src, + c.Tgt, + c.Id, + c.Info, + c.Operation, + ) +} + +// Copy returns copy of ChannelOperation +func (op *ChannelOperation) Copy() ChannelOperation { + return ChannelOperation{ + Edge: graph.Edge{ + Src: op.Src, + Tgt: op.Tgt, + Id: op.Id, + Info: op.Info.Copy(), + }, + Operation: op.Operation, + } +} + +// DifferenceBuffer represent multiple changes in a graph +// such as adding or deleting edges +type DifferenceBuffer []ChannelOperation + +// String returns string representation +func (d DifferenceBuffer) String() string { + s := "" + for i:=0; i 0 { + s1 = "\n" + } + return fmt.Sprintf("DifferenceBuffer[%v%v]", s1, s) +} + +// NewDifferenceBuffer create new empty DifferenceBuffer +func NewDifferenceBuffer() *DifferenceBuffer { + d := make(DifferenceBuffer, 0) + return &d +} + +// IsEmpty checks if buffer is empty(contains no operations in it) +func (diffBuff *DifferenceBuffer) IsEmpty() bool { + return len(*diffBuff) == 0 +} + +// Clear deletes all operations from DifferenceBuffer making it empty +func (diffBuff *DifferenceBuffer) Clear() { + *diffBuff = make([]ChannelOperation, 0) +} + +// Copy create copy of a DifferenceBuffer +func (diffBuff *DifferenceBuffer) Copy() *DifferenceBuffer { + d := make([]ChannelOperation, 0, len(*diffBuff)) + for _, op := range *diffBuff { + d = append(d, op.Copy()) + } + return (*DifferenceBuffer)(&d) +} + +// RoutingTable represent information about graph and neighbors +// TODO(mkl): better doc +// Methods of this struct is not thread safe +type RoutingTable struct { + // Contains node's view of lightning network + G *graph.Graph + // Contains changes to send to a different neighbors + // Changing RT modifies DifferenceBuffer + diff []*DifferenceBuffer +} + +// ShortestPath find shortest path between two node in routing table +func (rt *RoutingTable) ShortestPath(src, dst graph.Vertex) ([]graph.Vertex, error) { + return graph.ShortestPath(rt.G, src, dst) +} + +// NewRoutingTable creates new empty routing table +func NewRoutingTable() *RoutingTable { + return &RoutingTable{ + G: graph.NewGraph(), + diff: make([]*DifferenceBuffer, 0), + } +} + +// AddNodes add multiple nodes to the routing table +func (rt *RoutingTable) AddNodes(IDs ...graph.Vertex) { + for _, ID := range IDs { + rt.G.AddVertex(ID) + } +} + +// AddChannel adds channel to the routing table. +// It will add nodes if they are not present in RT. +// It will update all difference buffers +func (rt *RoutingTable) AddChannel(Node1, Node2 graph.Vertex, edgeID graph.EdgeID, info *graph.ChannelInfo) { + rt.AddNodes(Node1, Node2) + if !rt.HasChannel(Node1, Node2, edgeID) { + rt.G.AddUndirectedEdge(Node1, Node2, edgeID, info) + for _, diffBuff := range rt.diff { + *diffBuff = append(*diffBuff, NewChannelOperation(Node1, Node2, edgeID, info, AddChannelOP)) + } + } +} + +// HasChannel check if channel between two nodes exist in routing table +func (rt *RoutingTable) HasChannel(node1, node2 graph.Vertex, edgeID graph.EdgeID) bool { + return rt.G.HasEdge(node1, node2, edgeID) +} + +// RemoveChannel removes channel from the routing table. +// It will do nothing if channel does not exist in RT. +// It will update all difference buffers +func (rt *RoutingTable) RemoveChannel(Node1, Node2 graph.Vertex, edgeID graph.EdgeID) { + if rt.HasChannel(Node1, Node2, edgeID) { + rt.G.RemoveUndirectedEdge(Node1, Node2, edgeID) + for _, diffBuff := range rt.diff { + *diffBuff = append(*diffBuff, NewChannelOperation(Node1, Node2, edgeID, nil, RemoveChannelOp)) + } + } +} + +// NewDiffBuff create a new difference buffer in a routing table +func (rt *RoutingTable) NewDiffBuff() *DifferenceBuffer { + buff := NewDifferenceBuffer() + rt.diff = append(rt.diff, buff) + return buff +} + +// ApplyDiffBuff applies difference buffer to the routing table. +// It will modify RoutingTable's difference buffers. +func (rt *RoutingTable) ApplyDiffBuff(diffBuff *DifferenceBuffer) { + for _, op := range *diffBuff { + if op.Operation == AddChannelOP { + rt.AddChannel(op.Src, op.Tgt, op.Id, op.Info) + } else if op.Operation == RemoveChannelOp { + rt.RemoveChannel(op.Src, op.Tgt, op.Id) + } + } +} + +// Nodes return all nodes from routing table +func (rt *RoutingTable) Nodes() []graph.Vertex { + return rt.G.GetVertexes() +} + +// NumberOfNodes returns number of nodes in routing table +func (rt *RoutingTable) NumberOfNodes() int { + return rt.G.GetVertexCount() +} + +// AllChannels returns all channels from routing table +func (rt *RoutingTable) AllChannels() []graph.Edge { + edges := rt.G.GetUndirectedEdges() + return edges +} + +// AddTable adds other RoutingTable. +// Resulting table contains channels, nodes from both tables. +func (rt *RoutingTable) AddTable(rt1 *RoutingTable) { + newChannels := rt1.AllChannels() + for _, channel := range newChannels { + rt.AddChannel(channel.Src, channel.Tgt, channel.Id, channel.Info.Copy()) + } +} + +// Copy - creates a copy of RoutingTable +// It makes deep copy of routing table. +// Note: difference buffers are not copied! +func (rt *RoutingTable) Copy() *RoutingTable { + // TODO: add tests + channels := rt.AllChannels() + newRT := NewRoutingTable() + newRT.AddNodes(rt.Nodes()...) + for _, channel := range channels { + newRT.AddChannel(channel.Src, channel.Tgt, channel.Id, channel.Info.Copy()) + } + return newRT +} + +// String returns string representation of routing table +func (rt *RoutingTable) String() string { + rez := "" + edges := rt.G.GetUndirectedEdges() + for _, edge := range edges { + rez += fmt.Sprintf("%v %v %v", edge.Src, edge.Tgt, edge.Info) + } + return rez +} + +// Find k-shortest path between source and destination +func (rt *RoutingTable) KShortestPaths(src, dst graph.Vertex, k int) ([][]graph.Vertex, error) { + return graph.KShortestPaths(rt.G, src, dst, k) +} + +// Returns copy of channel info for channel in routing table +func (rt *RoutingTable) GetChannelInfo(id1, id2 graph.Vertex, edgeId graph.EdgeID) (*graph.ChannelInfo, error) { + info, err := rt.G.GetInfo(id1, id2, edgeId) + if err != nil { + return nil, err + } + return info.Copy(), nil +} \ No newline at end of file diff --git a/routing/rt/rt_test.go b/routing/rt/rt_test.go new file mode 100644 index 00000000..57d56583 --- /dev/null +++ b/routing/rt/rt_test.go @@ -0,0 +1,160 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package rt + +import ( + "github.com/lightningnetwork/lnd/routing/rt/graph" + "testing" +) + +var ( + NewID = vertexFromString +) + +func vertexFromString(s string) graph.Vertex { + return graph.NewVertex([]byte(s)) +} + +func edgeIdFromString(s string) graph.EdgeID { + e := graph.EdgeID{} + copy(e.Hash[:], []byte(s)) + return e +} + +func TestRT(t *testing.T) { + r := NewRoutingTable() + r.AddNodes(vertexFromString("1"), vertexFromString("2")) + + r.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil) + if !r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(1, 2) == false, want true`) + } + if !r.HasChannel(vertexFromString("2"), vertexFromString("1"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(2, 1) == false, want true`) + } +} + +func TestDiff(t *testing.T) { + r := NewRoutingTable() + buff := r.NewDiffBuff() + // Add 2 channels + r.AddNodes(vertexFromString("1"), vertexFromString("2"), vertexFromString("3")) + + r.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil) + r.AddChannel(vertexFromString("2"), vertexFromString("3"), edgeIdFromString("EdgeID"), nil) + r1 := NewRoutingTable() + r1.ApplyDiffBuff(buff) + if !r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(1, 2) == false, want true`) + } + if !r.HasChannel(vertexFromString("2"), vertexFromString("3"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(2, 3) == false, want true`) + } + // Remove channel + r.RemoveChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) + r1.ApplyDiffBuff(buff) + if r.HasChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(1, 2) == true, want false`) + } + if r.HasChannel(vertexFromString("2"), vertexFromString("1"), edgeIdFromString("EdgeID")) { + t.Error(`r.HasChannel(2, 1) == true, want false`) + } +} + +func TestRTChannels(t *testing.T) { + rt := NewRoutingTable() + rt.AddNodes(vertexFromString("1"), vertexFromString("2"), vertexFromString("3")) + + rt.AddChannel(vertexFromString("1"), vertexFromString("2"), edgeIdFromString("EdgeID"), nil) + rt.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), nil) + + channels := rt.AllChannels() + if len(channels) != 2 { + t.Errorf(`rt.AllChannels == %v, want %v`, len(channels), 2) + } +} + +func TestRTAddTable(t *testing.T) { + rt1 := NewRoutingTable() + rt1.AddNodes(NewID("0"), NewID("1"), NewID("2"), NewID("3"), NewID("5"), NewID("6")) + + rt1.AddChannel(NewID("0"), NewID("1"), edgeIdFromString("EdgeID"), nil) + rt1.AddChannel(NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), nil) + + rt2 := NewRoutingTable() + rt2.AddNodes(NewID("0"), NewID("1"), NewID("2"), NewID("3"), NewID("5"), NewID("6")) + + rt2.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), nil) + rt2.AddChannel(NewID("5"), NewID("6"), edgeIdFromString("EdgeID"), nil) + rt1.AddTable(rt2) + expectedChannels := [][]graph.Vertex{ + []graph.Vertex{NewID("0"), NewID("1")}, + []graph.Vertex{NewID("1"), NewID("2")}, + []graph.Vertex{NewID("2"), NewID("3")}, + []graph.Vertex{NewID("5"), NewID("6")}, + } + for _, c := range expectedChannels { + if !rt1.HasChannel(c[0], c[1], edgeIdFromString("EdgeID")) { + t.Errorf("After addition, channel between %v and %v is absent", c[0], c[1]) + } + } +} + +func TestChannelInfoCopy(t *testing.T) { + c := &graph.ChannelInfo{Cpt: 10, Wgt: 10} + c1 := c.Copy() + if *c != *c1 { + t.Errorf("*c.Copy() != *c, *c=%v, *c.Copy()=%v", *c, *c1) + } + c2 := (*graph.ChannelInfo)(nil) + c2ans := c2.Copy() + if c2ans != nil { + t.Errorf("c.Copy()=%v, for c=nil, want nil", c2ans) + } +} + +func TestRTCopy(t *testing.T) { + r := NewRoutingTable() + // Add 2 channels + r.AddNodes(NewID("1"), NewID("2"), NewID("3")) + + r.AddChannel(NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{10, 11}) + r.AddChannel(NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{20, 21}) + // Create copy of this rt and make sure they are equal + rCopy := r.Copy() + channelsExp := []struct { + id1, id2 graph.Vertex + edgeID graph.EdgeID + info *graph.ChannelInfo + isErrNil bool + }{ + {NewID("1"), NewID("2"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{10, 11}, true}, + {NewID("2"), NewID("3"), edgeIdFromString("EdgeID"), &graph.ChannelInfo{20, 21}, true}, + // Channel do not exist + {NewID("3"), NewID("4"), edgeIdFromString("EdgeID"), nil, false}, + } + for _, c := range channelsExp { + info, err := rCopy.GetChannelInfo(c.id1, c.id2, c.edgeID) + if (err != nil) && (info != nil) { + t.Errorf("rCopy.GetChannelInfo give not nil result and err") + continue + } + if (err == nil) != c.isErrNil { + t.Errorf("err == nil: %v, want: %v, err: %v", err == nil, c.isErrNil, err) + continue + } + if info == nil && c.info != nil { + t.Errorf("rCopy.GetChannelInfo(%v, %v, %v)==nil, nil. want ", c.id1, c.id2, c.edgeID) + continue + } + if info != nil && c.info == nil { + t.Errorf("rCopy.GetChannelInfo(%v, %v, %v)==, nil. want nil", c.id1, c.id2, c.edgeID) + continue + } + if (info != nil) && c.info != nil && *info != *c.info { + t.Errorf("*rCopy.GetChannelInfo(%v, %v, %v)==%v, nil. want %v", c.id1, c.id2, c.edgeID, *info, c.info) + } + } +} \ No newline at end of file diff --git a/routing/rt/visualizer/config.go b/routing/rt/visualizer/config.go new file mode 100644 index 00000000..87753068 --- /dev/null +++ b/routing/rt/visualizer/config.go @@ -0,0 +1,125 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package visualizer + +// Configuration used for graph visualisation. +type VisualizerConfig struct { + // General options. + GlobalCfg *GlobalConfig + // Options for node visualisation. + NodeCfg *NodeConfig + // Options ofr highlighted nodes visualisation. + HighlightedNodeCfg *NodeConfig + // Options for edges visualisation. + EdgeCfg *EdgeConfig + // Options for highlighted edges visualisation. + HighlightedEdgeCfg *EdgeConfig + // Indicate whether shortcuts should be used. + // Shortcut of a string is a prefix of a string that is unique + // for a given set of strings. + EnableShortcut bool +} + +var DefaultVisualizerConfig VisualizerConfig = VisualizerConfig{ + GlobalCfg: &DefaultGlobalConfig, + NodeCfg: &DefaultNodeConfig, + HighlightedNodeCfg: &DefaultHighlightedNodeConfig, + EdgeCfg: &DefaultEdgeConfig, + HighlightedEdgeCfg: &DefaultHighlightedEdgeConfig, + EnableShortcut: false, +} + +type GlobalConfig struct { + // Title of an image. + Name string + Dir bool + // Allow multigraph. + Strict bool + // Background color. + BgColor string +} + +var DefaultGlobalConfig GlobalConfig = GlobalConfig{ + Name: `"Routing Table"`, + Dir: true, + Strict: false, // Allow multigraphs + BgColor: "black", +} + +type NodeConfig struct { + Shape string + Style string + FontSize string + FontColor string + Color string + FillColor string +} + +var DefaultNodeConfig NodeConfig = NodeConfig{ + Shape: "circle", + Style: "filled", + FontSize: "12", + FontColor: "black", + Color: "white", + FillColor: "white", +} + +var DefaultHighlightedNodeConfig NodeConfig = NodeConfig{ + Shape: "circle", + Style: "filled", + FontSize: "12", + FontColor: "black", + Color: "blue", + FillColor: "blue", +} + +type EdgeConfig struct { + FontSize string + FontColor string + Scale string + Dir string + Style string + Color string +} + +var DefaultEdgeConfig EdgeConfig = EdgeConfig{ + FontSize: "12", + FontColor: "gold", + Scale: "2.5", + Dir: "none", + Style: "solid", + Color: "white", +} + +var DefaultHighlightedEdgeConfig EdgeConfig = EdgeConfig{ + FontSize: "12", + FontColor: "gold", + Scale: "2.5", + Dir: "none", + Style: "solid", + Color: "blue", +} + +func SupportedFormatsAsMap() map[string]struct{} { + rez := make(map[string]struct{}) + for _, format := range supportedFormats { + rez[format] = struct{}{} + } + return rez +} + +// SupportedFormats contains list of image formats that can be +// used for output. +func SupportedFormats() []string { + return supportedFormats +} + +var supportedFormats = []string{ + "bmp", "canon", "cgimage", "cmap", "cmapx", "cmapx_np", "dot", "eps", "exr", + "fig", "gif", "gv", "icns", "ico", "imap", "imap_np", "ismap", "jp2", "jpe", + "jpeg", "jpg", "pct", "pdf", "pic", "pict", "plain", "plain-ext", "png", + "pov", "ps", "ps2", "psd", "sgi", "svg", "svgz", "tga", "tif", "tiff", "tk", + "vml", "vmlz", "xdot", "xdot1.2", "xdot1.4", +} diff --git a/routing/rt/visualizer/prefix_tree/node.go b/routing/rt/visualizer/prefix_tree/node.go new file mode 100644 index 00000000..78c28433 --- /dev/null +++ b/routing/rt/visualizer/prefix_tree/node.go @@ -0,0 +1,116 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package prefix_tree + +import "errors" + +// Node represents the general interface for node in prefix tree +type Node interface { + Name() string + Len() int + Child(nextSymbol string) Node + Childs() map[string]Node + AddChild(symbol string, child Node) error + Parent() Node + PreviousSymbol() string + IsRoot() bool + IsLeaf() bool + IsTerminal() bool + Terminal() + Path() (string, error) +} + +type node struct { + name string + childs map[string]Node + parent Node + previousSymbol string + isTerminal bool +} + +// NewNode create new node +func NewNode(name string, parent Node, previousSymbol string, isTerminal bool) Node { + return &node{ + name: name, + childs: make(map[string]Node), + parent: parent, + previousSymbol: previousSymbol, + isTerminal: isTerminal, + } +} + +func (nd *node) Name() string { + return nd.name +} + +func (nd *node) Len() int { + return len(nd.childs) +} + +func (nd *node) Child(nextSymbol string) Node { + if _, ok := nd.childs[nextSymbol]; !ok { + return nil + } + return nd.childs[nextSymbol] +} + +func (nd *node) Childs() map[string]Node { + return nd.childs +} + +func (nd *node) AddChild(symbol string, child Node) error { + if _, ok := nd.childs[symbol]; ok { + return errors.New("Node already exists") + } + nd.childs[symbol] = child + return nil +} + +func (nd *node) Parent() Node { + return nd.parent +} + +func (nd *node) PreviousSymbol() string { + return nd.previousSymbol +} + +func (nd *node) IsRoot() bool { + return nd.parent == nil +} + +func (nd *node) IsLeaf() bool { + return len(nd.childs) == 0 +} + +func (nd *node) IsTerminal() bool { + return nd.isTerminal +} + +func (nd *node) Terminal() { + nd.isTerminal = true +} + +func (nd *node) Path() (path string, err error) { + var xNode Node = nd + for { + if xNode.IsRoot() { + break + } + path += xNode.PreviousSymbol() + if xNode == xNode.Parent() { + return "", CyclicDataError + } + xNode = xNode.Parent() + } + return Reverse(path), nil +} + +func Reverse(reversePath string) (path string) { + length := len(reversePath) + for i := length - 1; i >= 0; i-- { + path += string(reversePath[i]) + } + return path +} diff --git a/routing/rt/visualizer/prefix_tree/prefix_tree.go b/routing/rt/visualizer/prefix_tree/prefix_tree.go new file mode 100644 index 00000000..632058a5 --- /dev/null +++ b/routing/rt/visualizer/prefix_tree/prefix_tree.go @@ -0,0 +1,120 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package prefix_tree + +import ( + "errors" + "strconv" +) + +var ( + CyclicDataError = errors.New("Cyclic data") + NodeNotFoundError = errors.New("Node not found") + NotEnoughData = errors.New("Not enough data") +) + +type PrefixTree interface { + Add(str string) error + Shortcut(fullname string) (string, error) + Autocomplete(prefix string) (string, error) + Len() int +} + +type prefixTree struct { + root Node + nodes int +} + +func NewPrefixTree() PrefixTree { + root := NewNode("0", nil, "", false) + return &prefixTree{root: root} +} + +func (tree *prefixTree) Add(str string) error { + var node Node = tree.root + for _, symbolRune := range str { + symbol := string(byte(symbolRune)) + child := node.Child(symbol) + if child == nil { + child = NewNode(strconv.Itoa(tree.Len()), node, symbol, false) + err := node.AddChild(symbol, child) + if err != nil { + return err + } + tree.nodes++ + } + node = child + } + node.Terminal() + return nil +} + +// Find shortcut for a given string. Shortcut is unique prefix for a given string. +// If there is only one string in prefix tree return first symbol +func (tree *prefixTree) Shortcut(fullname string) (string, error) { + var node Node = tree.root + for _, symbolRune := range fullname { + symbol := string(byte(symbolRune)) + child := node.Child(symbol) + if child == nil { + return "", NodeNotFoundError + } + node = child + } + if !node.IsTerminal() { + return "", errors.New("Node MUST be terminal") + } + if !node.IsLeaf() { + return fullname, nil + } + highest := node + for { + if highest.Parent() == nil || highest.Parent().IsTerminal() || highest.Parent().Len() != 1 { + break + } + if highest == highest.Parent() { + return "", CyclicDataError + } + highest = highest.Parent() + } + path, err := highest.Path() + if err != nil { + return "", err + } + if path == "" { + return fullname[0:1], nil + } + return path, nil +} + +func (tree *prefixTree) Autocomplete(prefix string) (string, error) { + var node Node = tree.root + for _, symbolRune := range prefix { + symbol := string(byte(symbolRune)) + child := node.Child(symbol) + if child == nil { + return "", NodeNotFoundError + } + node = child + } + for !node.IsLeaf() { + if node.IsTerminal() || node.Len() != 1 { + return "", NotEnoughData + } + childs := node.Childs() + for _, value := range childs { + node = value + } + } + path, err := node.Path() + if err != nil { + return "", err + } + return path, nil +} + +func (tree *prefixTree) Len() int { + return tree.nodes +} diff --git a/routing/rt/visualizer/prefix_tree/prefix_tree_test.go b/routing/rt/visualizer/prefix_tree/prefix_tree_test.go new file mode 100644 index 00000000..73b9dc6a --- /dev/null +++ b/routing/rt/visualizer/prefix_tree/prefix_tree_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package prefix_tree + +import "testing" + +func TestPrefixTree(t *testing.T) { + pt := NewPrefixTree() + pt.Add("walk") + pt.Add("hello") + pt.Add("hi") + pt.Add("hell") + pt.Add("world") + pt.Add("www") + + shortcut, _ := pt.Shortcut("world") + if expected := "wo"; shortcut != expected { + t.Errorf("expected: %s, actual: %s", expected, shortcut) + } + + shortcut, _ = pt.Shortcut("www") + if expected := "ww"; shortcut != expected { + t.Errorf("expected: %s, actual: %s", expected, shortcut) + } + + autocompleted, _ := pt.Autocomplete("wo") + if expected := "world"; autocompleted != expected { + t.Errorf("expected: %s, actual: %s", expected, autocompleted) + } + + autocompleted, _ = pt.Autocomplete("ww") + if expected := "www"; autocompleted != expected { + t.Errorf("expected: %s, actual: %s", expected, autocompleted) + } + + pt.Add("123") + pt.Add("456") + pt.Add("1234") + + shortcut, _ = pt.Shortcut("123") + if expected := "123"; shortcut != expected { + t.Errorf("expected: %s, actual: %s", expected, shortcut) + } +} + +func TestPrefixTreeOneNode(t *testing.T) { + pt := NewPrefixTree() + pt.Add("123") + shortcut, err := pt.Shortcut("123") + if err != nil { + t.Errorf("error getting shortcut for 123: %v, want: %v", err, nil) + } + expectedShortcut := "1" + if shortcut != expectedShortcut { + t.Errorf("expected: %v, actual: %v", expectedShortcut, shortcut) + } + + expectedAutocomplete := "123" + autocomplete, err := pt.Autocomplete("123") + + if err != nil { + t.Errorf("error getting autocomplete for 123: %v, want: %v", err, nil) + } + if autocomplete != expectedAutocomplete { + t.Errorf("expected: %v, actual: %v", expectedAutocomplete, autocomplete) + } + +} diff --git a/routing/rt/visualizer/visualizer.go b/routing/rt/visualizer/visualizer.go new file mode 100644 index 00000000..390eeafc --- /dev/null +++ b/routing/rt/visualizer/visualizer.go @@ -0,0 +1,174 @@ +// Copyright (c) 2016 Bitfury Group Limited +// Distributed under the MIT software license, see the accompanying +// file LICENSE or http://www.opensource.org/licenses/mit-license.php + +package visualizer + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/lightningnetwork/lnd/routing/rt/visualizer/prefix_tree" + "github.com/lightningnetwork/lnd/routing/rt/graph" + "github.com/awalterschulze/gographviz" + "encoding/hex" +) + +type visualizer struct { + // Graph used for visualisation. + G *graph.Graph + // Vertexes which should be highlighted. + HighlightedNodes []graph.Vertex + // Edges which should be highlighted. + HighlightedEdges []graph.Edge + // Configuration parameters used for visualisation. + Config *VisualizerConfig + // Function applied to node to obtain its label + ApplyToNode func(graph.Vertex) string + // Function applied to edge to obtain its label + ApplyToEdge func(*graph.ChannelInfo) string + // Prefix used for creating shortcuts. + pt prefix_tree.PrefixTree + graphviz *gographviz.Graph +} + +// New creates new visualiser. +func New(g *graph.Graph, highlightedNodes []graph.Vertex, + highlightedEdges []graph.Edge, config *VisualizerConfig) *visualizer { + if config == nil { + config = &DefaultVisualizerConfig + } + return &visualizer{ + G: g, + HighlightedNodes: highlightedNodes, + HighlightedEdges: highlightedEdges, + Config: config, + ApplyToNode: func(v graph.Vertex) string { return hex.EncodeToString(v.ToByte()) }, + ApplyToEdge: func(info *graph.ChannelInfo) string { return "nil" }, + pt: prefix_tree.NewPrefixTree(), + graphviz: gographviz.NewGraph(), + } +} + +// EnableShortcut enables/disables shortcuts. +// Shortcut is a small unique string used for labeling. +func (viz *visualizer) EnableShortcut(value bool) { + viz.Config.EnableShortcut = value +} + +// BuildPrefixTree builds prefix tree for nodes. +// It is needed for shortcuts. +func (viz *visualizer) BuildPrefixTree() { + for _, node := range viz.G.GetVertexes() { + id := viz.ApplyToNode(node) + viz.pt.Add(id) + } +} + +// Draw creates graph representation in Graphviz dot language. +func (viz *visualizer) Draw() string { + viz.base() + viz.drawNodes() + viz.drawEdges() + return viz.graphviz.String() +} + +// Base makes initialization. +func (viz *visualizer) base() { + viz.graphviz.SetName(viz.Config.GlobalCfg.Name) + viz.graphviz.SetDir(viz.Config.GlobalCfg.Dir) + viz.graphviz.SetStrict(viz.Config.GlobalCfg.Strict) + // TODO(evg): use viz.Add(...) instead viz.Attrs.Add(...) + viz.graphviz.Attrs.Add("bgcolor", viz.Config.GlobalCfg.BgColor) +} + +func (viz *visualizer) drawNodes() { + for _, node := range viz.G.GetVertexes() { + if viz.isHighlightedNode(node) { + viz.drawNode(node, viz.Config.HighlightedNodeCfg) + } else { + viz.drawNode(node, viz.Config.NodeCfg) + } + } +} + +func (viz *visualizer) drawNode(node graph.Vertex, cfg *NodeConfig) { + id := viz.ApplyToNode(node) + if viz.Config.EnableShortcut { + // TODO(evg): processing errors + id, _ = viz.pt.Shortcut(id) + } + attrs := gographviz.Attrs{ + "shape": cfg.Shape, + "style": cfg.Style, + "fontsize": cfg.FontSize, + "fontcolor": cfg.FontColor, + "color": cfg.Color, + "fillcolor": cfg.FillColor, + } + viz.graphviz.AddNode(viz.Config.GlobalCfg.Name, id, attrs) +} + +func (viz *visualizer) isHighlightedNode(node graph.Vertex) bool { + for _, value := range viz.HighlightedNodes { + if node.String() == value.String() { + return true + } + } + return false +} + +func (viz *visualizer) drawEdges() { + for _, edge := range viz.G.GetUndirectedEdges() { + if viz.isHighlightedEdge(edge) { + viz.drawEdge(edge, viz.Config.HighlightedEdgeCfg) + } else { + viz.drawEdge(edge, viz.Config.EdgeCfg) + } + } +} + +func (viz *visualizer) drawEdge(edge graph.Edge, cfg *EdgeConfig) { + src := viz.ApplyToNode(edge.Src) + tgt := viz.ApplyToNode(edge.Tgt) + if viz.Config.EnableShortcut { + // TODO(evg): processing errors + src, _ = viz.pt.Shortcut(src) + tgt, _ = viz.pt.Shortcut(tgt) + } + attrs := gographviz.Attrs{ + "fontsize": cfg.FontSize, + "fontcolor": cfg.FontColor, + "labeldistance": cfg.Scale, + "dir": cfg.Dir, + "style": cfg.Style, + "color": cfg.Color, + "label": viz.ApplyToEdge(edge.Info), + } + viz.graphviz.AddEdge(src, tgt, true, attrs) +} + +func (viz *visualizer) isHighlightedEdge(edge graph.Edge) bool { + for _, value := range viz.G.GetEdges() { + if edge == value { + return true + } + } + return false +} + +// Run graphviz command line utility (such as neato). +// Used for creation image from textual graph representation. +func Run(utility string, TempFile, ImageFile *os.File) error { + extension := filepath.Ext(ImageFile.Name())[1:] + _, err := exec.Command(utility, "-T"+extension, "-o"+ImageFile.Name(), TempFile.Name()).Output() + return err +} + +// Opens file in a command line open program. +// Used for displaying graphical files. +func Open(ImageFile *os.File) error { + _, err := exec.Command("open", ImageFile.Name()).Output() + return err +} diff --git a/rpcserver.go b/rpcserver.go index 35ee44e5..0a17b1d2 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -12,7 +12,7 @@ import ( "sync" "sync/atomic" - "github.com/BitfuryLightning/tools/rt/graph" + "github.com/lightningnetwork/lnd/routing/rt/graph" "github.com/btcsuite/fastsha256" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lightning-onion" @@ -621,6 +621,16 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) case err := <-errChan: return err case nextPayment := <-payChan: + // Query the routing table for a potential path to the + // destination node. If a path is ultimately + // unavailable, then an error will be returned. + destNode := nextPayment.Dest + targetVertex := graph.NewVertex(destNode) + path, err := r.server.routingMgr.FindPath(targetVertex) + if err != nil { + return err + } + rpcsLog.Tracef("[sendpayment] selected route: %v", path) // If we're in debug HTLC mode, then all outgoing // HTLC's will pay to the same debug rHash. Otherwise, // we pay to the rHash specified within the RPC @@ -631,17 +641,14 @@ func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) } else { copy(rHash[:], nextPayment.PaymentHash) } - // Construct and HTLC packet which a payment route (if // one is found) to the destination using a Sphinx // onoin packet to encode the route. - dest := hex.EncodeToString(nextPayment.Dest) - htlcPkt, err := r.constructPaymentRoute(dest, + htlcPkt, err := r.constructPaymentRoute([]byte(nextPayment.Dest), nextPayment.Amt, rHash) if err != nil { return err } - // We launch a new goroutine to execute the current // payment so we can continue to serve requests while // this payment is being dispatiched. @@ -695,7 +702,7 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context, // Construct and HTLC packet which a payment route (if // one is found) to the destination using a Sphinx // onoin packet to encode the route. - htlcPkt, err := r.constructPaymentRoute(nextPayment.DestString, + htlcPkt, err := r.constructPaymentRoute([]byte(nextPayment.DestString), nextPayment.Amt, rHash) if err != nil { return nil, err @@ -714,7 +721,7 @@ func (r *rpcServer) SendPaymentSync(ctx context.Context, // encapsulates a Sphinx onion packet that encodes the end-to-end route any // payment instructions necessary to complete an HTLC. If a route is unable to // be located, then an error is returned indicating as much. -func (r *rpcServer) constructPaymentRoute(destPubkey string, amt int64, +func (r *rpcServer) constructPaymentRoute(destPubkey []byte, amt int64, rHash [32]byte) (*htlcPacket, error) { const queryTimeout = time.Duration(time.Second * 10) @@ -722,9 +729,8 @@ func (r *rpcServer) constructPaymentRoute(destPubkey string, amt int64, // Query the routing table for a potential path to the destination // node. If a path is ultimately unavailable, then an error will be // returned. - targetVertex := graph.NewID(destPubkey) - path, err := r.server.routingMgr.FindPath(targetVertex, - queryTimeout) + targetVertex := graph.NewVertex(destPubkey) + path, err := r.server.routingMgr.FindPath(targetVertex) if err != nil { return nil, err } @@ -747,10 +753,7 @@ func (r *rpcServer) constructPaymentRoute(destPubkey string, amt int64, OnionBlob: sphinxPacket, } - firstHopPub, err := hex.DecodeString(path[1].String()) - if err != nil { - return nil, err - } + firstHopPub := path[1].ToByte() destInterface := wire.ShaHash(fastsha256.Sum256(firstHopPub)) return &htlcPacket{ @@ -763,17 +766,14 @@ func (r *rpcServer) constructPaymentRoute(destPubkey string, amt int64, // the onion route specified by the passed list of graph vertexes. The blob // returned from this function can immediately be included within an HTLC add // packet to be sent to the first hop within the route. -func generateSphinxPacket(vertexes []graph.ID, paymentHash []byte) ([]byte, error) { +func generateSphinxPacket(vertexes []graph.Vertex, paymentHash []byte) ([]byte, error) { // First convert all the vertexs from the routing table to in-memory // public key objects. These objects are necessary in order to perform // the series of ECDH operations required to construct the Sphinx // packet below. route := make([]*btcec.PublicKey, len(vertexes)) for i, vertex := range vertexes { - vertexBytes, err := hex.DecodeString(vertex.String()) - if err != nil { - return nil, err - } + vertexBytes := vertex.ToByte() pub, err := btcec.ParsePubKey(vertexBytes, btcec.S256()) if err != nil { @@ -1091,11 +1091,11 @@ func (r *rpcServer) ShowRoutingTable(ctx context.Context, for _, channel := range rtCopy.AllChannels() { channels = append(channels, &lnrpc.RoutingTableLink{ - Id1: channel.Id1.String(), - Id2: channel.Id2.String(), - Outpoint: channel.EdgeID.String(), - Capacity: channel.Info.Capacity(), - Weight: channel.Info.Weight(), + Id1: hex.EncodeToString(channel.Src.ToByte()), + Id2: hex.EncodeToString(channel.Tgt.ToByte()), + Outpoint: channel.Id.String(), + Capacity: channel.Info.Cpt, + Weight: channel.Info.Wgt, }, ) } diff --git a/server.go b/server.go index f4a2c510..ce853bcc 100644 --- a/server.go +++ b/server.go @@ -1,7 +1,6 @@ package main import ( - "encoding/hex" "fmt" "net" "sync" @@ -18,8 +17,8 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil" - "github.com/BitfuryLightning/tools/routing" - "github.com/BitfuryLightning/tools/rt/graph" + "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/rt/graph" ) // server is the main server of the Lightning Network Daemon. The server @@ -122,8 +121,36 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier, // Create a new routing manager with ourself as the sole node within // the graph. - selfVertex := hex.EncodeToString(serializedPubKey) - s.routingMgr = routing.NewRoutingManager(graph.NewID(selfVertex), nil) + selfVertex := serializedPubKey + routingMgrConfig := &routing.RoutingConfig{} + routingMgrConfig.SendMessage = func (receiver [33]byte, msg lnwire.Message) error { + receiverID := graph.NewVertex(receiver[:]) + if receiverID == graph.NilVertex { + peerLog.Critical("receiverID == graph.NilVertex") + return fmt.Errorf("receiverID == graph.NilVertex") + } + + var targetPeer *peer + for _, peer := range s.peers { // TODO: threadsafe api + nodePub := peer.addr.IdentityKey.SerializeCompressed() + nodeVertex := graph.NewVertex(nodePub[:]) + + // We found the the target + if receiverID == nodeVertex { + targetPeer = peer + break + } + } + + if targetPeer != nil { + targetPeer.queueMsg(msg, nil) + } else { + srvrLog.Errorf("Can't find peer to send message %v", + receiverID) + } + return nil + } + s.routingMgr = routing.NewRoutingManager(graph.NewVertex(selfVertex), routingMgrConfig) s.htlcSwitch = newHtlcSwitch(serializedPubKey, s.routingMgr) s.rpcServer = newRpcServer(s) @@ -308,32 +335,6 @@ out: case *openChanReq: s.handleOpenChanReq(msg) } - case msg := <-s.routingMgr.ChOut: - msg1 := msg.(*routing.RoutingMessage) - if msg1.ReceiverID == nil { - peerLog.Critical("msg1.GetReceiverID() == nil") - continue - } - receiverID := msg1.ReceiverID.String() - - var targetPeer *peer - for _, peer := range s.peers { // TODO: threadsafe api - nodePub := peer.addr.IdentityKey.SerializeCompressed() - idStr := hex.EncodeToString(nodePub) - - // We found the the target - if receiverID == idStr { - targetPeer = peer - break - } - } - - if targetPeer != nil { - targetPeer.queueMsg(msg1.Msg, nil) - } else { - srvrLog.Errorf("Can't find peer to send message %v", - receiverID) - } case <-s.quit: break out }