diff --git a/routing/errors.go b/routing/errors.go new file mode 100644 index 000000000..0dcf2c4fc --- /dev/null +++ b/routing/errors.go @@ -0,0 +1,23 @@ +package routing + +import "errors" + +var ( + // ErrNoPathFound is returned when a path to the target destination + // does not exist in the graph. + ErrNoPathFound = errors.New("unable to find a path to " + + "destination") + + // ErrInsufficientCapacity is returned when a path if found, yet the + // capacity of one of the channels in the path is insufficient to carry + // the payment. + ErrInsufficientCapacity = errors.New("channel graph has " + + "insufficient capacity for the payment") + + // ErrMaxHopsExceeded is returned when a candidate path is found, but + // the length of that path exceeds HopLimit. + ErrMaxHopsExceeded = errors.New("potential path has too many hops") + + // ErrTargetNotInNetwork is returned when a + ErrTargetNotInNetwork = errors.New("target not found") +) diff --git a/routing/graph.go b/routing/graph.go new file mode 100644 index 000000000..f3dfa121d --- /dev/null +++ b/routing/graph.go @@ -0,0 +1,4 @@ +package routing + +// TODO(roasbeef): abstract out graph to interface +// * add in-memory version of graph for tests diff --git a/routing/log.go b/routing/log.go new file mode 100644 index 000000000..6e2494576 --- /dev/null +++ b/routing/log.go @@ -0,0 +1,70 @@ +package routing + +import ( + "errors" + "io" + + "github.com/btcsuite/btclog" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until either UseLogger or SetLogWriter are called. +func DisableLog() { + log = btclog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} + +// SetLogWriter uses a specified io.Writer to output package logging info. +// This allows a caller to direct package logging output without needing a +// dependency on seelog. If the caller is also using btclog, UseLogger should +// be used instead. +func SetLogWriter(w io.Writer, level string) error { + if w == nil { + return errors.New("nil writer") + } + + lvl, ok := btclog.LogLevelFromString(level) + if !ok { + return errors.New("invalid log level") + } + + l, err := btclog.NewLoggerFromWriter(w, lvl) + if err != nil { + return err + } + + UseLogger(l) + return nil +} + +// logClosure is used to provide a closure over expensive logging operations +// so don't have to be performed when the logging level doesn't warrant it. +type logClosure func() string + +// String invokes the underlying function and returns the result. +func (c logClosure) String() string { + return c() +} + +// newLogClosure returns a new closure over a function that returns a string +// which itself provides a Stringer interface so that it can be used with the +// logging system. +func newLogClosure(c func() string) logClosure { + return logClosure(c) +} diff --git a/routing/manager.go b/routing/manager.go deleted file mode 100644 index 5e4cd4f43..000000000 --- a/routing/manager.go +++ /dev/null @@ -1,470 +0,0 @@ -// 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" - "github.com/lightningnetwork/lnd/routing/rt/graph" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/roasbeef/btcd/wire" -) - -func channelOperationsFromRT(r *rt.RoutingTable) []lnwire.ChannelOperation { - channels := r.AllChannels() - chOps := make([]lnwire.ChannelOperation, len(channels)) - 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 deleted file mode 100644 index 361733290..000000000 --- a/routing/mocknet.go +++ /dev/null @@ -1,83 +0,0 @@ -// 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/pathfind.go b/routing/pathfind.go index 4fb0f0cb4..2c65f9f10 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -1 +1,348 @@ package routing + +import ( + "math" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcutil" +) + +const ( + // HopLimit is the maximum number hops that is permissible as a route. + // Any potential paths found that lie above this limit will be rejected + // with an error. This value is computed using the current fixed-size + // packet length of the Sphinx construction. + HopLimit = 20 + + // infinity is used as a starting distance in our shortest path search. + infinity = math.MaxFloat64 +) + +// Route represents a path through the channel graph which runs over one or +// more channels in succession. This struct carries all the information +// required to craft the Sphinx onion packet, and send the payment along the +// first hop in the path. A route is only selected as valid if all the channels +// have sufficient capacity to carry the initial payment amount after fees are +// accounted for. +type Route struct { + // TotalTimeLock is the cumulative (final) time lock across the entire + // route. This is the CLTV value that should be extended to the first + // hop in the route. All other hops will decrement the time-lock as + // advertised, leaving enough time for all hops to wait for or present + // the payment pre-image to complete the payment. + TotalTimeLock uint32 + + // TotalFees is the sum of the fees paid at each hop within the final + // route. In the case of a one-hop payment, this value will be zero as + // we don't need to pay a fee it ourself. + TotalFees btcutil.Amount + + // TotalAmount is the total amount of funds required to complete a + // payment over this route. This value includes the cumulative fees at + // each hop. As a result, the HTLC extended to the first-hop in the + // route will need to have at least this many satoshis, otherwise the + // route will fail at an intermediate node due to an insufficient + // amount of fees. + TotalAmount btcutil.Amount + + // Hops contains details concerning the specific forwarding details at + // each hop. + Hops []*Hop +} + +// Hop represents the forwarding details at a particular position within the +// final route. This struct houses the values necessary to create the HTLC +// which will travel along this hop, and also encode the per-hop payload +// included within the Sphinx packet. +type Hop struct { + // Channels is the active payment channel that this hop will travel + // along. + Channel *channeldb.ChannelEdge + + // TimeLockDelta is the delta that this hop will subtract from the HTLC + // before extending it to the next hop in the route. + TimeLockDelta uint16 + + // AmtToForward is the amount that this hop will forward to the next + // hop. This value is less than the value that the incoming HTLC + // carries as a fee will be subtracted by the hop. + AmtToForward btcutil.Amount + + // Fee is the total fee that this hop will subtract from the incoming + // payment, this difference nets the hop fees for forwarding the + // payment. + Fee btcutil.Amount +} + +// computeFee computes the fee to forward an HTLC of `amt` satoshis over the +// passed active payment channel. This value is currently computed as specified +// in BOLT07, but will likely change in the near future. +func computeFee(amt btcutil.Amount, edge *channeldb.ChannelEdge) btcutil.Amount { + return edge.FeeBaseMSat + (amt*edge.FeeProportionalMillionths)/1000000 +} + +// newRoute returns a fully valid route between the source and target that's +// capable of supporting a payment of `amtToSend` after fees are fully +// computed. IF the route is too long, or the selected path cannot support the +// fully payment including fees, then a non-nil error is returned. prevHop maps +// a vertex to the channel required to get to it. +func newRoute(amtToSend btcutil.Amount, source, target vertex, + prevHop map[vertex]edgeWithPrev) (*Route, error) { + + // As an initial sanity check, the potential route is immediately + // invalidate if it spans more than 20 hops. The current Sphinx (onion + // routing) implementation can only encode up to 20 hops as the entire + // packet is fixed size. If this route is more than 20 hops, then it's + // invalid. + if len(prevHop) > HopLimit { + return nil, ErrMaxHopsExceeded + } + + // If the potential route if below the max hop limit, then we'll use + // the prevHop map to unravel the path. We end up with a list of edges + // in the reverse direction which we'll use to properly calculate the + // timelock and fee values. + pathEdges := make([]*channeldb.ChannelEdge, 0, len(prevHop)) + prev := target + for prev != source { // TODO(roasbeef): assumes no cycles + // Add the current hop to the limit of path edges then walk + // backwards from this hop via the prev pointer for this hop + // within the prevHop map. + pathEdges = append(pathEdges, prevHop[prev].edge) + prev = newVertex(prevHop[prev].prevNode) + } + + route := &Route{ + Hops: make([]*Hop, len(pathEdges)), + } + + // The running amount is the total amount of satoshis required at this + // point in the route. We start this value at the amount we want to + // send to the destination. This value will then get successively + // larger as we compute the fees going backwards. + runningAmt := amtToSend + pathLength := len(pathEdges) + for i, edge := range pathEdges { + // Now we create the hop struct for this point in the route. + // The amount to forward is the running amount, and we compute + // the required fee based on this amount. + nextHop := &Hop{ + Channel: edge, + AmtToForward: runningAmt, + Fee: computeFee(runningAmt, edge), + TimeLockDelta: edge.Expiry, + } + edge.Node.PubKey.Curve = nil + + // As a sanity check, we ensure that the selected channel has + // enough capacity to forward the required amount which + // includes the fee dictated at each hop. + if nextHop.AmtToForward > nextHop.Channel.Capacity { + return nil, ErrInsufficientCapacity + } + + // We don't pay any fees to ourselves on the first-hop channel, + // so we don't tally up the running fee and amount. + if i != len(pathEdges)-1 { + // For a node to forward an HTLC, then following + // inequality most hold true: amt_in - fee >= + // amt_to_forward. Therefore we add the fee this node + // consumes in order to calculate the amount that it + // show be forwarded by the prior node which is the + // next hop in our loop. + runningAmt += nextHop.Fee + + // Next we tally the total fees (thus far) in the + // route, and also accumulate the total timelock in the + // route by adding the node's time lock delta which is + // the amount of blocks it'll subtract from the + // incoming time lock. + route.TotalFees += nextHop.Fee + } else { + nextHop.Fee = 0 + } + + route.TotalTimeLock += uint32(nextHop.TimeLockDelta) + + // Finally, as we're currently talking the route backwards, we + // reverse the index in order to place this hop at the proper + // spot in the forward direction of the route. + route.Hops[pathLength-1-i] = nextHop + } + + // The total amount required for this route will be the value the + // source extends to the first hop in the route. + route.TotalAmount = runningAmt + + return route, nil +} + +// vertex is a simple alias for the serialization of a compressed Bitcoin +// public key. +type vertex [33]byte + +// newVertex returns a new vertex given a public key. +func newVertex(pub *btcec.PublicKey) vertex { + var v vertex + copy(v[:], pub.SerializeCompressed()) + return v +} + +// nodeWithDist is a helper struct that couples the distance from the current +// source to a node with a pointer to the node itself. +type nodeWithDist struct { + dist float64 + node *channeldb.LightningNode +} + +// edgeWithPrev is a helper struct used in path finding that couples an +// directional edge with the node's ID in the opposite direction. +type edgeWithPrev struct { + edge *channeldb.ChannelEdge + prevNode *btcec.PublicKey +} + +// edgeWeight computes the weight of an edge. This value is used when searching +// for the shortest path within the channel graph between two nodes. Currently +// this is just 1 + the cltv delta value required at this hop, this value +// should be tuned with experimental and empirical data. +// +// TODO(roasbeef): compute robust weight metric +func edgeWeight(e *channeldb.ChannelEdge) float64 { + return float64(1 + e.Expiry) +} + +// findRoute attempts to find a path from the source node within the +// ChannelGraph to the target node that's capable of supporting a payment of +// `amt` value. The current approach is used a multiple pass path finding +// algorithm. First we employ a modified version of Dijkstra's algorithm to +// find a potential set of shortest paths, the distance metric is related to +// the time-lock+fee along the route. Once we have a set of candidate routes, +// we calculate the required fee and time lock values running backwards along +// the route. The route that's selected is the one with the lowest total fee. +// +// TODO(roasbeef): make member, add caching +// * add k-path +func findRoute(graph *channeldb.ChannelGraph, target *btcec.PublicKey, + amt btcutil.Amount) (*Route, error) { + + // First initialize empty list of all the node that we've yet to + // visited. + // TODO(roasbeef): make into incremental fibonacci heap rather than + // loading all into memory. + var unvisited []*channeldb.LightningNode + + // For each node/vertex the graph we create an entry in the distance + // map for the node set with a distance of "infinity". We also mark + // add the node to our set of unvisited nodes. + distance := make(map[vertex]nodeWithDist) + if err := graph.ForEachNode(func(node *channeldb.LightningNode) error { + // TODO(roasbeef): with larger graph can just use disk seeks + // with a visited map + distance[newVertex(node.PubKey)] = nodeWithDist{ + dist: infinity, + node: node, + } + + unvisited = append(unvisited, node) + return nil + }); err != nil { + return nil, err + } + + // Next we obtain the source node from the graph, and initialize it + // with a distance of 0. This indicates our starting point in the graph + // traversal. + sourceNode, err := graph.SourceNode() + if err != nil { + return nil, err + } + sourceVertex := newVertex(sourceNode.PubKey) + distance[sourceVertex] = nodeWithDist{ + dist: 0, + node: sourceNode, + } + + // We'll use this map as a series of "previous" hop pointers. So to get + // to `vertex` we'll take the edge that it's mapped to within `prev`. + prev := make(map[vertex]edgeWithPrev) + + for len(unvisited) != 0 { + var bestNode *channeldb.LightningNode + smallestDist := infinity + + // First we examine our list of unvisited nodes, for the most + // optimal vertex to examine next. + for i, node := range unvisited { + // The "best" node to visit next is node with the + // smallest distance from the source of all the + // unvisited nodes. + v := newVertex(node.PubKey) + if nodeInfo := distance[v]; nodeInfo.dist < smallestDist { + smallestDist = nodeInfo.dist + bestNode = nodeInfo.node + + // Since we're going to visit this node, we can + // remove it from the set of unvisited nodes. + unvisited[i] = unvisited[len(unvisited)-1] + unvisited[len(unvisited)-1] = nil // Avoid GC leak. + unvisited = unvisited[:len(unvisited)-1] + + break + } + } + + // If we've reached our target, then we're done here and can + // exit the graph traversal early. + if bestNode.PubKey.IsEqual(target) { + break + } + + // Now that we've found the next potential step to take we'll + // examine all the outgoing edge (channels) from this node to + // further our graph traversal. + pivot := newVertex(bestNode.PubKey) + err := bestNode.ForEachChannel(nil, func(edge *channeldb.ChannelEdge) error { + // Compute the tentative distance to this new + // channel/edge which is the distance to our current + // pivot node plus the weight of this edge. + tempDist := distance[pivot].dist + edgeWeight(edge) + + // If this new tentative distance is better than the + // current best known distance to this node, then we + // record the new better distance, and also populate + // our "next hop" map with this edge. + // TODO(roasbeef): add capacity to relaxation criteria? + // * also add min payment? + v := newVertex(edge.Node.PubKey) + if tempDist < distance[v].dist { + // TODO(roasbeef): unconditionally add for all + // paths + distance[v] = nodeWithDist{ + dist: tempDist, + node: edge.Node, + } + prev[v] = edgeWithPrev{ + edge: edge, + prevNode: bestNode.PubKey, + } + } + return nil + }) + if err != nil { + return nil, err + } + } + + // If the target node isn't found in the prev hop map, then a path + // doesn't exist, so we terminate in an error. + if _, ok := prev[newVertex(target)]; !ok { + return nil, ErrNoPathFound + } + + // Otherwise, we construct a new route which calculate the relevant + // total fees and proper time lock values for each hop. + targetVerex := newVertex(target) + return newRoute(amt, sourceVertex, targetVerex, prev) +} diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go new file mode 100644 index 000000000..e567ab899 --- /dev/null +++ b/routing/pathfind_test.go @@ -0,0 +1,395 @@ +package routing + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "io/ioutil" + "net" + "os" + "strings" + "testing" + "time" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" +) + +const ( + // basicGraphFilePath is the file path for a basic graph used within + // the tests. The basic graph consists of 5 nodes with 5 channels + // connecting them. + basicGraphFilePath = "testdata/basic_graph.json" +) + +// testGraph is the struct which coresponds to the JSON format used to encode +// graphs within the files in the testdata directory. +// +// TODO(roasbeef): add test graph auto-generator +type testGraph struct { + Info []string `json:"info"` + Nodes []testNode `json:"nodes"` + Edges []testChan `json:"edges"` +} + +// testNode represents a node within the test graph above. We skip certain +// information such as the node's IP address as that information isn't needed +// for our tests. +type testNode struct { + Source bool `json:"source"` + PubKey string `json:"pubkey"` + Alias string `json:"alias"` +} + +// testChan represents the JSON version of a payment channel. This struct +// matches the Json that's encoded under the "edges" key within the test graph. +type testChan struct { + Node1 string `json:"node_1"` + Node2 string `json:"node_2"` + ChannelID uint64 `json:"channel_id"` + ChannelPoint string `json:"channel_point"` + Flags uint16 `json:"flags"` + Expiry uint16 `json:"expiry"` + MinHTLC int64 `json:"min_htlc"` + FeeBaseMsat int64 `json:"fee_base_msat"` + FeeRate float64 `json:"fee_rate"` + Capacity int64 `json:"capacity"` +} + +// makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing +// purposes. A callback which cleans up the created temporary directories is +// also returned and intended to be executed after the test completes. +func makeTestGraph() (*channeldb.ChannelGraph, func(), error) { + // First, create a temporary directory to be used for the duration of + // this test. + tempDirName, err := ioutil.TempDir("", "channeldb") + if err != nil { + return nil, nil, err + } + + // Next, create channeldb for the first time. + cdb, err := channeldb.Open(tempDirName) + if err != nil { + return nil, nil, err + } + + cleanUp := func() { + cdb.Close() + os.RemoveAll(tempDirName) + } + + return cdb.ChannelGraph(), cleanUp, nil +} + +// aliasMap is a map from a node's alias to its public key. This type is +// provided in order to allow easily look up from the human rememberable alias +// to an exact node's public key. +type aliasMap map[string]*btcec.PublicKey + +// parseTestGraph returns a fully populated ChannelGraph given a path to a JSON +// file which encodes a test graph. +func parseTestGraph(path string) (*channeldb.ChannelGraph, func(), aliasMap, error) { + graphJson, err := ioutil.ReadFile(path) + if err != nil { + return nil, nil, nil, err + } + + // First unmarshal the JSON graph into an instance of the testGraph + // struct. Using the struct tags created above in the struct, the JSON + // will be properly parsed into the struct above. + var g testGraph + if err := json.Unmarshal(graphJson, &g); err != nil { + return nil, nil, nil, err + } + + // We'll use this fake address for the IP address of all the nodes in + // our tests. This value isn't needed for path finding so it doesn't + // need to be unique. + testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888") + if err != nil { + return nil, nil, nil, err + } + + // Next, create a temporary graph database for usage within the test. + graph, cleanUp, err := makeTestGraph() + if err != nil { + return nil, nil, nil, err + } + + aliasMap := make(map[string]*btcec.PublicKey) + var source *channeldb.LightningNode + + // First we insert all the nodes within the graph as vertexes. + for _, node := range g.Nodes { + pubBytes, err := hex.DecodeString(node.PubKey) + if err != nil { + return nil, nil, nil, err + } + pub, err := btcec.ParsePubKey(pubBytes, btcec.S256()) + if err != nil { + return nil, nil, nil, err + } + + dbNode := &channeldb.LightningNode{ + LastUpdate: time.Now(), + Address: testAddr, + PubKey: pub, + Alias: node.Alias, + } + + // We require all aliases within the graph to be unique for our + // tests. + if _, ok := aliasMap[node.Alias]; ok { + return nil, nil, nil, errors.New("aliases for nodes " + + "must be unique!") + } else { + // If the alias is unique, then add the node to the + // alias map for easy lookup. + aliasMap[node.Alias] = pub + } + + // If the node is tagged as the source, then we create a + // pointer to is so we can mark the source in the graph + // properly. + if node.Source { + // If we come across a node that's marked as the + // source, and we've already set the source in a prior + // iteration, then the JSON has an error as only ONE + // node can be the source in the graph. + if source != nil { + return nil, nil, nil, errors.New("JSON is invalid " + + "multiple nodes are tagged as the source") + } + + source = dbNode + } + + // With the node fully parsed, add it as a vertex within the + // graph. + if err := graph.AddLightningNode(dbNode); err != nil { + return nil, nil, nil, err + } + } + + // Set the selected source node + if err := graph.SetSourceNode(source); err != nil { + return nil, nil, nil, err + } + + // With all the vertexes inserted, we can now insert the edges into the + // test graph. + for _, edge := range g.Edges { + node1Bytes, err := hex.DecodeString(edge.Node1) + if err != nil { + return nil, nil, nil, err + } + node1Pub, err := btcec.ParsePubKey(node1Bytes, btcec.S256()) + if err != nil { + return nil, nil, nil, err + } + + node2Bytes, err := hex.DecodeString(edge.Node2) + if err != nil { + return nil, nil, nil, err + } + node2Pub, err := btcec.ParsePubKey(node2Bytes, btcec.S256()) + if err != nil { + return nil, nil, nil, err + } + + fundingTXID := strings.Split(edge.ChannelPoint, ":")[0] + txidBytes, err := wire.NewShaHashFromStr(fundingTXID) + if err != nil { + return nil, nil, nil, err + } + fundingPoint := wire.OutPoint{ + Hash: *txidBytes, + Index: 0, + } + + // We first insert the existence of the edge between the two + // nodes. + if err := graph.AddChannelEdge(node1Pub, node2Pub, &fundingPoint, + edge.ChannelID); err != nil { + return nil, nil, nil, err + } + + edge := &channeldb.ChannelEdge{ + ChannelID: edge.ChannelID, + ChannelPoint: fundingPoint, + LastUpdate: time.Now(), + Expiry: edge.Expiry, + MinHTLC: btcutil.Amount(edge.MinHTLC), + FeeBaseMSat: btcutil.Amount(edge.FeeBaseMsat), + FeeProportionalMillionths: btcutil.Amount(edge.FeeRate), + Capacity: btcutil.Amount(edge.Capacity), + } + + // As the graph itself is directed, we need to insert two edges + // into the graph: one from node1->node2 and one from + // node2->node1. A flag of 0 indicates this is the routing + // policy for the first node, and a flag of 1 indicates its the + // information for the second node. + edge.Flags = 0 + if err := graph.UpdateEdgeInfo(edge); err != nil { + return nil, nil, nil, err + } + + edge.Flags = 1 + if err := graph.UpdateEdgeInfo(edge); err != nil { + return nil, nil, nil, err + } + } + + return graph, cleanUp, aliasMap, nil +} + +func TestBasicGraphPathFinding(t *testing.T) { + graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath) + defer cleanUp() + if err != nil { + t.Fatalf("unable to create graph: %v", err) + } + + // With the test graph loaded, we'll test some basic path finding using + // the pre-generated graph. Consult the testdata/basic_graph.json file + // to follow along with the assumptions we'll use to test the path + // finding. + + const paymentAmt = btcutil.Amount(100) + target := aliases["sophon"] + route, err := findRoute(graph, target, paymentAmt) + if err != nil { + t.Fatalf("unable to find route: %v", err) + } + + // The length of the route selected should be of exactly length two. + if len(route.Hops) != 2 { + t.Fatalf("route is of incorrect length, expected %v got %v", 2, + len(route.Hops)) + } + + // As each hop only decrements a single block from the time-lock, the + // total time lock value should be two. + if route.TotalTimeLock != 2 { + t.Fatalf("expected time lock of %v, instead have %v", 2, + route.TotalTimeLock) + } + + // The first hop in the path should be an edge from roasbeef to goku. + if !route.Hops[0].Channel.Node.PubKey.IsEqual(aliases["songoku"]) { + t.Fatalf("first hop should be goku, is instead: %v", + route.Hops[0].Channel.Node.Alias) + } + + // WE shoul + + // The second hop should be from goku to sophon. + if !route.Hops[1].Channel.Node.PubKey.IsEqual(aliases["sophon"]) { + t.Fatalf("second hop should be sophon, is instead: %v", + route.Hops[0].Channel.Node.Alias) + } + + // Next, attempt to query for a path to Luo Ji for 100 satoshis, there + // exist two possible paths in the graph, but the shorter (1 hop) path + // should be selected. + target = aliases["luoji"] + route, err = findRoute(graph, target, paymentAmt) + if err != nil { + t.Fatalf("unable to find route: %v", err) + } + + // The length of the path should be exactly one hop as it's the + // "shortest" known path in the graph. + if len(route.Hops) != 1 { + t.Fatalf("shortest path not selected, should be of length 1, "+ + "is instead: %v", len(route.Hops)) + } + + // As we have a direct path, the total time lock value should be + // exactly one. + if route.TotalTimeLock != 1 { + t.Fatalf("expected time lock of %v, instead have %v", 1, + route.TotalTimeLock) + } + + // Additionally, since this is a single-hop payment, we shouldn't have + // to pay any fees in total, so the total amount should be the payment + // amount. + if route.TotalAmount != paymentAmt { + t.Fatalf("incorrect total amount, expected %v got %v", + paymentAmt, route.TotalAmount) + } +} + +func TestNewRoutePathTooLong(t *testing.T) { + // Ensure that potential paths which are over the maximum hop-limit are + // rejected. + var v vertex + fakePath := make(map[vertex]edgeWithPrev) + for i := 0; i < 21; i++ { + copy(v[:], bytes.Repeat([]byte{byte(2 + i)}, 33)) + fakePath[v] = edgeWithPrev{} + } + + _, err := newRoute(10, v, v, fakePath) + if err != ErrMaxHopsExceeded { + t.Fatalf("path should have been rejected") + } +} + +func TestPathNotAvailable(t *testing.T) { + graph, cleanUp, _, err := parseTestGraph(basicGraphFilePath) + defer cleanUp() + if err != nil { + t.Fatalf("unable to create graph: %v", err) + } + + // With the test graph loaded, we'll test that queries for target that + // are either unreachable within the graph, or unknown result in an + // error. + unknownNodeStr := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281536fd7011ec59c882" + unknownNodeBytes, err := hex.DecodeString(unknownNodeStr) + if err != nil { + t.Fatalf("unable to parse bytes: %v", err) + } + unknownNode, err := btcec.ParsePubKey(unknownNodeBytes, btcec.S256()) + if err != nil { + t.Fatalf("unable to parse pubkey: %v", err) + } + + if _, err := findRoute(graph, unknownNode, 100); err != ErrNoPathFound { + t.Fatalf("path shouldn't have been found: %v", err) + } +} + +func TestPathInsufficientCapacity(t *testing.T) { + graph, cleanUp, aliases, err := parseTestGraph(basicGraphFilePath) + defer cleanUp() + if err != nil { + t.Fatalf("unable to create graph: %v", err) + } + + // Next, test that attempting to find a path in which the current + // channel graph cannot support due to insufficient capacity triggers + // an error. + + // To test his we'll attempt to make a payment of 1 BTC, or 100 million + // satoshis. The largest channel in the basic graph is of size 100k + // satoshis, so we shouldn't be able to find a path to sophon even + // though we have a 2-hop link. + target := aliases["sophon"] + + const payAmt = btcutil.SatoshiPerBitcoin + _, err = findRoute(graph, target, payAmt) + if err != ErrInsufficientCapacity { + t.Fatalf("graph shouldn't be able to support payment: %v", err) + } +} + +func TestPathInsufficientCapacityWithFee(t *testing.T) { + // TODO(roasbeef): encode live graph to json +} diff --git a/routing/router.go b/routing/router.go new file mode 100644 index 000000000..93cbc1fe2 --- /dev/null +++ b/routing/router.go @@ -0,0 +1,850 @@ +package routing + +import ( + "encoding/hex" + "sync" + "sync/atomic" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" +) + +// FeeSchema is the set fee configuration for a Lighting Node on the network. +// Using the coefficients described within he schema, the required fee to +// forward outgoing payments can be derived. +// +// TODO(roasbeef): should be in switch instead? +type FeeSchema struct { + // TODO(rosbeef): all these should be in msat instead + + // BaseFee is the base amount that will be chained for ANY payment + // forwarded. + BaseFee btcutil.Amount + + // FeeRate is the rate that will be charged for forwarding payments. + // The fee rate has a granularity of 1/1000 th of a mili-satoshi, or a + // millionth of a satoshi. + FeeRate btcutil.Amount +} + +// Config defines the configuration for the ChannelRouter. ALL elements within +// the configuration MUST be non-nil for the ChannelRouter to carry out its +// duties. +type Config struct { + // Graph is the channel graph that the ChannelRouter will use to gather + // metrics from and also to carry out path finding queries. + // TODO(roasbeef): make into an interface + Graph *channeldb.ChannelGraph + + // Chain is the router's source to the most up-to-date blockchain data. + // All incoming advertised channels will be checked against the chain + // to ensure that the channels advertised are still open. + // TODO(roasbeef): remove after discovery service is in + Chain lnwallet.BlockChainIO + + // Notifier is an instance of the ChainNotifier that the router uses to + // received notifications of incoming blocks. With each new incoming + // block found, the router may be able to partially prune the channel + // graph as channels may have been pruned. + // TODO(roasbeef): could possibly just replace this with an epoch + // channel. + Notifier chainntnfs.ChainNotifier + + // FeeSchema is the set fee schema that will be announced on to the + // network. + // TODO(roasbeef): should either be in discovery or switch + FeeSchema *FeeSchema + + // Broadcast is a function that is used to broadcast a particular set + // of messages to all peers that the daemon is connected to. If + // supplied, the exclude parameter indicates that the target peer should + // be excluded from the broadcast. + Broadcast func(exclude *btcec.PublicKey, msg ...lnwire.Message) error + + // SendMessages is a function which allows the ChannelRouter to send a + // set of messages to a particular peer identified by the target public + // key. + SendMessages func(target *btcec.PublicKey, msg ...lnwire.Message) error + + // TODO(roasbeef): need a SendToSwitch func + // * possibly lift switch into package? + // * +} + +// ChannelRouter is the layer 3 router within the Lightning stack. Below the +// ChannelRouter is the HtlcSwitch, and below that is the Bitcoin blockchain +// itself. The primary role of the ChannelRouter is to respond to queries for +// potential routes that can support a payment amount, and also general graph +// reachability questions. The router will prune the channel graph +// automatically as new blocks are discovered which spend certain known funding +// outpoints, thereby closing their respective channels. Additionally, it's the +// duty of the router to sync up newly connected peers with the latest state of +// the channel graph. +type ChannelRouter struct { + sync.RWMutex + + cfg *Config + + self *channeldb.LightningNode + + // TODO(roasbeef): make LRU, invalidate upon new block connect + shortestPathCache map[[33]byte][]*Route + nodeCache map[[33]byte]*channeldb.LightningNode + edgeCache map[wire.OutPoint]*channeldb.ChannelEdge + + newBlocks chan *chainntnfs.BlockEpoch + + networkMsgs chan *routingMsg + + syncRequests chan *syncRequest + + fakeSig *btcec.Signature + + started uint32 + stopped uint32 + quit chan struct{} + wg sync.WaitGroup +} + +// New creates a new instance of the ChannelRouter with the specified +// configuration parameters. As part of initialization, if the router detects +// that the channel graph isn't fully in sync with the latest UTXO (since the +// channel graph is a subset of the UTXO set) set, then the router will proceed +// to fully sync to the latest state of the UTXO set. +func New(cfg Config) (*ChannelRouter, error) { + // TODO(roasbeef): remove this place holder after sigs are properly + // stored in the graph. + s := "30450221008ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa" + + "1bf0314f882d70220299105481d63e0f4bc2a88121167221b6700d72a0e" + + "ad154c03be696a292d24ae" + fakeSigHex, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + fakeSig, err := btcec.ParseSignature(fakeSigHex, btcec.S256()) + if err != nil { + return nil, err + } + + self, err := cfg.Graph.SourceNode() + if err != nil { + return nil, err + } + + return &ChannelRouter{ + cfg: &cfg, + self: self, + fakeSig: fakeSig, + networkMsgs: make(chan *routingMsg), + syncRequests: make(chan *syncRequest), + quit: make(chan struct{}), + }, nil +} + +// Start launches all the goroutines the ChannelRouter requires to carry out +// its duties. If the router has already been started, then this method is a +// noop. +func (r *ChannelRouter) Start() error { + if !atomic.CompareAndSwapUint32(&r.started, 0, 1) { + return nil + } + + log.Tracef("Channel Router starting") + + // First we register for new notifications of newly discovered blocks. + // We do this immediately so we'll later be able to consume any/all + // blocks which were discovered as we prune the channel graph using a + // snapshot of the chain state. + blockEpochs, err := r.cfg.Notifier.RegisterBlockEpochNtfn() + if err != nil { + return err + } + r.newBlocks = blockEpochs.Epochs + + // Before we begin normal operation of the router, we first need to + // synchronize the channel graph to the latest state of the UTXO set. + if err := r.syncGraphWithChain(); err != nil { + return err + } + + r.wg.Add(1) + go r.networkHandler() + + return nil +} + +// Stop signals the ChannelRouter to gracefully halt all routines. This method +// will *block* until all goroutines have excited. If the channel router has +// already stopped then this method will return immediately. +func (r *ChannelRouter) Stop() error { + if !atomic.CompareAndSwapUint32(&r.stopped, 0, 1) { + return nil + } + + log.Infof("Channel Router shutting down") + + close(r.quit) + r.wg.Wait() + + return nil +} + +// syncGraphWithChain attempts to synchronize the current channel graph with +// the latest UTXO set state. This process involves pruning from the channel +// graph any channels which have been closed by spending their funding output +// since we've been down. +func (r *ChannelRouter) syncGraphWithChain() error { + // First, we'll need to check to see if we're already in sync with the + // latest state of the UTXO set. + bestHash, bestHeight, err := r.cfg.Chain.GetBestBlock() + if err != nil { + return err + } + pruneHash, pruneHeight, err := r.cfg.Graph.PruneTip() + if err != nil { + switch { + // If the graph has never been pruned, or hasn't fully been + // created yet, then we don't treat this as an explicit error. + case err == channeldb.ErrGraphNeverPruned: + case err == channeldb.ErrGraphNotFound: + default: + return err + } + } + + log.Infof("Prune tip for Channel Graph: height=%v, hash=%v", pruneHeight, + pruneHash) + + switch { + + // If the graph has never been pruned, then we can exit early as this + // entails it's being created for the first time and hasn't seen any + // block or created channels. + case pruneHeight == 0 || pruneHash == nil: + return nil + + // If the block hashes and heights match exactly, then we don't need to + // prune the channel graph as we're already fully in sync. + case bestHash.IsEqual(pruneHash) && uint32(bestHeight) == pruneHeight: + return nil + } + + log.Infof("Syncing channel graph from height=%v (hash=%v) to height=%v "+ + "(hash=%v)", pruneHeight, pruneHash, bestHeight, bestHash) + + // If we're not yet caught up, then we'll walk forward in the chain in + // the chain pruning the channel graph with each new block in the chain + // that hasn't yet been consumed by the channel graph. + var numChansClosed uint32 + for nextHeight := pruneHeight + 1; nextHeight <= uint32(bestHeight); nextHeight++ { + // Using the next height, fetch the next block to use in our + // incremental graph pruning routine. + nextHash, err := r.cfg.Chain.GetBlockHash(int64(nextHeight)) + if err != nil { + return err + } + nextBlock, err := r.cfg.Chain.GetBlock(nextHash) + if err != nil { + return err + } + + // We're only interested in all prior outputs that've been + // spent in the block, so collate all the referenced previous + // outpoints within each tx and input. + var spentOutputs []*wire.OutPoint + for _, tx := range nextBlock.Transactions { + for _, txIn := range tx.TxIn { + spentOutputs = append(spentOutputs, + &txIn.PreviousOutPoint) + } + } + + // With the spent outputs gathered, attempt to prune the + // channel graph, also passing in the hash+height of the block + // being pruned so the prune tip can be updated. + numClosed, err := r.cfg.Graph.PruneGraph(spentOutputs, nextHash, + nextHeight) + if err != nil { + return err + } + + log.Infof("Block %v (height=%v) closed %v channels", + nextHash, nextHeight, numClosed) + + numChansClosed += numClosed + } + + log.Infof("Graph pruning complete: %v channels we're closed since "+ + "height %v", numChansClosed, pruneHeight) + + return nil +} + +// networkHandler is the primary goroutine for the ChannelRouter. The roles of +// this goroutine include answering queries related to the state of the +// network, syncing up newly connected peers, and also periodically +// broadcasting our latest state to all connected peers. +// +// NOTE: This MUST be run as a goroutine. +func (r *ChannelRouter) networkHandler() { + defer r.wg.Done() + + var announcementBatch []lnwire.Message + + trickleTimer := time.NewTicker(time.Millisecond * 300) + defer trickleTimer.Stop() + + for { + select { + // A new fully validated network message has just arrived. As a + // result we'll modify the channel graph accordingly depending + // on the exact type of the message. + case netMsg := <-r.networkMsgs: + // TODO(roasbeef): this loop would mostly be moved to + // the discovery service + + // Process the network announcement to determine if this + // is either a new announcement from our PoV or an + // update to a prior vertex/edge we previously + // accepted. + accepted := r.processNetworkAnnouncement(netMsg.msg) + + // If the update was accepted, then add it to our next + // announcement batch to be broadcast once the trickle + // timer ticks gain. + if accepted { + announcementBatch = append(announcementBatch, netMsg.msg) + } + + // TODO(roasbeef): remove all unconnected vertexes + // after N blocks pass with no corresponding + // announcements. + + // A new block has arrived, so we can prune the channel graph + // of any channels which were closed in the block. + case newBlock, ok := <-r.newBlocks: + // If the channel has been closed, then this indicates + // the daemon is shutting down, so we exit ourselves. + if !ok { + return + } + + block, err := r.cfg.Chain.GetBlock(newBlock.Hash) + if err != nil { + log.Errorf("unable to get block: %v", err) + continue + } + + log.Infof("Pruning channel graph using block %v (height=%v)", + newBlock.Hash, newBlock.Height) + + // We're only interested in all prior outputs that've + // been spent in the block, so collate all the + // referenced previous outpoints within each tx and + // input. + var spentOutputs []*wire.OutPoint + for _, tx := range block.Transactions { + for _, txIn := range tx.TxIn { + spentOutputs = append(spentOutputs, + &txIn.PreviousOutPoint) + } + } + + // With the spent outputs gathered, attempt to prune + // the channel graph, also passing in the hash+height + // of the block being pruned so the prune tip can be + // updated. + numClosed, err := r.cfg.Graph.PruneGraph(spentOutputs, + newBlock.Hash, uint32(newBlock.Height)) + if err != nil { + log.Errorf("unable to prune routing table: %v", err) + continue + } + + log.Infof("Block %v (height=%v) closed %v channels", + newBlock.Hash, newBlock.Height, numClosed) + + // The trickle timer has ticked, which indicates we should + // flush to the network the pending batch of new announcements + // we've received since the last trickle tick. + case <-trickleTimer.C: + // If the current announcement batch is nil, then we + // have no further work here. + if len(announcementBatch) == 0 { + continue + } + + log.Infof("Broadcasting batch of %v new announcements", + len(announcementBatch)) + + // If we have new things to announce then broadcast + // then to all our immediately connected peers. + err := r.cfg.Broadcast(nil, announcementBatch...) + if err != nil { + log.Errorf("unable to send batch announcement: %v", err) + continue + } + + // If we we're able to broadcast the current batch + // successfully, then we reset the batch for a new + // round of announcements. + announcementBatch = nil + + // We've just received a new request to synchronize a peer with + // our latest graph state. This indicates that a peer has just + // connected for the first time, so for now we dump our entire + // graph and allow them to sift through the (subjectively) new + // information on their own. + case syncReq := <-r.syncRequests: + nodePub := syncReq.node.SerializeCompressed() + log.Infof("Synchronizing channel graph with %x", nodePub) + + if err := r.syncChannelGraph(syncReq); err != nil { + log.Errorf("unable to sync graph state with %x: %v", + nodePub, err) + } + + // The router has been signalled to exit, to we exit our main + // loop so the wait group can be decremented. + case <-r.quit: + return + } + } +} + +// processNetworkAnnouncement processes a new network relate authenticated +// channel or node announcement. If the update didn't affect the internal state +// of the draft due to either being out of date, invalid, or redundant, then +// false is returned. Otherwise, true is returned indicating that the caller +// may want to batch this request to be broadcast to immediate peers during th +// next announcement epoch. +func (r *ChannelRouter) processNetworkAnnouncement(msg lnwire.Message) bool { + switch msg := msg.(type) { + + // A new node announcement has arrived which either presents a new + // node, or a node updating previously advertised information. + case *lnwire.NodeAnnouncement: + // Before proceeding ensure that we aren't already away of this + // node, and if we are then this is a newer update that we + // known of. + lastUpdate, exists, err := r.cfg.Graph.HasLightningNode(msg.NodeID) + if err != nil { + log.Errorf("Unable to query for the existence of node: %v", + err) + return false + } + + // If we've reached this pint then we're aware of th vertex + // being advertised. So we now check if the new message has a + // new time stamp, if not then we won't accept the new data as + // it would override newer data. + msgTimestamp := time.Unix(int64(msg.Timestamp), 0) + if exists && lastUpdate.After(msgTimestamp) || + lastUpdate.Equal(msgTimestamp) { + + log.Debugf("Ignoring outdated announcement for %x", + msg.NodeID.SerializeCompressed()) + return false + } + + node := &channeldb.LightningNode{ + LastUpdate: msgTimestamp, + Address: msg.Address, + PubKey: msg.NodeID, + Alias: msg.Alias.String(), + } + + if err = r.cfg.Graph.AddLightningNode(node); err != nil { + log.Errorf("unable to add node %v: %v", msg.NodeID, err) + return false + } + + log.Infof("Updated vertex data for node=%x", + msg.NodeID.SerializeCompressed()) + + // A new channel announcement has arrived, this indicates the + // *creation* of a new channel within the graph. This only advertises + // the existence of a channel and not yet the routing policies in + // either direction of the channel. + case *lnwire.ChannelAnnouncement: + // Prior to processing the announcement we first check if we + // already know of this channel, if so, then we can exit early. + channelID := msg.ChannelID.ToUint64() + _, _, exists, err := r.cfg.Graph.HasChannelEdge(channelID) + if err != nil && err != channeldb.ErrGraphNoEdgesFound { + log.Errorf("unable to check for edge existence: %v", err) + return false + } else if exists { + log.Debugf("Ignoring announcement for known chan_id=%v", + channelID) + return false + } + + // Before we can add the channel to the channel graph, we need + // to obtain the full funding outpoint that's encoded within + // the channel ID. + fundingPoint, err := r.fetchChanPoint(&msg.ChannelID) + if err != nil { + log.Errorf("unable to fetch chan point for chan_id=%v: %v", + channelID, err) + return false + } + + // Now that we have the funding outpoint of the channel, ensure + // that it hasn't yet been spent. If so, then this channel has + // been closed so we'll ignore it. + if _, err := r.cfg.Chain.GetUtxo(&fundingPoint.Hash, + fundingPoint.Index); err != nil { + log.Errorf("unable to fetch utxo for chan_id=%v: %v", + channelID, err) + return false + } + + // TODO(roasbeef): also add capacity here two instead of on the + // directed edges. + err = r.cfg.Graph.AddChannelEdge(msg.FirstNodeID, + msg.SecondNodeID, fundingPoint, channelID) + if err != nil { + log.Errorf("unable to add channel: %v", err) + return false + } + + log.Infof("New channel discovered! Link "+ + "connects %x and %x with ChannelPoint(%v), chan_id=%v", + msg.FirstNodeID.SerializeCompressed(), + msg.SecondNodeID.SerializeCompressed(), + fundingPoint, channelID) + + // A new authenticated channel update has has arrived, this indicates + // that the directional information for an already known channel has + // been updated. All updates are signed and validated before reaching + // us, so we trust the data to be legitimate. + case *lnwire.ChannelUpdateAnnouncement: + chanID := msg.ChannelID.ToUint64() + edge1Timestamp, edge2Timestamp, _, err := r.cfg.Graph.HasChannelEdge(chanID) + if err != nil && err != channeldb.ErrGraphNoEdgesFound { + log.Errorf("unable to check for edge existence: %v", err) + return false + } + + // As edges are directional edge node has a unique policy for + // the direction of th edge they control. Therefore we first + // check if we already have the most up to date information for + // that edge. If so, then we can exit early. + updateTimestamp := time.Unix(int64(msg.Timestamp), 0) + switch msg.Flags { + + // A flag set of 0 indicates this is an announcement for + // the "first" node in the channel. + case 0: + if edge1Timestamp.After(updateTimestamp) || + edge1Timestamp.Equal(updateTimestamp) { + + log.Debugf("Ignoring announcement (flags=%v) "+ + "for known chan_id=%v", msg.Flags, + chanID) + return false + } + + // Similarly, a flag set of 1 indicates this is an + // announcement for the "second" node in the channel. + case 1: + if edge2Timestamp.After(updateTimestamp) || + edge2Timestamp.Equal(updateTimestamp) { + + log.Debugf("Ignoring announcement (flags=%v) "+ + "for known chan_id=%v", msg.Flags, + chanID) + return false + } + } + + // Before we can update the channel information, we need to get + // the UTXO itself so we can store the proper capacity. + chanPoint, err := r.fetchChanPoint(&msg.ChannelID) + if err != nil { + log.Errorf("unable to fetch chan point: %v", err) + return false + } + utxo, err := r.cfg.Chain.GetUtxo(&chanPoint.Hash, + chanPoint.Index) + if err != nil { + log.Errorf("unable to fetch utxo for chan_id=%v: %v", + chanID, err) + return false + } + + // TODO(roasbeef): should be msat here + chanUpdate := &channeldb.ChannelEdge{ + ChannelID: chanID, + ChannelPoint: *chanPoint, + LastUpdate: updateTimestamp, + Flags: msg.Flags, + Expiry: msg.Expiry, + MinHTLC: btcutil.Amount(msg.HtlcMinimumMstat), + FeeBaseMSat: btcutil.Amount(msg.FeeBaseMstat), + FeeProportionalMillionths: btcutil.Amount(msg.FeeProportionalMillionths), + // TODO(roasbeef): this is a hack, needs to be removed + // after commitment fees are dynamic. + Capacity: btcutil.Amount(utxo.Value) - 5000, + } + + err = r.cfg.Graph.UpdateEdgeInfo(chanUpdate) + if err != nil { + log.Errorf("unable to add channel: %v", err) + return false + } + + log.Infof("New channel update applied: %v", + spew.Sdump(chanUpdate)) + } + + return true +} + +// syncRequest represents a request from an outside sub-system to the wallet to +// sync a new node to the latest graph state. +type syncRequest struct { + node *btcec.PublicKey +} + +// SynchronizeNode sends a message to the ChannelRouter indicating it should +// synchronize routing state with the target node. This method is to be +// utilized when a node connections for the first time to provide it with the +// latest channel graph state. +func (r *ChannelRouter) SynchronizeNode(pub *btcec.PublicKey) { + select { + case r.syncRequests <- &syncRequest{ + node: pub, + }: + case <-r.quit: + return + } +} + +// syncChannelGraph attempts to synchronize the target node in the syncReq to +// the latest channel graph state. In order to accomplish this, (currently) the +// entire graph is read from disk, then serialized to the format defined within +// the current wire protocol. This cache of graph data is then sent directly to +// the target node. +func (r *ChannelRouter) syncChannelGraph(syncReq *syncRequest) error { + targetNode := syncReq.node + + // TODO(roasbeef): need to also store sig data in db + // * will be nice when we switch to pairing sigs would only need one ^_^ + + // We'll collate all the gathered routing messages into a single slice + // containing all the messages to be sent to the target peer. + var announceMessages []lnwire.Message + + // First run through all the vertexes in the graph, retrieving the data + // for the announcement we originally retrieved. + var numNodes uint32 + if err := r.cfg.Graph.ForEachNode(func(node *channeldb.LightningNode) error { + alias, err := lnwire.NewAlias(node.Alias) + if err != nil { + return err + } + + ann := &lnwire.NodeAnnouncement{ + Signature: r.fakeSig, + Timestamp: uint32(node.LastUpdate.Unix()), + Address: node.Address, + NodeID: node.PubKey, + Alias: alias, + } + announceMessages = append(announceMessages, ann) + + numNodes++ + + return nil + }); err != nil { + return err + } + + // With the vertexes gathered, we'll no retrieve the initial + // announcement, as well as the latest channel update announcement for + // both of the directed edges that make up the channel. + // TODO(roasbeef): multi-sig keys should also be stored in DB + var numEdges uint32 + if err := r.cfg.Graph.ForEachChannel(func(e1, e2 *channeldb.ChannelEdge) error { + chanID := lnwire.NewChanIDFromInt(e1.ChannelID) + chanAnn := &lnwire.ChannelAnnouncement{ + FirstNodeSig: r.fakeSig, + SecondNodeSig: r.fakeSig, + ChannelID: chanID, + FirstBitcoinSig: r.fakeSig, + SecondBitcoinSig: r.fakeSig, + FirstNodeID: e1.Node.PubKey, + SecondNodeID: e2.Node.PubKey, + FirstBitcoinKey: e1.Node.PubKey, + SecondBitcoinKey: e2.Node.PubKey, + } + + chanUpdate1 := &lnwire.ChannelUpdateAnnouncement{ + Signature: r.fakeSig, + ChannelID: chanID, + Timestamp: uint32(e1.LastUpdate.Unix()), + Flags: 0, + Expiry: e1.Expiry, + HtlcMinimumMstat: uint32(e1.MinHTLC), + FeeBaseMstat: uint32(e1.FeeBaseMSat), + FeeProportionalMillionths: uint32(e1.FeeProportionalMillionths), + } + chanUpdate2 := &lnwire.ChannelUpdateAnnouncement{ + Signature: r.fakeSig, + ChannelID: chanID, + Timestamp: uint32(e2.LastUpdate.Unix()), + Flags: 1, + Expiry: e2.Expiry, + HtlcMinimumMstat: uint32(e2.MinHTLC), + FeeBaseMstat: uint32(e2.FeeBaseMSat), + FeeProportionalMillionths: uint32(e2.FeeProportionalMillionths), + } + + numEdges++ + + announceMessages = append(announceMessages, chanAnn) + announceMessages = append(announceMessages, chanUpdate1) + announceMessages = append(announceMessages, chanUpdate2) + return nil + }); err != nil && err != channeldb.ErrGraphNoEdgesFound { + log.Errorf("unable to sync edges w/ peer: %v", err) + return err + } + + log.Infof("Syncing channel graph state with %x, sending %v "+ + "nodes and %v edges", targetNode.SerializeCompressed(), + numNodes, numEdges) + + // With all the announcement messages gathered, send them all in a + // single batch to the target peer. + return r.cfg.SendMessages(targetNode, announceMessages...) +} + +// fetchChanPoint retrieves the original outpoint which is encoded within the +// channelID. +func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ChannelID) (*wire.OutPoint, error) { + // First fetch the block hash by the block number encoded, then use + // that hash to fetch the block itself. + blockNum := int64(chanID.BlockHeight) + blockHash, err := r.cfg.Chain.GetBlockHash(blockNum) + if err != nil { + return nil, err + } + fundingBlock, err := r.cfg.Chain.GetBlock(blockHash) + if err != nil { + return nil, err + } + + // TODO(roasbeef): skipping validation here as + // the discovery service should handle full + // validate + + // Finally once we have the block itself, we seek to the targeted + // transaction index to obtain the funding output and txid. + fundingTx := fundingBlock.Transactions[chanID.TxIndex] + return &wire.OutPoint{ + Hash: fundingTx.TxSha(), + Index: uint32(chanID.TxPosition), + }, nil +} + +// routingMsg couples a routing related wire message with the peer that +// originally sent it. +type routingMsg struct { + msg lnwire.Message + peer *btcec.PublicKey +} + +// ProcessRoutingMessags sends a new routing message along with the peer that +// sent the routing message to the ChannelRouter. The announcement will be +// processed then added to a queue for batched tickled announcement to all +// connected peers. +// +// TODO(roasbeef): need to move to discovery package +func (r *ChannelRouter) ProcessRoutingMessage(msg lnwire.Message, src *btcec.PublicKey) { + // TODO(roasbeef): msg wrappers to add a doneChan + + rMsg := &routingMsg{ + msg: msg, + peer: src, + } + + select { + case r.networkMsgs <- rMsg: + case <-r.quit: + return + } +} + +// FindRoute attempts to query the ChannelRouter for the "best" path to a +// particular target destination which is able to send `amt` after factoring in +// channel capacities and cumulative fees along the route. +func (r *ChannelRouter) FindRoute(target *btcec.PublicKey, amt btcutil.Amount) (*Route, error) { + dest := target.SerializeCompressed() + + log.Debugf("Searching for path to %x, sending %v", dest, amt) + + // We can short circuit the routing by opportunistically checking to + // see if the target vertex event exists in the current graph. + if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil { + return nil, err + } else if !exists { + log.Debugf("Target %x is not in known graph", dest) + return nil, ErrTargetNotInNetwork + } + + // TODO(roasbeef): add k-shortest paths + route, err := findRoute(r.cfg.Graph, target, amt) + if err != nil { + log.Errorf("Unable to find path: %v", err) + return nil, err + } + + // TODO(roabseef): also create the Sphinx packet and add in the route + + log.Debugf("Obtained path sending %v to %x: %v", amt, dest, + newLogClosure(func() string { + return spew.Sdump(route) + }), + ) + + return route, nil +} + +// SendPayment... +// +// TODO(roasbeef): pipe through the htlcSwitch, move the payment storage info +// to the router, add interface for payment storage +// TODO(roasbeef): add version that takes a route object +func (r *ChannelRouter) SendPayment() error { + return nil +} + +// TopologyClient... +// TODO(roasbeef): put in discovery package? +type TopologyClient struct { +} + +// TopologyChange... +type TopologyChange struct { + NewNodes []*channeldb.LinkNode + NewChannels []*channeldb.ChannelEdge +} + +// notifyTopologyChange... +func (r *ChannelRouter) notifyTopologyChange() { +} + +// SubscribeTopology.... +func (r *ChannelRouter) SubscribeTopology() (*TopologyClient, error) { + return nil, nil +} diff --git a/routing/rt/graph/bfs.go b/routing/rt/graph/bfs.go deleted file mode 100644 index 848a5b696..000000000 --- a/routing/rt/graph/bfs.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index 3d7c44a72..000000000 --- a/routing/rt/graph/bfs_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// 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 deleted file mode 100644 index 42b59f9f4..000000000 --- a/routing/rt/graph/dijkstra.go +++ /dev/null @@ -1,67 +0,0 @@ -// 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 deleted file mode 100644 index 81eba92c3..000000000 --- a/routing/rt/graph/dijkstra_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// 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 deleted file mode 100644 index 66a0360dd..000000000 --- a/routing/rt/graph/graph.go +++ /dev/null @@ -1,357 +0,0 @@ -// 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 deleted file mode 100644 index 68ba8d9c6..000000000 --- a/routing/rt/graph/graph_test.go +++ /dev/null @@ -1,166 +0,0 @@ -// 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 deleted file mode 100644 index 1b9e09f86..000000000 --- a/routing/rt/graph/ksp.go +++ /dev/null @@ -1,68 +0,0 @@ -// 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 deleted file mode 100644 index a3ede9fb2..000000000 --- a/routing/rt/graph/ksp_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index 6d3475185..000000000 --- a/routing/rt/graph/path.go +++ /dev/null @@ -1,39 +0,0 @@ -// 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 deleted file mode 100644 index 65c87b6dd..000000000 --- a/routing/rt/rt.go +++ /dev/null @@ -1,258 +0,0 @@ -// 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 deleted file mode 100644 index 57d565837..000000000 --- a/routing/rt/rt_test.go +++ /dev/null @@ -1,160 +0,0 @@ -// 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 deleted file mode 100644 index 87753068f..000000000 --- a/routing/rt/visualizer/config.go +++ /dev/null @@ -1,125 +0,0 @@ -// 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 deleted file mode 100644 index 78c284330..000000000 --- a/routing/rt/visualizer/prefix_tree/node.go +++ /dev/null @@ -1,116 +0,0 @@ -// 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 deleted file mode 100644 index 632058a5e..000000000 --- a/routing/rt/visualizer/prefix_tree/prefix_tree.go +++ /dev/null @@ -1,120 +0,0 @@ -// 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 deleted file mode 100644 index 73b9dc6a2..000000000 --- a/routing/rt/visualizer/prefix_tree/prefix_tree_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// 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 deleted file mode 100644 index 390eeafc7..000000000 --- a/routing/rt/visualizer/visualizer.go +++ /dev/null @@ -1,174 +0,0 @@ -// 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/routing/testdata/basic_graph.json b/routing/testdata/basic_graph.json new file mode 100644 index 000000000..22ba8809e --- /dev/null +++ b/routing/testdata/basic_graph.json @@ -0,0 +1,121 @@ +{ + "info": [ + "This file encodes a basic graph that resembles the following ascii graph:", + "", +" 50k satoshis ┌──────┐ ", +" ┌───────────────────▶│luo ji│◀─┐ ", +" │ └──────┘ │ ", +" │ │ ", +" │ │ ", +" │ │ ", +" │ │ ", +" │ │ ", +" ▼ │ ┌──────┐ ", +" ┌────────┐ │ │sophon│◀┐ ", +" │satoshi │ │ └──────┘ │ ", +" └────────┘ │ │ ", +" ▲ │ │ 500 satoshis ", +" │ ┌───────────────────┘ │ ", +" │ │ 100k satoshis │ ", +" │ │ │ ", +" │ │ │ ┌────────┐ ", +" └──────────┤ └─▶│son goku│ ", +" 10k satoshis │ └────────┘ ", +" │ ▲ ", +" │ │ ", +" │ │ ", +" ▼ │ ", +" ┌──────────┐ │ ", +" │ roasbeef │◀─────────────────────────────────────┘ ", +" └──────────┘ 100k satoshis " + ], + "nodes": [ + { + "source": true, + "pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "alias": "roasbeef" + }, + { + "source": false, + "pubkey": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "alias": "songoku" + }, + { + "source": false, + "pubkey": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "alias": "satoshi" + }, + { + "source": false, + "pubkey": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "alias": "luoji" + }, + { + "source": false, + "pubkey": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb", + "alias": "sophon" + } + ], + "edges": [ + { + "node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "node_2": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "channel_id": 12345, + "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", + "flags": 0, + "expiry": 1, + "min_htlc": 1, + "fee_base_msat": 10, + "fee_rate": 0.001, + "capacity": 100000 + }, + { + "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb", + "channel_id": 11111, + "channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0", + "flags": 0, + "expiry": 1, + "min_htlc": 1, + "fee_base_msat": 10, + "fee_rate": 0.001, + "capacity": 500 + }, + { + "node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 11111, + "channel_point": "72cd6e8422c407fb6d098690f1130b7ded7ec2f7f5e1d30bd9d521f015363793:0", + "flags": 0, + "expiry": 1, + "min_htlc": 1, + "fee_base_msat": 10, + "fee_rate": 0.001, + "capacity": 10000 + }, + { + "node_1": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", + "node_2": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "channel_id": 11111, + "channel_point": "25376aa6cb81913ad30416bd22d4083241bd6d68e811d0284d3c3a17795c458a:0", + "flags": 0, + "expiry": 1, + "min_htlc": 1, + "fee_base_msat": 10, + "fee_rate": 0.001, + "capacity": 100000 + }, + { + "node_1": "02e7b1aaac10977c38e9c61c74dc66840de211bcec3021603e7977bc5e28edabfd", + "node_2": "03c19f0027ffbb0ae0e14a4d958788793f9d74e107462473ec0c3891e4feb12e99", + "channel_id": 11111, + "channel_point": "704a5675c91b1c674309a6475fc51072c2913d6117ee6103c9f1b86956bcbe02:0", + "flags": 0, + "expiry": 1, + "min_htlc": 1, + "fee_base_msat": 10, + "fee_rate": 0.001, + "capacity": 50000 + } + ] +}