diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index be63d4e7..47d90327 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -11,15 +11,12 @@ import ( "io/ioutil" "math" "os" - "os/exec" "strconv" "strings" "sync" "syscall" - "github.com/awalterschulze/gographviz" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil" "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" "github.com/lightningnetwork/lnd/lnrpc" @@ -2343,13 +2340,7 @@ var describeGraphCommand = cli.Command{ Category: "Peers", Description: "Prints a human readable version of the known channel " + "graph from the PoV of the node", - Usage: "Describe the network graph.", - Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "render", - Usage: "If set, 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.svg'", - }, - }, + Usage: "Describe the network graph.", Action: actionDecorator(describeGraph), } @@ -2364,12 +2355,6 @@ 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("render") { - return drawChannelGraph(graph) - } - printRespJSON(graph) return nil } @@ -2405,135 +2390,6 @@ func normalizeFunc(edges []*lnrpc.ChannelEdge, scaleFactor float64) func(int64) } } -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.svg") - 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(false) - - const numKeyChars = 10 - - truncateStr := func(k string, n uint) string { - return k[:n] - } - - // 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 := fmt.Sprintf(`"%v"`, truncateStr(node.PubKey, numKeyChars)) - - attrs := gographviz.Attrs{} - - if node.Color != "" { - attrs["color"] = fmt.Sprintf(`"%v"`, node.Color) - } - - graphCanvas.AddNode(graphName, nodeID, attrs) - } - - normalize := normalizeFunc(graph.Edges, 3) - - // 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 := fmt.Sprintf(`"%v"`, truncateStr(edge.Node1Pub, numKeyChars)) - dest := fmt.Sprintf(`"%v"`, truncateStr(edge.Node2Pub, numKeyChars)) - - // The weight for our edge will be the total capacity of the - // channel, in BTC. - // TODO(roasbeef): can also factor in the edges time-lock delta - // and fee information - 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 its channel ID. - chanIDStr := strconv.FormatUint(edge.ChannelId, 10) - edgeLabel := fmt.Sprintf(`"cid:%v"`, truncateStr(chanIDStr, 7)) - - // We'll also use a normalized version of the channels' - // capacity in satoshis in order to modulate the "thickness" of - // the line that creates the edge within the graph. - normalizedCapacity := normalize(edge.Capacity) - edgeThickness := strconv.FormatFloat(normalizedCapacity, 'f', -1, 64) - - // If there's only a single channel in the graph, then we'll - // just set the edge thickness to 1 for everything. - if math.IsNaN(normalizedCapacity) { - edgeThickness = "1" - } - - // TODO(roasbeef): color code based on percentile capacity - graphCanvas.AddEdge(src, dest, false, gographviz.Attrs{ - "penwidth": edgeThickness, - "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 - } - - var errBuffer bytes.Buffer - - // 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"+"svg", "-o"+imageFile.Name(), - tempDotFile.Name()) - drawCmd.Stderr = &errBuffer - if err := drawCmd.Run(); err != nil { - fmt.Println("error rendering graph: ", errBuffer.String()) - fmt.Println("dot: ", graphDotString) - - return err - } - - errBuffer.Reset() - - // Finally, we'll open the drawn graph to display to the user. - openCmd := exec.Command("open", imageFile.Name()) - openCmd.Stderr = &errBuffer - if err := openCmd.Run(); err != nil { - fmt.Println("error opening rendered graph image: ", - errBuffer.String()) - return err - } - - return nil -} - var listPaymentsCommand = cli.Command{ Name: "listpayments", Category: "Payments",