diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 15d86c49..5d7fcd0e 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -6,11 +6,16 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" + "os/exec" + "strconv" "strings" + "github.com/awalterschulze/gographviz" "github.com/lightningnetwork/lnd/lnrpc" "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcutil" "github.com/urfave/cli" "golang.org/x/net/context" ) @@ -762,7 +767,13 @@ var DescribeGraphCommand = cli.Command{ Name: "describegraph", Description: "prints a human readable version of the known channel " + "graph from the PoV of the node", - Usage: "describegraph", + Usage: "describegraph", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "draw", + Usage: "If true, then an image of graph will be generated and displayed. The generated image is stored within the current directory with a file name of 'graph.png'", + }, + }, Action: describeGraph, } @@ -776,10 +787,114 @@ func describeGraph(ctx *cli.Context) error { return err } + // If the draw flag is on, then we'll use the 'dot' command to create a + // visualization of the graph itself. + if ctx.Bool("draw") { + return drawChannelGraph(graph) + } + printRespJson(graph) return nil } +func drawChannelGraph(graph *lnrpc.ChannelGraph) error { + // First we'll create a temporary file that we'll write the compiled + // string that describes our graph in the dot format to. + tempDotFile, err := ioutil.TempFile("", "") + if err != nil { + return err + } + defer os.Remove(tempDotFile.Name()) + + // Next, we'll create (or re-create) the file that the final graph + // image will be written to. + imageFile, err := os.Create("graph.png") + if err != nil { + return err + } + + // With our temporary files set up, we'll initialize the graphviz + // object that we'll use to draw our graph. + graphName := "LightningNetwork" + graphCanvas := gographviz.NewGraph() + graphCanvas.SetName(graphName) + graphCanvas.SetDir(true) + + // For each node within the graph, we'll add a new vertex to the graph. + for _, node := range graph.Nodes { + // Rather than using the entire hex-encoded string, we'll only + // use the first 10 characters. We also add a prefix of "Z" as + // graphviz is unable to parse the compressed pubkey as a + // non-integer. + // + // TODO(roasbeef): should be able to get around this? + nodeID := "Z" + node.PubKey[:10] + + graphCanvas.AddNode(graphName, nodeID, gographviz.Attrs{}) + } + + // Similarly, for each edge we'll add an edge between the corresponding + // nodes added to the graph above. + for _, edge := range graph.Edges { + // Once again, we add a 'Z' prefix so we're compliant with the + // dot grammar. + src := "Z" + edge.Node1Pub[:10] + dest := "Z" + edge.Node2Pub[:10] + + // The weight for our edge will be the total capacity of the + // channel, in BTC. + amt := btcutil.Amount(edge.Capacity).ToBTC() + edgeWeight := strconv.FormatFloat(amt, 'f', -1, 64) + + // The label for each edge will simply be a truncated version + // of it's channel ID. + edgeLabel := strconv.FormatUint(edge.ChannelId, 10)[:10] + + // We'll only draw an edge from a source to a destination if + // the source is currently advertising a route in that + // direction. + if edge.Node1Policy.TimeLockDelta != 0 { + graphCanvas.AddEdge(src, dest, true, gographviz.Attrs{ + "weight": edgeWeight, + "label": edgeLabel, + }) + } + if edge.Node2Policy.TimeLockDelta != 0 { + graphCanvas.AddEdge(dest, src, true, gographviz.Attrs{ + "weight": edgeWeight, + "label": edgeLabel, + }) + } + } + + // With the declarative generation of the graph complete, we now write + // the dot-string description of the graph + graphDotString := graphCanvas.String() + if _, err := tempDotFile.WriteString(graphDotString); err != nil { + return err + } + if err := tempDotFile.Sync(); err != nil { + return err + } + + // Once our dot file has been written to disk, we can use the dot + // command itself to generate the drawn rendering of the graph + // described. + drawCmd := exec.Command("dot", "-T"+"png", "-o"+imageFile.Name(), + tempDotFile.Name()) + if err := drawCmd.Run(); err != nil { + return err + } + + // Finally, we'll open the drawn graph to display to the user. + openCmd := exec.Command("open", imageFile.Name()) + if err := openCmd.Run(); err != nil { + return err + } + + return nil +} + var ListPaymentsCommand = cli.Command{ Name: "listpayments", Usage: "listpayments", diff --git a/glide.lock b/glide.lock index 6e434e63..401f899d 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 63c8e025e68860690ecd8c0f5d6a2ce0c20aae601f23add3cb41d702f07c2980 -updated: 2017-01-16T17:29:52.63308745-08:00 +hash: a292fd48b31862c25e68915190bd53af1c2a5138151e45a38ffa7f192b8456ac +updated: 2017-01-23T20:26:07.380822159-08:00 imports: - name: github.com/aead/chacha20 version: 7e1038a97ad08a9a16cb88ed7a6778b366ba4d99 @@ -7,6 +7,13 @@ imports: - chacha - name: github.com/aead/poly1305 version: 7ab7663051fa9fa57de391f966a859e781d48b0a +- name: github.com/awalterschulze/gographviz + version: 761fd5fbb34e4c2c138c280395b65b48e4ff5a53 + subpackages: + - ast + - parser + - scanner + - token - name: github.com/boltdb/bolt version: 583e8937c61f1af6513608ccc75c97b6abdf4ff9 - name: github.com/btcsuite/bolt @@ -37,8 +44,6 @@ imports: version: 31079b6807923eb23992c421b114992b95131b55 - name: github.com/codahale/chacha20 version: ec07b4f69a3f70b1dd2a8ad77230deb1ba5d6953 -- name: github.com/codahale/chacha20poly1305 - version: f8a5c48301822c3d7dd26d78e68ea2968db0ab20 - name: github.com/davecgh/go-spew version: 346938d642f2ec3594ed81d874461961cd0faa76 subpackages: diff --git a/glide.yaml b/glide.yaml index 04cd6919..34a010f1 100644 --- a/glide.yaml +++ b/glide.yaml @@ -7,7 +7,6 @@ import: - package: github.com/btcsuite/go-flags - package: github.com/btcsuite/seelog version: ^2.1.0 -- package: github.com/codahale/chacha20poly1305 - package: github.com/davecgh/go-spew subpackages: - spew @@ -61,3 +60,5 @@ import: - package: github.com/aead/chacha20 - package: github.com/go-errors/errors - package: github.com/tv42/zbase32 +- package: github.com/awalterschulze/gographviz + version: ^1.0.0