diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index e9b4ebd07..b6e0c12df 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 10e462e10..556692291 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 d61acf0ef..a5ced703b 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 763b240f5..8daff08cc 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 b176e5926..0e080eed1 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 69d8dc370..48302f681 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 000000000..361733290 --- /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 000000000..848a5b696 --- /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 000000000..3d7c44a72 --- /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 000000000..42b59f9f4 --- /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 000000000..81eba92c3 --- /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 000000000..66a0360dd --- /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 000000000..68ba8d9c6 --- /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 000000000..1b9e09f86 --- /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 000000000..a3ede9fb2 --- /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 000000000..6d3475185 --- /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 000000000..65c87b6dd --- /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 000000000..57d565837 --- /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 000000000..87753068f --- /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 000000000..78c284330 --- /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 000000000..632058a5e --- /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 000000000..73b9dc6a2 --- /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 000000000..390eeafc7 --- /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 35ee44e56..0a17b1d20 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 f4a2c5101..ce853bcc1 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 }