diff --git a/network/backend/backend.go b/network/backend/backend.go index b82fc79..0f27d55 100644 --- a/network/backend/backend.go +++ b/network/backend/backend.go @@ -18,7 +18,7 @@ type Backend interface { Info(context.Context) (*models.Info, error) - GetNode(context.Context, string) (*models.Node, error) + GetNode(context.Context, string, bool) (*models.Node, error) GetWalletBalance(context.Context) (*models.WalletBalance, error) diff --git a/network/backend/lnd/lnd.go b/network/backend/lnd/lnd.go index 11536c7..e369f9f 100644 --- a/network/backend/lnd/lnd.go +++ b/network/backend/lnd/lnd.go @@ -365,7 +365,7 @@ func (l Backend) GetChannelInfo(ctx context.Context, channel *models.Channel) er return nil } -func (l Backend) GetNode(ctx context.Context, pubkey string) (*models.Node, error) { +func (l Backend) GetNode(ctx context.Context, pubkey string, includeChannels bool) (*models.Node, error) { l.logger.Debug("GetNode") clt, err := l.Client(ctx) @@ -374,7 +374,7 @@ func (l Backend) GetNode(ctx context.Context, pubkey string) (*models.Node, erro } defer clt.Close() - req := &lnrpc.NodeInfoRequest{PubKey: pubkey} + req := &lnrpc.NodeInfoRequest{PubKey: pubkey, IncludeChannels: includeChannels} resp, err := clt.GetNodeInfo(ctx, req) if err != nil { return nil, errors.WithStack(err) diff --git a/network/backend/lnd/proto.go b/network/backend/lnd/proto.go index 320440b..fa4ac7d 100644 --- a/network/backend/lnd/proto.go +++ b/network/backend/lnd/proto.go @@ -268,6 +268,20 @@ func nodeProtoToNode(resp *lnrpc.NodeInfo) *models.Node { Addr: resp.Node.Addresses[i].Addr, } } + channels := []*models.Channel{} + for _, c := range resp.Channels { + ch := &models.Channel{ + ID: c.ChannelId, + ChannelPoint: c.ChanPoint, + Capacity: c.Capacity, + Policy1: protoToRoutingPolicy(c.Node1Policy), + Policy2: protoToRoutingPolicy(c.Node2Policy), + } + if c.Node1Pub != resp.Node.PubKey { + ch.Policy1, ch.Policy2 = ch.Policy2, ch.Policy1 + } + channels = append(channels, ch) + } return &models.Node{ NumChannels: resp.NumChannels, @@ -276,6 +290,7 @@ func nodeProtoToNode(resp *lnrpc.NodeInfo) *models.Node { PubKey: resp.Node.PubKey, Alias: resp.Node.Alias, Addresses: addresses, + Channels: channels, } } diff --git a/network/backend/mock/mock.go b/network/backend/mock/mock.go index 8124b4f..5d059bd 100644 --- a/network/backend/mock/mock.go +++ b/network/backend/mock/mock.go @@ -54,7 +54,7 @@ func (b *Backend) SubscribeRoutingEvents(ctx context.Context, channel chan *mode return nil } -func (b *Backend) GetNode(ctx context.Context, pubkey string) (*models.Node, error) { +func (b *Backend) GetNode(ctx context.Context, pubkey string, includeChannels bool) (*models.Node, error) { return &models.Node{}, nil } diff --git a/network/models/node.go b/network/models/node.go index 6ac0741..0158044 100644 --- a/network/models/node.go +++ b/network/models/node.go @@ -10,6 +10,7 @@ type Node struct { Alias string ForcedAlias string Addresses []*NodeAddress + Channels []*Channel } type NodeAddress struct { diff --git a/ui/controller.go b/ui/controller.go index 752a71a..e292dc4 100644 --- a/ui/controller.go +++ b/ui/controller.go @@ -2,6 +2,7 @@ package ui import ( "context" + "time" "github.com/jroimartin/gocui" @@ -240,6 +241,9 @@ func (c *controller) OnEnter(g *gocui.Gui, v *gocui.View) error { case views.CHANNELS: index := c.views.Channels.Index() c.models.Channels.SetCurrent(index) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) + defer cancel() + c.models.RefreshCurrentNode(ctx) c.views.Main = c.views.Channel return ToggleView(g, view, c.views.Channels) @@ -302,6 +306,16 @@ func (c *controller) OnEnter(g *gocui.Gui, v *gocui.View) error { return nil } +func (c *controller) NodeInfo(g *gocui.Gui, v *gocui.View) error { + if v.Name() != views.CHANNEL { + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + c.models.RefreshCurrentNode(ctx) + return nil +} + func ToggleView(g *gocui.Gui, v1, v2 views.View) error { maxX, maxY := g.Size() err := v1.Delete(g) diff --git a/ui/keybindings.go b/ui/keybindings.go index cb2bd30..22a8d55 100644 --- a/ui/keybindings.go +++ b/ui/keybindings.go @@ -120,5 +120,10 @@ func setKeyBinding(c *controller, g *gocui.Gui) error { return err } + err = g.SetKeybinding("", 'c', gocui.ModNone, c.NodeInfo) + if err != nil { + return err + } + return nil } diff --git a/ui/models/channels.go b/ui/models/channels.go index 130af71..c1bb49c 100644 --- a/ui/models/channels.go +++ b/ui/models/channels.go @@ -10,11 +10,12 @@ import ( type ChannelsSort func(*models.Channel, *models.Channel) bool type Channels struct { - current *models.Channel - index map[string]*models.Channel - list []*models.Channel - sort ChannelsSort - mu sync.RWMutex + current *models.Channel + index map[string]*models.Channel + list []*models.Channel + sort ChannelsSort + mu sync.RWMutex + CurrentNode *models.Node } func (c *Channels) List() []*models.Channel { diff --git a/ui/models/models.go b/ui/models/models.go index 793e65e..08faf95 100644 --- a/ui/models/models.go +++ b/ui/models/models.go @@ -67,7 +67,7 @@ func (m *Models) RefreshChannels(ctx context.Context) error { if channels[i].Node == nil { channels[i].Node, err = m.network.GetNode(ctx, - channels[i].RemotePubKey) + channels[i].RemotePubKey, false) if err != nil { m.logger.Debug("refreshChannels: cannot find Node", logging.String("pubkey", channels[i].RemotePubKey)) @@ -136,3 +136,11 @@ func (m *Models) RefreshRouting(update interface{}) func(context.Context) error return nil }) } + +func (m *Models) RefreshCurrentNode(ctx context.Context) (err error) { + cur := m.Channels.Current() + if cur != nil { + m.Channels.CurrentNode, err = m.network.GetNode(ctx, cur.RemotePubKey, true) + } + return +} diff --git a/ui/views/channel.go b/ui/views/channel.go index f95302e..95297b0 100644 --- a/ui/views/channel.go +++ b/ui/views/channel.go @@ -96,9 +96,10 @@ func (c *Channel) Set(g *gocui.Gui, x0, y0, x1, y1 int) error { footer.FgColor = gocui.ColorBlack footer.Clear() blackBg := color.Black(color.Background) - fmt.Fprintf(footer, "%s%s %s%s %s%s\n", + fmt.Fprintf(footer, "%s%s %s%s %s%s %s%s\n", blackBg("F2"), "Menu", blackBg("Enter"), "Channels", + blackBg("C"), "Get disabled", blackBg("F10"), "Quit", ) return nil @@ -163,6 +164,19 @@ func formatAmount(amt int64) string { return fmt.Sprintf("error: %d", amt) } +func formatDisabledCount(cnt int, total uint32) string { + perc := uint32(cnt) * 100 / total + disabledStr := "" + if perc >= 25 && perc < 50 { + disabledStr = color.Yellow(color.Bold)(fmt.Sprintf("%4d", cnt)) + } else if perc >= 50 { + disabledStr = color.Red(color.Bold)(fmt.Sprintf("%4d", cnt)) + } else { + disabledStr = fmt.Sprintf("%4d", cnt) + } + return fmt.Sprintf("%s / %d (%d%%)", disabledStr, total, perc) +} + func (c *Channel) display() { p := message.NewPrinter(language.English) v := c.view @@ -199,6 +213,21 @@ func (c *Channel) display() { cyan(" Total Capacity:"), formatAmount(channel.Node.TotalCapacity)) fmt.Fprintf(v, "%s %d\n", cyan(" Total Channels:"), channel.Node.NumChannels) + + if c.channels.CurrentNode != nil && c.channels.CurrentNode.PubKey == channel.RemotePubKey { + disabledOut := 0 + disabledIn := 0 + for _, ch := range c.channels.CurrentNode.Channels { + if ch.Policy1 != nil && ch.Policy1.Disabled { + disabledOut++ + } + if ch.Policy2 != nil && ch.Policy2.Disabled { + disabledIn++ + } + } + fmt.Fprintf(v, "\n %s %s\n", cyan("Disabled from node:"), formatDisabledCount(disabledOut, channel.Node.NumChannels)) + fmt.Fprintf(v, " %s %s\n", cyan("Disabled to node: "), formatDisabledCount(disabledIn, channel.Node.NumChannels)) + } } if channel.Policy1 != nil && channel.WeFirst {